F6 #34: delete the dead two-pane diff-view UI, keep the baseline data layer

F10 (#29) made the rendered preview the single review surface and hid the F6
two-pane vscode.diff view (command + ctrl+alt+d set when:false). This removes
that now-unreachable view code:

- DiffViewController: drop toggle/findDiffTab/epochLabel/isDiffOpen, the
  `cowriting-baseline:` TextDocumentContentProvider + BASELINE_SCHEME + baselineUri
  + the content-provider change emitter, and the toggleDiffView command. The
  baseline DATA layer is fully intact — ensureBaseline/advance/pin/capture,
  getBaseline, baselineFilePath, onDidChangeBaseline, persistence (INV-19), and
  the machine-landing auto-advance (INV-18) that F7/F10 consume.
- package.json: remove the toggleDiffView command, its commandPalette entry, and
  the ctrl+alt+d keybinding.
- E2E: diffView suite keeps the baseline-data-layer tests, drops the two-pane
  view tests; the F10 + no-workspace suites assert toggleDiffView is now absent
  (was: declared-but-hidden).

Deliberate deviation from the issue's literal acceptance: pinDiffBaseline is
KEPT. The canonical Solution Design (coauthoring-interactive-review.md §6.7)
scopes the removal to the two-pane VIEW only ("keep the controller + baseline
store"); pin() lives in the baseline lifecycle (§6.4), never touches vscode.diff,
and is exercised by live F7 baseline-reset tests. Where the P3 capture draft and
the approved spec conflict, the spec wins (documentation-leads-automation).

194 unit + 49 E2E green; typecheck + build clean. No F7/F10 behavior change.

Closes #34

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-12 02:10:14 -07:00
parent fd263ec674
commit 3520397e41
6 changed files with 64 additions and 199 deletions
+17 -106
View File
@@ -1,12 +1,15 @@
/**
* DiffViewController — F6 diff-view toggle (spec §6.2/§6.4). Owns the baseline
* DiffViewController — F6 baseline data layer (spec §6.4/§6.7). Owns the baseline
* lifecycle (initialize at first sight / advance at every machine landing / pin
* on demand), serves the baseline as a readonly `cowriting-baseline:` virtual
* document, and toggles a native vscode.diff (baseline left, the LIVE document
* right). A pure view: never mutates the document, sidecar, or attribution
* state (INV-19).
* on demand) and serves it to F7/F10 via `getBaseline` + the additive
* `onDidChangeBaseline` event. A pure data layer: never mutates the document,
* sidecar, or attribution state (INV-19).
*
* F6 works on ANY text document, not just workspace files — it needs no
* The F6 two-pane `vscode.diff` *view* (toggle UI + the `cowriting-baseline:`
* virtual document) was removed in #34 once F10 made the rendered preview the
* single review surface; only the baseline store survives here (spec §6.7).
*
* The baseline works on ANY text document, not just workspace files — it needs no
* `.threads/` sidecar, only a stable doc identity + a storage home. So it has
* its OWN diffable predicate (any `file:` or `untitled:` doc), decoupled from
* F2's workspace `isTracked`. Persistable docs (`file:`) keep their baseline in
@@ -14,40 +17,24 @@
* untitled buffers have no durable identity, so their baseline is in-memory
* only (lost on reload) — the same degrade the storage-unavailable path uses.
*/
import * as path from "node:path";
import { createHash } from "node:crypto";
import * as vscode from "vscode";
import { BaselineStore, type Baseline, type BaselineReason } from "./baselineStore";
export const BASELINE_SCHEME = "cowriting-baseline";
export class DiffViewController implements vscode.Disposable {
private readonly disposables: vscode.Disposable[] = [];
/** Source of truth for the content provider, keyed by `document.uri.toString()`. */
/** Source of truth for the baseline, keyed by `document.uri.toString()`. */
private readonly baselines = new Map<string, Baseline>();
private readonly onDidChangeEmitter = new vscode.EventEmitter<vscode.Uri>();
/** F7 (additive): fires on every baseline capture (open / advance / pin) so
* the track-changes preview refreshes without polling. Mirrors the internal
* content-provider change signal but carries the real document URI. */
* the track-changes preview refreshes without polling. Carries the real
* document URI. */
private readonly onDidChangeBaselineEmitter = new vscode.EventEmitter<{ uri: string }>();
readonly onDidChangeBaseline = this.onDidChangeBaselineEmitter.event;
private storageWarned = false;
constructor(private readonly store: BaselineStore | null) {
const provider: vscode.TextDocumentContentProvider = {
onDidChange: this.onDidChangeEmitter.event,
provideTextDocumentContent: (uri) => {
// The baseline URI carries the real document URI in its query (see baselineUri).
return this.baselines.get(uri.query)?.text ?? "";
},
};
this.disposables.push(
this.onDidChangeEmitter,
this.onDidChangeBaselineEmitter,
vscode.workspace.registerTextDocumentContentProvider(BASELINE_SCHEME, provider),
vscode.commands.registerCommand("cowriting.toggleDiffView", () =>
this.toggle(vscode.window.activeTextEditor),
),
vscode.commands.registerCommand("cowriting.pinDiffBaseline", () =>
this.pinCommand(vscode.window.activeTextEditor),
),
@@ -60,7 +47,7 @@ export class DiffViewController implements vscode.Disposable {
// ---- diffability / identity --------------------------------------------------------
/** Any text document F6 can diff: a saved file OR an unsaved buffer. */
/** Any text document F6 tracks a baseline for: a saved file OR an unsaved buffer. */
private isDiffable(document: vscode.TextDocument): boolean {
return document.uri.scheme === "file" || document.uri.scheme === "untitled";
}
@@ -76,11 +63,6 @@ export class DiffViewController implements vscode.Disposable {
private storageKey(uriKey: string): string {
return createHash("sha256").update(uriKey).digest("hex");
}
/** The readonly virtual-doc URI; its query carries the real document URI. */
private baselineUri(document: vscode.TextDocument): vscode.Uri {
const name = path.basename(document.uri.path) || "untitled";
return vscode.Uri.from({ scheme: BASELINE_SCHEME, path: "/" + name, query: this.uriKey(document) });
}
// ---- baseline lifecycle (§6.4) -----------------------------------------------------
@@ -109,13 +91,13 @@ export class DiffViewController implements vscode.Disposable {
this.capture(document, "machine-landing");
}
/** Human pin: baseline := now; the open diff visibly empties (left = right). */
/** Human pin: baseline := now; the preview's change-marks empty (left = right). */
pin(document: vscode.TextDocument): void {
if (!this.isDiffable(document)) return;
this.capture(document, "pinned");
}
/** Capture buffer text at this epoch, persist (if persistable), refresh any open diff. */
/** Capture buffer text at this epoch, persist (if persistable), notify F7/F10. */
private capture(document: vscode.TextDocument, reason: BaselineReason): void {
const key = this.uriKey(document);
const baseline: Baseline = { uri: key, text: document.getText(), capturedAt: new Date().toISOString(), reason };
@@ -127,8 +109,6 @@ export class DiffViewController implements vscode.Disposable {
this.warnStorageOnce();
}
}
// An open diff re-requests the left side when its baseline URI changes.
this.onDidChangeEmitter.fire(this.baselineUri(document));
this.onDidChangeBaselineEmitter.fire({ uri: key });
}
@@ -136,78 +116,13 @@ export class DiffViewController implements vscode.Disposable {
if (this.storageWarned) return;
this.storageWarned = true;
void vscode.window.showWarningMessage(
"Cowriting: diff-view storage is unavailable — baselines are kept in memory only and won't survive a reload.",
"Cowriting: baseline storage is unavailable — baselines are kept in memory only and won't survive a reload.",
);
}
// ---- toggle UX (§6.5 PUC-1) --------------------------------------------------------
/**
* If this doc's baseline diff is open → close it and reveal the normal editor;
* else open vscode.diff (baseline left, the live document right). Warns only
* when there is no active text editor (or a non-diffable scheme).
*/
private async toggle(editor: vscode.TextEditor | undefined): Promise<void> {
if (!editor || !this.isDiffable(editor.document)) {
void vscode.window.showWarningMessage(
"Cowriting: focus a text editor to toggle its diff view.",
);
return;
}
const document = editor.document;
const openTab = this.findDiffTab(document.uri);
if (openTab) {
await vscode.window.tabGroups.close(openTab);
await vscode.window.showTextDocument(document, { preview: false });
return;
}
this.ensureBaseline(document);
const baseline = this.baselines.get(this.uriKey(document))!;
const name = path.basename(document.uri.path) || "untitled";
const unsaved = this.isPersistable(document) ? "" : " (unsaved)";
const title = `${name} — my changes since ${this.epochLabel(baseline)}${unsaved}`;
await vscode.commands.executeCommand(
"vscode.diff",
this.baselineUri(document),
document.uri,
title,
{ preview: false },
);
}
/** The open baseline-diff tab for this document, if any. */
private findDiffTab(modified: vscode.Uri): vscode.Tab | undefined {
for (const group of vscode.window.tabGroups.all) {
for (const tab of group.tabs) {
const input = tab.input;
if (
input instanceof vscode.TabInputTextDiff &&
input.original.scheme === BASELINE_SCHEME &&
input.modified.toString() === modified.toString()
) {
return tab;
}
}
}
return undefined;
}
/** Human-readable epoch for the diff tab title (§5 / §6.5). */
private epochLabel(baseline: Baseline): string {
const time = new Date(baseline.capturedAt).toLocaleTimeString();
switch (baseline.reason) {
case "opened":
return `opened ${time}`;
case "machine-landing":
return `Claude landed ${time}`;
case "pinned":
return `pinned ${time}`;
}
}
private pinCommand(editor: vscode.TextEditor | undefined): void {
if (!editor || !this.isDiffable(editor.document)) {
void vscode.window.showWarningMessage("Cowriting: focus a text editor to pin its diff baseline.");
void vscode.window.showWarningMessage("Cowriting: focus a text editor to pin its review baseline.");
return;
}
this.pin(editor.document);
@@ -219,10 +134,6 @@ export class DiffViewController implements vscode.Disposable {
const b = this.baselines.get(uriString);
return b ? { text: b.text, reason: b.reason, capturedAt: b.capturedAt } : undefined;
}
/** Is this doc's baseline diff currently open in any tab group? */
isDiffOpen(uriString: string): boolean {
return this.findDiffTab(vscode.Uri.parse(uriString)) !== undefined;
}
/** Absolute on-disk path of this doc's persisted baseline, or undefined (untitled/in-memory). */
baselineFilePath(uriString: string): string | undefined {
if (!this.store || vscode.Uri.parse(uriString).scheme !== "file") return undefined;