Native surfaces migration — evolve the extension onto VS Code's native review surfaces (9-task plan, spec v0.2.1) (#72)
This commit was merged in pull request #72.
This commit is contained in:
@@ -21,6 +21,7 @@ import { minimizeReplace, PendingEditRegistry } from "./pendingEdits";
|
||||
import type { VersionGuard } from "./versionGuard";
|
||||
import { isAuthorable } from "./workspacePath";
|
||||
import type { AuthorSpan } from "./trackChangesModel";
|
||||
import type { CoeditingRegistry } from "./coeditingRegistry";
|
||||
|
||||
/** Test-facing snapshot of live attribution state for a document. */
|
||||
export interface RenderedSpan {
|
||||
@@ -56,8 +57,11 @@ export class AttributionController implements vscode.Disposable {
|
||||
/**
|
||||
* F6 (§6.2/§6.4): the single machine-landing signal. Fired after a real
|
||||
* (non-no-op) seam apply succeeds. INV-9 makes the seam the sole machine-edit
|
||||
* ingress, so this is the sole signal — DiffViewController subscribes to
|
||||
* advance the baseline; no call-site wiring, no future driver can forget it.
|
||||
* ingress, so this is the sole signal. Native-surfaces migration (INV-7/
|
||||
* INV-18 retirement, spec §6.4): DiffViewController no longer subscribes
|
||||
* here to advance the baseline — a landed edit stays a visible
|
||||
* change-since-baseline until commit (head mode) or "Mark Changes as
|
||||
* Reviewed" (snapshot mode). Other consumers may still subscribe.
|
||||
*/
|
||||
private readonly applyEmitter = new vscode.EventEmitter<{ document: vscode.TextDocument }>();
|
||||
readonly onDidApplyAgentEdit: vscode.Event<{ document: vscode.TextDocument }> = this.applyEmitter.event;
|
||||
@@ -66,12 +70,35 @@ export class AttributionController implements vscode.Disposable {
|
||||
private readonly store: SidecarRouter,
|
||||
private readonly rootDir: string | undefined,
|
||||
private readonly guard: VersionGuard,
|
||||
private readonly registry: CoeditingRegistry,
|
||||
) {
|
||||
this.disposables.push(this.statusItem, this.output, this.applyEmitter);
|
||||
this.disposables.push(
|
||||
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) {
|
||||
// Finding 5 (final whole-branch review): exit hides the status-bar
|
||||
// item too — otherwise a stale "N orphaned attributions" warning
|
||||
// for a doc that's no longer even coediting stays pinned in the bar
|
||||
// until some unrelated render happens to fire for it.
|
||||
if (vscode.window.activeTextEditor?.document.uri.toString() === uri) this.statusItem.hide();
|
||||
return;
|
||||
}
|
||||
const doc = vscode.workspace.textDocuments.find((d) => d.uri.toString() === uri);
|
||||
if (doc) this.loadAll(doc);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -101,6 +128,7 @@ export class AttributionController implements vscode.Disposable {
|
||||
/** Load the sidecar and re-resolve every attribution (live span | orphan). */
|
||||
loadAll(document: vscode.TextDocument): void {
|
||||
if (!this.isTracked(document)) return;
|
||||
if (!this.registry.isCoediting(document.uri)) return;
|
||||
const docPath = this.keyOf(document);
|
||||
const s = this.state(docPath);
|
||||
const artifact = this.store.load(docPath);
|
||||
@@ -142,6 +170,7 @@ export class AttributionController implements vscode.Disposable {
|
||||
|
||||
private onDidChange(e: vscode.TextDocumentChangeEvent): void {
|
||||
if (!this.isTracked(e.document) || e.contentChanges.length === 0) return;
|
||||
if (!this.registry.isCoediting(e.document.uri)) return;
|
||||
const docPath = this.keyOf(e.document);
|
||||
if (!e.document.isDirty && this.matchesDisk(e.document)) {
|
||||
// Disk sync (revert / external reload): buffer now equals the file on
|
||||
@@ -291,9 +320,10 @@ export class AttributionController implements vscode.Disposable {
|
||||
);
|
||||
}
|
||||
if (ok && opts?.landBaseline !== false) {
|
||||
// F6 (INV-18): a real machine landing — advance the baseline. F12 (INV-48)
|
||||
// suppresses this for optimistic apply: the proposed text is in the buffer
|
||||
// but the change stays PENDING (baseline at pre-proposal) until accept.
|
||||
// F6 (INV-18, retired as a baseline advance — spec §6.4/INV-7): fire the
|
||||
// machine-landing signal. F12 (INV-48) suppresses this for optimistic
|
||||
// apply: the proposed text is in the buffer but the change stays PENDING
|
||||
// until accept.
|
||||
this.applyEmitter.fire({ document });
|
||||
}
|
||||
return ok;
|
||||
@@ -302,8 +332,10 @@ export class AttributionController implements vscode.Disposable {
|
||||
/**
|
||||
* F12/#64 (INV-51): fire the machine-landing signal WITHOUT applying text — used
|
||||
* by finalize-in-place, where the proposed text already landed in the buffer via
|
||||
* optimistic apply (`landBaseline:false`) and accept only needs to advance the
|
||||
* F6 baseline so the now-accepted change stops reading as pending.
|
||||
* optimistic apply (`landBaseline:false`) and accept marks the landing so the
|
||||
* now-accepted change is recorded (INV-7: the baseline itself no longer
|
||||
* advances on landing — it advances only on commit / "Mark Changes as
|
||||
* Reviewed").
|
||||
*/
|
||||
signalLanded(document: vscode.TextDocument): void {
|
||||
if (this.isTracked(document)) this.applyEmitter.fire({ document });
|
||||
|
||||
Reference in New Issue
Block a user