spec(f6): v0.2.0 — diff-view on any file + untitled; global storage (#19)
Broaden F6 beyond workspace files: own diffable predicate (file:/untitled:) decoupled from F2 isTracked; baseline → global storage keyed by URI hash; untitled buffers in-memory. Adds §11 amendment + updates §1.7/§6.3/§6.5/§6.7/ INV-19 and the change log. Built in session 0019. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -8,14 +8,15 @@ status: graduated
|
||||
| **Author(s)** | Ben Stull (with Claude) |
|
||||
| **Reviewers / approvers** | Ben Stull |
|
||||
| **Status** | `draft` |
|
||||
| **Version** | v0.1.0 |
|
||||
| **Source artifacts** | Feature `benstull/vscode-cowriting-plugin#17` (F6) · Epic `#1` (closed — F6 is the first post-epic UX feature) · Parent specs: `vscode-cowriting-plugin-content/specs/coauthoring-inner-loop.md`, `specs/coauthoring-attribution.md`, `specs/coauthoring-propose-accept.md`, `specs/coauthoring-cross-rung-format.md` + `specs/coauthoring-sidecar-contract.md` (all graduated) · F5 shipped: `#14` (PR #15, session 0015) · Lineage: `ben.stull/rfc-app#48` |
|
||||
| **Version** | v0.2.0 |
|
||||
| **Source artifacts** | Feature `benstull/vscode-cowriting-plugin#17` (F6) · Epic `#1` (closed — F6 is the first post-epic UX feature) · Parent specs: `vscode-cowriting-plugin-content/specs/coauthoring-inner-loop.md`, `specs/coauthoring-attribution.md`, `specs/coauthoring-propose-accept.md`, `specs/coauthoring-cross-rung-format.md` + `specs/coang-sidecar-contract.md` (all graduated) · F5 shipped: `#14` (PR #15, session 0015) · Lineage: `ben.stull/rfc-app#48` |
|
||||
|
||||
**Change log**
|
||||
|
||||
| Date | Version | Change | By |
|
||||
| --- | --- | --- | --- |
|
||||
| 2026-06-11 | v0.1.0 | Initial draft — brainstorming session 0017 | Ben Stull + Claude |
|
||||
| 2026-06-11 | v0.2.0 | Broaden to **any file + untitled buffers** (not just workspace files); baseline → **global** storage keyed by a URI hash; F6 decoupled from F2 `isTracked` with its own diffable predicate. `#19`, session 0019. See §11. | Ben Stull + Claude |
|
||||
|
||||
---
|
||||
|
||||
@@ -23,7 +24,7 @@ status: graduated
|
||||
|
||||
### 1.1 Executive Summary
|
||||
|
||||
F6 gives the human coauthor the review surface the machine already has. F4
|
||||
F6 gives the human coauthor the review surface the already has. F4
|
||||
made every machine change reviewable-before-landing (proposal diffs); F3
|
||||
paints who owns the current text; but the operator's **own** contribution is
|
||||
visible only as final text. F6 adds a one-gesture **diff-view toggle**: flip
|
||||
@@ -86,7 +87,8 @@ Self-review during coauthoring becomes as cheap as accepting a proposal.
|
||||
|
||||
**In scope:** the baseline lifecycle (initialize at first track, advance at
|
||||
machine landings, pin on demand); persistence of the baseline across window
|
||||
reloads (extension workspace storage); the toggle command + keybinding; a
|
||||
reloads (extension **global** storage — v0.2.0, §11); the toggle command +
|
||||
keybinding; a
|
||||
native `vscode.diff` view (readonly baseline left, the **live document**
|
||||
right — editing continues inside the diff view); test-facing surface; host
|
||||
E2E + unit coverage; manual smoke.
|
||||
@@ -167,10 +169,11 @@ seam owner: `AttributionController.onDidApplyAgentEdit`.
|
||||
now); everything the operator changes from here shows against the pin.
|
||||
- **PUC-4 (survive reload)** Reload the window mid-session → the baseline
|
||||
(and therefore the diff) is the same as before the reload.
|
||||
- **PUC-5 (graceful edges)** An untracked document refuses the toggle with
|
||||
a warning; a missing/ancient baseline is visible in the diff title
|
||||
(epoch + when) and one pin gesture recovers; external file changes (e.g.
|
||||
a git pull) show up as changes — pin recovers there too.
|
||||
- **PUC-5 (graceful edges)** The toggle works on **any** text document — a
|
||||
file in or out of the workspace, or an untitled buffer (v0.2.0, §11); only a
|
||||
non-text-editor focus warns. A missing/ancient baseline is visible in the
|
||||
diff title (epoch + when) and one pin gesture recovers; external file changes
|
||||
(e.g. a git pull) show up as changes — pin recovers there too.
|
||||
|
||||
## 5. UX Layout
|
||||
|
||||
@@ -203,8 +206,9 @@ Parent invariants INV-1..INV-17 carry over unchanged. F6 adds:
|
||||
by pin.
|
||||
- **INV-19 (pure view, repo-free)** The diff view never mutates the
|
||||
document, the sidecar, or attribution state; the baseline snapshot lives
|
||||
in extension workspace storage, never in the repo — `.threads/`, the
|
||||
cross-rung contract (INV-14..17), and `SCHEMA_VERSION` are untouched.
|
||||
in extension **global** storage (`context.globalStorageUri`, v0.2.0 — was
|
||||
workspace storage), never in the repo — `.threads/`, the cross-rung
|
||||
contract (INV-14..17), and `SCHEMA_VERSION` are untouched.
|
||||
|
||||
### 6.2 High-level architecture
|
||||
|
||||
@@ -245,15 +249,22 @@ No sidecar change of any kind (INV-19). The baseline is a private,
|
||||
per-workspace extension artifact:
|
||||
|
||||
```jsonc
|
||||
// <context.storageUri>/baselines/<repo-relative-docPath>.json
|
||||
// v0.2.0 (#19): GLOBAL storage, keyed by a hash of the document URI so ANY
|
||||
// file (in- or out-of-folder) maps to a stable filename:
|
||||
// <context.globalStorageUri>/baselines/<sha256(documentUri)>.json
|
||||
{
|
||||
"docPath": "notes/chapter-1.md",
|
||||
"uri": "file:///abs/path/notes/chapter-1.md", // the document URI (identity)
|
||||
"text": "…full document text at the epoch…",
|
||||
"capturedAt": "ISO-8601",
|
||||
"reason": "opened" | "machine-landing" | "pinned"
|
||||
}
|
||||
```
|
||||
|
||||
**Untitled buffers (v0.2.0):** an unsaved buffer (`untitled:` scheme) has no
|
||||
durable identity to persist against, so its baseline is **in-memory only**
|
||||
(lost on reload) — the same degrade the storage-unavailable path uses. Saving
|
||||
it makes it a `file:` doc with a persisted baseline.
|
||||
|
||||
- **One baseline per document** — the newest epoch wins; no history of past
|
||||
baselines (state, not history — the F3/F4 precedent).
|
||||
- **Owner:** the extension alone; the file lives outside the repo (VS
|
||||
@@ -314,14 +325,14 @@ per-workspace extension artifact:
|
||||
baseline written at the last landing/pin is what the reloaded window
|
||||
diffs against. (The toggle's *tab* does not need to survive reload — the
|
||||
baseline does.)
|
||||
- **PUC-5 (graceful edges):** untracked / non-file schemes → warning, no
|
||||
diff (the F2 `isTracked` rule). Storage unavailable
|
||||
(`context.storageUri` undefined or write failure) → degrade to in-memory
|
||||
baselines + one warning (the session still works; reload survival is
|
||||
lost). Ancient baseline (workspace reopened weeks later) → the diff
|
||||
title shows `since opened <date>` — self-describing — and pin recovers.
|
||||
External-to-session file changes appear as changes (INV-18's stated
|
||||
modulo); pin recovers.
|
||||
- **PUC-5 (graceful edges):** v0.2.0 (§11) — the diffable predicate is any
|
||||
`file:` or `untitled:` doc, so only a non-text-editor focus → warning, no
|
||||
diff. Untitled buffers diff in-memory (no persistence). Storage unavailable
|
||||
(`context.globalStorageUri` write failure) → degrade to in-memory baselines
|
||||
+ one warning (the session still works; reload survival is lost). Ancient
|
||||
baseline (reopened weeks later) → the diff title shows `since opened <date>`
|
||||
— self-describing — and pin recovers. External-to-session file changes
|
||||
appear as changes (INV-18's stated modulo); pin recovers.
|
||||
|
||||
### 6.6 Non-functional requirements & cross-cutting concerns
|
||||
|
||||
@@ -341,7 +352,7 @@ maintain.
|
||||
| --- | --- | --- |
|
||||
| **Baseline epoch (the load-bearing fork)** | **Machine-landing–anchored auto-baseline**: initialize at first track, advance on every successful seam application, plus a human **pin** gesture. INV-9/10 make the resulting diff operator-authored by construction (INV-18) | **turn start** (under F4 propose-by-default the document doesn't change at turn start — the epoch collapses into the accept; not a distinct moment anymore); **last save** (duplicates native `compareWithSaved` / local history / timeline — #17's complement-not-duplicate rule); **fixed session-start** (machine landings bleed into the "my changes" diff — breaks INV-18); **multiple selectable baselines** (a selector UI for epochs nobody asked for — YAGNI; pin covers the operator-chosen case; selectable is additive later) |
|
||||
| Pre-accept operator edits absorbed by an advance | Accepted trade-off: the diff reads "my changes since the machine last landed", not "my whole session"; pin gives manual control | **baseline splicing** (apply the accepted replacement *into* the baseline so older operator edits stay visible — needs current→baseline range mapping with murky edge cases when the machine edits operator-authored-since-baseline text; deferred additive refinement, §9) |
|
||||
| Snapshot home | **Extension workspace storage** (`context.storageUri`) via vscode-free `BaselineStore`; survives reload; zero repo/contract footprint (INV-19) | **sidecar `.threads/`** (full document text per accept dwarfs the artifact and churns git diffs — INV-2; drags INV-14..17 into a local view-state concern no other rung needs); **gitignored repo cache** (litters the operator's working tree; needs `.gitignore` management; risks accidental commit); **in-memory only** (baseline dies at reload — the post-reload diff is empty and lies) |
|
||||
| Snapshot home | **Extension global storage** (`context.globalStorageUri`, v0.2.0 — was workspace storage) via vscode-free `BaselineStore`, keyed by a URI hash so it works for any file; survives reload; zero repo/contract footprint (INV-19) | **sidecar `.threads/`** (full document text per accept dwarfs the artifact and churns git diffs — INV-2; drags INV-14..17 into a local view-state concern no other rung needs); **gitignored repo cache** (litters the operator's working tree; needs `.gitignore` management; risks accidental commit); **in-memory only** (baseline dies at reload — the post-reload diff is empty and lies) |
|
||||
| Whose changes the diff shows | Whole-document diff; operator-authorship is INV-18's corollary, **no filtering** | **F3-attribution-filtered diff** (F3 is state-not-history — cannot reconstruct before-text; #17 itself notes this); **per-author diff lenses** (no user story; heavy) |
|
||||
| View primitive | **Native `vscode.diff`**: readonly `cowriting-baseline:` virtual doc left, the **live document** right (editable in place; decorations/threads keep working) | **decoration-based inline diff** in the normal editor (re-implements diff rendering VS Code already has; F4's fenced-diff primitive is for proposals, not whole documents); **webview diff** (bespoke UI against the F2–F5 native-primitives precedent) |
|
||||
| Machine-landing signal | Additive `onDidApplyAgentEdit` event on the seam owner — INV-9's single ingress is the single signal source | **call-site wiring** (ProposalController + the hidden command both call `advance` — two sites today, N tomorrow, and each new seam driver must remember); **watching attribution records** (indirect, save-coupled, reconstructive) |
|
||||
@@ -476,8 +487,52 @@ performed once on this machine.
|
||||
enters the document, INV-9/10). **Pin** — the human gesture resetting the
|
||||
baseline to now. **Advance** — the automatic baseline re-capture at a
|
||||
machine landing (INV-18).
|
||||
- Feature #17 (F6) · Epic #1 (closed) · F5 #14 (PR #15) · F4 #12 (PR #13) ·
|
||||
F3 #6 (PR #7) · F2 #4 (PR #5) · parent specs `coauthoring-inner-loop.md`,
|
||||
`coauthoring-attribution.md`, `coauthoring-propose-accept.md`,
|
||||
`coauthoring-cross-rung-format.md`, `coauthoring-sidecar-contract.md` ·
|
||||
lineage `ben.stull/rfc-app#48`.
|
||||
- Feature #17 (F6) · Epic #1 · F5 #14 (PR #15) · F4 #12 (PR #13) ·
|
||||
F3 #6 (PR #7) · F2 #4 (PR #5) · v0.2.0 follow-up #19 · parent specs
|
||||
`coauthoring-inner-loop.md`, `coauthoring-attribution.md`,
|
||||
`coauthoring-propose-accept.md`, `coauthoring-cross-rung-format.md`,
|
||||
`coauthoring-sidecar-contract.md` · lineage `ben.stull/rfc-app#48`.
|
||||
|
||||
---
|
||||
|
||||
## 11. Amendment v0.2.0 — diff-view on any file (#19, session 0019)
|
||||
|
||||
v0.1.0 scoped F6 to **tracked** documents (a saved file inside the opened
|
||||
workspace folder), reusing F2's `isTracked`. In manual testing this surfaced as
|
||||
the toggle refusing untitled buffers and out-of-folder files — friction with no
|
||||
real cause, since **F6 needs no `.threads/` sidecar**, only a stable document
|
||||
identity and a storage home. This amendment broadens F6 to **any** text
|
||||
document; the inner-loop ladder F2–F5 is unchanged.
|
||||
|
||||
**Deltas (supersede the matching v0.1.0 text above):**
|
||||
|
||||
- **Diffable predicate (replaces the `isTracked` gate for F6).** A document is
|
||||
diffable iff its scheme is `file:` **or** `untitled:`. F6 no longer consults
|
||||
the workspace root. The two commands are therefore registered
|
||||
**workspace-independently** — `DiffViewController` is constructed before the
|
||||
no-folder activation branch, so `cowriting.toggleDiffView` /
|
||||
`cowriting.pinDiffBaseline` are live (never stubbed) even with no folder open.
|
||||
Only a non-text-editor focus warns.
|
||||
- **Snapshot home = global storage (replaces workspace storage in §2/§6.3/§6.7
|
||||
and INV-19).** Baselines live under `context.globalStorageUri`
|
||||
(machine-wide, always present, independent of any workspace), keyed by
|
||||
`sha256(documentUri)` — one filesystem-safe key for in-folder, out-of-folder,
|
||||
and (transiently) untitled docs. Still never the repo; INV-19 holds verbatim
|
||||
apart from the storage-root word. `Baseline.docPath` → `Baseline.uri`.
|
||||
- **Untitled buffers are in-memory only.** An `untitled:` doc has no durable
|
||||
identity, so its baseline is held in memory and lost on reload (the
|
||||
storage-unavailable degrade); the moment it is saved it becomes a `file:` doc
|
||||
with a persisted baseline. The diff title carries an `(unsaved)` marker.
|
||||
- **Machine-landing advance wiring is unchanged in scope.** The seam fires only
|
||||
on workspace files, so `onDidApplyAgentEdit → advance` stays in the with-root
|
||||
branch; F6's baseline capture-on-open is self-wired in the controller for any
|
||||
diffable doc.
|
||||
|
||||
**Unchanged:** the view primitive (native `vscode.diff`, live doc on the
|
||||
right), the epoch model (`opened`/`machine-landing`/`pinned` + pin), INV-18, the
|
||||
no-LLM host-E2E tier. **Tests:** host E2E adds an out-of-workspace-file case
|
||||
(persisted in global storage) and an untitled-buffer case (in-memory, not
|
||||
persisted), plus a no-folder activation case proving the toggle works folder-
|
||||
less; `BaselineStore` unit tests re-keyed.
|
||||
|
||||
**Shipped:** `#19` (session 0019) — see that session's transcript.
|
||||
|
||||
Reference in New Issue
Block a user