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:
+16
-13
@@ -1,10 +1,12 @@
|
||||
/**
|
||||
* 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-
|
||||
* testable), one file per docPath. INV-19: the storage dir is VS Code's
|
||||
* per-workspace extension storage, NEVER the repo — so the baseline can never
|
||||
* be committed, merged, or read by another rung, and the sidecar / cross-rung
|
||||
* contract (INV-14..17) are untouched by construction.
|
||||
* testable), one file per storage key. INV-19: the storage dir is VS Code's
|
||||
* per-extension GLOBAL storage, NEVER the repo — so the baseline can never be
|
||||
* committed, merged, or read by another rung. F6 works on *any* file, so the
|
||||
* 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 path from "node:path";
|
||||
@@ -12,7 +14,8 @@ import * as path from "node:path";
|
||||
export type BaselineReason = "opened" | "machine-landing" | "pinned";
|
||||
|
||||
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). */
|
||||
text: string;
|
||||
capturedAt: string;
|
||||
@@ -20,23 +23,23 @@ export interface Baseline {
|
||||
}
|
||||
|
||||
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) {}
|
||||
|
||||
/** `<storageDir>/baselines/<repo-relative-docPath>.json` (§6.3). */
|
||||
baselinePath(docPath: string): string {
|
||||
return path.join(this.storageDir, "baselines", `${docPath}.json`);
|
||||
/** `<storageDir>/baselines/<key>.json` — `key` is a filesystem-safe hash (§6.3). */
|
||||
baselinePath(key: string): string {
|
||||
return path.join(this.storageDir, "baselines", `${key}.json`);
|
||||
}
|
||||
|
||||
load(docPath: string): Baseline | null {
|
||||
const p = this.baselinePath(docPath);
|
||||
load(key: string): Baseline | null {
|
||||
const p = this.baselinePath(key);
|
||||
if (!fs.existsSync(p)) return null;
|
||||
return JSON.parse(fs.readFileSync(p, "utf8")) as Baseline;
|
||||
}
|
||||
|
||||
/** Newest epoch wins — overwrite in place, no history (state-not-history, the F3/F4 precedent). */
|
||||
save(docPath: string, baseline: Baseline): void {
|
||||
const p = this.baselinePath(docPath);
|
||||
save(key: string, baseline: Baseline): void {
|
||||
const p = this.baselinePath(key);
|
||||
fs.mkdirSync(path.dirname(p), { recursive: true });
|
||||
fs.writeFileSync(p, JSON.stringify(baseline, null, 2) + "\n", "utf8");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user