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:
+1
-15
@@ -69,14 +69,9 @@
|
|||||||
"title": "Propose Agent Edit (internal seam)",
|
"title": "Propose Agent Edit (internal seam)",
|
||||||
"category": "Cowriting"
|
"category": "Cowriting"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"command": "cowriting.toggleDiffView",
|
|
||||||
"title": "Cowriting: Toggle Diff View",
|
|
||||||
"category": "Cowriting"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"command": "cowriting.pinDiffBaseline",
|
"command": "cowriting.pinDiffBaseline",
|
||||||
"title": "Cowriting: Pin Diff Baseline to Now",
|
"title": "Cowriting: Pin Review Baseline to Now",
|
||||||
"category": "Cowriting"
|
"category": "Cowriting"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -103,10 +98,6 @@
|
|||||||
"command": "cowriting.rejectProposal",
|
"command": "cowriting.rejectProposal",
|
||||||
"when": "false"
|
"when": "false"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"command": "cowriting.toggleDiffView",
|
|
||||||
"when": "false"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"command": "cowriting.pinDiffBaseline",
|
"command": "cowriting.pinDiffBaseline",
|
||||||
"when": "false"
|
"when": "false"
|
||||||
@@ -145,11 +136,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"keybindings": [
|
"keybindings": [
|
||||||
{
|
|
||||||
"command": "cowriting.toggleDiffView",
|
|
||||||
"key": "ctrl+alt+d",
|
|
||||||
"when": "false"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"command": "cowriting.showTrackChangesPreview",
|
"command": "cowriting.showTrackChangesPreview",
|
||||||
"key": "ctrl+alt+r",
|
"key": "ctrl+alt+r",
|
||||||
|
|||||||
+17
-106
@@ -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
|
* lifecycle (initialize at first sight / advance at every machine landing / pin
|
||||||
* on demand), serves the baseline as a readonly `cowriting-baseline:` virtual
|
* on demand) and serves it to F7/F10 via `getBaseline` + the additive
|
||||||
* document, and toggles a native vscode.diff (baseline left, the LIVE document
|
* `onDidChangeBaseline` event. A pure data layer: never mutates the document,
|
||||||
* right). A pure view: never mutates the document, sidecar, or attribution
|
* sidecar, or attribution state (INV-19).
|
||||||
* 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
|
* `.threads/` sidecar, only a stable doc identity + a storage home. So it has
|
||||||
* its OWN diffable predicate (any `file:` or `untitled:` doc), decoupled from
|
* its OWN diffable predicate (any `file:` or `untitled:` doc), decoupled from
|
||||||
* F2's workspace `isTracked`. Persistable docs (`file:`) keep their baseline in
|
* 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
|
* untitled buffers have no durable identity, so their baseline is in-memory
|
||||||
* only (lost on reload) — the same degrade the storage-unavailable path uses.
|
* only (lost on reload) — the same degrade the storage-unavailable path uses.
|
||||||
*/
|
*/
|
||||||
import * as path from "node:path";
|
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
import { BaselineStore, type Baseline, type BaselineReason } from "./baselineStore";
|
import { BaselineStore, type Baseline, type BaselineReason } from "./baselineStore";
|
||||||
|
|
||||||
export const BASELINE_SCHEME = "cowriting-baseline";
|
|
||||||
|
|
||||||
export class DiffViewController implements vscode.Disposable {
|
export class DiffViewController implements vscode.Disposable {
|
||||||
private readonly disposables: 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 baselines = new Map<string, Baseline>();
|
||||||
private readonly onDidChangeEmitter = new vscode.EventEmitter<vscode.Uri>();
|
|
||||||
/** F7 (additive): fires on every baseline capture (open / advance / pin) so
|
/** F7 (additive): fires on every baseline capture (open / advance / pin) so
|
||||||
* the track-changes preview refreshes without polling. Mirrors the internal
|
* the track-changes preview refreshes without polling. Carries the real
|
||||||
* content-provider change signal but carries the real document URI. */
|
* document URI. */
|
||||||
private readonly onDidChangeBaselineEmitter = new vscode.EventEmitter<{ uri: string }>();
|
private readonly onDidChangeBaselineEmitter = new vscode.EventEmitter<{ uri: string }>();
|
||||||
readonly onDidChangeBaseline = this.onDidChangeBaselineEmitter.event;
|
readonly onDidChangeBaseline = this.onDidChangeBaselineEmitter.event;
|
||||||
private storageWarned = false;
|
private storageWarned = false;
|
||||||
|
|
||||||
constructor(private readonly store: BaselineStore | null) {
|
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.disposables.push(
|
||||||
this.onDidChangeEmitter,
|
|
||||||
this.onDidChangeBaselineEmitter,
|
this.onDidChangeBaselineEmitter,
|
||||||
vscode.workspace.registerTextDocumentContentProvider(BASELINE_SCHEME, provider),
|
|
||||||
vscode.commands.registerCommand("cowriting.toggleDiffView", () =>
|
|
||||||
this.toggle(vscode.window.activeTextEditor),
|
|
||||||
),
|
|
||||||
vscode.commands.registerCommand("cowriting.pinDiffBaseline", () =>
|
vscode.commands.registerCommand("cowriting.pinDiffBaseline", () =>
|
||||||
this.pinCommand(vscode.window.activeTextEditor),
|
this.pinCommand(vscode.window.activeTextEditor),
|
||||||
),
|
),
|
||||||
@@ -60,7 +47,7 @@ export class DiffViewController implements vscode.Disposable {
|
|||||||
|
|
||||||
// ---- diffability / identity --------------------------------------------------------
|
// ---- 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 {
|
private isDiffable(document: vscode.TextDocument): boolean {
|
||||||
return document.uri.scheme === "file" || document.uri.scheme === "untitled";
|
return document.uri.scheme === "file" || document.uri.scheme === "untitled";
|
||||||
}
|
}
|
||||||
@@ -76,11 +63,6 @@ export class DiffViewController implements vscode.Disposable {
|
|||||||
private storageKey(uriKey: string): string {
|
private storageKey(uriKey: string): string {
|
||||||
return createHash("sha256").update(uriKey).digest("hex");
|
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) -----------------------------------------------------
|
// ---- baseline lifecycle (§6.4) -----------------------------------------------------
|
||||||
|
|
||||||
@@ -109,13 +91,13 @@ export class DiffViewController implements vscode.Disposable {
|
|||||||
this.capture(document, "machine-landing");
|
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 {
|
pin(document: vscode.TextDocument): void {
|
||||||
if (!this.isDiffable(document)) return;
|
if (!this.isDiffable(document)) return;
|
||||||
this.capture(document, "pinned");
|
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 {
|
private capture(document: vscode.TextDocument, reason: BaselineReason): void {
|
||||||
const key = this.uriKey(document);
|
const key = this.uriKey(document);
|
||||||
const baseline: Baseline = { uri: key, text: document.getText(), capturedAt: new Date().toISOString(), reason };
|
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();
|
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 });
|
this.onDidChangeBaselineEmitter.fire({ uri: key });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,78 +116,13 @@ export class DiffViewController implements vscode.Disposable {
|
|||||||
if (this.storageWarned) return;
|
if (this.storageWarned) return;
|
||||||
this.storageWarned = true;
|
this.storageWarned = true;
|
||||||
void vscode.window.showWarningMessage(
|
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 {
|
private pinCommand(editor: vscode.TextEditor | undefined): void {
|
||||||
if (!editor || !this.isDiffable(editor.document)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
this.pin(editor.document);
|
this.pin(editor.document);
|
||||||
@@ -219,10 +134,6 @@ export class DiffViewController implements vscode.Disposable {
|
|||||||
const b = this.baselines.get(uriString);
|
const b = this.baselines.get(uriString);
|
||||||
return b ? { text: b.text, reason: b.reason, capturedAt: b.capturedAt } : undefined;
|
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). */
|
/** Absolute on-disk path of this doc's persisted baseline, or undefined (untitled/in-memory). */
|
||||||
baselineFilePath(uriString: string): string | undefined {
|
baselineFilePath(uriString: string): string | undefined {
|
||||||
if (!this.store || vscode.Uri.parse(uriString).scheme !== "file") return undefined;
|
if (!this.store || vscode.Uri.parse(uriString).scheme !== "file") return undefined;
|
||||||
|
|||||||
+8
-7
@@ -50,13 +50,14 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// --- F6: diff-view toggle (Features #17 + #19) — workspace-INDEPENDENT ---
|
// --- F6: baseline data layer (Features #17 + #19) — workspace-INDEPENDENT ---
|
||||||
// F6 works on ANY diffable doc (file or untitled), so it is constructed
|
// The two-pane vscode.diff VIEW was removed in #34 (F10's rendered preview is
|
||||||
// regardless of whether a folder is open, and its commands are always live
|
// the single review surface); only the baseline store survives, consumed by
|
||||||
// (never stubbed). Baseline lives in VS Code's per-extension GLOBAL storage
|
// F7/F10. It tracks a baseline for ANY diffable doc (file or untitled), so it
|
||||||
// (always present), never the repo (INV-19). The machine-landing advance is
|
// is constructed regardless of whether a folder is open. Baseline lives in VS
|
||||||
// wired in the with-root branch below (landings only occur on workspace files
|
// Code's per-extension GLOBAL storage (always present), never the repo
|
||||||
// through the seam). The controller self-wires baseline capture on open.
|
// (INV-19). The machine-landing advance is wired below. The controller
|
||||||
|
// self-wires baseline capture on open.
|
||||||
const baselineStorageDir = context.globalStorageUri?.fsPath;
|
const baselineStorageDir = context.globalStorageUri?.fsPath;
|
||||||
const baselineStore = baselineStorageDir ? new BaselineStore(baselineStorageDir) : null;
|
const baselineStore = baselineStorageDir ? new BaselineStore(baselineStorageDir) : null;
|
||||||
const diffViewController = new DiffViewController(baselineStore);
|
const diffViewController = new DiffViewController(baselineStore);
|
||||||
|
|||||||
@@ -40,8 +40,11 @@ suite("no-workspace authoring (F8 — real folder-less, #8 lineage)", () => {
|
|||||||
"cowriting.toggleAttribution",
|
"cowriting.toggleAttribution",
|
||||||
"cowriting.acceptProposal",
|
"cowriting.acceptProposal",
|
||||||
"cowriting.rejectProposal",
|
"cowriting.rejectProposal",
|
||||||
|
// #34: the F6 two-pane diff VIEW was removed (F10 preview is the single
|
||||||
|
// review surface); its toggle command is gone (the baseline store stays).
|
||||||
|
"cowriting.toggleDiffView",
|
||||||
]) {
|
]) {
|
||||||
assert.ok(!all.includes(retired), `${retired} is retired (F10 preview-only)`);
|
assert.ok(!all.includes(retired), `${retired} is retired`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -73,25 +76,24 @@ suite("no-workspace authoring (F8 — real folder-less, #8 lineage)", () => {
|
|||||||
assert.strictEqual(api.sidecarRouter.sidecarPath(key), undefined, "untitled artifact is in-memory only (no disk)");
|
assert.strictEqual(api.sidecarRouter.sidecarPath(key), undefined, "untitled artifact is in-memory only (no disk)");
|
||||||
});
|
});
|
||||||
|
|
||||||
// F6 (#19) is workspace-INDEPENDENT: its commands are real (not stubs) even
|
// F6 (#19) baseline data layer is workspace-INDEPENDENT: it captures a baseline
|
||||||
// with no folder open, and the toggle works on an untitled buffer.
|
// for an untitled buffer even with no folder open (the two-pane VIEW was
|
||||||
test("F6 toggle works with no folder open (untitled buffer)", async () => {
|
// removed in #34; only the data layer remains). pinDiffBaseline stays real.
|
||||||
|
test("F6 baseline data layer works with no folder open (untitled buffer)", async () => {
|
||||||
const all = await vscode.commands.getCommands(true);
|
const all = await vscode.commands.getCommands(true);
|
||||||
assert.ok(all.includes("cowriting.toggleDiffView"), "toggleDiffView registered");
|
|
||||||
assert.ok(all.includes("cowriting.pinDiffBaseline"), "pinDiffBaseline registered");
|
assert.ok(all.includes("cowriting.pinDiffBaseline"), "pinDiffBaseline registered");
|
||||||
|
const ext = vscode.extensions.getExtension("benstull.vscode-cowriting-plugin")!;
|
||||||
|
const api = (await ext.activate()) as CowritingApi;
|
||||||
const untitled = await vscode.workspace.openTextDocument({ content: "no-folder scratch\n", language: "markdown" });
|
const untitled = await vscode.workspace.openTextDocument({ content: "no-folder scratch\n", language: "markdown" });
|
||||||
await vscode.window.showTextDocument(untitled);
|
await vscode.window.showTextDocument(untitled);
|
||||||
await new Promise((r) => setTimeout(r, 300));
|
await new Promise((r) => setTimeout(r, 300));
|
||||||
await vscode.commands.executeCommand("cowriting.toggleDiffView");
|
const key = untitled.uri.toString();
|
||||||
|
const baseline = api.diffViewController.getBaseline(key);
|
||||||
|
assert.ok(baseline, "baseline captured for the untitled buffer with no folder");
|
||||||
|
assert.strictEqual(baseline!.reason, "opened");
|
||||||
|
// pin resets the baseline to now — works folder-less.
|
||||||
|
await vscode.commands.executeCommand("cowriting.pinDiffBaseline");
|
||||||
await new Promise((r) => setTimeout(r, 300));
|
await new Promise((r) => setTimeout(r, 300));
|
||||||
const opened = vscode.window.tabGroups.all.some((g) =>
|
assert.strictEqual(api.diffViewController.getBaseline(key)!.reason, "pinned", "pin works with no folder");
|
||||||
g.tabs.some(
|
|
||||||
(t) =>
|
|
||||||
t.input instanceof vscode.TabInputTextDiff &&
|
|
||||||
t.input.original.scheme === "cowriting-baseline" &&
|
|
||||||
t.input.modified.toString() === untitled.uri.toString(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
assert.ok(opened, "a cowriting-baseline diff opened for the untitled buffer with no folder");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,10 +23,13 @@ async function getApi(): Promise<CowritingApi> {
|
|||||||
}
|
}
|
||||||
const settle = () => new Promise((r) => setTimeout(r, 300));
|
const settle = () => new Promise((r) => setTimeout(r, 300));
|
||||||
|
|
||||||
// Order-dependent (F2–F4 pattern): later tests consume earlier state. Owns
|
// The F6 two-pane vscode.diff VIEW was removed in #34 (F10's rendered preview is
|
||||||
// docs/diffview.md exclusively. F6 works on ANY file (#19), so the last two
|
// the single review surface); only the baseline DATA layer survives, consumed by
|
||||||
// tests use an out-of-workspace file and an untitled buffer.
|
// F7/F10. This suite covers that data layer. Order-dependent (F2–F4 pattern):
|
||||||
suite("F6 diff-view toggle (host E2E — any file, programmatic seam ingress, no LLM)", () => {
|
// later tests consume earlier state. Owns docs/diffview.md exclusively. The
|
||||||
|
// baseline works on ANY file (#19), so the last two tests use an out-of-workspace
|
||||||
|
// file and an untitled buffer.
|
||||||
|
suite("F6 baseline data layer (host E2E — any file, programmatic seam ingress, no LLM)", () => {
|
||||||
const TARGET = "A target sentence Claude will rewrite via the seam.";
|
const TARGET = "A target sentence Claude will rewrite via the seam.";
|
||||||
const REPLACEMENT = "A SENTENCE CLAUDE REWROTE via the seam.";
|
const REPLACEMENT = "A SENTENCE CLAUDE REWROTE via the seam.";
|
||||||
|
|
||||||
@@ -40,21 +43,6 @@ suite("F6 diff-view toggle (host E2E — any file, programmatic seam ingress, no
|
|||||||
assert.strictEqual(baseline!.text, doc.getText(), "baseline = open-time buffer");
|
assert.strictEqual(baseline!.text, doc.getText(), "baseline = open-time buffer");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("toggle opens a diff tab (original scheme cowriting-baseline) over the live doc (PUC-1)", async () => {
|
|
||||||
const api = await getApi();
|
|
||||||
assert.strictEqual(api.diffViewController.isDiffOpen(docUri()), false, "no diff open yet");
|
|
||||||
await vscode.commands.executeCommand("cowriting.toggleDiffView");
|
|
||||||
await settle();
|
|
||||||
assert.strictEqual(api.diffViewController.isDiffOpen(docUri()), true, "diff tab open");
|
|
||||||
const active = vscode.window.tabGroups.activeTabGroup.activeTab;
|
|
||||||
assert.ok(active && active.input instanceof vscode.TabInputTextDiff, "active tab is a diff");
|
|
||||||
assert.strictEqual(
|
|
||||||
(active!.input as vscode.TabInputTextDiff).original.scheme,
|
|
||||||
"cowriting-baseline",
|
|
||||||
"left side served by the baseline provider",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("typing leaves the baseline unchanged while the buffer diverges", async () => {
|
test("typing leaves the baseline unchanged while the buffer diverges", async () => {
|
||||||
const doc = await openDoc();
|
const doc = await openDoc();
|
||||||
const api = await getApi();
|
const api = await getApi();
|
||||||
@@ -105,17 +93,17 @@ suite("F6 diff-view toggle (host E2E — any file, programmatic seam ingress, no
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("pin resets the baseline to now: baseline == buffer, reason pinned (PUC-3)", async () => {
|
test("pin resets the baseline to now: baseline == buffer, reason pinned", async () => {
|
||||||
const doc = await openDoc();
|
const doc = await openDoc();
|
||||||
const api = await getApi();
|
const api = await getApi();
|
||||||
await vscode.commands.executeCommand("cowriting.pinDiffBaseline");
|
await vscode.commands.executeCommand("cowriting.pinDiffBaseline");
|
||||||
await settle();
|
await settle();
|
||||||
const baseline = api.diffViewController.getBaseline(docUri())!;
|
const baseline = api.diffViewController.getBaseline(docUri())!;
|
||||||
assert.strictEqual(baseline.reason, "pinned");
|
assert.strictEqual(baseline.reason, "pinned");
|
||||||
assert.strictEqual(baseline.text, doc.getText(), "pinned baseline == current buffer (diff empties)");
|
assert.strictEqual(baseline.text, doc.getText(), "pinned baseline == current buffer (change-marks empty)");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("the baseline is persisted in GLOBAL storage, never the repo (PUC-4, INV-19)", async () => {
|
test("the baseline is persisted in GLOBAL storage, never the repo (INV-19)", async () => {
|
||||||
await openDoc();
|
await openDoc();
|
||||||
const api = await getApi();
|
const api = await getApi();
|
||||||
const p = api.diffViewController.baselineFilePath(docUri());
|
const p = api.diffViewController.baselineFilePath(docUri());
|
||||||
@@ -131,25 +119,7 @@ suite("F6 diff-view toggle (host E2E — any file, programmatic seam ingress, no
|
|||||||
assert.ok(p!.includes(`${path.sep}baselines${path.sep}`), "baseline lives under <globalStorage>/baselines/");
|
assert.ok(p!.includes(`${path.sep}baselines${path.sep}`), "baseline lives under <globalStorage>/baselines/");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("toggle again closes the diff tab and reveals the normal editor (PUC-1)", async () => {
|
test("a baseline is captured + persisted for a file OUTSIDE the workspace folder (#19)", async () => {
|
||||||
const api = await getApi();
|
|
||||||
if (!api.diffViewController.isDiffOpen(docUri())) {
|
|
||||||
await openDoc();
|
|
||||||
await vscode.commands.executeCommand("cowriting.toggleDiffView");
|
|
||||||
await settle();
|
|
||||||
}
|
|
||||||
assert.strictEqual(api.diffViewController.isDiffOpen(docUri()), true, "diff open before close");
|
|
||||||
await vscode.commands.executeCommand("cowriting.toggleDiffView");
|
|
||||||
await settle();
|
|
||||||
assert.strictEqual(api.diffViewController.isDiffOpen(docUri()), false, "diff tab closed");
|
|
||||||
assert.strictEqual(
|
|
||||||
vscode.window.activeTextEditor?.document.uri.toString(),
|
|
||||||
docUri(),
|
|
||||||
"the normal editor for the doc is active after closing the diff",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("toggle works on a file OUTSIDE the workspace folder, persisted in global storage (#19)", async () => {
|
|
||||||
const api = await getApi();
|
const api = await getApi();
|
||||||
const outsideDir = fs.mkdtempSync(path.join(os.tmpdir(), "cowriting-outside-"));
|
const outsideDir = fs.mkdtempSync(path.join(os.tmpdir(), "cowriting-outside-"));
|
||||||
const outsidePath = path.join(outsideDir, "outside.md");
|
const outsidePath = path.join(outsideDir, "outside.md");
|
||||||
@@ -162,19 +132,14 @@ suite("F6 diff-view toggle (host E2E — any file, programmatic seam ingress, no
|
|||||||
const baseline = api.diffViewController.getBaseline(outsideUri.toString());
|
const baseline = api.diffViewController.getBaseline(outsideUri.toString());
|
||||||
assert.ok(baseline, "baseline captured for an out-of-folder file");
|
assert.ok(baseline, "baseline captured for an out-of-folder file");
|
||||||
assert.strictEqual(baseline!.reason, "opened");
|
assert.strictEqual(baseline!.reason, "opened");
|
||||||
await vscode.commands.executeCommand("cowriting.toggleDiffView");
|
|
||||||
await settle();
|
|
||||||
assert.strictEqual(api.diffViewController.isDiffOpen(outsideUri.toString()), true, "diff opens for the outside file");
|
|
||||||
const fp = api.diffViewController.baselineFilePath(outsideUri.toString());
|
const fp = api.diffViewController.baselineFilePath(outsideUri.toString());
|
||||||
assert.ok(fp && fs.existsSync(fp), "outside-file baseline persisted in global storage");
|
assert.ok(fp && fs.existsSync(fp), "outside-file baseline persisted in global storage");
|
||||||
assert.ok(!fp!.startsWith(WS + path.sep), "not under the workspace folder");
|
assert.ok(!fp!.startsWith(WS + path.sep), "not under the workspace folder");
|
||||||
// close it so it doesn't bleed into the untitled test's active-tab checks
|
|
||||||
await vscode.commands.executeCommand("cowriting.toggleDiffView");
|
|
||||||
await settle();
|
|
||||||
fs.rmSync(outsideDir, { recursive: true, force: true });
|
fs.rmSync(outsideDir, { recursive: true, force: true });
|
||||||
|
void doc;
|
||||||
});
|
});
|
||||||
|
|
||||||
test("toggle works on an UNTITLED buffer, baseline in-memory only (#19)", async () => {
|
test("an UNTITLED buffer gets an in-memory baseline, never persisted to disk (#19)", async () => {
|
||||||
const api = await getApi();
|
const api = await getApi();
|
||||||
const untitled = await vscode.workspace.openTextDocument({ content: "scratch line\n", language: "markdown" });
|
const untitled = await vscode.workspace.openTextDocument({ content: "scratch line\n", language: "markdown" });
|
||||||
await vscode.window.showTextDocument(untitled);
|
await vscode.window.showTextDocument(untitled);
|
||||||
@@ -184,8 +149,5 @@ suite("F6 diff-view toggle (host E2E — any file, programmatic seam ingress, no
|
|||||||
const baseline = api.diffViewController.getBaseline(key);
|
const baseline = api.diffViewController.getBaseline(key);
|
||||||
assert.ok(baseline, "untitled buffer got an in-memory baseline");
|
assert.ok(baseline, "untitled buffer got an in-memory baseline");
|
||||||
assert.strictEqual(api.diffViewController.baselineFilePath(key), undefined, "untitled is NOT persisted to disk");
|
assert.strictEqual(api.diffViewController.baselineFilePath(key), undefined, "untitled is NOT persisted to disk");
|
||||||
await vscode.commands.executeCommand("cowriting.toggleDiffView");
|
|
||||||
await settle();
|
|
||||||
assert.strictEqual(api.diffViewController.isDiffOpen(key), true, "diff opens for the untitled buffer");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -215,16 +215,19 @@ suite("F10 interactive review (host E2E — preview is the single review surface
|
|||||||
api.attributionController.getSpans("docs/f10clean.md").some((s) => s.authorKind === "agent"),
|
api.attributionController.getSpans("docs/f10clean.md").some((s) => s.authorKind === "agent"),
|
||||||
"agent span recorded (attribution data layer intact)",
|
"agent span recorded (attribution data layer intact)",
|
||||||
);
|
);
|
||||||
// the retired toggle is gone from the palette (no editor decorations — INV-32).
|
// the retired in-editor surfaces are gone from the palette (no editor decorations — INV-32).
|
||||||
const all = await vscode.commands.getCommands(true);
|
const all = await vscode.commands.getCommands(true);
|
||||||
assert.ok(!all.includes("cowriting.toggleAttribution"), "cowriting.toggleAttribution is retired");
|
assert.ok(!all.includes("cowriting.toggleAttribution"), "cowriting.toggleAttribution is retired");
|
||||||
// the F6 diff toggle's keybinding is hidden (when:false) — it is not a user surface.
|
// #34: the F6 two-pane diff VIEW was removed (F10 preview is the single review
|
||||||
|
// surface) — its toggle command + ctrl+alt+d keybinding are gone entirely.
|
||||||
|
assert.ok(!all.includes("cowriting.toggleDiffView"), "cowriting.toggleDiffView command is gone (#34)");
|
||||||
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../../../../package.json"), "utf8"));
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../../../../package.json"), "utf8"));
|
||||||
const dKb = (pkg.contributes.keybindings as Array<{ command: string; key: string; when?: string }>).find(
|
const dKb = (pkg.contributes.keybindings as Array<{ command: string; key: string; when?: string }>).find(
|
||||||
(k) => k.command === "cowriting.toggleDiffView" && k.key === "ctrl+alt+d",
|
(k) => k.command === "cowriting.toggleDiffView",
|
||||||
);
|
);
|
||||||
assert.ok(dKb, "the ctrl+alt+d toggleDiffView keybinding is declared");
|
assert.ok(!dKb, "the toggleDiffView keybinding is gone from package.json (#34)");
|
||||||
assert.strictEqual(dKb!.when, "false", "…but hidden (when:false) — F6 is a data layer, not a user surface");
|
// …but the F6 baseline data layer survives: pinDiffBaseline stays a real command.
|
||||||
|
assert.ok(all.includes("cowriting.pinDiffBaseline"), "pinDiffBaseline (baseline data layer) is kept");
|
||||||
void key;
|
void key;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user