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:
+40
-6
@@ -15,6 +15,7 @@ import { gitUserEmail } from "./identity";
|
||||
import { addThread, appendMessage, setStatus } from "./threadModel";
|
||||
import type { VersionGuard } from "./versionGuard";
|
||||
import { isAuthorable } from "./workspacePath";
|
||||
import type { CoeditingRegistry } from "./coeditingRegistry";
|
||||
|
||||
/** Test-facing snapshot of what is currently rendered for a document. */
|
||||
export interface RenderedThread {
|
||||
@@ -41,18 +42,24 @@ export class ThreadController implements vscode.Disposable {
|
||||
private readonly disposables: vscode.Disposable[] = [];
|
||||
private readonly docs = new Map<string, DocState>(); // keyed by docPath
|
||||
|
||||
/** Kept as ONE object; re-assigned on every registry change so VS Code re-queries
|
||||
* (spec §6.4 v0.2.1 — reassignment is the API's only "ranges changed" signal). */
|
||||
private readonly rangeProvider: vscode.CommentingRangeProvider = {
|
||||
provideCommentingRanges: (document) => {
|
||||
if (!isAuthorable(document.uri.scheme)) return [];
|
||||
if (!this.registry.isCoediting(document.uri)) return [];
|
||||
return [new vscode.Range(0, 0, Math.max(0, document.lineCount - 1), 0)];
|
||||
},
|
||||
};
|
||||
|
||||
constructor(
|
||||
private readonly store: SidecarRouter,
|
||||
private readonly rootDir: string | undefined,
|
||||
private readonly guard: VersionGuard,
|
||||
private readonly registry: CoeditingRegistry,
|
||||
) {
|
||||
this.controller = vscode.comments.createCommentController("cowriting.threads", "Coauthoring Threads");
|
||||
this.controller.commentingRangeProvider = {
|
||||
provideCommentingRanges: (document) => {
|
||||
if (!isAuthorable(document.uri.scheme)) return [];
|
||||
return [new vscode.Range(0, 0, Math.max(0, document.lineCount - 1), 0)];
|
||||
},
|
||||
};
|
||||
this.controller.commentingRangeProvider = this.rangeProvider;
|
||||
this.disposables.push(this.controller);
|
||||
|
||||
this.disposables.push(
|
||||
@@ -66,6 +73,20 @@ export class ThreadController implements vscode.Disposable {
|
||||
),
|
||||
vscode.workspace.onDidChangeTextDocument((e) => this.onDidChange(e)),
|
||||
vscode.workspace.onDidSaveTextDocument((d) => this.onDidSave(d)),
|
||||
// INV-10 (PUC-7): force VS Code to re-query commenting ranges on every gate
|
||||
// change, and hide/restore rendered threads — exit hides only (dispose the
|
||||
// rendered vsThreads), re-enter restores (renderAll reloads from the
|
||||
// sidecar, which is the durable source of truth regardless of gate state).
|
||||
this.registry.onDidChange(({ uri, coediting }) => {
|
||||
this.controller.commentingRangeProvider = this.rangeProvider;
|
||||
const doc = vscode.workspace.textDocuments.find((d) => d.uri.toString() === uri);
|
||||
if (!doc) return;
|
||||
if (coediting) {
|
||||
this.renderAll(doc);
|
||||
} else {
|
||||
this.disposeRendered(this.keyOf(doc));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -111,6 +132,7 @@ export class ThreadController implements vscode.Disposable {
|
||||
async createThreadOnSelection(firstBody = "New thread"): Promise<string | undefined> {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (!editor || editor.selection.isEmpty || !isAuthorable(editor.document.uri.scheme)) return undefined;
|
||||
if (!this.registry.isCoediting(editor.document.uri)) return undefined;
|
||||
if (this.guard.isReadOnly(this.keyOf(editor.document))) return undefined;
|
||||
const document = editor.document;
|
||||
const state = this.ensureState(document);
|
||||
@@ -152,6 +174,7 @@ export class ThreadController implements vscode.Disposable {
|
||||
|
||||
/** Load + (re)render every thread for a document at its resolved anchor (or orphaned). */
|
||||
renderAll(document: vscode.TextDocument): void {
|
||||
if (!this.registry.isCoediting(document.uri)) return;
|
||||
const docPath = this.keyOf(document);
|
||||
const state = this.ensureState(document);
|
||||
// fresh artifact from disk (reload / external change)
|
||||
@@ -269,6 +292,17 @@ export class ThreadController implements vscode.Disposable {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** PUC-7 (INV-10): exit hides only — dispose the rendered vsThreads for a doc
|
||||
* without touching its sidecar-backed artifact (re-enter restores via renderAll). */
|
||||
private disposeRendered(docPath: string): void {
|
||||
const state = this.docs.get(docPath);
|
||||
if (!state) return;
|
||||
for (const vsThread of state.vsThreads.values()) vsThread.dispose();
|
||||
state.vsThreads.clear();
|
||||
state.live.clear();
|
||||
state.orphaned.clear();
|
||||
}
|
||||
|
||||
// ---- test-facing surface --------------------------------------------------------
|
||||
|
||||
getRendered(docPath: string): RenderedThread[] {
|
||||
|
||||
Reference in New Issue
Block a user