From fe455692187af31ed76a943041e52f1bd09ab660 Mon Sep 17 00:00:00 2001 From: Ben Stull Date: Fri, 12 Jun 2026 00:17:26 -0700 Subject: [PATCH] F10 SLICE-3: ProposalController.listProposals + onDidChangeProposals + keyFor (#29) Co-Authored-By: Claude Opus 4.8 (1M context) --- src/proposalController.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/proposalController.ts b/src/proposalController.ts index c65530b..5415b71 100644 --- a/src/proposalController.ts +++ b/src/proposalController.ts @@ -18,6 +18,7 @@ import { addProposal, removeProposal } from "./proposalModel"; import type { AttributionController } from "./attributionController"; import type { VersionGuard } from "./versionGuard"; import { isAuthorable } from "./workspacePath"; +import type { ProposalView } from "./trackChangesModel"; /** Test-facing snapshot of what is currently rendered for a document. */ export interface RenderedProposal { @@ -44,6 +45,9 @@ export class ProposalController implements vscode.Disposable { private readonly disposables: vscode.Disposable[] = []; private readonly docs = new Map(); // keyed by docPath private readonly statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 89); + private readonly onDidChangeProposalsEmitter = new vscode.EventEmitter<{ uri: string }>(); + /** Fires on propose / accept / reject / external sidecar change (F10). */ + readonly onDidChangeProposals = this.onDidChangeProposalsEmitter.event; constructor( private readonly store: SidecarRouter, @@ -52,11 +56,16 @@ export class ProposalController implements vscode.Disposable { private readonly guard: VersionGuard, ) { this.disposables.push(this.statusItem); + this.disposables.push(this.onDidChangeProposalsEmitter); this.disposables.push( vscode.workspace.onDidChangeTextDocument((e) => this.onDidChange(e)), ); } + private fireChanged(document: vscode.TextDocument): void { + this.onDidChangeProposalsEmitter.fire({ uri: document.uri.toString() }); + } + private isTracked(document: vscode.TextDocument): boolean { return isAuthorable(document.uri.scheme); } @@ -64,6 +73,27 @@ export class ProposalController implements vscode.Disposable { private keyOf(document: vscode.TextDocument): string { return this.store.keyOf(docIdentity(document)); } + /** The doc key F4 uses (F8 routing) — exposed for F10's preview. */ + keyFor(document: vscode.TextDocument): string { + return this.keyOf(document); + } + /** Resolved proposal views for the F10 preview (anchorStart=null when unresolved). */ + listProposals(document: vscode.TextDocument): ProposalView[] { + const docPath = this.keyOf(document); + const artifact = this.store.load(docPath) ?? emptyArtifact(docPath); + const text = document.getText(); + return artifact.proposals.map((p) => { + const fp = artifact.anchors[p.anchorId]?.fingerprint; + const resolved = fp ? resolve(text, fp) : "orphaned"; + return { + id: p.id, + anchorStart: resolved === "orphaned" ? null : resolved.start, + anchorEnd: resolved === "orphaned" ? null : resolved.end, + replaced: fp?.text ?? "", + replacement: p.replacement, + }; + }); + } private ensureState(document: vscode.TextDocument): DocState { const docPath = this.keyOf(document); let state = this.docs.get(docPath); @@ -181,6 +211,7 @@ export class ProposalController implements vscode.Disposable { } } this.renderStatus(state); + this.fireChanged(document); } /** Shared-watcher entry point (extension.ts): a sidecar changed externally. */