1 Commits

2 changed files with 261 additions and 161 deletions
+18 -1
View File
@@ -9,10 +9,27 @@ status: draft
| **Type** | Throwaway discovery **prototype** (handbook §5.3) — code is disposable | | **Type** | Throwaway discovery **prototype** (handbook §5.3) — code is disposable |
| **Goal** | Prove the API-dependent design assumptions on the **real** VS Code surfaces, cheaply, **before** the full build — so post-ship iteration is polish, not design | | **Goal** | Prove the API-dependent design assumptions on the **real** VS Code surfaces, cheaply, **before** the full build — so post-ship iteration is polish, not design |
| **Non-goal** | Production code, real Claude/SDK wiring (except E4), persistence durability, polish, breadth | | **Non-goal** | Production code, real Claude/SDK wiring (except E4), persistence durability, polish, breadth |
| **Status** | `draft` | | **Status** | `draft`**in progress: E1/E3/E5 done, E2/E4 remain** |
--- ---
## Status (2026-07-01, session 0063)
Three of the five experiments are built and passed in `vscode-cowriting-prototype`:
- **E1 — PASS with pivot.** VS Code does **not** render extension CodeLens inside
a diff editor; per-hunk CodeLens works in a normal editor, and tweak-then-Keep
attributes machine words vs. writer edits correctly. Folded back into the
Solution Design as **D18** (the review surface is the live buffer itself,
in-buffer pending changes — see `coauthoring-native-surfaces.md` v0.2.0).
- **E3 — PASS.** The `markdown-it` contribution renders author-colored marks +
deletions in the built-in preview, theme-aware, with a working toggle.
- **E5 — PASS.** Title-bar + status-bar entry points read as obvious without the
Source Control pane.
- **E2 (baseline router: QuickDiff vs git `HEAD` + snapshot) and E4 (comment →
reply → offer → proposal) are not built yet** — now the riskiest unvalidated
assumptions and the spike's remaining work.
## Why this exists ## Why this exists
Static mockups validated the *look* and *state coverage*; they prove **nothing** Static mockups validated the *look* and *state coverage*; they prove **nothing**
+243 -160
View File
@@ -8,14 +8,15 @@ status: draft
| **Author(s)** | Ben Stull (with Claude) | | **Author(s)** | Ben Stull (with Claude) |
| **Reviewers / approvers** | Ben Stull | | **Reviewers / approvers** | Ben Stull |
| **Status** | `draft` | | **Status** | `draft` |
| **Version** | v0.1.0 | | **Version** | v0.2.0 |
| **Source artifacts** | North-star architecture spec (design-only) for human↔machine markdown coediting. Reference: VS Code's native review surfaces (SCM/QuickDiff, diff editor, multi-diff editor, Markdown preview, Comments). Supersedes: none. | | **Source artifacts** | North-star architecture spec for human↔machine markdown coediting. Reference: VS Code's native review surfaces (SCM/QuickDiff, diff editor, Markdown preview, Comments). Inputs: the shipped plugin (F1F12), the rung-2 feasibility spike (`vscode-cowriting-prototype`), `coauthoring-feasibility-spike.md`. Supersedes: none. |
**Change log** **Change log**
| Date | Version | Change | By | | Date | Version | Change | By |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| 2026-06-27 | v0.1.0 | Initial draft — brainstorming session 0062 | Ben Stull + Claude | | 2026-06-27 | v0.1.0 | Initial draft — brainstorming session 0062 | Ben Stull + Claude |
| 2026-07-01 | v0.2.0 | Session 0063 recommendation folded in: review surface is **in-buffer pending changes + per-hunk CodeLens** (supersedes the multi-diff Keep/Undo surface — D18, resolves Q2/Q7, amends INV-5/INV-12); **Q1 resolved — evolve the shipped extension in place** (D17); **comments-first ask surface** (D19); D11 updated to shipped reality (D21); rung-2 spike findings (E1/E3/E5) recorded | Ben Stull + Claude |
--- ---
@@ -110,9 +111,9 @@ between blind trust and tedious manual reconciliation.
staying in control — seeing what changed, accepting/rejecting changes staying in control — seeing what changed, accepting/rejecting changes
individually, knowing authorship, reading the rendered result, and following the individually, knowing authorship, reading the rendered result, and following the
machine's progress — entirely within their editor. machine's progress — entirely within their editor.
- **Out of scope:** committing a specific product packaging or release; multi-file / - **Out of scope:** marketplace release; multi-file / whole-project machine
whole-project machine orchestration; a server or cloud backend. This is a orchestration; a server or cloud backend. (v0.1.0 also deferred the packaging
**design-only north star** (see §2): packaging and delivery decisions are deferred. decision; v0.2.0 resolves it — the shipped extension evolves in place, D17/§13-Q1.)
- **Non-goals:** replacing the writer's judgment (the machine proposes; the human - **Non-goals:** replacing the writer's judgment (the machine proposes; the human
disposes); non-markdown document formats in v1; a real-time multi-human disposes); non-markdown document formats in v1; a real-time multi-human
collaboration server. collaboration server.
@@ -122,8 +123,9 @@ between blind trust and tedious manual reconciliation.
- **Assumptions:** the writer works in an editor with native diff, source-control, - **Assumptions:** the writer works in an editor with native diff, source-control,
multi-change review, Comments, and Markdown-preview surfaces (VS Code is the multi-change review, Comments, and Markdown-preview surfaces (VS Code is the
target); a capable machine coauthor is available to run locally. target); a capable machine coauthor is available to run locally.
- **Constraints:** serverless and git-native (no external store); the writer's - **Constraints:** serverless and git-native (no external store); nothing the
document is never mutated without their explicit acceptance; no secret bytes in machine proposes ever *lands* without the writer's explicit acceptance — a
pending suggestion is always revertible in one gesture; no secret bytes in
any persisted artifact. any persisted artifact.
- **Dependencies:** a local machine-coauthor runtime; the editor's stable extension - **Dependencies:** a local machine-coauthor runtime; the editor's stable extension
APIs for the surfaces above; the built-in **Git extension API** (to read `HEAD` for APIs for the surfaces above; the built-in **Git extension API** (to read `HEAD` for
@@ -154,13 +156,15 @@ Scenario: BUC-2 — keep or reject, change by change
Given the machine has proposed changes to my document Given the machine has proposed changes to my document
When I review them When I review them
Then I can keep or discard each change individually, or all at once Then I can keep or discard each change individually, or all at once
And nothing the machine proposed alters my document until I keep it And nothing the machine proposed lands in my document until I keep it
discarding a change restores my text exactly as it was
``` ```
- **BUC-2 acceptance criteria:** no proposed change alters the document until the - **BUC-2 acceptance criteria:** no proposed change *settles* until the writer
writer keeps it; each change is independently keepable/discardable; the writer can keeps it, and rejecting one restores the prior text exactly; each change is
**edit a proposed change before keeping it**, and those edits are attributed to the independently keepable/discardable; the writer can **edit a proposed change
writer; keep-all and reject-all exist. before keeping it**, and those edits are attributed to the writer; keep-all and
reject-all exist.
**BUC-3 — As a writer, I can tell who wrote which parts of my document and read a **BUC-3 — As a writer, I can tell who wrote which parts of my document and read a
clean rendered version, so that I can judge the result as finished prose.** clean rendered version, so that I can judge the result as finished prose.**
@@ -227,7 +231,7 @@ already know:
| What the writer needs | Native surface that delivers it | What it gives them, for free | | What the writer needs | Native surface that delivers it | What it gives them, for free |
| --- | --- | --- | | --- | --- | --- |
| See what changed (BUC-1) | Source-control gutter change bars + the **native diff editor** against a virtual *baseline* document | The exact diff experience they use for everything else: inline/side-by-side, navigation, accessibility | | See what changed (BUC-1) | Source-control gutter change bars + the **native diff editor** against a virtual *baseline* document | The exact diff experience they use for everything else: inline/side-by-side, navigation, accessibility |
| Keep/reject — and **tweak** — changes (BUC-2) | The **multi-diff review editor** with per-change **Keep/Undo** and a **writable proposed side** | One native surface to sweep all of the machine's changes — editing the proposal in place before keeping or discarding each (INV-12) | | Keep/reject — and **tweak** — changes (BUC-2) | **Pending changes in the live editor** — proposed edits land decorated-but-unsettled in the buffer, with per-hunk **✓ Keep / ✗ Reject** CodeLens (D18) | The paradigm the platform's own AI edit sessions use: review the change *in the document*, tweak it by just typing (INV-12), revert it with one gesture (INV-5) |
| Authorship + rendered result (BUC-3) | The **built-in Markdown preview**, extended via a `markdown-it` plugin to inject authorship + change marks | Native rendering, scroll-sync, theming, and cooperation with the writer's other Markdown extensions | | Authorship + rendered result (BUC-3) | The **built-in Markdown preview**, extended via a `markdown-it` plugin to inject authorship + change marks | Native rendering, scroll-sync, theming, and cooperation with the writer's other Markdown extensions |
| Discuss a passage — and have the machine respond (BUC-5) | The **Comments API** — threaded comments where, on a coedited document, the machine replies in-thread and offers an edit | The familiar review-comment experience, in the gutter, that can turn a remark into a proposed change | | Discuss a passage — and have the machine respond (BUC-5) | The **Comments API** — threaded comments where, on a coedited document, the machine replies in-thread and offers an edit | The familiar review-comment experience, in the gutter, that can turn a remark into a proposed change |
| Follow + stop the machine (BUC-4) | A progress **notification** + a shared **OutputChannel**, with Cancel | Live, stoppable, legible machine work | | Follow + stop the machine (BUC-4) | A progress **notification** + a shared **OutputChannel**, with Cancel | Live, stoppable, legible machine work |
@@ -252,12 +256,16 @@ of building review machinery without getting the polish, and asks the writer to
learn a foreign surface. **Delivering through the editor's native surfaces** gets learn a foreign surface. **Delivering through the editor's native surfaces** gets
that behavior for free, makes coediting feel built-in, and shrinks what must be that behavior for free, makes coediting feel built-in, and shrinks what must be
built and maintained to a thin layer of wiring over proven cores. The cost is built and maintained to a thin layer of wiring over proven cores. The cost is
accepting the platform's affordances where they differ from a bespoke one, plus a accepting the platform's affordances where they differ from a bespoke one. (The
small stable-API boundary on per-change Keep/Undo (§6.10, §13-Q2). v0.1.0 stable-API worry on per-change Keep/Undo resolved the other way: the spike
showed CodeLens does not render in a diff editor at all, which — together with the
shipped F12 experience — moved the review surface into the live buffer, D18.)
**Decision posture:** this is a **design-only north star**. It commits to the **Decision posture:** this is a **north star with a committed delivery vehicle**.
architecture and the native-surface mapping; it does **not** commit to a product It commits to the architecture and the native-surface mapping, and (as of v0.2.0,
packaging or a delivery sequence — those are taken later (§7.1, §13-Q1). D17) to the packaging: **the shipped extension evolves in place onto these
surfaces** — its pure cores are retained, its bespoke webview surfaces sunset
(§6.10, §7.1).
**Solution scope.** *In:* the architecture, surface mapping, data model, contracts, **Solution scope.** *In:* the architecture, surface mapping, data model, contracts,
and per-use-case design for native, in-editor human↔machine markdown coediting, and per-use-case design for native, in-editor human↔machine markdown coediting,
@@ -292,12 +300,16 @@ Scenario: PUC-1 — see the diff against the baseline checkpoint (realizes BUC-1
Scenario: PUC-2 — keep/reject the machine's proposed changes (realizes BUC-2) Scenario: PUC-2 — keep/reject the machine's proposed changes (realizes BUC-2)
Given I ask the machine to revise the document (or a selection) Given I ask the machine to revise the document (or a selection)
When the turn completes When the turn completes
Then the proposed changes open in a multi-diff review (baseline proposed) Then each proposed change appears in my editor as a pending, decorated change
And the proposed side is editable, so I can tweak the machine's wording before keeping it (proposed words marked by author color, replaced words struck) visible in
And each change offers Keep / Undo (Accept / Reject) place but not settled
And toolbar actions offer Keep all / Reject all And each change carries Keep / Reject actions (CodeLens above the change)
And my document is unchanged until I keep a change And Keep all / Reject all are available (CodeLens menu, title bar, status bar)
And I can tweak the proposed wording by simply typing in the pending text
And rejecting a change restores my text exactly as it was
And on keep, the machine's words are attributed to the machine and my tweaks to me And on keep, the machine's words are attributed to the machine and my tweaks to me
And "Review Changes" opens the native diff (baseline live) to sweep everything
at once
``` ```
```gherkin ```gherkin
@@ -374,8 +386,11 @@ panel to wireframe. Described in text (source of truth):
the document is coedited, title-bar buttons (**Review Changes**, **Ask Claude**, the document is coedited, title-bar buttons (**Review Changes**, **Ask Claude**,
**Toggle Annotations**, and in snapshot mode **Mark Changes as Reviewed**) and the **Toggle Annotations**, and in snapshot mode **Mark Changes as Reviewed**) and the
`✦ Coediting · N changes` status-bar item are the everyday entry points — visible `✦ Coediting · N changes` status-bar item are the everyday entry points — visible
without going anywhere. The review also **auto-opens** on new proposals, and the without going anywhere. **Ask Claude** opens a comment thread (on the selection if
live-progress notification carries a *Review* action. there is one, else on the whole document) — the comment box *is* the ask input
(D19). New proposals land as pending changes **in the buffer in front of the
writer** (no navigation at all), and the live-progress notification carries a
*Review* action.
- **Source Control gutter & viewlet (optional, for git users).** A dedicated - **Source Control gutter & viewlet (optional, for git users).** A dedicated
source-control entry, *"Cowriting,"* coexists with Git: its quick-diff provider source-control entry, *"Cowriting,"* coexists with Git: its quick-diff provider
draws the familiar change bars in the editor gutter against the baseline checkpoint, draws the familiar change bars in the editor gutter against the baseline checkpoint,
@@ -384,16 +399,26 @@ panel to wireframe. Described in text (source of truth):
— never the required path (INV-13). — never the required path (INV-13).
- **Native diff editor.** The writer's normal diff (`baseline ⟷ live`), with the - **Native diff editor.** The writer's normal diff (`baseline ⟷ live`), with the
platform's inline/side-by-side toggle, navigation, and accessibility. platform's inline/side-by-side toggle, navigation, and accessibility.
- **Multi-diff review editor.** When the machine proposes edits, a multi-diff editor - **Pending changes in the editor (the accept/reject surface, D18).** When the
opens showing `baseline ⟷ proposed`, each change carrying **Keep/Undo**; a machine proposes edits, each change lands **in the live buffer** as a pending,
toolbar carries **Keep all / Reject all**. This is the accept/reject surface. decorated change — proposed words marked in the machine's author color, replaced
words shown struck — with per-hunk **✓ Keep / ✗ Reject** CodeLens (and a
Keep-all/Reject-all menu). The writer tweaks a pending change by **typing in it**;
Reject restores the prior text exactly. This is the same review-in-the-document
paradigm the platform's own AI edit sessions use, so it reads as native even
though the decorations are the extension's. (The v0.1.0 multi-diff Keep/Undo
surface is superseded — the spike showed CodeLens cannot render in a diff editor,
§7.1/D18.)
- **Built-in Markdown preview.** The writer's normal "Open Preview to the Side"; - **Built-in Markdown preview.** The writer's normal "Open Preview to the Side";
with annotations on, authorship colors and change marks appear *inside* it. A with annotations on, authorship colors and change marks appear *inside* it. A
command/setting toggles annotations. command/setting toggles annotations.
- **Comments gutter.** Region threads via the native Comments API (create on - **Comments gutter — the primary ask surface (D19).** Region threads via the
selection, reply, resolve). On a coedited document, the machine replies in-thread native Comments API (create on selection, reply, resolve). The comment box is the
one native multi-line input the editor offers, so it is **how the writer asks for
anything**: an imperative ask ("tighten this paragraph") and a discussion remark
are the same gesture. On a coedited document, the machine replies in-thread
and offers an edit (BUC-5 / PUC-8); the offer, when accepted, becomes a pending and offers an edit (BUC-5 / PUC-8); the offer, when accepted, becomes a pending
proposed change reviewed in the multi-diff. A comment can be **started from the proposed change reviewed in the buffer (PUC-2). A comment can be **started from the
rendered preview** (select text there → "Comment on this"): the selection is mapped rendered preview** (select text there → "Comment on this"): the selection is mapped
back to the source range and a **native** thread is created — the preview shows a back to the source range and a **native** thread is created — the preview shows a
💬 marker that deep-links to it, but the thread itself renders in the gutter / 💬 marker that deep-links to it, but the thread itself renders in the gutter /
@@ -419,7 +444,7 @@ panel to wireframe. Described in text (source of truth):
### 5.2 Screen: native surfaces (serves PUC-1…PUC-6) ### 5.2 Screen: native surfaces (serves PUC-1…PUC-6)
Covered in the §5 text above. No custom screen; states map to platform states (diff Covered in the §5 text above. No custom screen; states map to platform states (diff
editor empty/identical, multi-diff empty, preview loading/error) plus the editor empty/identical, no pending changes, preview loading/error) plus the
coauthoring **displaced** state surfaced in the thread / proposed-change listing. coauthoring **displaced** state surfaced in the thread / proposed-change listing.
## 6. Technical Design ## 6. Technical Design
@@ -442,9 +467,15 @@ coauthoring **displaced** state surfaced in the thread / proposed-change listing
`threads` share one envelope; **unknown fields are preserved** on read/merge, so `threads` share one envelope; **unknown fields are preserved** on read/merge, so
the artifact round-trips to an outer review forge (e.g. Gitea/GitHub) without the artifact round-trips to an outer review forge (e.g. Gitea/GitHub) without
loss. loss.
- **INV-5 — proposed changes are pending until kept:** a machine-proposed edit does - **INV-5 — proposed changes are pending until kept:** a machine-proposed edit
**not** mutate the document until the writer keeps it (Keep in the multi-diff, or never **settles** until the writer keeps it. It appears in the live buffer as a
Accept). The machine proposes; the human disposes. visibly pending, decorated change (D18) — but it is fully revertible: **Reject
restores the prior text exactly**, the model retains each proposal's original,
and nothing is attributed as landed machine work until Keep. The machine
proposes; the human disposes. *(Amended in v0.2.0: v0.1.0 kept the buffer
byte-untouched until Keep; the guarantee is now "nothing lands irrevocably,"
which is what the writer actually needs — and what makes tweak-by-typing
possible, INV-12.)*
- **INV-6 — pure cores stay vscode-free:** attribution, anchoring, merge, diffing, - **INV-6 — pure cores stay vscode-free:** attribution, anchoring, merge, diffing,
and the progress reducer are pure modules with no `vscode` import, unit-tested in and the progress reducer are pure modules with no `vscode` import, unit-tested in
isolation; controllers are the only `vscode`-aware layer, so surfaces can change isolation; controllers are the only `vscode`-aware layer, so surfaces can change
@@ -486,24 +517,28 @@ coauthoring **displaced** state surfaced in the thread / proposed-change listing
remains, so re-entering restores state. remains, so re-entering restores state.
- **INV-11 — the native diff is author-blind:** the native diff editor shows *what* - **INV-11 — the native diff is author-blind:** the native diff editor shows *what*
changed against the baseline, not *who* changed it (the diff API has no per-author changed against the baseline, not *who* changed it (the diff API has no per-author
color scoping — §6.7). Authorship (human vs. machine) is shown on a surface the color scoping — §6.7). Authorship (human vs. machine) is shown on surfaces the
extension renders: the annotated preview (and optionally a decoration-based extension renders: the annotated preview and the in-editor author-colored
track-changes editor), never by recoloring the native diff. track-changes decorations (D21), never by recoloring the native diff.
- **INV-13 — no surface requires the Source Control pane:** the primary, - **INV-13 — no surface requires the Source Control pane:** the primary,
always-visible entry points for a coedited document are the **editor title-bar always-visible entry points for a coedited document are the **editor title-bar
actions** and a **status-bar item** (both gated on `cowriting.isCoediting`), and the actions** and a **status-bar item** (both gated on `cowriting.isCoediting`), and
review **auto-opens** on new machine proposals. The SCM viewlet entry and the gutter new machine proposals land **in the buffer in front of the writer** (D18) — the
review requires no navigation at all. The SCM viewlet entry and the gutter
dirty-diff are additional native affordances, not the required path — a writer dirty-diff are additional native affordances, not the required path — a writer
unfamiliar with git / Source Control never has to find them to coedit, review, or unfamiliar with git / Source Control never has to find them to coedit, review, or
settle a checkpoint. settle a checkpoint.
- **INV-12 — proposed changes are editable before they are kept:** the review - **INV-12 — proposed changes are editable before they are kept:** a pending
surface presents the machine's proposal on a **writable** side, so the writer can change sits in the live buffer, so the writer tweaks it by **typing directly in
tweak it in place before keeping it (the common "close, but a few human edits" it** (the common "close, but a few human edits" case) — no separate proposal
case). The live document stays untouched until Keep (INV-5 still holds); on Keep, document, no mode. Nothing settles until Keep (INV-5); on Keep, the words the
the words the machine proposed are attributed to the machine and the writer's machine proposed are attributed to the machine and the writer's tweaks to the
tweaks to the writer — so authorship is correct **at the moment the change is writer — by diffing *final ↔ the machine's original proposal ↔ baseline* — so
made**, never reconstructed afterward. Editing happens at review time; once kept, authorship is correct **at the moment the change is made**, never reconstructed
the text is just text (no lingering "trapped proposal" state to untangle). afterward. Once kept, the text is just text (no lingering "trapped proposal"
state to untangle). A pending change re-anchors around the writer's edits; one
displaced by heavy rewriting collapses to a whole-proposal Keep/Reject (D20,
INV-1 discipline).
### 6.2 High-level architecture ### 6.2 High-level architecture
@@ -523,7 +558,7 @@ flowchart LR
subgraph Ctrls["Thin controllers (vscode-aware)"] subgraph Ctrls["Thin controllers (vscode-aware)"]
SC[SourceControl + QuickDiffProvider] SC[SourceControl + QuickDiffProvider]
BP[Baseline provider<br/>TextDocumentContentProvider] BP[Baseline provider<br/>TextDocumentContentProvider]
PR[Review controller<br/>multi-diff Keep/Undo] PR[Pending-change controller<br/>in-buffer decorations + CodeLens]
PV[Preview extension<br/>extendMarkdownIt] PV[Preview extension<br/>extendMarkdownIt]
TC[ThreadController<br/>Comments API] TC[ThreadController<br/>Comments API]
LP[liveProgress relay] LP[liveProgress relay]
@@ -532,7 +567,7 @@ flowchart LR
subgraph Native["Native VS Code surfaces"] subgraph Native["Native VS Code surfaces"]
GUT[Gutter dirty-diff + SCM viewlet] GUT[Gutter dirty-diff + SCM viewlet]
DIFF[Native diff editor] DIFF[Native diff editor]
MDIFF[Multi-diff review + Keep/Undo] MDIFF[Live editor buffer<br/>pending changes + CodeLens]
MDPREV[Built-in Markdown preview] MDPREV[Built-in Markdown preview]
COMM[Comments gutter/peek] COMM[Comments gutter/peek]
NOTE[Notification + OutputChannel] NOTE[Notification + OutputChannel]
@@ -555,9 +590,10 @@ flowchart LR
- **Baseline provider** — a `TextDocumentContentProvider` for the - **Baseline provider** — a `TextDocumentContentProvider` for the
`cowriting-baseline:` scheme; serves the pinned baseline text for any document URI `cowriting-baseline:` scheme; serves the pinned baseline text for any document URI
from the out-of-repo baseline store (INV-7). from the out-of-repo baseline store (INV-7).
- **Review controller** — turns the machine's emitted edits into pending proposed - **Pending-change controller** — turns the machine's emitted edits into pending
changes, opens the multi-diff review, and applies Keep/Undo by delegating to the proposed changes, lands them in the live buffer as decorated pending text
proposed-change model; honors INV-5. (retaining each proposal's original for exact revert), and renders per-hunk
Keep/Reject CodeLens; Keep settles + attributes, Reject restores; honors INV-5.
- **Preview extension** — contributes `markdown-it` so the pure `renderAnnotations` - **Preview extension** — contributes `markdown-it` so the pure `renderAnnotations`
transform runs inside the built-in preview; owns no webview. transform runs inside the built-in preview; owns no webview.
- **Sidecar router + store** — persistence: a repo-committed sidecar when the - **Sidecar router + store** — persistence: a repo-committed sidecar when the
@@ -608,21 +644,23 @@ state (INV-7), not durable coauthoring metadata.
(drives gutter bars). Registered on the "Cowriting" `SourceControl`. (drives gutter bars). Registered on the "Cowriting" `SourceControl`.
- **Machine-edit seam** — the single boundary where machine text enters the model: - **Machine-edit seam** — the single boundary where machine text enters the model:
`proposeAgentEdit(docUri, range, newText, provenance)` → produces **pending** `proposeAgentEdit(docUri, range, newText, provenance)` → produces **pending**
`proposals[]` entries (never mutates the doc — INV-5). Block/word granularity via `proposals[]` entries; nothing settles until Keep (INV-5). Block/word granularity via
the pure diff engine (`diffToBlockHunks` / `wordEditHunks`): a block (paragraph, the pure diff engine (`diffToBlockHunks` / `wordEditHunks`): a block (paragraph,
header, list item, fenced code) is the *decision* unit a writer keeps or rejects; header, list item, fenced code) is the *decision* unit a writer keeps or rejects;
the word diff inside it is the *attribution* unit, so keeping a block attributes the word diff inside it is the *attribution* unit, so keeping a block attributes
only the words that actually changed. only the words that actually changed.
- **Review controller** — `openReview(docUri)` opens the multi-diff - **Pending-change controller** — on new proposals, lands each **optimistically in
(`baseline ⟷ proposed`); `keep(proposalId)` = Accept (applies the edit, attributes the live buffer** as decorated pending text (the model retains the proposal's
changed words); `undo(proposalId)` = Reject (drops it); `keepAll()` / `undoAll()` `original` for exact revert) and renders per-hunk **✓ Keep / ✗ Reject** CodeLens
= Keep/Reject all (apply descending, skip + report any displaced change). The (D18). `keep(proposalId)` = Accept: settles the change and attributes it —
**proposed side is a writable scratch document** (untitled / in-memory) seeded with machine-proposed words to the machine, any writer tweaks (typed directly into the
the machine's text (INV-12); the left side (baseline) is read-only. `keep` diffs pending text, INV-12) to the writer, by comparing *final ↔ the machine's original
the **final** (possibly writer-tweaked) proposal against the baseline, applies it proposal ↔ baseline*. `reject(proposalId)` restores the retained original
to the live document, and attributes machine-proposed words to the machine and exactly. `keepAll()` / `rejectAll()` apply descending, skip + report any
writer tweaks to the writer by comparing *final ↔ the machine's original proposal*. displaced change. Pending changes re-anchor around writer edits; a displaced one
The live document is untouched until `keep` (INV-5). collapses to whole-proposal Keep/Reject (D20). `openReview(docUri)` opens the
**native diff editor** (`baseline ⟷ live`) as the sweep-everything reference
view — read-only, author-blind (INV-11); decisions stay in the buffer.
- **Preview extension** — `extendMarkdownIt(md) → md.use(cowritingPlugin)`; the - **Preview extension** — `extendMarkdownIt(md) → md.use(cowritingPlugin)`; the
plugin invokes the pure `renderAnnotations(tokens, attributions, proposals, plugin invokes the pure `renderAnnotations(tokens, attributions, proposals,
options)` transform, gated by the `cowriting.annotations` setting/command. options)` transform, gated by the `cowriting.annotations` setting/command.
@@ -637,8 +675,12 @@ state (INV-7), not durable coauthoring metadata.
`isCoediting(docUri)` before attaching a surface: the QuickDiffProvider returns no `isCoediting(docUri)` before attaching a surface: the QuickDiffProvider returns no
original for non-coedited docs, the comment-response controller ignores them, and original for non-coedited docs, the comment-response controller ignores them, and
the annotation/edit commands are disabled (`when` clause `cowriting.isCoediting`). the annotation/edit commands are disabled (`when` clause `cowriting.isCoediting`).
- **ThreadController + comment-response**create/reply/resolve via the Comments - **ThreadController + comment-response — the primary ask surface (D19)** —
API. On a coedited document only, a new comment fires `runEditTurn` to **reply in create/reply/resolve via the Comments API. The comment box is the editor's one
native multi-line input, so **asking is commenting**: the title-bar **Ask
Claude** action just creates a thread (on the selection, else whole-document) and
focuses its input; no bespoke input surface exists. On a coedited document only,
a new comment fires `runEditTurn` to **reply in
the thread** and, when the remark implies a change, **offer an edit**; an accepted the thread** and, when the remark implies a change, **offer an edit**; an accepted
offer routes through the machine-edit seam to a **pending** proposed change offer routes through the machine-edit seam to a **pending** proposed change
(INV-5) — the comment never edits the document directly. The reply is posted as a (INV-5) — the comment never edits the document directly. The reply is posted as a
@@ -677,34 +719,37 @@ sequenceDiagram
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
actor U as Writer actor U as Writer
participant R as Review ctrl participant R as Pending-change ctrl
participant S as Machine runtime participant S as Machine runtime
participant M as Multi-diff editor participant E as Live editor buffer
U->>R: Ask the machine to revise U->>R: Ask the machine to revise
R->>S: runEditTurn(prompt, {onProgress, signal, env}) R->>S: runEditTurn(prompt, {onProgress, signal, env})
S-->>R: proposed text S-->>R: proposed text
R->>R: proposeAgentEdit → pending proposals[] (INV-5) R->>R: proposeAgentEdit → pending proposals[] (INV-5)
R->>M: openReview (baseline ⟷ proposed) R->>E: land pending changes (decorated, original retained)
U->>M: Keep / Undo per change · Keep all / Reject all U->>E: tweak by typing · Keep / ✗ Reject per change (CodeLens)
M->>R: keep(p)/undo(p) E->>R: keep(p)/reject(p) · keepAll()/rejectAll()
R-->>U: document updated only on Keep; authorship recorded R-->>U: Keep settles + attributes · Reject restores the original exactly
``` ```
- **Implementation:** the machine never edits the document; it emits **pending** - **Implementation:** the machine emits **pending** proposed changes; each lands in
proposed changes (INV-5). The multi-diff editor is the review surface; Keep = the live buffer as a decorated pending change with its original retained, and
Accept (apply + attribute changed words), Undo = Reject. Keep-all/reject-all apply nothing settles until Keep (INV-5). Per-hunk **CodeLens** carries ✓ Keep /
descending and skip + report any displaced change. **Stable-API boundary:** the ✗ Reject (with a Keep-all/Reject-all menu); keep-all/reject-all apply descending
multi-diff editor is stable; per-change inline **Keep/Undo overlays** as some AI and skip + report any displaced change. The **native diff editor**
tools render them lean on evolving editor APIs — the **stable fallback** is (`baseline ⟷ live`) is the sweep-everything reference view; the buffer is where
per-hunk **CodeLens** Keep/Reject within the diff (§6.10, §13-Q2). decisions happen. **Why in-buffer (D18):** the rung-2 spike verified VS Code does
- **Editable before keep (INV-12):** the proposal renders on the **writable** side of **not** render extension CodeLens inside a diff editor, ruling out the v0.1.0
the review, so the writer can tweak the machine's wording ("close, but a few human multi-diff Keep/Undo surface; the shipped F12 experience proved the in-buffer
edits") before keeping. `keep` attributes the machine's words and the writer's model end-to-end, and it matches the platform's own AI edit-session paradigm.
tweaks separately by diffing *final ↔ machine-original ↔ baseline*. Because editing - **Editable before keep (INV-12):** the pending change sits in the live buffer, so
happens on the proposal (a scratch doc), not the live document, INV-5 holds and the writer tweaks it by typing directly in it ("close, but a few human edits")
authorship is correct at creation — the preview (Option 1, the authorship surface) before keeping. `keep` attributes the machine's words and the writer's tweaks
then shows clean green/blue with no post-hoc untangling. *Open detail (§13-Q7):* separately by diffing *final ↔ machine-original ↔ baseline*, so authorship is
how per-change Keep/Undo behaves once the writer has freely edited across hunks. correct at creation — the preview and the in-editor track-changes then show clean
green/blue with no post-hoc untangling. Pending changes **re-anchor** around
writer edits; one displaced by heavy rewriting collapses to a whole-proposal
Keep/Reject (D20, resolving the old Q7).
#### Always-visible entry points (no Source Control navigation) — INV-13 #### Always-visible entry points (no Source Control navigation) — INV-13
@@ -713,8 +758,9 @@ sequenceDiagram
it exists). When `cowriting.isCoediting` is set, the actions surface where the it exists). When `cowriting.isCoediting` is set, the actions surface where the
writer already is: **editor title-bar buttons** (Review Changes, Ask Claude, Toggle writer already is: **editor title-bar buttons** (Review Changes, Ask Claude, Toggle
Annotations, and in snapshot mode Mark Changes as Reviewed) and a **status-bar Annotations, and in snapshot mode Mark Changes as Reviewed) and a **status-bar
item** (`✦ Coediting · N changes`, click → Review Changes). The multi-diff review item** (`✦ Coediting · N changes`, click → Review Changes). New proposals land
**auto-opens** on new proposals, and the live-progress notification carries a **in the buffer in front of the writer** (D18 — the review comes to them), and
the live-progress notification carries a
*Review* action. The SCM viewlet entry and the gutter dirty-diff remain as native *Review* action. The SCM viewlet entry and the gutter dirty-diff remain as native
extras for git-literate users, never the required path. extras for git-literate users, never the required path.
@@ -774,7 +820,7 @@ sequenceDiagram
T-->>U: machine reply posted in thread (onBehalfOf provenance) T-->>U: machine reply posted in thread (onBehalfOf provenance)
U->>T: accept the offered edit U->>T: accept the offered edit
T->>R: proposeAgentEdit → pending proposal (INV-5) T->>R: proposeAgentEdit → pending proposal (INV-5)
R-->>U: review in multi-diff (PUC-2) R-->>U: pending change lands in the buffer (PUC-2)
``` ```
- **Implementation:** gated on `isCoediting` (INV-10) — inert otherwise. The turn - **Implementation:** gated on `isCoediting` (INV-10) — inert otherwise. The turn
@@ -809,7 +855,7 @@ sequenceDiagram
- **Observability:** the OutputChannel stream (PUC-5) is the user-facing turn log; - **Observability:** the OutputChannel stream (PUC-5) is the user-facing turn log;
no telemetry service (a local extension — §9). no telemetry service (a local extension — §9).
- **Accessibility:** inherited from the native surfaces — the diff editor, - **Accessibility:** inherited from the native surfaces — the diff editor,
multi-diff, Markdown preview, and Comments all carry the editor's accessibility. CodeLens, Markdown preview, and Comments all carry the editor's accessibility.
This is a primary *reason* for the native-first choice. This is a primary *reason* for the native-first choice.
### 6.7 Key decisions & alternatives considered ### 6.7 Key decisions & alternatives considered
@@ -817,20 +863,21 @@ sequenceDiagram
| Decision | Chosen | Alternatives considered | Why chosen | | Decision | Chosen | Alternatives considered | Why chosen |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| Diff surface | `QuickDiffProvider` + native diff editor against a `cowriting-baseline:` virtual doc | bespoke webview diff; decoration-only inline diff | Native gutter + diff editor for free: theming, inline/side toggle, a11y, navigation; zero custom rendering (INV-3) | | Diff surface | `QuickDiffProvider` + native diff editor against a `cowriting-baseline:` virtual doc | bespoke webview diff; decoration-only inline diff | Native gutter + diff editor for free: theming, inline/side toggle, a11y, navigation; zero custom rendering (INV-3) |
| Accept/reject surface | **Multi-diff editor with Keep/Undo**, backed by the proposed-change model | source-control stage/unstage; in-editor CodeLens Keep/Reject; bespoke webview ✓/✗ | Closest to a modern "review the machine's edits" feel; reviews all changes in one native surface. Stable fallback: per-hunk CodeLens (§6.10) | | Accept/reject surface (v0.2.0, D18 — supersedes the v0.1.0 multi-diff choice) | **Pending changes in the live buffer** with per-hunk **✓ Keep / ✗ Reject CodeLens**, backed by the proposed-change model; native diff editor as the read-only sweep view | multi-diff editor with Keep/Undo (v0.1.0 choice — **ruled out**: the spike verified CodeLens does not render in a diff editor, and inline overlays lean on evolving APIs); a separate writable proposal document (spike E1 pivot — works, but puts an artifact between the writer and their document); source-control stage/unstage; bespoke webview ✓/✗ | The platform's own AI edit-session paradigm: review the change in the document, tweak by typing, revert in one gesture. Shipped and proven end-to-end (F12); all-stable APIs (decorations + CodeLens) |
| Rendered preview | **Extend the built-in Markdown preview** (`markdown-it` contribution) | bespoke sealed webview; both surfaces | Native scroll-sync, theming, refresh, and cooperation with the writer's other Markdown extensions; no webview to own (INV-3) | | Rendered preview | **Extend the built-in Markdown preview** (`markdown-it` contribution) | bespoke sealed webview; both surfaces | Native scroll-sync, theming, refresh, and cooperation with the writer's other Markdown extensions; no webview to own (INV-3) |
| Baseline source | follows the file's home (INV-7): **git `HEAD`** where tracked (resets on commit, storage-free), a **snapshot** (set on entry, re-pinnable; global storage / in-memory) where there is no commit to anchor to — outside any repo, untracked, or untitled | always a stored snapshot; always defer to git (breaks outside a repo) | Git-tracked files get the native "uncommitted work, reset on commit" diff for free; `~/Downloads` / untitled still work via the snapshot fallback. Mirrors the in-repo/out-of-repo `SidecarRouter` | | Baseline source | follows the file's home (INV-7): **git `HEAD`** where tracked (resets on commit, storage-free), a **snapshot** (set on entry, re-pinnable; global storage / in-memory) where there is no commit to anchor to — outside any repo, untracked, or untitled | always a stored snapshot; always defer to git (breaks outside a repo) | Git-tracked files get the native "uncommitted work, reset on commit" diff for free; `~/Downloads` / untitled still work via the snapshot fallback. Mirrors the in-repo/out-of-repo `SidecarRouter` |
| Persistence envelope | one forward-compatible, unknown-field-preserving sidecar (INV-4) | a bespoke per-feature format; preview-only ephemeral state | Round-trips to an outer review forge; survives reload; one shared `anchors`/`provenance` primitive | | Persistence envelope | one forward-compatible, unknown-field-preserving sidecar (INV-4) | a bespoke per-feature format; preview-only ephemeral state | Round-trips to an outer review forge; survives reload; one shared `anchors`/`provenance` primitive |
| Core/controller split | pure vscode-free cores + thin controllers | UI-coupled logic | Unit-testable cores, swappable surfaces; the surface can change without touching the logic (INV-6) | | Core/controller split | pure vscode-free cores + thin controllers | UI-coupled logic | Unit-testable cores, swappable surfaces; the surface can change without touching the logic (INV-6) |
| Decision vs attribution unit | block = decision unit (keep/reject); word = attribution unit | per-word proposals (too granular to decide on); per-file (all-or-nothing) | The writer decides at a readable grain (a paragraph) while authorship stays word-precise | | Decision vs attribution unit | block = decision unit (keep/reject); word = attribution unit | per-word proposals (too granular to decide on); per-file (all-or-nothing) | The writer decides at a readable grain (a paragraph) while authorship stays word-precise |
| Authorship surface | **Option 1** — the annotated preview is the authorship view (green/blue track-changes incl. deletions); native diff stays author-blind | Option 2 (in-editor decorations); Option 3 (owned author-colored track-changes diff that replaces native diff) | Preview shows author-colored changes for near-zero maintenance; editable proposals (INV-12) keep authorship clean *at creation*, so in-editor / post-hoc authorship rendering is unnecessary. Opts 2/3 deferred | | Authorship surface (v0.2.0, D21 — updates D11 to shipped reality) | the annotated preview **and** the in-editor author-colored track-changes decorations (both shipped); native diff stays author-blind (INV-11) | preview-only (v0.1.0 D11 — overtaken: the in-editor decorations shipped 2026-06-27 and earn their keep); Option 3 (owned author-colored track-changes diff replacing native diff) | Both surfaces ride the same pure render/attribution cores; the writer gets authorship where they read (preview) and where they write (editor). Opt 3 still out |
| Editing proposals | the proposed side of the review is **writable**; pre-Keep tweaks are absorbed and attributed (machine vs. writer) at Keep (INV-12) | machine proposal read-only (tweak only after accepting) | Most revisions are "close, but a few human edits"; editing at review time keeps authorship clean at creation and avoids messy post-acceptance untangling | | Editing proposals | pending changes are **tweaked by typing directly in the buffer**; pre-Keep tweaks are absorbed and attributed (machine vs. writer) at Keep (INV-12) | a separate writable proposal document (v0.1.0); machine proposal read-only (tweak only after accepting) | Most revisions are "close, but a few human edits"; editing in place, at review time, keeps authorship clean at creation with no extra artifact and no messy post-acceptance untangling |
| Ask surface (v0.2.0, D19) | **comments-first** — the native comment thread input is the ask input; title-bar "Ask Claude" creates/focuses a thread (selection or whole-document) | the shipped bespoke split-pane input webview (sunset — one custom surface eliminated); single-line `InputBox` (too small for real asks) | The Comments API is the editor's one native multi-line input; ask-for-an-edit and remark-on-a-passage collapse into a single familiar gesture, and the machine's answer lands in the same thread (D8/D10) |
| Coediting activation | opt-in per document via "Coedit this Document with Claude"; every surface gated on the active coediting set (INV-10) | always-on for all markdown; opt-in per workspace/folder | Per-document opt-in is the only grain that avoids hijacking Comments/SCM/preview for the writer's other files while keeping single-file precision | | Coediting activation | opt-in per document via "Coedit this Document with Claude"; every surface gated on the active coediting set (INV-10) | always-on for all markdown; opt-in per workspace/folder | Per-document opt-in is the only grain that avoids hijacking Comments/SCM/preview for the writer's other files while keeping single-file precision |
| Change discovery (resolves Q3) | primary entry = **editor title bar + status-bar item + auto-open review**; the SCM viewlet + gutter are optional extras (INV-13) | rely on the Source Control pane / gutter as the entry point | Writers working outside git won't find (or know) the Source Control pane; always-visible title-bar / status-bar actions need no git familiarity | | Change discovery (resolves Q3) | primary entry = **editor title bar + status-bar item**, and proposals land **in the buffer in front of the writer** (D18); the SCM viewlet + gutter are optional extras (INV-13) | rely on the Source Control pane / gutter as the entry point | Writers working outside git won't find (or know) the Source Control pane; always-visible title-bar / status-bar actions need no git familiarity |
| Authorship in the diff | the native diff is **author-blind** (what changed); authorship is shown in the preview, optionally a decoration track-changes editor (INV-11) | recolor machine changes inside the native diff editor | **Not possible:** `diffEditor.*` colors are global theme tokens with no per-author scoping; `inlineEdit.*`/chat tokens differentiate accept-state not author; decorations over the diff pane composite over green/red and are unreliable (vanish on tab-switch). Verified against the VS Code API (§13-Q5) | | Authorship in the diff | the native diff is **author-blind** (what changed); authorship is shown in the preview, optionally a decoration track-changes editor (INV-11) | recolor machine changes inside the native diff editor | **Not possible:** `diffEditor.*` colors are global theme tokens with no per-author scoping; `inlineEdit.*`/chat tokens differentiate accept-state not author; decorations over the diff pane composite over green/red and are unreliable (vanish on tab-switch). Verified against the VS Code API (§13-Q5) |
| Comment → machine response | on a coedited doc, a comment fires a turn that replies + offers an edit → pending proposal | comment is inert (human-only threads) | Turns the familiar review-comment gesture into a coediting action without a new surface; the opt-in gate (INV-10) keeps it from firing on other docs | | Comment → machine response | on a coedited doc, a comment fires a turn that replies + offers an edit → pending proposal | comment is inert (human-only threads) | Turns the familiar review-comment gesture into a coediting action without a new surface; the opt-in gate (INV-10) keeps it from firing on other docs |
| Comments & the preview | comments are **native** (source gutter + Comments panel); the preview can *initiate* a comment (selection → source range via the `markdown-it` source map) and show a deep-linking 💬 marker, but cannot **host** the thread (it's a webview — verified) | render a bespoke comment widget inside the preview webview | Keeps comments on the native surface (no INV-3 exception, keeps the Comments panel + gutter); the writer can still comment on what they're reading and add whole-document comments (top-anchored) | | Comments & the preview | comments are **native** (source gutter + Comments panel); the preview can *initiate* a comment (selection → source range via the `markdown-it` source map) and show a deep-linking 💬 marker, but cannot **host** the thread (it's a webview — verified) | render a bespoke comment widget inside the preview webview | Keeps comments on the native surface (no INV-3 exception, keeps the Comments panel + gutter); the writer can still comment on what they're reading and add whole-document comments (top-anchored) |
| Scope | design-only north star (no packaging/migration commitment) | commit a packaging + delivery sequence now | Keeps the architecture decision separate from the delivery decision (§2, §7.1) | | Scope & packaging (v0.2.0, D17 — resolves Q1) | north star **with a committed vehicle**: the shipped extension **evolves in place** onto these surfaces (pure cores retained, webview surfaces sunset) | greenfield extension importing the cores as a library (re-derives wiring + E2E harness the shipped repo already has); stay design-only (stalls — §7.3) | ~60% of the shipped code (21 pure modules, ~3.5k LOC) *is* this design's cores already; evolve-in-place keeps tests, history, and continuous shipping |
### 6.8 Testing strategy ### 6.8 Testing strategy
@@ -841,15 +888,18 @@ sequenceDiagram
markdown-it transform (token-in → annotated-token-out). markdown-it transform (token-in → annotated-token-out).
- **Integration / E2E (`@vscode/test-electron`):** drive the controllers in the - **Integration / E2E (`@vscode/test-electron`):** drive the controllers in the
Extension Development Host — mark-reviewed → assert checkpoint reset; ask-to-edit Extension Development Host — mark-reviewed → assert checkpoint reset; ask-to-edit
→ assert pending proposals + multi-diff opened; Keep/Undo → assert document → assert pending proposals landed decorated in the buffer with originals
mutation only on Keep + authorship recorded; toggle annotations → assert preview retained; Keep/Reject → assert Keep settles + attributes and Reject restores the
original text exactly; toggle annotations → assert preview
HTML marks; thread create → re-anchor → displaced. Where the harness cannot drive HTML marks; thread create → re-anchor → displaced. Where the harness cannot drive
a native surface's chrome directly (multi-diff Keep/Undo, Comments UI, preview a native surface's chrome directly (CodeLens clicks, Comments UI, preview
DOM), assert controller/provider state and pure-transform output instead. **No DOM), assert controller/provider state and pure-transform output instead. **No
Playwright** — these are native surfaces, not a webview. E2E are first-class plan Playwright** — these are native surfaces, not a webview. E2E are first-class plan
tasks (handbook §4 / §6.8). tasks (handbook §4 / §6.8). The shipped repo's 19 E2E suites carry forward under
evolve-in-place (D17).
- **Named E2E scenarios this design must pass:** PUC-1 pin→diff; PUC-2 - **Named E2E scenarios this design must pass:** PUC-1 pin→diff; PUC-2
propose→review→keep/undo→keep-all; PUC-3 annotate-toggle; PUC-4/6 propose→tweak→keep/reject→keep-all (incl. reject-restores-exactly); PUC-3
annotate-toggle; PUC-4/6
thread→reanchor→displaced; PUC-5 progress→cancel. thread→reanchor→displaced; PUC-5 progress→cancel.
### 6.9 Failure modes, rollback & flags ### 6.9 Failure modes, rollback & flags
@@ -860,63 +910,91 @@ sequenceDiagram
"start writing, or ask Claude to draft something" affordance; not an error. A "start writing, or ask Claude to draft something" affordance; not an error. A
coedited document always has a baseline (set on entry, INV-7), so there is **no coedited document always has a baseline (set on entry, INV-7), so there is **no
"missing baseline" state** to handle. "missing baseline" state** to handle.
- **Multi-diff Keep/Undo API unavailable/changed** → fall back to per-hunk CodeLens - **Save while changes are pending** → the buffer (and on save, the disk file)
Keep/Reject in the diff editor (§6.10); the proposed-change *model* is unchanged. contains visibly-pending machine text; safe because every pending proposal's
original is retained in the sidecar and Reject restores it exactly after reload
(INV-5, re-anchored on load per INV-1/PUC-6).
- **Feature flags / settings:** `cowriting.annotations` (preview annotations on/off), - **Feature flags / settings:** `cowriting.annotations` (preview annotations on/off),
`cowriting.liveProgress.revealOutput`. The whole capability is additive — off by `cowriting.liveProgress.revealOutput`. The whole capability is additive — off by
absence of coediting mode for the document (INV-10). absence of coediting mode for the document (INV-10).
### 6.10 Migration & compatibility ### 6.10 Migration & compatibility
**Greenfield — nothing to migrate.** This is a from-scratch design for in-editor **Evolve in place (D17, resolves Q1).** The v0.1.0 draft called this greenfield;
human↔machine markdown coediting; it does not replace an existing data format or that undersold the shipped extension: **~60% of its code — 21 pure, vscode-free
contract that must be carried. modules (~3.5k LOC) — already implements this design's cores** (attribution span
algebra, fingerprint anchoring, the sidecar envelope + router + merge, the
block/word diff engine, the render/annotation transform, the turn-progress
reducer, the machine turn runner). The delivery vehicle is therefore the shipped
extension itself, migrated **surface by surface**:
- **Data migration:** none. The sidecar envelope is additive and - **Retained as-is (the cores):** the 21 pure modules and their unit suites; the
unknown-field-preserving (INV-4) from v1, so future versions extend it without a sidecar contract (INV-4) and stores; the `@cline/sdk` turn runner + live
break. The baseline (out-of-repo, disposable) needs no migration ever. progress; the in-buffer pending-change experience (F12) and the in-editor
- **Backwards compatibility:** N/A at v1 (nothing precedes it); the envelope's author-colored track-changes decorations — both already match this design (D18,
forward-compatibility rule (INV-4) protects future consumers. D21).
- **Cutover:** N/A (design-only; no packaging committed — §7.1). - **Rewired (thin controllers):** baseline moves behind the router (git `HEAD` +
- **Stable-API boundary (recorded risk):** the native diff editor, SCM/QuickDiff, snapshot, INV-7 — today it is snapshot-only); a QuickDiffProvider +
multi-diff editor, Markdown-preview `markdown-it` contribution, Comments, and `cowriting-baseline:` content provider replace nothing (net-new native surface);
decorations are **stable**. The per-change inline **Keep/Undo** overlay that some the CoeditingRegistry opt-in gate (INV-10) replaces the current always-on-for-
AI tools render uses **evolving** editor APIs; the design's stable contract is markdown behavior; ThreadController gains the comment-response loop (D8/D10).
therefore "multi-diff review + per-hunk Keep/Reject CodeLens," with the inline - **Sunset (the bespoke webview stack, ~800 LOC):** the sealed review-panel
overlay as a progressive enhancement where the API allows (§13-Q2). webview (`trackChangesPreview` + its client) is replaced by the extended
built-in Markdown preview (D3); the split-pane ask-input webview is replaced by
comments-first asking (D19). Each sunset lands only after its native replacement
is green.
- **Data migration:** none needed — the sidecar envelope carries forward
(additive, unknown-field-preserving, INV-4). The baseline (out-of-repo,
disposable) needs no migration ever.
- **Stable-API boundary:** every surface this design now stands on — diff editor,
SCM/QuickDiff, Markdown-preview `markdown-it` contribution, Comments,
decorations, CodeLens — is **stable API**. The one evolving-API worry from
v0.1.0 (per-change overlays in a multi-diff review) is gone with the in-buffer
decision (D18).
## 7. Delivery Plan ## 7. Delivery Plan
### 7.1 Approach / strategy ### 7.1 Approach / strategy
This is a **design-only north star** — it ships no code. Delivery proceeds in **four Delivery proceeds in **four de-risking rungs, riskiest-API-assumption first**, so
de-risking rungs, riskiest-API-assumption first**, so any forced design change is any forced design change is discovered on throwaway code rather than after the
discovered on throwaway code rather than after the full build (the explicit goal: full build (the explicit goal: post-ship iteration is *polish*, not *design*).
post-ship iteration is *polish*, not *design*): The vehicle is the **shipped extension, evolved in place** (D17/§6.10):
1. **Static mockups (done).** Validate visual design + state coverage. *(Already 1. **Static mockups (done).** Validate visual design + state coverage. *(Already
surfaced and closed the empty-state, comments-from-preview, and discoverability surfaced and closed the empty-state, comments-from-preview, and discoverability
gaps.)* gaps.)*
2. **Throwaway feasibility spike (next).** A disposable VS Code extension (a §5.3 2. **Throwaway feasibility spike (partially done — 3 of 5 experiments).** A
*prototype*; the machine **stubbed** with canned edits) whose only job is to prove disposable VS Code extension (`vscode-cowriting-prototype`; the machine
the API-dependent assumptions on the real surfaces and return a **go/no-go per **stubbed** with canned edits) proving the API-dependent assumptions on the
assumption — closing the build-time open questions (Q2, Q4, Q7) before any plan is real surfaces. Companion: `coauthoring-feasibility-spike.md`. **Findings so
written.** Riskiest first: (a) the multi-diff proposed side is **editable** with far (2026-07-01):**
working **Keep/Undo** (INV-12); (b) the **baseline router** renders QuickDiff - **E1 (editable proposals + Keep/Undo) — PASS with pivot.** VS Code does
against a snapshot *and* git `HEAD`, going clean on commit / Mark-Reviewed (INV-7); **not** render extension CodeLens inside a diff editor, ruling out the
(c) the **`markdown-it`** plugin injects authorship + deletions in the built-in multi-diff review surface; per-hunk CodeLens works in a normal editor, and
preview with a toggle (PUC-3); (d) the **comment → reply → offer → proposal** loop tweak-then-Keep attributes machine words vs. writer edits correctly. Folded
(PUC-8); (e) the **title-bar + status-bar** discoverability reads as obvious back as **D18** (with the shipped F12 experience): the review surface is the
(INV-13). Companion: `coauthoring-feasibility-spike.md`. Output: a findings report live buffer itself.
+ any spec deltas folded back here. - **E3 (preview authorship) — PASS.** The `markdown-it` contribution injects
3. **Vertical slice.** One real end-to-end flow (enter → Ask Claude via the **real author-colored marks + deletions into the built-in preview, theme-aware,
`@cline/sdk`** → real proposal → review → keep) on a real document, minimal with a working toggle (D3 confirmed).
breadth — proving the loop works for real once the surfaces are validated. - **E5 (discoverability) — PASS.** Title-bar + status-bar entry points read as
4. **Full build.** The implementation plan decomposed from this (spike-revised) spec, obvious with no Source Control pane (INV-13 confirmed).
built surface-by-surface in the validated order: **pure cores → diff surface** - **E2 (baseline router: QuickDiff against git `HEAD` *and* snapshot, clean on
(highest-confidence native win) **→ preview extension → multi-diff accept/reject** commit / Mark-Reviewed — INV-7) and E4 (comment → reply → offer → proposal —
(largest API-boundary risk, last, with the CodeLens fallback ready). One SPEC → PUC-8) remain — now the riskiest unvalidated assumptions and the next
one plan → one execution per increment. concrete work.**
3. **Vertical slice.** One real end-to-end flow (enter → ask via a comment thread
with the **real `@cline/sdk`** → real pending change in the buffer → tweak →
keep) on a real document, minimal breadth — proving the loop works for real
once E2/E4 are validated.
4. **Full build (the evolve-in-place migration, §6.10).** The implementation plan
decomposed from this (spike-revised) spec, migrated surface-by-surface in the
validated order: **baseline router + QuickDiff/diff surface** (E2) **→
coediting opt-in gate → preview extension replaces the review-panel webview →
comments-first ask + comment-response loop replaces the input webview** (E4) —
each bespoke surface sunsetting only after its native replacement is green.
One SPEC → one plan → one execution per increment.
### 7.2 Rollout / launch plan ### 7.2 Rollout / launch plan
@@ -928,11 +1006,13 @@ host E2E green (§6.8). Marketplace packaging/release is out of scope here.
| Risk | Likelihood / impact | Mitigation | | Risk | Likelihood / impact | Mitigation |
| --- | --- | --- | | --- | --- | --- |
| Per-change Keep/Undo overlay relies on evolving editor APIs | M / M | Stable contract = multi-diff + per-hunk CodeLens Keep/Reject; overlay is progressive enhancement (§6.10) | | Baseline router assumptions fail on real git repos (E2 unvalidated) | M / M | E2 is the next spike experiment; snapshot mode (shipped today) is the always-works fallback (INV-7) |
| Comment-response loop feels laggy or noisy (E4 unvalidated) | M / M | E4 spikes it with latency notes; D10's off-switch ("Stop editing with Claude") bounds the blast radius |
| Save-while-pending leaves machine text on disk before a decision | M / L | Pending state + originals persist in the sidecar; Reject restores exactly after reload (§6.9); status-bar count keeps pending changes visible |
| Two source controls (Git + Cowriting) confuse the SCM viewlet | L / M | Multiple SCM providers coexist by design; name it clearly; QuickDiff alone (no full SCM) is the minimal fallback | | Two source controls (Git + Cowriting) confuse the SCM viewlet | L / M | Multiple SCM providers coexist by design; name it clearly; QuickDiff alone (no full SCM) is the minimal fallback |
| Built-in preview can't host interactive ✓/✗ inside the preview | M / L | Accept/reject lives in the multi-diff surface, not the preview; the preview is read/annotate only (a deliberate split) | | Built-in preview can't host interactive ✓/✗ inside the preview | M / L | Accept/reject lives in the buffer (D18), not the preview; the preview is read/annotate only (a deliberate split) |
| Native surfaces are harder to E2E than a queryable webview | M / L | Assert controller/provider state + pure-transform output | | Native surfaces are harder to E2E than a queryable webview | M / L | Assert controller/provider state + pure-transform output; the shipped E2E harness carries forward (D17) |
| Design-only spec stalls (never delivered) | M / L | §13-Q1 names the delivery decision and owner; the architecture stands regardless | | Evolve-in-place migration destabilizes a working product mid-flight | M / M | Surface-by-surface order (§7.1 rung 4): each bespoke surface sunsets only after its native replacement is green; the pure cores never move |
## 8. Success Metrics & Measurement ## 8. Success Metrics & Measurement
@@ -948,7 +1028,7 @@ host E2E green (§6.8). Marketplace packaging/release is out of scope here.
| # | Metric (scores PUC-n) | Target | Fed by | Review cadence | | # | Metric (scores PUC-n) | Target | Fed by | Review cadence |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| PM-1 | PUC-1 diff opens in the native diff editor with an accurate baseline⟷live | works for any file incl. untitled | host E2E | per build | | PM-1 | PUC-1 diff opens in the native diff editor with an accurate baseline⟷live | works for any file incl. untitled | host E2E | per build |
| PM-2 | PUC-2 no document mutation until Keep; each change independently keep/undo-able | 100% (INV-5) | host E2E | per build | | PM-2 | PUC-2 no proposed change settles until Keep; Reject restores the original exactly; each change independently keep/reject-able | 100% (INV-5) | host E2E | per build |
| PM-3 | PUC-3 annotations render in the built-in preview and toggle clean | on/off both correct | unit + host E2E | per build | | PM-3 | PUC-3 annotations render in the built-in preview and toggle clean | on/off both correct | unit + host E2E | per build |
## 9. Telemetry, Observability & Monitoring ## 9. Telemetry, Observability & Monitoring
@@ -1009,17 +1089,14 @@ a bug reaches us as a tracker issue.
| # | Question | Owner | Blocks | | # | Question | Owner | Blocks |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| Q1 | Product packaging & delivery sequence — the decision to act on this north star | Ben | Any implementation plan | | Q4 | Does any need genuinely require a webview the native surfaces can't serve (INV-3 escape hatch)? — narrowed at v0.2.0: the two shipped webviews both have native replacements (D3, D19); the one candidate left is mermaid rendering in the preview, expected to ride `markdown.previewScripts` (the `markdown-mermaid` pattern), to be confirmed at the preview-surface build | Ben | Per-surface build |
| Q2 | Per-change Keep/Undo: how far can the stable API go before falling back to per-hunk CodeLens? | Ben | PUC-2 build (last surface) |
| Q4 | Does any need genuinely require a webview the native surfaces can't serve (INV-3 escape hatch)? | Ben | Per-surface build |
| Q7 | Once the writer freely edits the proposal across hunks (INV-12), how do per-change Keep/Undo behave — do per-hunk actions survive arbitrary edits, or does free editing collapse the change set to a single Keep/Reject-all? | Ben | PUC-2 build |
**Resolved** **Resolved**
| # | Decision | Resolution | Date | | # | Decision | Resolution | Date |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| D1 | Scope / altitude | Design-only north star — architecture + surface mapping, no packaging/migration commitment | 2026-06-27 | | D1 | Scope / altitude | Design-only north star — architecture + surface mapping, no packaging/migration commitment. *(Amended by D17: the packaging commitment is now taken.)* | 2026-06-27 |
| D2 | Accept/reject primary surface | Multi-diff editor with Keep/Undo (CodeLens stable fallback) | 2026-06-27 | | D2 | Accept/reject primary surface | Multi-diff editor with Keep/Undo (CodeLens stable fallback). *(Superseded by D18: the spike showed CodeLens cannot render in a diff editor.)* | 2026-06-27 |
| D3 | Rendered preview surface | Extend the built-in Markdown preview via a `markdown-it` contribution (no webview) | 2026-06-27 | | D3 | Rendered preview surface | Extend the built-in Markdown preview via a `markdown-it` contribution (no webview) | 2026-06-27 |
| D4 | Diff surface | `QuickDiffProvider` + native diff editor against a `cowriting-baseline:` virtual doc | 2026-06-27 | | D4 | Diff surface | `QuickDiffProvider` + native diff editor against a `cowriting-baseline:` virtual doc | 2026-06-27 |
| D5 | Core capabilities | Pinned baseline, char-precise authorship attribution, durable git-native sidecar, live cancellable progress, region threads, on-behalf-of identity, pure-core/thin-controller discipline | 2026-06-27 | | D5 | Core capabilities | Pinned baseline, char-precise authorship attribution, durable git-native sidecar, live cancellable progress, region threads, on-behalf-of identity, pure-core/thin-controller discipline | 2026-06-27 |
@@ -1028,12 +1105,17 @@ a bug reaches us as a tracker issue.
| D8 | Comments can summon the machine | On a coedited doc, a comment fires a turn that replies in-thread + offers an edit → pending proposal (BUC-5, INV-5); inert on non-coedited docs | 2026-06-27 | | D8 | Comments can summon the machine | On a coedited doc, a comment fires a turn that replies in-thread + offers an edit → pending proposal (BUC-5, INV-5); inert on non-coedited docs | 2026-06-27 |
| D9 | Native diff is author-blind | Verified the diff editor has no per-author color API; authorship lives in the preview (and optionally a decoration track-changes editor), not the native diff (INV-11) | 2026-06-27 | | D9 | Native diff is author-blind | Verified the diff editor has no per-author color API; authorship lives in the preview (and optionally a decoration track-changes editor), not the native diff (INV-11) | 2026-06-27 |
| D10 | Comment trigger | While a document is in coediting mode, **every** new comment summons the machine (no per-comment gate); coediting mode is the control, "Stop editing with Claude" the off switch | 2026-06-27 | | D10 | Comment trigger | While a document is in coediting mode, **every** new comment summons the machine (no per-comment gate); coediting mode is the control, "Stop editing with Claude" the off switch | 2026-06-27 |
| D11 | Authorship surface (Q5) | Option 1 — the annotated preview is the authorship view; native diff stays author-blind; in-editor decorations (opt 2) and an owned track-changes diff (opt 3) are out of scope | 2026-06-27 | | D11 | Authorship surface (Q5) | Option 1 — the annotated preview is the authorship view; native diff stays author-blind; in-editor decorations (opt 2) and an owned track-changes diff (opt 3) are out of scope. *(Updated by D21: opt 2 shipped and is retained.)* | 2026-06-27 |
| D12 | Editable proposals | The review's proposed side is writable; the writer tweaks before Keep; machine words vs. writer tweaks are attributed at Keep (INV-12); live doc untouched until Keep (INV-5) | 2026-06-27 | | D12 | Editable proposals | The review's proposed side is writable; the writer tweaks before Keep; machine words vs. writer tweaks are attributed at Keep (INV-12); live doc untouched until Keep (INV-5). *(Amended by D18/D20: the writable surface is the live buffer itself.)* | 2026-06-27 |
| D13 | Baseline semantics & source | Baseline = the last settled version. Its **source follows the file's home**: git `HEAD` where tracked (auto-resets on commit, storage-free), else a snapshot set on entry (global storage / in-memory). A coedited doc always has a baseline; the only empty state is a genuinely empty document (INV-7) | 2026-06-27 | | D13 | Baseline semantics & source | Baseline = the last settled version. Its **source follows the file's home**: git `HEAD` where tracked (auto-resets on commit, storage-free), else a snapshot set on entry (global storage / in-memory). A coedited doc always has a baseline; the only empty state is a genuinely empty document (INV-7) | 2026-06-27 |
| D14 | Pin is the non-git fallback only (resolves Q8) | "Mark Changes as Reviewed" exists **only in snapshot mode** (no committed `HEAD`); git-tracked files have no manual pin — the writer **commits** and the baseline follows `HEAD`. The pin command is `when`-gated on `cowriting.baselineMode == snapshot` | 2026-06-27 | | D14 | Pin is the non-git fallback only (resolves Q8) | "Mark Changes as Reviewed" exists **only in snapshot mode** (no committed `HEAD`); git-tracked files have no manual pin — the writer **commits** and the baseline follows `HEAD`. The pin command is `when`-gated on `cowriting.baselineMode == snapshot` | 2026-06-27 |
| D15 | Comments & the preview | Native comment threads can't render in the preview (it's a webview — verified). The preview *initiates* a comment (selection → source range via the `markdown-it` source map) and shows deep-linking 💬 markers; a whole-document comment anchors at the top. Threads stay native (gutter + Comments panel); no bespoke in-webview comment UI | 2026-06-27 | | D15 | Comments & the preview | Native comment threads can't render in the preview (it's a webview — verified). The preview *initiates* a comment (selection → source range via the `markdown-it` source map) and shows deep-linking 💬 markers; a whole-document comment anchors at the top. Threads stay native (gutter + Comments panel); no bespoke in-webview comment UI | 2026-06-27 |
| D16 | Change discovery (resolves Q3) | No flow requires the Source Control pane (INV-13). Primary entry = editor title-bar actions + `✦ Coediting · N changes` status-bar item + auto-open review; SCM viewlet + gutter kept as optional git-user extras | 2026-06-27 | | D16 | Change discovery (resolves Q3) | No flow requires the Source Control pane (INV-13). Primary entry = editor title-bar actions + `✦ Coediting · N changes` status-bar item + auto-open review; SCM viewlet + gutter kept as optional git-user extras. *(v0.2.0: "auto-open review" becomes "proposals land in the buffer in front of the writer," D18.)* | 2026-06-27 |
| D17 | Packaging & delivery vehicle (resolves Q1) | **Evolve the shipped extension in place** onto the native surfaces: the 21 pure core modules (~3.5k LOC, ~60%) are retained as-is, thin controllers are rewired, and the two bespoke webviews sunset only after their native replacements are green (§6.10). Greenfield rejected (re-derives wiring + E2E harness); staying design-only rejected (stalls) | 2026-07-01 |
| D18 | Accept/reject surface (resolves Q2; supersedes D2) | **Pending changes live in the buffer** — proposals land optimistically as decorated pending text (originals retained), decided by per-hunk ✓ Keep / ✗ Reject CodeLens, tweaked by typing. Grounds: the spike verified CodeLens does **not** render in a diff editor (killing the multi-diff surface); the shipped F12 proved the in-buffer model end-to-end; it matches the platform's own AI edit-session paradigm and uses only stable APIs. INV-5 reworded from "buffer untouched" to "nothing settles until Keep; Reject restores exactly" | 2026-07-01 |
| D19 | Ask surface | **Comments-first**: the native comment thread input is the ask input for both imperative asks and remarks; the title-bar "Ask Claude" creates/focuses a thread (selection, else whole-document); the shipped split-pane input webview sunsets. One custom surface eliminated; ask and discuss collapse into one native gesture (D8/D10) | 2026-07-01 |
| D20 | Tweaks × per-hunk decisions (resolves Q7; amends D12) | Free editing happens in the live buffer; each pending change **re-anchors** around writer edits so per-hunk Keep/Reject survives ordinary tweaking; a change displaced by heavy rewriting collapses to a whole-proposal Keep/Reject (skip + report in keep-all/reject-all) — the INV-1 discipline applied to proposals | 2026-07-01 |
| D21 | Authorship surfaces (updates D11) | The annotated preview **and** the in-editor author-colored track-changes decorations are both authorship surfaces (the latter shipped 2026-06-27, after D11 deferred it); both ride the same pure cores; the native diff stays author-blind (INV-11). Option 3 (owned track-changes diff) remains out | 2026-07-01 |
## 14. Glossary & References ## 14. Glossary & References
@@ -1043,8 +1125,9 @@ a bug reaches us as a tracker issue.
Disposable; never committed by us (INV-7). Disposable; never committed by us (INV-7).
- **QuickDiffProvider** — the VS Code API that supplies gutter dirty-diff bars from a - **QuickDiffProvider** — the VS Code API that supplies gutter dirty-diff bars from a
virtual *original* document. virtual *original* document.
- **Multi-diff editor** — VS Code's native multi-change review surface; here the - **Pending change** — a machine-proposed edit landed optimistically in the live
accept/reject surface (Keep/Undo). buffer as decorated, unsettled text (original retained); decided by per-hunk
✓ Keep / ✗ Reject CodeLens, tweaked by typing (D18). The accept/reject surface.
- **Coauthoring sidecar** — the forward-compatible, unknown-field-preserving artifact - **Coauthoring sidecar** — the forward-compatible, unknown-field-preserving artifact
that persists anchors, authorship, proposed changes, and threads, and round-trips that persists anchors, authorship, proposed changes, and threads, and round-trips
to an outer review forge. to an outer review forge.
@@ -1059,8 +1142,8 @@ a bug reaches us as a tracker issue.
- **Pure core / thin controller** — the vscode-free, unit-tested logic vs. the - **Pure core / thin controller** — the vscode-free, unit-tested logic vs. the
vscode-aware wiring split (INV-6). vscode-aware wiring split (INV-6).
- **References:** VS Code APIs — SCM/`QuickDiffProvider`, diff editor (`vscode.diff`), - **References:** VS Code APIs — SCM/`QuickDiffProvider`, diff editor (`vscode.diff`),
multi-diff editor, Markdown preview `markdown-it` contribution, Comments, Markdown preview `markdown-it` contribution, Comments,
`TextDocumentContentProvider`, decorations. *Prior art (patterns proven in earlier `TextDocumentContentProvider`, decorations, CodeLens. *Prior art (patterns proven in earlier
coauthoring work, reused here on their own merits): baseline pinning, char-precise coauthoring work, reused here on their own merits): baseline pinning, char-precise
attribution, the portable coauthoring sidecar, live turn progress, and editable attribution, the portable coauthoring sidecar, live turn progress, and editable
proposals (tweak-before-keep with mixed human/machine authorship).* proposals (tweak-before-keep with mixed human/machine authorship).*