feat: gate every surface on CoeditingRegistry (INV-10) — commenting ranges re-assigned on gate change (spec §6.4 v0.2.1)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
BenStullsBets
2026-07-02 07:41:13 -07:00
parent cf65528711
commit a323b827a8
18 changed files with 171 additions and 18 deletions
+3
View File
@@ -18,6 +18,9 @@ async function openDoc(): Promise<vscode.TextDocument> {
const uri = vscode.Uri.file(path.join(WS, DOC_REL));
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc);
// INV-10: attribution tracking is gated on coediting (Task 4) — idempotent
// across this order-dependent suite's repeated openDoc() calls.
await vscode.commands.executeCommand("cowriting.coeditDocument");
return doc;
}
async function getApi(): Promise<CowritingApi> {
+3
View File
@@ -34,6 +34,9 @@ suite("F10 review preview — pending Claude proposal renders cw-ins-claude in t
const uri = vscode.Uri.file(abs);
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc);
// INV-10: entering coediting is required for attribution tracking + F12
// optimistic-apply to fire — this suite never entered it (pre-migration).
await vscode.commands.executeCommand("cowriting.coeditDocument");
await settle();
const api = await getApi();
const key = uri.toString();
+23
View File
@@ -32,4 +32,27 @@ suite("coediting registry (PUC-7)", () => {
assert.strictEqual(api.coeditingRegistry.isCoediting(doc.uri), false);
assert.strictEqual(api.scmSurfaceController.changeCount(doc.uri.toString()), 0);
});
// Task 4 (INV-10, PUC-7): the no-hijack scenario — a non-entered doc gets no
// thread-creation surface at all; entering unlocks it; exiting hides the
// rendered thread (not the sidecar-backed data); re-entering restores it.
test("non-entered doc gets no surfaces; exit detaches; re-enter restores threads", async () => {
const api = await activateApi();
const doc = await vscode.workspace.openTextDocument({ language: "markdown", content: "# a\n\npara\n" });
const ed = await vscode.window.showTextDocument(doc);
// not entered → no thread creation
ed.selection = new vscode.Selection(2, 0, 2, 4);
const before = await api.threadController.createThreadOnSelection("hi");
assert.strictEqual(before, undefined);
// entered → works
await vscode.commands.executeCommand("cowriting.coeditDocument");
const id = await api.threadController.createThreadOnSelection("hi");
assert.ok(id);
// exit → rendered thread set empty; re-enter → restored from sidecar
await vscode.commands.executeCommand("cowriting.stopCoediting");
assert.strictEqual(api.threadController.getRendered(api.proposalController.keyFor(doc)).length, 0);
await vscode.commands.executeCommand("cowriting.coeditDocument");
await settle();
assert.strictEqual(api.threadController.getRendered(api.proposalController.keyFor(doc)).length, 1);
});
});
+2
View File
@@ -17,6 +17,8 @@ async function open(docRel: string): Promise<vscode.TextDocument> {
const uri = vscode.Uri.file(path.join(WS, docRel));
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc);
// INV-10: thread creation/rendering is gated on coediting (Task 4).
await vscode.commands.executeCommand("cowriting.coeditDocument");
return doc;
}
async function getApi(): Promise<CowritingApi> {
+3
View File
@@ -21,6 +21,9 @@ async function freshDoc(rel: string, body: string): Promise<{ doc: vscode.TextDo
const uri = vscode.Uri.file(abs);
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc);
// INV-10: proposal rendering (F12 optimistic-apply, driven by
// onDidChangeProposals) is gated on coediting (Task 4).
await vscode.commands.executeCommand("cowriting.coeditDocument");
await settle();
return { doc, key: uri.toString() };
}
+6
View File
@@ -20,6 +20,12 @@ async function freshDoc(rel: string, body: string): Promise<{ doc: vscode.TextDo
fs.writeFileSync(abs, body, "utf8");
const uri = vscode.Uri.file(abs);
const doc = await vscode.workspace.openTextDocument(uri);
// INV-10: cowriting.editDocument now warns instead of editing a non-entered
// doc (Task 4) — enter it here (briefly making it active) so the tests below
// exercise the routing/targeting behavior, not the gate. The caller
// re-establishes whichever doc it wants active afterward.
await vscode.window.showTextDocument(doc);
await vscode.commands.executeCommand("cowriting.coeditDocument");
return { doc, key: uri.toString() };
}
+3
View File
@@ -20,6 +20,9 @@ async function freshDoc(rel: string, body: string): Promise<vscode.TextDocument>
fs.writeFileSync(abs, body, "utf8");
const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(abs));
await vscode.window.showTextDocument(doc);
// INV-10: attribution tracking (accept's per-word attribution, INV-40) is
// gated on coediting (Task 4).
await vscode.commands.executeCommand("cowriting.coeditDocument");
await settle();
return doc;
}
+3
View File
@@ -20,6 +20,9 @@ async function freshDoc(rel: string, body: string): Promise<vscode.TextDocument>
fs.writeFileSync(abs, body, "utf8");
const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(abs));
await vscode.window.showTextDocument(doc);
// INV-10: proposal rendering (F12 optimistic-apply, driven by
// onDidChangeProposals) is gated on coediting (Task 4).
await vscode.commands.executeCommand("cowriting.coeditDocument");
await settle();
return doc;
}
+4
View File
@@ -37,6 +37,8 @@ suite("F8 out-of-workspace authoring (host E2E — programmatic seam, no LLM)",
const uri = vscode.Uri.file(outsidePath);
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc);
// INV-10: attribution tracking + thread creation are gated on coediting (Task 4).
await vscode.commands.executeCommand("cowriting.coeditDocument");
await settle();
const api = await getApi();
const key = uri.toString();
@@ -80,6 +82,8 @@ suite("F8 out-of-workspace authoring (host E2E — programmatic seam, no LLM)",
const TARGET = "The untitled draft sentence.";
const untitled = await vscode.workspace.openTextDocument({ content: `${TARGET}\n`, language: "markdown" });
await vscode.window.showTextDocument(untitled);
// INV-10: attribution tracking is gated on coediting (Task 4).
await vscode.commands.executeCommand("cowriting.coeditDocument");
await settle();
const api = await getApi();
const key = untitled.uri.toString();
+4
View File
@@ -18,6 +18,10 @@ async function openDoc(): Promise<vscode.TextDocument> {
const uri = vscode.Uri.file(path.join(WS, DOC_REL));
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc);
// INV-10: proposal rendering (and the F12 optimistic-apply it drives via
// onDidChangeProposals) is gated on coediting (Task 4) — idempotent across
// this order-dependent suite's repeated openDoc() calls.
await vscode.commands.executeCommand("cowriting.coeditDocument");
return doc;
}
async function getApi(): Promise<CowritingApi> {
+3
View File
@@ -18,6 +18,9 @@ async function openSample(): Promise<vscode.TextDocument> {
const uri = vscode.Uri.file(path.join(WS, DOC_REL));
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc);
// INV-10: thread creation/rendering is gated on coediting (Task 4) — idempotent
// across this order-dependent suite's repeated openSample() calls.
await vscode.commands.executeCommand("cowriting.coeditDocument");
return doc;
}