fix: restore proposals + attribution on coediting enter (PUC-7) — registry onDidChange subscribers

Task 4 gated every surface on CoeditingRegistry but only wired
ThreadController/EditorProposalController to registry.onDidChange for
restore-on-enter — ProposalController.renderAll and
AttributionController.loadAll were never called on the enter transition, so
a document's FIRST enter with pre-existing sidecar proposals/attribution
showed threads but not proposal decorations/CodeLens or committed
author-coloring until a later edit happened to fire them (a gap Task 4's
own report flagged as known and unfixed).

Both controllers now subscribe to registry.onDidChange in their
constructors and call renderAll/loadAll on entry only (both already
self-gate on isCoediting; exit stays hide-only, mirroring
ThreadController). Construction order in extension.ts (attribution before
proposals before EditorProposalController) keeps the same-tick read order
correct.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
BenStullsBets
2026-07-02 08:04:36 -07:00
parent a323b827a8
commit a36353d041
3 changed files with 115 additions and 1 deletions
+15
View File
@@ -77,6 +77,21 @@ export class AttributionController implements vscode.Disposable {
vscode.workspace.onDidChangeTextDocument((e) => this.onDidChange(e)),
vscode.workspace.onDidSaveTextDocument((d) => this.onDidSave(d)),
vscode.window.onDidChangeActiveTextEditor(() => this.renderActive()),
// PUC-7 restore-on-enter: `loadAll` is the only path that (re)populates
// s.spans/s.orphans from the sidecar — without this, a document's FIRST
// entry into coediting (registry.enter(), extension.ts) never runs it,
// so pre-existing committed authorship never surfaces until the next
// edit. Mirrors ThreadController's own registry subscription; `loadAll`
// already gates on isCoediting, and exit intentionally does nothing here
// (no data to wipe — EditorProposalController's own gate clears the
// decorations that read spansFor). Registered before ProposalController/
// EditorProposalController (construction order in extension.ts) so their
// renders see fresh spans on the same enter transition.
this.registry.onDidChange(({ uri, coediting }) => {
if (!coediting) return;
const doc = vscode.workspace.textDocuments.find((d) => d.uri.toString() === uri);
if (doc) this.loadAll(doc);
}),
);
}
+17
View File
@@ -63,6 +63,23 @@ export class ProposalController implements vscode.Disposable {
this.disposables.push(this.onDidChangeProposalsEmitter);
this.disposables.push(
vscode.workspace.onDidChangeTextDocument((e) => this.onDidChange(e)),
// PUC-7 restore-on-enter (mirrors ThreadController/AttributionController):
// `renderAll` is the only path that recomputes state.live/unresolved AND
// fires onDidChangeProposals — the event EditorProposalController listens
// to for optimistic-apply (decorations/CodeLens). Without this, a FIRST
// entry into coediting (registry.enter()) never fires it, so pre-existing
// pending proposals stay un-applied/undecorated until the next edit.
// `renderAll` already gates on isCoediting; exit intentionally does
// nothing here (it clears only the live/unresolved recompute, not the
// persisted artifact — EditorProposalController's own gate hides the
// decorations). Registered after AttributionController's own subscription
// (construction order in extension.ts) so decorateCommitted's spansFor
// read sees fresh attribution on the same enter transition.
this.registry.onDidChange(({ uri, coediting }) => {
if (!coediting) return;
const doc = vscode.workspace.textDocuments.find((d) => d.uri.toString() === uri);
if (doc) this.renderAll(doc);
}),
);
}