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
+27 -5
View File
@@ -19,6 +19,8 @@ import { CoeditingRegistry } from "./coeditingRegistry";
import { ScmSurfaceController } from "./scmSurface";
const CHANNEL_NAME = "Cowriting (Cline SDK)";
/** The exact warning copy for every gated edit gesture (INV-10). */
const NOT_COEDITING_WARNING = "Run ✦ Coedit this Document with Claude first.";
export interface CowritingApi {
threadController: ThreadController;
@@ -144,16 +146,22 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
// F5 (INV-16): one shared guard — newer-major sidecars are read-only, one
// warning per doc across the three co-owning controllers.
const versionGuard = new VersionGuard(sidecarRouter);
const threadController = new ThreadController(sidecarRouter, root, versionGuard);
const threadController = new ThreadController(sidecarRouter, root, versionGuard, coeditingRegistry);
context.subscriptions.push(threadController);
// --- F3: live attribution (Feature #6) ---
const attributionController = new AttributionController(sidecarRouter, root, versionGuard);
const attributionController = new AttributionController(sidecarRouter, root, versionGuard, coeditingRegistry);
context.subscriptions.push(attributionController);
// --- F4: propose/accept (Feature #12) — constructed before the preview so F10
// can route ✓/✗ through it ---
const proposalController = new ProposalController(sidecarRouter, attributionController, root, versionGuard);
const proposalController = new ProposalController(
sidecarRouter,
attributionController,
root,
versionGuard,
coeditingRegistry,
);
context.subscriptions.push(proposalController);
// --- F7/F10: the review preview is the single interactive review surface ---
@@ -166,12 +174,18 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
attributionController,
proposalController,
liveProgressUi,
coeditingRegistry,
);
context.subscriptions.push(trackChangesPreviewController);
// --- F12 (#64): the editor surface — optimistic-apply proposals into the buffer,
// decorate the diff, and provide Accept ▾/Reject ▾ CodeLens (INV-48/49/52/53). ---
const editorProposalController = new EditorProposalController(proposalController, diffViewController, attributionController);
const editorProposalController = new EditorProposalController(
proposalController,
diffViewController,
attributionController,
coeditingRegistry,
);
context.subscriptions.push(editorProposalController);
// #46 (INV-42): accept every pending proposal on the active doc in one gesture
@@ -289,6 +303,11 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
}
if (!editor) return; // unreachable once reason is null, but narrows the type
const document = editor.document;
// INV-10: a non-entered doc gets the gate warning, not a turn.
if (!coeditingRegistry.isCoediting(document.uri)) {
void vscode.window.showWarningMessage(NOT_COEDITING_WARNING);
return;
}
const selection = editor.selection; // non-empty (selectionRejection guaranteed it)
// Capture the selection text + anchor BEFORE prompting — the inline prompt
// moves the cursor to the document top (where its box anchors), which would
@@ -394,8 +413,11 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
);
// Render threads + attributions for already-open editors, and on future opens.
// INV-10: gated on the registry too — belt-and-suspenders with the per-controller
// gates (renderAll/loadAll already early-return), but keeps the intent explicit
// at the call site and avoids the no-op calls for a non-entered doc.
const renderIfOpen = (doc: vscode.TextDocument) => {
if (isAuthorable(doc.uri.scheme)) {
if (isAuthorable(doc.uri.scheme) && coeditingRegistry.isCoediting(doc.uri)) {
threadController.renderAll(doc);
attributionController.loadAll(doc);
proposalController.renderAll(doc);