feat(f6): diff-view toggle on any file — global storage, untitled in-memory (#19)

F6 no longer requires a workspace file. It gets its own diffable predicate
(any file: or untitled:) decoupled from F2 isTracked, keys baselines by a
sha256 of the document URI, and stores them in VS Code GLOBAL storage
(context.globalStorageUri) — always present, never the repo (INV-19). Untitled
buffers have no durable identity → in-memory baseline only (lost on reload).

DiffViewController is now constructed before the workspace-root check and its
commands are always live (never stubbed) — the machine-landing advance wiring
stays in the with-root branch since the seam only fires on workspace files.
BaselineStore generalized to key-by-hash (Baseline.docPath → uri).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-11 07:47:05 -07:00
parent 9e7ef4e052
commit e617f504e7
4 changed files with 144 additions and 119 deletions
+16 -13
View File
@@ -1,10 +1,12 @@
/** /**
* BaselineStore — load/save one diff-view baseline JSON per document (F6 §6.3). * BaselineStore — load/save one diff-view baseline JSON per document (F6 §6.3).
* The CoauthorStore shape (src/store.ts): vscode-free (Node fs only, unit- * The CoauthorStore shape (src/store.ts): vscode-free (Node fs only, unit-
* testable), one file per docPath. INV-19: the storage dir is VS Code's * testable), one file per storage key. INV-19: the storage dir is VS Code's
* per-workspace extension storage, NEVER the repo — so the baseline can never * per-extension GLOBAL storage, NEVER the repo — so the baseline can never be
* be committed, merged, or read by another rung, and the sidecar / cross-rung * committed, merged, or read by another rung. F6 works on *any* file, so the
* contract (INV-14..17) are untouched by construction. * key is a hash of the document URI (the controller derives it), not a
* workspace-relative path; an untitled buffer has no durable identity and is
* never persisted here (in-memory only — see DiffViewController).
*/ */
import * as fs from "node:fs"; import * as fs from "node:fs";
import * as path from "node:path"; import * as path from "node:path";
@@ -12,7 +14,8 @@ import * as path from "node:path";
export type BaselineReason = "opened" | "machine-landing" | "pinned"; export type BaselineReason = "opened" | "machine-landing" | "pinned";
export interface Baseline { export interface Baseline {
docPath: string; /** the document URI (the identity the key is derived from) — for round-trip/debug. */
uri: string;
/** full document text captured at the epoch (from the buffer, not disk — §6.3). */ /** full document text captured at the epoch (from the buffer, not disk — §6.3). */
text: string; text: string;
capturedAt: string; capturedAt: string;
@@ -20,23 +23,23 @@ export interface Baseline {
} }
export class BaselineStore { export class BaselineStore {
/** @param storageDir absolute VS Code workspace-storage dir (context.storageUri.fsPath). */ /** @param storageDir absolute VS Code GLOBAL-storage dir (context.globalStorageUri.fsPath). */
constructor(private readonly storageDir: string) {} constructor(private readonly storageDir: string) {}
/** `<storageDir>/baselines/<repo-relative-docPath>.json` (§6.3). */ /** `<storageDir>/baselines/<key>.json` — `key` is a filesystem-safe hash (§6.3). */
baselinePath(docPath: string): string { baselinePath(key: string): string {
return path.join(this.storageDir, "baselines", `${docPath}.json`); return path.join(this.storageDir, "baselines", `${key}.json`);
} }
load(docPath: string): Baseline | null { load(key: string): Baseline | null {
const p = this.baselinePath(docPath); const p = this.baselinePath(key);
if (!fs.existsSync(p)) return null; if (!fs.existsSync(p)) return null;
return JSON.parse(fs.readFileSync(p, "utf8")) as Baseline; return JSON.parse(fs.readFileSync(p, "utf8")) as Baseline;
} }
/** Newest epoch wins — overwrite in place, no history (state-not-history, the F3/F4 precedent). */ /** Newest epoch wins — overwrite in place, no history (state-not-history, the F3/F4 precedent). */
save(docPath: string, baseline: Baseline): void { save(key: string, baseline: Baseline): void {
const p = this.baselinePath(docPath); const p = this.baselinePath(key);
fs.mkdirSync(path.dirname(p), { recursive: true }); fs.mkdirSync(path.dirname(p), { recursive: true });
fs.writeFileSync(p, JSON.stringify(baseline, null, 2) + "\n", "utf8"); fs.writeFileSync(p, JSON.stringify(baseline, null, 2) + "\n", "utf8");
} }
+85 -79
View File
@@ -1,14 +1,21 @@
/** /**
* DiffViewController — F6 diff-view toggle (spec §6.2/§6.4). Owns the baseline * DiffViewController — F6 diff-view toggle (spec §6.2/§6.4). Owns the baseline
* lifecycle (initialize at first track / 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), serves the baseline as a readonly `cowriting-baseline:` virtual
* document, and toggles a native vscode.diff (baseline left, the LIVE document * document, and toggles a native vscode.diff (baseline left, the LIVE document
* right). A pure view: never mutates the document, sidecar, or attribution * right). A pure view: never mutates the document, sidecar, or attribution
* state (INV-19). Baselines persist via the vscode-free BaselineStore; if * state (INV-19).
* storage is unavailable the controller degrades to in-memory baselines + one *
* warning (reload survival is lost; the toggle still works) — §6.5 PUC-5. * F6 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
* VS Code's per-extension GLOBAL storage keyed by a hash of the document URI;
* 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 * as path from "node:path";
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";
@@ -16,20 +23,17 @@ 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; mirrors what the store persists. */ /** Source of truth for the content provider, 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>(); private readonly onDidChangeEmitter = new vscode.EventEmitter<vscode.Uri>();
private storageWarned = false; private storageWarned = false;
constructor( constructor(private readonly store: BaselineStore | null) {
private readonly store: BaselineStore | null,
private readonly rootDir: string,
) {
const provider: vscode.TextDocumentContentProvider = { const provider: vscode.TextDocumentContentProvider = {
onDidChange: this.onDidChangeEmitter.event, onDidChange: this.onDidChangeEmitter.event,
provideTextDocumentContent: (uri) => { provideTextDocumentContent: (uri) => {
const docPath = this.docPathFromBaselineUri(uri); // The baseline URI carries the real document URI in its query (see baselineUri).
return this.baselines.get(docPath)?.text ?? ""; return this.baselines.get(uri.query)?.text ?? "";
}, },
}; };
this.disposables.push( this.disposables.push(
@@ -41,37 +45,49 @@ export class DiffViewController implements vscode.Disposable {
vscode.commands.registerCommand("cowriting.pinDiffBaseline", () => vscode.commands.registerCommand("cowriting.pinDiffBaseline", () =>
this.pinCommand(vscode.window.activeTextEditor), this.pinCommand(vscode.window.activeTextEditor),
), ),
// F6 captures a baseline for any diffable doc the moment it is first seen,
// independent of the workspace gate (so "opened" is the open-time text).
vscode.workspace.onDidOpenTextDocument((d) => this.ensureBaseline(d)),
); );
for (const d of vscode.workspace.textDocuments) this.ensureBaseline(d);
} }
// ---- tracking / uri helpers -------------------------------------------------------- // ---- diffability / identity --------------------------------------------------------
private isTracked(document: vscode.TextDocument): boolean { /** Any text document F6 can diff: a saved file OR an unsaved buffer. */
return document.uri.scheme === "file" && document.uri.fsPath.startsWith(this.rootDir); private isDiffable(document: vscode.TextDocument): boolean {
return document.uri.scheme === "file" || document.uri.scheme === "untitled";
} }
private docPathOf(uri: vscode.Uri): string { /** Only `file:` docs have a durable identity to persist a baseline against. */
return vscode.workspace.asRelativePath(uri, false); private isPersistable(document: vscode.TextDocument): boolean {
return document.uri.scheme === "file";
} }
/** The readonly virtual-doc URI whose content the provider serves for this doc. */ /** In-memory map key — the full document URI. */
private baselineUri(docPath: string): vscode.Uri { private uriKey(document: vscode.TextDocument): string {
return vscode.Uri.from({ scheme: BASELINE_SCHEME, path: "/" + docPath }); return document.uri.toString();
} }
private docPathFromBaselineUri(uri: vscode.Uri): string { /** Filesystem-safe storage key for a persistable doc: sha256 of its URI. */
return uri.path.replace(/^\//, ""); 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) ----------------------------------------------------- // ---- baseline lifecycle (§6.4) -----------------------------------------------------
/** First sight of a tracked doc: load the stored baseline, else capture `opened`. */ /** First sight of a diffable doc: load the stored baseline, else capture `opened`. */
ensureBaseline(document: vscode.TextDocument): void { ensureBaseline(document: vscode.TextDocument): void {
if (!this.isTracked(document)) return; if (!this.isDiffable(document)) return;
const docPath = this.docPathOf(document.uri); const key = this.uriKey(document);
if (this.baselines.has(docPath)) return; if (this.baselines.has(key)) return;
if (this.store) { if (this.store && this.isPersistable(document)) {
try { try {
const stored = this.store.load(docPath); const stored = this.store.load(this.storageKey(key));
if (stored) { if (stored) {
this.baselines.set(docPath, stored); this.baselines.set(key, stored);
return; return;
} }
} catch { } catch {
@@ -83,35 +99,30 @@ export class DiffViewController implements vscode.Disposable {
/** Machine landing (INV-18): re-capture so landed text never shows as a change. */ /** Machine landing (INV-18): re-capture so landed text never shows as a change. */
advance(document: vscode.TextDocument): void { advance(document: vscode.TextDocument): void {
if (!this.isTracked(document)) return; if (!this.isDiffable(document)) return;
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 open diff visibly empties (left = right). */
pin(document: vscode.TextDocument): void { pin(document: vscode.TextDocument): void {
if (!this.isTracked(document)) return; if (!this.isDiffable(document)) return;
this.capture(document, "pinned"); this.capture(document, "pinned");
} }
/** Capture buffer text at this epoch, persist, and refresh any open diff's left side. */ /** Capture buffer text at this epoch, persist (if persistable), refresh any open diff. */
private capture(document: vscode.TextDocument, reason: BaselineReason): void { private capture(document: vscode.TextDocument, reason: BaselineReason): void {
const docPath = this.docPathOf(document.uri); const key = this.uriKey(document);
const baseline: Baseline = { const baseline: Baseline = { uri: key, text: document.getText(), capturedAt: new Date().toISOString(), reason };
docPath, this.baselines.set(key, baseline);
text: document.getText(), if (this.store && this.isPersistable(document)) {
capturedAt: new Date().toISOString(),
reason,
};
this.baselines.set(docPath, baseline);
if (this.store) {
try { try {
this.store.save(docPath, baseline); this.store.save(this.storageKey(key), baseline);
} catch { } catch {
this.warnStorageOnce(); this.warnStorageOnce();
} }
} }
// An open diff re-requests the left side when its baseline URI changes. // An open diff re-requests the left side when its baseline URI changes.
this.onDidChangeEmitter.fire(this.baselineUri(docPath)); this.onDidChangeEmitter.fire(this.baselineUri(document));
} }
private warnStorageOnce(): void { private warnStorageOnce(): void {
@@ -122,44 +133,21 @@ export class DiffViewController implements vscode.Disposable {
); );
} }
// ---- commands (toggle implemented in SLICE-3, Task 4) ------------------------------
private pinCommand(editor: vscode.TextEditor | undefined): void {
if (!editor || !this.isTracked(editor.document)) {
void vscode.window.showWarningMessage("Cowriting: open a tracked workspace document to pin its diff baseline.");
return;
}
this.pin(editor.document);
}
// ---- test-facing surface (§6.4) ----------------------------------------------------
getBaseline(docPath: string): { text: string; reason: BaselineReason; capturedAt: string } | undefined {
const b = this.baselines.get(docPath);
return b ? { text: b.text, reason: b.reason, capturedAt: b.capturedAt } : undefined;
}
/** Absolute on-disk path of this doc's persisted baseline, or undefined if in-memory. */
baselineFilePath(docPath: string): string | undefined {
return this.store?.baselinePath(docPath);
}
// ---- toggle UX (§6.5 PUC-1) -------------------------------------------------------- // ---- toggle UX (§6.5 PUC-1) --------------------------------------------------------
/** /**
* If this doc's baseline diff is the active/open tab → close it and reveal the * If this doc's baseline diff is open → close it and reveal the normal editor;
* normal editor; if the active editor is a tracked doc with no diff open → * else open vscode.diff (baseline left, the live document right). Warns only
* open vscode.diff (baseline left, the live document right). Untracked → warn, * when there is no active text editor (or a non-diffable scheme).
* no diff.
*/ */
private async toggle(editor: vscode.TextEditor | undefined): Promise<void> { private async toggle(editor: vscode.TextEditor | undefined): Promise<void> {
if (!editor || !this.isTracked(editor.document)) { if (!editor || !this.isDiffable(editor.document)) {
void vscode.window.showWarningMessage( void vscode.window.showWarningMessage(
"Cowriting: open a tracked workspace document to toggle its diff view.", "Cowriting: focus a text editor to toggle its diff view.",
); );
return; return;
} }
const document = editor.document; const document = editor.document;
const docPath = this.docPathOf(document.uri);
const openTab = this.findDiffTab(document.uri); const openTab = this.findDiffTab(document.uri);
if (openTab) { if (openTab) {
await vscode.window.tabGroups.close(openTab); await vscode.window.tabGroups.close(openTab);
@@ -167,11 +155,13 @@ export class DiffViewController implements vscode.Disposable {
return; return;
} }
this.ensureBaseline(document); this.ensureBaseline(document);
const baseline = this.baselines.get(docPath)!; const baseline = this.baselines.get(this.uriKey(document))!;
const title = `${path.basename(docPath)} — my changes since ${this.epochLabel(baseline)}`; 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( await vscode.commands.executeCommand(
"vscode.diff", "vscode.diff",
this.baselineUri(docPath), this.baselineUri(document),
document.uri, document.uri,
title, title,
{ preview: false }, { preview: false },
@@ -208,12 +198,28 @@ export class DiffViewController implements vscode.Disposable {
} }
} }
/** private pinCommand(editor: vscode.TextEditor | undefined): void {
* Test-facing (§6.4): is this doc's baseline diff currently open in any tab if (!editor || !this.isDiffable(editor.document)) {
* group? The diff's `modified` side is the document's own file: URI. void vscode.window.showWarningMessage("Cowriting: focus a text editor to pin its diff baseline.");
*/ return;
isDiffOpen(docPath: string): boolean { }
return this.findDiffTab(vscode.Uri.file(path.join(this.rootDir, docPath))) !== undefined; this.pin(editor.document);
}
// ---- test-facing surface (§6.4) ----------------------------------------------------
getBaseline(uriString: string): { text: string; reason: BaselineReason; capturedAt: string } | undefined {
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;
return this.store.baselinePath(this.storageKey(uriString));
} }
dispose(): void { dispose(): void {
+18 -10
View File
@@ -44,6 +44,18 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
}), }),
); );
// --- F6: diff-view toggle (Features #17 + #19) — workspace-INDEPENDENT ---
// F6 works on ANY diffable doc (file or untitled), so it is constructed
// regardless of whether a folder is open, and its commands are always live
// (never stubbed). Baseline lives in VS Code's per-extension GLOBAL storage
// (always present), never the repo (INV-19). The machine-landing advance is
// wired in the with-root branch below (landings only occur on workspace files
// through the seam). The controller self-wires baseline capture on open.
const baselineStorageDir = context.globalStorageUri?.fsPath;
const baselineStore = baselineStorageDir ? new BaselineStore(baselineStorageDir) : null;
const diffViewController = new DiffViewController(baselineStore);
context.subscriptions.push(diffViewController);
// --- F2: region-anchored threads (Feature #4) --- // --- F2: region-anchored threads (Feature #4) ---
const root = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; const root = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
if (!root) { if (!root) {
@@ -66,11 +78,12 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
"cowriting.acceptProposal", "cowriting.acceptProposal",
"cowriting.rejectProposal", "cowriting.rejectProposal",
"cowriting.proposeAgentEdit", "cowriting.proposeAgentEdit",
"cowriting.toggleDiffView",
"cowriting.pinDiffBaseline",
]) { ]) {
context.subscriptions.push(vscode.commands.registerCommand(command, stub)); context.subscriptions.push(vscode.commands.registerCommand(command, stub));
} }
// F6 (toggleDiffView / pinDiffBaseline) is NOT stubbed here — it works
// without a folder (on untitled buffers and out-of-folder files); its real
// commands were registered above by DiffViewController.
return undefined; return undefined;
} }
const store = new CoauthorStore(root); const store = new CoauthorStore(root);
@@ -88,14 +101,10 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
const proposalController = new ProposalController(store, attributionController, root, versionGuard); const proposalController = new ProposalController(store, attributionController, root, versionGuard);
context.subscriptions.push(proposalController); context.subscriptions.push(proposalController);
// --- F6: diff-view toggle (Feature #17) --- // --- F6 machine-landing wiring (with-root only) ---
// Baseline lives in VS Code workspace storage, never the repo (INV-19).
// storageUri can be undefined in odd host states → in-memory fallback (§6.5).
const storageDir = context.storageUri?.fsPath;
const baselineStore = storageDir ? new BaselineStore(storageDir) : null;
const diffViewController = new DiffViewController(baselineStore, root);
context.subscriptions.push(diffViewController);
// The seam's single machine-landing signal advances the baseline (INV-18). // The seam's single machine-landing signal advances the baseline (INV-18).
// The seam fires only on workspace files, so this wiring belongs here; the
// DiffViewController itself was constructed above (workspace-independent).
context.subscriptions.push( context.subscriptions.push(
attributionController.onDidApplyAgentEdit((e) => diffViewController.advance(e.document)), attributionController.onDidApplyAgentEdit((e) => diffViewController.advance(e.document)),
); );
@@ -245,7 +254,6 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
threadController.renderAll(doc); threadController.renderAll(doc);
attributionController.loadAll(doc); attributionController.loadAll(doc);
proposalController.renderAll(doc); proposalController.renderAll(doc);
diffViewController.ensureBaseline(doc);
} }
}; };
vscode.workspace.textDocuments.forEach(renderIfOpen); vscode.workspace.textDocuments.forEach(renderIfOpen);
+25 -17
View File
@@ -13,41 +13,49 @@ afterEach(() => {
fs.rmSync(dir, { recursive: true, force: true }); fs.rmSync(dir, { recursive: true, force: true });
}); });
function sample(docPath = "notes/chapter-1.md"): Baseline { const KEY = "a1b2c3"; // a stand-in for the controller's sha256(uri) key
return { docPath, text: "hello\nworld\n", capturedAt: "2026-06-11T00:00:00.000Z", reason: "opened" };
function sample(uri = "file:///ws/notes/chapter-1.md"): Baseline {
return { uri, text: "hello\nworld\n", capturedAt: "2026-06-11T00:00:00.000Z", reason: "opened" };
} }
describe("BaselineStore", () => { describe("BaselineStore", () => {
it("returns null for a document with no baseline", () => { it("returns null for a key with no baseline", () => {
expect(new BaselineStore(dir).load("notes/chapter-1.md")).toBeNull(); expect(new BaselineStore(dir).load(KEY)).toBeNull();
}); });
it("computes the baseline path as baselines/<docPath>.json", () => { it("computes the baseline path as baselines/<key>.json", () => {
const store = new BaselineStore(dir); const store = new BaselineStore(dir);
expect(store.baselinePath("notes/chapter-1.md")).toBe( expect(store.baselinePath(KEY)).toBe(path.join(dir, "baselines", "a1b2c3.json"));
path.join(dir, "baselines", "notes", "chapter-1.md.json"),
);
}); });
it("save then load round-trips the baseline (including nested docPaths)", () => { it("save then load round-trips the baseline (including the uri identity)", () => {
const store = new BaselineStore(dir); const store = new BaselineStore(dir);
const b = sample(); const b = sample();
store.save(b.docPath, b); store.save(KEY, b);
expect(store.load(b.docPath)).toEqual(b); expect(store.load(KEY)).toEqual(b);
}); });
it("overwrites in place: the newest epoch wins, no history kept", () => { it("overwrites in place: the newest epoch wins, no history kept", () => {
const store = new BaselineStore(dir); const store = new BaselineStore(dir);
store.save("d.md", { docPath: "d.md", text: "v1", capturedAt: "2026-06-11T00:00:00.000Z", reason: "opened" }); store.save(KEY, { uri: "untitled:Untitled-1", text: "v1", capturedAt: "2026-06-11T00:00:00.000Z", reason: "opened" });
store.save("d.md", { docPath: "d.md", text: "v2", capturedAt: "2026-06-11T00:01:00.000Z", reason: "pinned" }); store.save(KEY, { uri: "untitled:Untitled-1", text: "v2", capturedAt: "2026-06-11T00:01:00.000Z", reason: "pinned" });
expect(store.load("d.md")).toEqual({ docPath: "d.md", text: "v2", capturedAt: "2026-06-11T00:01:00.000Z", reason: "pinned" }); expect(store.load(KEY)).toEqual({ uri: "untitled:Untitled-1", text: "v2", capturedAt: "2026-06-11T00:01:00.000Z", reason: "pinned" });
}); });
it("writes pretty JSON with a trailing newline", () => { it("writes pretty JSON with a trailing newline", () => {
const store = new BaselineStore(dir); const store = new BaselineStore(dir);
const b = sample("d.md"); const b = sample();
store.save("d.md", b); store.save(KEY, b);
const raw = fs.readFileSync(store.baselinePath("d.md"), "utf8"); const raw = fs.readFileSync(store.baselinePath(KEY), "utf8");
expect(raw).toBe(JSON.stringify(b, null, 2) + "\n"); expect(raw).toBe(JSON.stringify(b, null, 2) + "\n");
}); });
it("distinct keys are independent files (no collision)", () => {
const store = new BaselineStore(dir);
store.save("k1", sample("file:///a.md"));
store.save("k2", sample("file:///b.md"));
expect(store.load("k1")!.uri).toBe("file:///a.md");
expect(store.load("k2")!.uri).toBe("file:///b.md");
});
}); });