431ebeb979
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1150 lines
78 KiB
Markdown
1150 lines
78 KiB
Markdown
---
|
||
status: draft
|
||
---
|
||
# Solution Design: Coediting Markdown with a Machine — a Native VS Code Experience
|
||
|
||
| | |
|
||
| --- | --- |
|
||
| **Author(s)** | Ben Stull (with Claude) |
|
||
| **Reviewers / approvers** | Ben Stull |
|
||
| **Status** | `draft` |
|
||
| **Version** | v0.2.0 |
|
||
| **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 (F1–F12), the rung-2 feasibility spike (`vscode-cowriting-prototype`), `coauthoring-feasibility-spike.md`. Supersedes: none. |
|
||
|
||
**Change log**
|
||
|
||
| Date | Version | Change | By |
|
||
| --- | --- | --- | --- |
|
||
| 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 |
|
||
|
||
---
|
||
|
||
## 1. Business Context
|
||
|
||
*The business lens — solution-agnostic throughout. No mechanism is proposed until §2.*
|
||
|
||
### 1.1 Executive Summary
|
||
|
||
A growing share of real writing — specs, docs, RFCs, design notes, prose — happens
|
||
in markdown, and writers increasingly want a machine to help author it. But
|
||
coediting a markdown *document* with a machine is awkward today. When the machine
|
||
edits the file it hands back a **rewrite, not a reviewable change**: the writer is
|
||
left staring at a wall of new text with no natural answer to "what did you actually
|
||
change?" The writer can't easily **keep some of the machine's edits and drop
|
||
others**; after a few rounds nobody can tell **which words are the human's and which
|
||
the machine's**; and there's no **clean rendered view** of the work-in-progress with
|
||
the changes made legible. In practice "writing with a machine" collapses into
|
||
copy-paste between a chat window and the file — throwing away the diff, granular
|
||
control, and provenance. This design delivers human↔machine markdown coediting
|
||
**inside the editor**, through the surfaces a writer already uses to review
|
||
changes, so coediting *feels like a native capability of the editor* rather than a
|
||
chat window bolted onto the side. The value: writers can let a machine help with
|
||
real documents while staying in control of what lands and confident about what
|
||
changed.
|
||
|
||
### 1.2 Background
|
||
|
||
Markdown is the lingua franca of technical and semi-technical writing — it is where
|
||
specifications, documentation, RFCs, and a great deal of prose are actually drafted
|
||
and revised. At the same time, capable machine coauthors have arrived, and writers
|
||
want to use them on these documents, not just on code.
|
||
|
||
But the modalities available for "writing with a machine" were built for other
|
||
shapes of work, and they serve *document coediting* poorly:
|
||
|
||
- **Chat copy-paste.** Paste the document (or a passage) into a conversation, get a
|
||
rewrite back, paste it into the file. The diff is gone, authorship is gone,
|
||
granular accept/reject is gone, and the file and the conversation drift apart.
|
||
- **Whole-file rewrite.** The machine overwrites the file. The writer must diff it
|
||
by hand to see what happened and has only all-or-nothing control.
|
||
- **Inline code completions.** Built for short, accept-as-you-type code suggestions
|
||
in the editor — not for reviewing a machine's substantive revisions to a prose
|
||
document, where the writer wants to see, weigh, and selectively keep changes.
|
||
|
||
None of these lets the writer do the thing that matters for a document: **review the
|
||
machine's proposed revision the way they'd review any change — as a diff they can
|
||
read, accept or reject piece by piece, attribute, and preview rendered.**
|
||
|
||
Crucially, the editor *already has* best-in-class machinery for exactly this — diff
|
||
viewing, source-control review, multi-change accept/reject, threaded comments, and
|
||
a rendered Markdown preview — but none of it is wired up for coediting with a
|
||
machine. The opportunity is to deliver machine coediting *through* those surfaces.
|
||
|
||
### 1.3 Business Actors / Roles
|
||
|
||
| Role | Responsible for (in the business) |
|
||
| --- | --- |
|
||
| **Writer (human)** | Authors and owns a markdown document; decides what changes land in it. |
|
||
| **Machine coauthor** | A capable machine writing assistant that the writer asks to help revise the document; it proposes changes on the writer's behalf and never lands them unilaterally. |
|
||
|
||
### 1.4 Problem Statement
|
||
|
||
A writer who wants a machine to help revise a markdown document has no good way to
|
||
do it while staying in control. They cannot see exactly what the machine changed,
|
||
cannot keep some changes and reject others, cannot tell who wrote which words, and
|
||
cannot read a clean rendered result — at least not without leaving their editor and
|
||
diffing, cherry-picking, and reconciling by hand. The machine's revision arrives as
|
||
an opaque rewrite rather than a reviewable proposal, so the writer must choose
|
||
between blind trust and tedious manual reconciliation.
|
||
|
||
### 1.5 Pain Points
|
||
|
||
| # | Pain | Who feels it | Cost / frequency today |
|
||
| --- | --- | --- | --- |
|
||
| PP-1 | The machine's revision arrives as a rewrite, not a reviewable change — "what did you change?" has no easy answer | Writer (human) | Every machine revision; manual diffing each time |
|
||
| PP-2 | Accepting the machine's help is all-or-nothing — no way to keep some edits and reject others in place | Writer (human) | Every revision with mixed-quality changes |
|
||
| PP-3 | Authorship blurs — after a few rounds nobody can tell which words are the human's and which the machine's | Writer (human) | Cumulative; erodes trust and review confidence |
|
||
| PP-4 | No clean rendered view of the in-progress document with the changes made legible | Writer (human) | Every time the writer wants to judge the result as prose |
|
||
| PP-5 | The machine works opaquely — ask, wait, a blob appears; no sense of progress or ability to stop it | Writer (human) | Every machine turn |
|
||
|
||
### 1.6 Targeted Business Outcomes
|
||
|
||
| # | Outcome | Success metric | Baseline → Target | Guardrail (must not regress) | How / when measured |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| BO-1 | Writers adopt machine coauthoring for *real documents* because it's reviewable and in-editor | A writer completes a full revise → review → keep/reject → read loop without leaving the editor or copy-pasting | chat copy-paste / blind rewrite → in-editor reviewable coediting | The writer always stays in control of what lands | Dogfooding walkthrough |
|
||
| BO-2 | Coediting feels like a built-in editor capability, not a separate tool | The diff, accept/reject, and preview use surfaces the writer already knows | bespoke/foreign UI → native surfaces | Full capability (see-change, selective accept, authorship, preview, progress) preserved | Dogfooding walkthrough |
|
||
|
||
### 1.7 Scope (business)
|
||
|
||
- **In scope:** enabling a writer to coedit a markdown document with a machine while
|
||
staying in control — seeing what changed, accepting/rejecting changes
|
||
individually, knowing authorship, reading the rendered result, and following the
|
||
machine's progress — entirely within their editor.
|
||
- **Out of scope:** marketplace release; multi-file / whole-project machine
|
||
orchestration; a server or cloud backend. (v0.1.0 also deferred the packaging
|
||
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
|
||
disposes); non-markdown document formats in v1; a real-time multi-human
|
||
collaboration server.
|
||
|
||
### 1.8 Assumptions · Constraints · Dependencies
|
||
|
||
- **Assumptions:** the writer works in an editor with native diff, source-control,
|
||
multi-change review, Comments, and Markdown-preview surfaces (VS Code is the
|
||
target); a capable machine coauthor is available to run locally.
|
||
- **Constraints:** serverless and git-native (no external store); nothing the
|
||
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.
|
||
- **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
|
||
git-tracked baselines — §6.4). Files outside a repo degrade to the snapshot
|
||
baseline, so git is not strictly required.
|
||
|
||
### 1.9 Business Use Cases
|
||
|
||
**BUC-1 — As a writer, I can see exactly what the machine changed in my document
|
||
since a point I chose, so that I can judge it instead of trusting blindly.**
|
||
|
||
```gherkin
|
||
Scenario: BUC-1 — see what changed
|
||
Given I marked my document as "good" at some earlier moment
|
||
And the machine has since revised it
|
||
When I ask to see what changed
|
||
Then the changes are shown to me as a clear before/after I can read
|
||
```
|
||
|
||
- **BUC-1 acceptance criteria:** the writer sees an accurate before/after of the
|
||
document against the chosen point.
|
||
|
||
**BUC-2 — As a writer, I can keep or reject the machine's proposed changes one at a
|
||
time, so that I stay in control of what lands.**
|
||
|
||
```gherkin
|
||
Scenario: BUC-2 — keep or reject, change by change
|
||
Given the machine has proposed changes to my document
|
||
When I review them
|
||
Then I can keep or discard each change individually, or all at once
|
||
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 *settles* until the writer
|
||
keeps it, and rejecting one restores the prior text exactly; each change is
|
||
independently keepable/discardable; the writer can **edit a proposed change
|
||
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
|
||
clean rendered version, so that I can judge the result as finished prose.**
|
||
|
||
```gherkin
|
||
Scenario: BUC-3 — authorship and rendered result
|
||
Given my document has mixed human/machine authorship and recent changes
|
||
When I open its rendered view with annotations on
|
||
Then I see the document rendered, with changes and authorship visibly marked
|
||
And I can turn annotations off to read it clean
|
||
```
|
||
|
||
- **BUC-3 acceptance criteria:** the rendered document shows changes and authorship
|
||
when annotations are on, renders clean when off.
|
||
|
||
**BUC-4 — As a writer, I can follow the machine's progress while it works and stop
|
||
it, so that it is never an opaque black box.**
|
||
|
||
```gherkin
|
||
Scenario: BUC-4 — visible, stoppable machine work
|
||
Given I asked the machine to revise my document
|
||
When it is working
|
||
Then I can see live progress
|
||
And I can cancel before it finishes
|
||
```
|
||
|
||
**BUC-5 — As a writer, I can leave a remark on a passage of a document I'm coediting
|
||
and have the machine respond and offer to make the change, so that a comment can
|
||
become an edit without me leaving the document.**
|
||
|
||
```gherkin
|
||
Scenario: BUC-5 — a comment summons a response and an offer to edit
|
||
Given I have invited the machine to coedit this document
|
||
When I leave a comment on a passage
|
||
Then the machine replies to my comment
|
||
And it offers to make the change I described
|
||
And if I accept, its edit arrives as a proposed change I review like any other
|
||
```
|
||
|
||
- **BUC-5 acceptance criteria:** on an invited document a comment elicits a machine
|
||
reply and an offer; accepting produces a reviewable proposed change (it never
|
||
edits directly); on a *non-invited* document nothing is summoned.
|
||
|
||
**BUC-3a (unhappy) — As a writer, when a comment, an attributed span, or a proposed
|
||
change can no longer be confidently placed after edits, I am shown it as displaced
|
||
rather than silently moved to the wrong place.**
|
||
|
||
---
|
||
|
||
## 2. Solution Proposal
|
||
|
||
**Build software: a VS Code extension that lets a writer coedit a markdown document
|
||
with a machine coauthor, delivering the whole loop through the editor's own native
|
||
review surfaces.**
|
||
|
||
The enabling idea: **model the coauthored document as a tiny source control.** The
|
||
point the writer last marked "good" (the **baseline checkpoint**, set on entering
|
||
coediting and re-pinnable) is the source-control
|
||
*original*; the live document is the working tree; the machine's proposed edits are
|
||
*pending changes*. Once the relationship is framed as source control, the editor's
|
||
own machinery supplies the experience the writer needs — and it is machinery they
|
||
already know:
|
||
|
||
| 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 |
|
||
| 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 |
|
||
| 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 |
|
||
|
||
**Coediting is something a document opts into.** A writer turns a document into a
|
||
coedited document with one command — **"Coedit this Document with Claude"** — and
|
||
**only then** do the surfaces above attach to it: the SourceControl/QuickDiff entry,
|
||
the auto-responding comments, the annotation toggle, and the machine-edit entry
|
||
points. The extension registers its providers once but **scopes every surface to the
|
||
active coediting set** (INV-10), so it never hijacks Comments, Source Control, or the
|
||
preview for the writer's *other* markdown files. This gate is what makes the
|
||
auto-responding-comments behavior safe: a comment only summons the machine on a
|
||
document the writer has explicitly invited it into.
|
||
|
||
**Why this approach over the alternatives.** Doing nothing leaves writers in the
|
||
copy-paste-from-chat world (§1.2), which structurally cannot offer a diff,
|
||
selective accept, or authorship. Building a **bespoke coediting UI** (custom panels,
|
||
hand-rolled diff rendering, overlay decorations) can *imitate* these surfaces but
|
||
won't inherit the platform's behavior — theming, scroll-sync, accessibility, the
|
||
keyboard model, navigation, cooperation with other extensions — so it pays the cost
|
||
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
|
||
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
|
||
accepting the platform's affordances where they differ from a bespoke one. (The
|
||
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 **north star with a committed delivery vehicle**.
|
||
It commits to the architecture and the native-surface mapping, and (as of v0.2.0,
|
||
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,
|
||
and per-use-case design for native, in-editor human↔machine markdown coediting,
|
||
including baseline pinning, authorship attribution, durable git-native persistence,
|
||
live machine progress, region threads, and on-behalf-of identity. *Out:* product
|
||
packaging/release, multi-file orchestration, non-markdown formats. *Non-goal:*
|
||
mutating the writer's document without explicit acceptance.
|
||
|
||
---
|
||
|
||
## 3. Product Personas
|
||
|
||
| Product persona | In the plugin | Maps to business role(s) |
|
||
| --- | --- | --- |
|
||
| **PP-1 Writer** | Pins a baseline, asks the machine to revise the document (or a selection), reviews proposed changes in the diff, reads the annotated preview, opens region threads, watches/stops the machine. | Writer (human) |
|
||
| **PP-2 Machine coauthor** | Runs a turn locally, streams progress, and emits **proposed** edits (never lands them); authored spans carry its provenance. | Machine coauthor |
|
||
|
||
## 4. Product Use Cases
|
||
|
||
```gherkin
|
||
Scenario: PUC-1 — see the diff against the baseline checkpoint (realizes BUC-1)
|
||
Given I am coediting a document (its baseline is the last commit if it's in git, else a snapshot taken on entry)
|
||
When I (or the machine) change the document
|
||
Then the source-control gutter shows change bars for what changed since the checkpoint
|
||
And a Review Changes button in the editor title bar and a "✦ Coediting · N changes" status-bar item open the review without visiting the Source Control pane
|
||
And "Open Changes" opens the native diff editor (baseline ⟷ live), inline or side-by-side at my choice
|
||
And committing the file (when it's in a git repo) advances the baseline to the new commit (the diff goes clean)
|
||
And "Mark Changes as Reviewed" is offered only when the file has no commit to anchor to (outside a repo, untitled, or not-yet-committed), and resets the checkpoint there
|
||
```
|
||
|
||
```gherkin
|
||
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)
|
||
When the turn completes
|
||
Then each proposed change appears in my editor as a pending, decorated change
|
||
(proposed words marked by author color, replaced words struck) — visible in
|
||
place but not settled
|
||
And each change carries ✓ Keep / ✗ Reject actions (CodeLens above the 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 "Review Changes" opens the native diff (baseline ⟷ live) to sweep everything
|
||
at once
|
||
```
|
||
|
||
```gherkin
|
||
Scenario: PUC-3 — read the annotated rendered preview (realizes BUC-3)
|
||
Given my document has changes and mixed authorship
|
||
When I open the built-in Markdown preview with annotations enabled
|
||
Then insert/delete marks and authorship colors render inside the native preview
|
||
And toggling annotations off renders the document clean
|
||
```
|
||
|
||
```gherkin
|
||
Scenario: PUC-4 — region thread (realizes BUC-3a)
|
||
Given I select a passage — in the editor or in the rendered preview — and add a coauthoring thread
|
||
And I can also attach a comment to the whole document (anchored at its top) for a global remark
|
||
When I edit around it or reopen the document
|
||
Then the thread re-anchors; if it cannot be confidently placed it is shown displaced, never silently moved
|
||
And a thread started from the preview is created natively on the source (the preview shows a 💬 marker that deep-links to it)
|
||
```
|
||
|
||
```gherkin
|
||
Scenario: PUC-5 — live machine progress (realizes BUC-4)
|
||
Given I started a machine turn
|
||
When the machine is working
|
||
Then a notification shows the live activity line and token count
|
||
And an OutputChannel streams the full text
|
||
And I can cancel the turn
|
||
```
|
||
|
||
```gherkin
|
||
Scenario: PUC-6 — reopen / external change (realizes BUC-3a)
|
||
Given I reopen the workspace or an external change lands (a git pull)
|
||
When the document loads
|
||
Then baseline, proposed changes, attributions, and threads re-resolve against current text
|
||
And anything that cannot be confidently placed is shown displaced
|
||
```
|
||
|
||
```gherkin
|
||
Scenario: PUC-7 — enter (and leave) coediting mode (realizes INV-10)
|
||
Given an ordinary markdown document
|
||
When I run "Coedit this Document with Claude" (command, editor title, or context menu)
|
||
Then this document gains the Cowriting source-control entry, the annotation toggle, and machine-edit/comment-response behavior
|
||
And the baseline is established — the last commit if the file is in git, otherwise a snapshot of the current content
|
||
And any uncommitted changes already in a git-tracked file appear as my (human) changes since the baseline
|
||
And my other markdown documents are unaffected
|
||
And running "Stop editing with Claude" (context menu) removes those surfaces (the saved sidecar is kept for next time)
|
||
```
|
||
|
||
```gherkin
|
||
Scenario: PUC-8 — comment summons a machine reply + offer to edit (realizes BUC-5)
|
||
Given I am coediting this document
|
||
When I add a comment — on a passage (editor or preview selection) or on the whole document
|
||
Then a machine turn runs (with live progress, PUC-5) and replies in the thread
|
||
And a passage comment gives the machine that passage as context; a whole-document comment gives it the full document
|
||
And it offers to make the change; accepting opens its edit as a pending proposed change (PUC-2)
|
||
And on a document I am not coediting, adding a comment summons nothing
|
||
```
|
||
|
||
## 5. UX Layout
|
||
|
||
The experience is delivered through **native VS Code surfaces**; there is no bespoke
|
||
panel to wireframe. Described in text (source of truth):
|
||
|
||
- **Entering coediting.** A **"Coedit this Document with Claude"** command (command
|
||
palette, editor title menu, and editor/explorer context menu, markdown-gated)
|
||
turns the active document into a coedited document; a **"Stop editing with Claude"**
|
||
command — in the same command palette **and the editor/explorer context menu** —
|
||
reverses it. A subtle status-bar item ("✦ Coediting") indicates when the active
|
||
document is in coediting mode **and the change count** (`✦ Coediting · N changes`),
|
||
and **clicking it opens the review** — so the writer never needs to find the Source
|
||
Control pane (INV-13). Until a document is entered, **none** of the surfaces below
|
||
attach to it (INV-10). **Entering also sets the baseline to the document's current
|
||
content** — a clean checkpoint to measure future changes against (INV-7).
|
||
- **Primary actions — editor title bar + status bar (no navigation, INV-13).** When
|
||
the document is coedited, title-bar buttons (**Review Changes**, **Ask Claude**,
|
||
**Toggle Annotations**, and in snapshot mode **Mark Changes as Reviewed**) and the
|
||
`✦ Coediting · N changes` status-bar item are the everyday entry points — visible
|
||
without going anywhere. **Ask Claude** opens a comment thread (on the selection if
|
||
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 entry, *"Cowriting,"* coexists with Git: its quick-diff provider
|
||
draws the familiar change bars in the editor gutter against the baseline checkpoint,
|
||
its resource group lists the document's pending coauthoring changes, and "Open
|
||
Changes" uses the native diff editor. A bonus home for those who use Source Control
|
||
— never the required path (INV-13).
|
||
- **Native diff editor.** The writer's normal diff (`baseline ⟷ live`), with the
|
||
platform's inline/side-by-side toggle, navigation, and accessibility.
|
||
- **Pending changes in the editor (the accept/reject surface, D18).** When the
|
||
machine proposes edits, each change lands **in the live buffer** as a pending,
|
||
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";
|
||
with annotations on, authorship colors and change marks appear *inside* it. A
|
||
command/setting toggles annotations.
|
||
- **Comments gutter — the primary ask surface (D19).** Region threads via the
|
||
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
|
||
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
|
||
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 /
|
||
Comments panel, never inside the webview (the Comments API can't render into a
|
||
webview — §6.7). A **whole-document** comment (anchored at the top) carries the
|
||
entire document as context.
|
||
- **Notification + OutputChannel.** Live machine progress, with a Cancel action.
|
||
|
||
### 5.1 Design tooling decision
|
||
|
||
- **Decision:** **Code directly.**
|
||
- **Rationale:** the experience is delivered through native VS Code chrome, so there
|
||
is no net-new bespoke visual surface to explore. Scoring the six-question rubric:
|
||
Q1 −1 (no net-new surface — native editor chrome), Q2 −1 (UI specified at
|
||
wireframe-level prose against known platform surfaces), Q3 −1 (no multi-page
|
||
visual language), Q4 −1 (the editor *is* the design system here), Q5 −1 (solo
|
||
build), Q6 −1 (logic-dominant — wiring cores to platform APIs). Total **−6 → Skip
|
||
Design**. The only "designed" pixels are the preview annotation stylesheet (a
|
||
small CSS theme using the editor's theme variables), which does not warrant
|
||
Design.
|
||
- **Design:** N/A — no bespoke UI surface (native VS Code surfaces only).
|
||
|
||
### 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
|
||
editor empty/identical, no pending changes, preview loading/error) plus the
|
||
coauthoring **displaced** state surfaced in the thread / proposed-change listing.
|
||
|
||
## 6. Technical Design
|
||
|
||
### 6.1 Invariants
|
||
|
||
- **INV-1 — never silently moved:** an anchored artifact (thread, attributed span,
|
||
proposed change) that cannot be confidently re-resolved becomes **displaced**
|
||
(visible, flagged), never relocated to a wrong span.
|
||
- **INV-2 — git-native and serverless:** durable coauthoring data is diffable,
|
||
human-resolvable JSON committed alongside the document; no server, no external
|
||
store. (The *baseline* is the exception — a throwaway local artifact kept outside
|
||
the repo, INV-7.)
|
||
- **INV-3 — native-first:** the diff, accept/reject, and rendered-preview surfaces
|
||
are delivered by **native VS Code APIs**, not bespoke webviews or decoration
|
||
overlays. New custom rendering requires an explicit, recorded justification
|
||
(§6.7) that no native surface fits.
|
||
- **INV-4 — forward-compatible & portable:** the persisted artifact carries a
|
||
`schemaVersion`; `anchors`, `provenance`, `attributions`, `proposals`, and
|
||
`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
|
||
loss.
|
||
- **INV-5 — proposed changes are pending until kept:** a machine-proposed edit
|
||
never **settles** until the writer keeps it. It appears in the live buffer as a
|
||
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,
|
||
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
|
||
without touching the logic.
|
||
- **INV-7 — baseline is the last settled version; its source follows the file's
|
||
home:** the baseline is the diff *original* — the last "settled" version.
|
||
**Where the file is tracked in a git repo, the baseline is the file at `HEAD`** (the
|
||
last commit) and **resets automatically when a commit is made** — the diff is the
|
||
file's uncommitted work, git-native and storage-free. **Where there is no commit to
|
||
anchor to** (outside any repo, untracked/ignored, brand-new, or untitled), the
|
||
baseline is a **snapshot** defaulted to the current content on entry and reset by
|
||
**"Mark Changes as Reviewed,"** stored in extension global storage keyed by URI
|
||
hash (in-memory for untitled). **"Mark Changes as Reviewed" exists only in
|
||
snapshot mode** (files with no committed `HEAD` to anchor to) — for git-tracked
|
||
files there is no manual pin; the writer **commits** and the baseline follows
|
||
`HEAD`. A new in-repo file is in snapshot mode until its first commit, then flips to
|
||
`HEAD`-tracking (and the pin command disappears). A coedited document therefore *always* has a baseline — there is
|
||
**no "missing baseline" state**; the only empty state is a genuinely empty
|
||
document. The baseline is disposable review state, never committed by us.
|
||
- **INV-8 — provenance carries on-behalf-of identity:** every authored span /
|
||
message records `provenance` (the human's git identity, or the machine acting
|
||
`onBehalfOf` a human), reused across attributions, proposed changes, and threads.
|
||
Changes **already present when a coediting session starts** (uncommitted work vs.
|
||
`HEAD`, *including* edits made outside this experience — by hand or by a machine
|
||
elsewhere) carry no prior provenance record, so they are **attributed to the writer
|
||
(human)**: the session cannot know their origin and treats them as the writer's
|
||
existing work. (In snapshot mode the baseline is the content at entry, so there is
|
||
nothing pre-existing to attribute.)
|
||
- **INV-9 — machine turns are observable and cancellable:** a turn streams progress
|
||
and honors an `AbortSignal`; the progress reducer is pure.
|
||
- **INV-10 — coediting is opt-in per document:** every coauthoring surface (the
|
||
"Cowriting" SourceControl + QuickDiff, auto-responding comments, preview
|
||
annotations, the machine-edit entry points) activates **only** for a document the
|
||
writer has explicitly entered into coediting mode ("Coedit this Document with
|
||
Claude"). The extension registers its providers globally but **gates every
|
||
surface on membership in the active coediting set**, so it never hijacks
|
||
Comments/SCM/preview for documents the writer did not opt in. Exiting coediting
|
||
(or closing the workspace) removes the document from the set; the durable sidecar
|
||
remains, so re-entering restores state.
|
||
- **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
|
||
color scoping — §6.7). Authorship (human vs. machine) is shown on surfaces the
|
||
extension renders: the annotated preview and the in-editor author-colored
|
||
track-changes decorations (D21), never by recoloring the native diff.
|
||
- **INV-13 — no surface requires the Source Control pane:** the primary,
|
||
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
|
||
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
|
||
unfamiliar with git / Source Control never has to find them to coedit, review, or
|
||
settle a checkpoint.
|
||
- **INV-12 — proposed changes are editable before they are kept:** a pending
|
||
change sits in the live buffer, so the writer tweaks it by **typing directly in
|
||
it** (the common "close, but a few human edits" case) — no separate proposal
|
||
document, no mode. Nothing settles until Keep (INV-5); on Keep, the words the
|
||
machine proposed are attributed to the machine and the writer's tweaks to the
|
||
writer — by diffing *final ↔ the machine's original proposal ↔ baseline* — so
|
||
authorship is correct **at the moment the change is made**, never reconstructed
|
||
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
|
||
|
||
**Pure, vscode-free cores** + **thin controllers** that wire those cores to **native
|
||
surfaces**.
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph Cores["Pure cores (vscode-free, unit-tested)"]
|
||
AT[Attribution tracker<br/>char-precise spans]
|
||
AN[Anchorer<br/>fingerprint + offsets + displaced]
|
||
MG[mergeArtifacts<br/>union-by-id, unknown-field-safe]
|
||
DF[Diff/hunk engine<br/>word- & block-level]
|
||
TP[turnProgress<br/>reducer]
|
||
RA[renderAnnotations<br/>markdown-it transform]
|
||
end
|
||
subgraph Ctrls["Thin controllers (vscode-aware)"]
|
||
SC[SourceControl + QuickDiffProvider]
|
||
BP[Baseline provider<br/>TextDocumentContentProvider]
|
||
PR[Pending-change controller<br/>in-buffer decorations + CodeLens]
|
||
PV[Preview extension<br/>extendMarkdownIt]
|
||
TC[ThreadController<br/>Comments API]
|
||
LP[liveProgress relay]
|
||
ST[Sidecar router + store]
|
||
end
|
||
subgraph Native["Native VS Code surfaces"]
|
||
GUT[Gutter dirty-diff + SCM viewlet]
|
||
DIFF[Native diff editor]
|
||
MDIFF[Live editor buffer<br/>pending changes + CodeLens]
|
||
MDPREV[Built-in Markdown preview]
|
||
COMM[Comments gutter/peek]
|
||
NOTE[Notification + OutputChannel]
|
||
end
|
||
SDK[[Machine-coauthor runtime<br/>dynamic import]]
|
||
|
||
AN --> SC --> GUT
|
||
BP --> DIFF
|
||
DF --> PR --> MDIFF
|
||
AT --> RA --> PV --> MDPREV
|
||
AN --> TC --> COMM
|
||
TP --> LP --> NOTE
|
||
SDK --> PR
|
||
Cores --- ST
|
||
Ctrls --- ST
|
||
```
|
||
|
||
- **SourceControl + QuickDiffProvider** — owns the "Cowriting" source control and
|
||
the gutter dirty-diff; must never write to the document.
|
||
- **Baseline provider** — a `TextDocumentContentProvider` for the
|
||
`cowriting-baseline:` scheme; serves the pinned baseline text for any document URI
|
||
from the out-of-repo baseline store (INV-7).
|
||
- **Pending-change controller** — turns the machine's emitted edits into pending
|
||
proposed changes, lands them in the live buffer as decorated pending text
|
||
(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`
|
||
transform runs inside the built-in preview; owns no webview.
|
||
- **Sidecar router + store** — persistence: a repo-committed sidecar when the
|
||
document is in a workspace, a global-storage sidecar keyed by URI hash otherwise.
|
||
|
||
### 6.3 Data model & ownership
|
||
|
||
| Entity | Owned by | Key fields | System of record |
|
||
| --- | --- | --- | --- |
|
||
| Coauthoring sidecar | the extension (sole writer) | `schemaVersion`, `document.path`, `anchors{}`, `provenance`, `attributions[]`, `proposals[]`, `threads[]`, unknown-field passthrough | git-committed `.threads/<path>.json` (in-workspace) or global-storage sidecar (out-of-workspace) |
|
||
| Baseline (checkpoint) | the extension | git-tracked → resolved from `HEAD` (no storage); else → snapshot {text, reason, time} keyed by URI hash | git object store (HEAD) **or** out-of-repo global storage (snapshot); never committed by us (INV-7) |
|
||
| Provenance | the extension | `kind` (human/machine), `id`, `email`, `onBehalfOf`, `agent{runtime,model,sessionId}` | embedded in sidecar (INV-8) |
|
||
|
||
The sidecar envelope is **forward-compatible and portable** (INV-4): additive,
|
||
unknown-field-preserving, so it round-trips to an outer review forge unchanged. The
|
||
**baseline is deliberately not** in this envelope: it is local, disposable review
|
||
state (INV-7), not durable coauthoring metadata.
|
||
|
||
```jsonc
|
||
// .threads/docs/spec.md.json — the coauthoring envelope (INV-4)
|
||
{
|
||
"schemaVersion": 1,
|
||
"document": { "path": "docs/spec.md" },
|
||
"anchors": { "a1": { "fingerprint": { "text": "…", "before": "…", "after": "…", "lineHint": 42 } } },
|
||
"attributions": [ { "anchorId": "a1", "provenance": <Provenance>, "span": { "start": 0, "end": 12 } } ],
|
||
"proposals": [ { "id": "p1", "anchorId": "a1", "status": "pending", // pending|accepted|rejected (INV-5)
|
||
"granularity": "block", // word|block — the decision unit
|
||
"provenance": <Provenance>, "before": "…", "after": "…" } ],
|
||
"threads": [ { "id": "t1", "anchorId": "a1", "status": "open", "messages": [ … ] } ]
|
||
// unknown top-level fields preserved verbatim (INV-4)
|
||
}
|
||
```
|
||
|
||
### 6.4 Interfaces & contracts
|
||
|
||
- **Baseline router + provider** — a `TextDocumentContentProvider` for
|
||
`cowriting-baseline:` whose `provideTextDocumentContent(uri)` resolves the *original*
|
||
by the file's home (INV-7): **git-tracked → the `HEAD` blob** (read via the built-in
|
||
Git extension API; auto-advances on commit, watched via
|
||
`repository.state.onDidChange`); **otherwise → a stored snapshot**
|
||
(`BaselineStore.snapshot(docUri, text, reason)` / `get(docUri)`), set on
|
||
`enterCoediting` (reason `entered`) and on **"Mark Changes as Reviewed"** (reason
|
||
`pinned`). The **pin command is gated to snapshot mode** — hidden/disabled for
|
||
`HEAD`-anchored files (`when: cowriting.baselineMode == snapshot`), where committing
|
||
is the reset. This mirrors the in-repo/out-of-repo `SidecarRouter` (§6.2). A coedited
|
||
document always resolves a baseline; non-coedited documents resolve none.
|
||
- **QuickDiffProvider** — `provideOriginalResource(docUri) → cowriting-baseline:` URI
|
||
(drives gutter bars). Registered on the "Cowriting" `SourceControl`.
|
||
- **Machine-edit seam** — the single boundary where machine text enters the model:
|
||
`proposeAgentEdit(docUri, range, newText, provenance)` → produces **pending**
|
||
`proposals[]` entries; nothing settles until Keep (INV-5). Block/word granularity via
|
||
the pure diff engine (`diffToBlockHunks` / `wordEditHunks`): a block (paragraph,
|
||
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
|
||
only the words that actually changed.
|
||
- **Pending-change controller** — on new proposals, lands each **optimistically in
|
||
the live buffer** as decorated pending text (the model retains the proposal's
|
||
`original` for exact revert) and renders per-hunk **✓ Keep / ✗ Reject** CodeLens
|
||
(D18). `keep(proposalId)` = Accept: settles the change and attributes it —
|
||
machine-proposed words to the machine, any writer tweaks (typed directly into the
|
||
pending text, INV-12) to the writer, by comparing *final ↔ the machine's original
|
||
proposal ↔ baseline*. `reject(proposalId)` restores the retained original
|
||
exactly. `keepAll()` / `rejectAll()` apply descending, skip + report any
|
||
displaced change. Pending changes re-anchor around writer edits; a displaced one
|
||
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
|
||
plugin invokes the pure `renderAnnotations(tokens, attributions, proposals,
|
||
options)` transform, gated by the `cowriting.annotations` setting/command.
|
||
- **Turn runner** — `runEditTurn(prompt, { onProgress, signal, env }) →
|
||
AgentResult`; pure `turnProgress` reducer; `liveProgress` relay to a notification
|
||
+ OutputChannel; Cancel → `abort()`. The machine runtime is loaded via runtime
|
||
dynamic `import()` (it is ESM and must not be bundled), and spawned with IDE
|
||
auto-connect disabled so it never triggers an OS automation-permission prompt.
|
||
- **CoeditingRegistry** (the activation gate, INV-10) — `enter(docUri)` /
|
||
`exit(docUri)` / `isCoediting(docUri) → boolean` / `onDidChange`. Holds the active
|
||
coediting set (persisted across reloads). **Every** controller above checks
|
||
`isCoediting(docUri)` before attaching a surface: the QuickDiffProvider returns no
|
||
original for non-coedited docs, the comment-response controller ignores them, and
|
||
the annotation/edit commands are disabled (`when` clause `cowriting.isCoediting`).
|
||
- **ThreadController + comment-response — the primary ask surface (D19)** —
|
||
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
|
||
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
|
||
machine `Comment` carrying `onBehalfOf` provenance (INV-8). Threads can be created
|
||
from an **editor selection**, a **preview selection** (mapped to the source range
|
||
via the `markdown-it` token source map — the preview shows a deep-linking 💬 marker
|
||
but cannot host the thread; the Comments API renders only in the gutter + Comments
|
||
panel), or on the **whole document** (anchored at line 0, full-document context).
|
||
All threads are native — the preview never hosts comment UI (INV-3).
|
||
|
||
### 6.5 Per–Product-Use-Case design
|
||
|
||
#### PUC-1 — see the diff against the baseline checkpoint
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
actor U as Writer
|
||
participant C as SCM/QuickDiff ctrl
|
||
participant B as Baseline provider
|
||
participant V as Native diff editor
|
||
U->>C: Mark Changes as Reviewed
|
||
C->>B: BaselineStore.pin(docUri, text)
|
||
U->>U: edits document (or the machine proposes)
|
||
C-->>U: gutter dirty-diff bars (provideOriginalResource)
|
||
U->>V: Open Changes
|
||
V->>B: read cowriting-baseline:docUri
|
||
V-->>U: native diff (baseline ⟷ live)
|
||
```
|
||
|
||
- **Implementation:** the QuickDiffProvider points at the `cowriting-baseline:` URI;
|
||
the platform renders gutter bars and powers "Open Changes" / `vscode.diff`. No
|
||
custom diff rendering. Honors INV-3, INV-7.
|
||
|
||
#### PUC-2 — keep/reject the machine's proposed changes
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
actor U as Writer
|
||
participant R as Pending-change ctrl
|
||
participant S as Machine runtime
|
||
participant E as Live editor buffer
|
||
U->>R: Ask the machine to revise
|
||
R->>S: runEditTurn(prompt, {onProgress, signal, env})
|
||
S-->>R: proposed text
|
||
R->>R: proposeAgentEdit → pending proposals[] (INV-5)
|
||
R->>E: land pending changes (decorated, original retained)
|
||
U->>E: tweak by typing · ✓ Keep / ✗ Reject per change (CodeLens)
|
||
E->>R: keep(p)/reject(p) · keepAll()/rejectAll()
|
||
R-->>U: Keep settles + attributes · Reject restores the original exactly
|
||
```
|
||
|
||
- **Implementation:** the machine emits **pending** proposed changes; each lands in
|
||
the live buffer as a decorated pending change with its original retained, and
|
||
nothing settles until Keep (INV-5). Per-hunk **CodeLens** carries ✓ Keep /
|
||
✗ Reject (with a Keep-all/Reject-all menu); keep-all/reject-all apply descending
|
||
and skip + report any displaced change. The **native diff editor**
|
||
(`baseline ⟷ live`) is the sweep-everything reference view; the buffer is where
|
||
decisions happen. **Why in-buffer (D18):** the rung-2 spike verified VS Code does
|
||
**not** render extension CodeLens inside a diff editor, ruling out the v0.1.0
|
||
multi-diff Keep/Undo surface; the shipped F12 experience proved the in-buffer
|
||
model end-to-end, and it matches the platform's own AI edit-session paradigm.
|
||
- **Editable before keep (INV-12):** the pending change sits in the live buffer, so
|
||
the writer tweaks it by typing directly in it ("close, but a few human edits")
|
||
before keeping. `keep` attributes the machine's words and the writer's tweaks
|
||
separately by diffing *final ↔ machine-original ↔ baseline*, so authorship is
|
||
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
|
||
|
||
- **Implementation:** reviewing changes must **never require finding the Source
|
||
Control pane** (writers working outside git — `~/Downloads`, untitled — won't know
|
||
it exists). When `cowriting.isCoediting` is set, the actions surface where the
|
||
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
|
||
item** (`✦ Coediting · N changes`, click → Review Changes). New proposals land
|
||
**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
|
||
extras for git-literate users, never the required path.
|
||
|
||
#### PUC-3 — annotated rendered preview
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
actor U as Writer
|
||
participant P as Built-in MD preview
|
||
participant E as Preview extension (extendMarkdownIt)
|
||
participant T as renderAnnotations (pure)
|
||
U->>P: Open Preview (annotations on)
|
||
P->>E: render document tokens
|
||
E->>T: transform(tokens, attributions, proposals)
|
||
T-->>E: tokens w/ authorship + change marks
|
||
E-->>P: annotated HTML (preview stylesheet)
|
||
P-->>U: native preview, annotated
|
||
```
|
||
|
||
- **Implementation:** a contributed `markdown-it` plugin runs the pure
|
||
`renderAnnotations` transform and a small preview stylesheet using the editor's
|
||
theme variables. Toggle via `cowriting.annotations`. No webview. Authorship
|
||
coloring + insert/delete marks are shown for changes since the baseline checkpoint;
|
||
a freshly entered or freshly pinned, zero-diff document renders clean.
|
||
|
||
#### PUC-4 / PUC-6 — threads & re-anchoring
|
||
|
||
- **Implementation:** ThreadController + Anchorer, **gated on `isCoediting(docUri)`**
|
||
(INV-10). On load / external change (FileSystemWatcher), every anchor re-resolves
|
||
(exact-unique → context-disambiguated → displaced, INV-1). Proposed changes and
|
||
attributions ride the same `anchors` primitive, so they re-anchor and displace by
|
||
the same ladder.
|
||
|
||
#### PUC-7 — enter / leave coediting
|
||
|
||
- **Implementation:** `cowriting.enterCoediting(docUri)` adds the URI to the
|
||
CoeditingRegistry's active set and persists it; `cowriting.exitCoediting` removes
|
||
it (the sidecar is kept). The registry fires `onDidChange`; controllers (QuickDiff,
|
||
comment-response, annotations, edit commands) attach/detach and the
|
||
`cowriting.isCoediting` context key flips so menu `when` clauses show/hide. Commands
|
||
are markdown-gated. No surface exists for a document until it is entered.
|
||
|
||
#### PUC-8 — comment summons a machine reply + offer
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
actor U as Writer
|
||
participant T as ThreadController
|
||
participant G as CoeditingRegistry
|
||
participant S as Machine runtime
|
||
participant R as Review ctrl
|
||
U->>T: add comment on a passage
|
||
T->>G: isCoediting(docUri)?
|
||
G-->>T: yes
|
||
T->>S: runEditTurn(comment + context, {onProgress, signal})
|
||
S-->>T: reply text (+ optional edit)
|
||
T-->>U: machine reply posted in thread (onBehalfOf provenance)
|
||
U->>T: accept the offered edit
|
||
T->>R: proposeAgentEdit → pending proposal (INV-5)
|
||
R-->>U: pending change lands in the buffer (PUC-2)
|
||
```
|
||
|
||
- **Implementation:** gated on `isCoediting` (INV-10) — inert otherwise. The turn
|
||
runs with live progress (PUC-5). The reply is a machine `Comment`; an accepted
|
||
offer never edits the document directly, only proposes (INV-5). **While the
|
||
document is in coediting mode, every new comment fires a turn (D10)** — the opt-in
|
||
mode is the control, so there is no per-comment gate; "Stop editing with Claude"
|
||
is the off switch. A comment may originate at an **editor selection**, a **preview
|
||
selection** (mapped to the source range via the `markdown-it` token source map), or
|
||
the **whole document** (top-anchored); the machine receives the passage — or the
|
||
full document — as context accordingly.
|
||
|
||
#### PUC-5 — live machine progress
|
||
|
||
- **Implementation:** pure `turnProgress` reducer; `runEditTurn`
|
||
`onProgress`/`AbortSignal`; `liveProgress` relay to a notification activity-line +
|
||
token count + an OutputChannel; Cancel → `abort()`. The machine runtime is spawned
|
||
with IDE auto-connect disabled so it never triggers an OS automation prompt.
|
||
|
||
### 6.6 Non-functional requirements & cross-cutting concerns
|
||
|
||
- **Security & privacy:** serverless; no secret bytes in sidecars or transcripts.
|
||
The machine runs locally; provenance records identity, not credentials. The
|
||
runtime is spawned with IDE auto-connect disabled (no spurious OS
|
||
automation-permission prompt).
|
||
- **Performance & scale:** anchor resolution is O(document) text search per anchor
|
||
on load/change — fine for human-scale documents; skip non-text/oversized files.
|
||
Native diff/preview rendering is the platform's, not ours.
|
||
- **Availability & resilience:** no service; the worst case is a displaced anchor
|
||
(handled, INV-1); a baseline always exists once coediting is entered (INV-7), so
|
||
there is no missing-baseline path.
|
||
- **Observability:** the OutputChannel stream (PUC-5) is the user-facing turn log;
|
||
no telemetry service (a local extension — §9).
|
||
- **Accessibility:** inherited from the native surfaces — the diff editor,
|
||
CodeLens, Markdown preview, and Comments all carry the editor's accessibility.
|
||
This is a primary *reason* for the native-first choice.
|
||
|
||
### 6.7 Key decisions & alternatives considered
|
||
|
||
| 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) |
|
||
| 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) |
|
||
| 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 |
|
||
| 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 |
|
||
| 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 | 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 |
|
||
| 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) |
|
||
| 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) |
|
||
| 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
|
||
|
||
- **Unit (vitest, vscode-free):** the pure cores — attribution spans, anchoring
|
||
(fingerprint/resolve/displaced/offset shift), `mergeArtifacts` (union-by-id,
|
||
unknown-field preservation), the diff engine (`wordEditHunks` /
|
||
`diffToBlockHunks`), the `turnProgress` reducer, and the `renderAnnotations`
|
||
markdown-it transform (token-in → annotated-token-out).
|
||
- **Integration / E2E (`@vscode/test-electron`):** drive the controllers in the
|
||
Extension Development Host — mark-reviewed → assert checkpoint reset; ask-to-edit
|
||
→ assert pending proposals landed decorated in the buffer with originals
|
||
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
|
||
a native surface's chrome directly (CodeLens clicks, Comments UI, preview
|
||
DOM), assert controller/provider state and pure-transform output instead. **No
|
||
Playwright** — these are native surfaces, not a webview. E2E are first-class plan
|
||
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
|
||
propose→tweak→keep/reject→keep-all (incl. reject-restores-exactly); PUC-3
|
||
annotate-toggle; PUC-4/6
|
||
thread→reanchor→displaced; PUC-5 progress→cancel.
|
||
|
||
### 6.9 Failure modes, rollback & flags
|
||
|
||
- **Displaced anchor** (INV-1) → shown displaced at last-known `lineHint`, never
|
||
moved → auto-re-anchors if the text reappears → no alert (local).
|
||
- **Empty document** (genuinely no content) → the only true empty state → a gentle
|
||
"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
|
||
"missing baseline" state** to handle.
|
||
- **Save while changes are pending** → the buffer (and on save, the disk file)
|
||
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),
|
||
`cowriting.liveProgress.revealOutput`. The whole capability is additive — off by
|
||
absence of coediting mode for the document (INV-10).
|
||
|
||
### 6.10 Migration & compatibility
|
||
|
||
**Evolve in place (D17, resolves Q1).** The v0.1.0 draft called this greenfield;
|
||
that undersold the shipped extension: **~60% of its code — 21 pure, vscode-free
|
||
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**:
|
||
|
||
- **Retained as-is (the cores):** the 21 pure modules and their unit suites; the
|
||
sidecar contract (INV-4) and stores; the `@cline/sdk` turn runner + live
|
||
progress; the in-buffer pending-change experience (F12) and the in-editor
|
||
author-colored track-changes decorations — both already match this design (D18,
|
||
D21).
|
||
- **Rewired (thin controllers):** baseline moves behind the router (git `HEAD` +
|
||
snapshot, INV-7 — today it is snapshot-only); a QuickDiffProvider +
|
||
`cowriting-baseline:` content provider replace nothing (net-new native surface);
|
||
the CoeditingRegistry opt-in gate (INV-10) replaces the current always-on-for-
|
||
markdown behavior; ThreadController gains the comment-response loop (D8/D10).
|
||
- **Sunset (the bespoke webview stack, ~800 LOC):** the sealed review-panel
|
||
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.1 Approach / strategy
|
||
|
||
Delivery proceeds in **four de-risking rungs, riskiest-API-assumption first**, so
|
||
any forced design change is discovered on throwaway code rather than after the
|
||
full build (the explicit goal: 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
|
||
surfaced and closed the empty-state, comments-from-preview, and discoverability
|
||
gaps.)*
|
||
2. **Throwaway feasibility spike (partially done — 3 of 5 experiments).** A
|
||
disposable VS Code extension (`vscode-cowriting-prototype`; the machine
|
||
**stubbed** with canned edits) proving the API-dependent assumptions on the
|
||
real surfaces. Companion: `coauthoring-feasibility-spike.md`. **Findings so
|
||
far (2026-07-01):**
|
||
- **E1 (editable proposals + Keep/Undo) — PASS with pivot.** VS Code does
|
||
**not** render extension CodeLens inside a diff editor, ruling out the
|
||
multi-diff review surface; per-hunk CodeLens works in a normal editor, and
|
||
tweak-then-Keep attributes machine words vs. writer edits correctly. Folded
|
||
back as **D18** (with the shipped F12 experience): the review surface is the
|
||
live buffer itself.
|
||
- **E3 (preview authorship) — PASS.** The `markdown-it` contribution injects
|
||
author-colored marks + deletions into the built-in preview, theme-aware,
|
||
with a working toggle (D3 confirmed).
|
||
- **E5 (discoverability) — PASS.** Title-bar + status-bar entry points read as
|
||
obvious with no Source Control pane (INV-13 confirmed).
|
||
- **E2 (baseline router: QuickDiff against git `HEAD` *and* snapshot, clean on
|
||
commit / Mark-Reviewed — INV-7) and E4 (comment → reply → offer → proposal —
|
||
PUC-8) remain — now the riskiest unvalidated assumptions and the next
|
||
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
|
||
|
||
A VS Code extension has **no §9 webapp deploy pipeline** (localhost/PPE/prod) — it is
|
||
not a webapp. "Done" for a delivered increment = acceptance met (§1.9) + unit and
|
||
host E2E green (§6.8). Marketplace packaging/release is out of scope here.
|
||
|
||
### 7.3 Risks & mitigations
|
||
|
||
| Risk | Likelihood / impact | Mitigation |
|
||
| --- | --- | --- |
|
||
| 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 |
|
||
| 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; the shipped E2E harness carries forward (D17) |
|
||
| 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.1 Business success metrics
|
||
|
||
| Outcome | Measured by (source) | Baseline captured how | Owner | Review cadence |
|
||
| --- | --- | --- | --- | --- |
|
||
| BO-1 (adoption for real docs) | Dogfooding: complete revise→review→keep/reject→preview without leaving the editor or copy-pasting | The copy-paste-from-chat flow as the before | Ben | At first build |
|
||
| BO-2 (feels built-in) | The diff, accept/reject, and preview use surfaces the writer already knows | Bespoke-UI alternatives as the before | Ben | At first build |
|
||
|
||
### 8.2 Product success metrics
|
||
|
||
| # | 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-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 |
|
||
|
||
## 9. Telemetry, Observability & Monitoring
|
||
|
||
A local VS Code extension — **no telemetry service, no server monitoring.** The
|
||
user-facing turn log is the OutputChannel (PUC-5). Deliberately omitted: events
|
||
pipeline, dashboards, alerts, SLOs (nothing is operated as a service). N/A —
|
||
TEL/SLO/ALR rows are not applicable to a serverless local extension.
|
||
|
||
## 10. Operations
|
||
|
||
### 10.1 Operational plan
|
||
|
||
N/A — a local VS Code extension has no day-to-day operator; the "operator" is the
|
||
developer running it. No on-call, no service runbooks. Routine maintenance is
|
||
dependency patching (§10.3).
|
||
|
||
### 10.2 Support & incident response
|
||
|
||
Pre-v1; issues are filed on the project tracker and triaged in a session. One line:
|
||
a bug reaches us as a tracker issue.
|
||
|
||
### 10.3 Maintenance & lifecycle
|
||
|
||
- **Dependency patching:** the machine-coauthor runtime and dev deps via the normal
|
||
tracker flow.
|
||
- **Backup / restore:** N/A — sidecars are git-committed; the baseline is disposable.
|
||
- **Capacity & cost:** local; the only cost is the machine's token usage, surfaced
|
||
live (PUC-5).
|
||
- **Deprecation / sunset:** this design is superseded only by a later architecture
|
||
spec.
|
||
|
||
## 11. Documentation plan
|
||
|
||
- **Where docs live:** spec + dev docs → framework repo; this Solution Design →
|
||
content repo `specs/`; no public-facing site in scope.
|
||
|
||
| # | Document | Audience | Location | New / updated |
|
||
| --- | --- | --- | --- | --- |
|
||
| DOC-1 | This Solution Design | dev/operator | content repo `specs/coauthoring-native-surfaces.md` | new |
|
||
| DOC-2 | README "how coediting maps to native editor surfaces" | dev/user | framework repo README | new (at delivery) |
|
||
|
||
## 12. Traceability matrix
|
||
|
||
| Pain | Business UC | Product UC | Tests | Measured by |
|
||
| --- | --- | --- | --- | --- |
|
||
| PP-1 | BUC-1 | PUC-1 | host E2E: pin→QuickDiff→native diff | PM-1 / BO-1 |
|
||
| PP-2 | BUC-2 | PUC-2 | host E2E: propose→review→keep/undo→keep-all | PM-2 / BO-1 |
|
||
| PP-3, PP-4 | BUC-3 | PUC-3 | unit (renderAnnotations) + host E2E: annotate toggle | PM-3 / BO-1 |
|
||
| PP-5 | BUC-4 | PUC-5 | host E2E: progress→cancel | BO-1 |
|
||
| (displacement) | BUC-3a | PUC-4/6 | unit (anchorer) + host E2E: reanchor→displaced | PM-1 |
|
||
| PP-1 (comment→edit) | BUC-5 | PUC-8 | host E2E: comment→reply→offer→pending proposal | BO-1 |
|
||
| (no hijack) | — | PUC-7 | host E2E: enter/exit gates every surface; non-coedited doc inert | BO-2 |
|
||
|
||
## 13. Open Questions & Decisions log
|
||
|
||
**Open**
|
||
|
||
| # | Question | Owner | Blocks |
|
||
| --- | --- | --- | --- |
|
||
| 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 |
|
||
|
||
**Resolved**
|
||
|
||
| # | Decision | Resolution | Date |
|
||
| --- | --- | --- | --- |
|
||
| 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). *(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 |
|
||
| 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 |
|
||
| D6 | Design tooling | Code directly (rubric −6); no bespoke UI surface | 2026-06-27 |
|
||
| D7 | Coediting is opt-in per document | A document enters coediting via "Coedit this Document with Claude"; every surface gates on the active coediting set (INV-10), so other markdown files are never hijacked | 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 |
|
||
| 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. *(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). *(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 |
|
||
| 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 |
|
||
| 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
|
||
|
||
- **Baseline (checkpoint)** — the last settled version, used as the diff *original*.
|
||
Its source follows the file's home: git `HEAD` for tracked files (resets on
|
||
commit), else a snapshot set on entry and reset by "Mark Changes as Reviewed."
|
||
Disposable; never committed by us (INV-7).
|
||
- **QuickDiffProvider** — the VS Code API that supplies gutter dirty-diff bars from a
|
||
virtual *original* document.
|
||
- **Pending change** — a machine-proposed edit landed optimistically in the live
|
||
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
|
||
that persists anchors, authorship, proposed changes, and threads, and round-trips
|
||
to an outer review forge.
|
||
- **Provenance** — the author envelope (the human's git identity, or the machine
|
||
acting on-behalf-of a human) reused across attributions, proposed changes, and
|
||
threads (INV-8).
|
||
- **Displaced** — an anchor that could not be confidently re-resolved; shown, never
|
||
silently moved (INV-1).
|
||
- **Decision unit vs attribution unit** — a block (paragraph/header/list-item/fence)
|
||
is what the writer keeps or rejects; the word diff inside it is what carries
|
||
authorship.
|
||
- **Pure core / thin controller** — the vscode-free, unit-tested logic vs. the
|
||
vscode-aware wiring split (INV-6).
|
||
- **References:** VS Code APIs — SCM/`QuickDiffProvider`, diff editor (`vscode.diff`),
|
||
Markdown preview `markdown-it` contribution, Comments,
|
||
`TextDocumentContentProvider`, decorations, CodeLens. *Prior art (patterns proven in earlier
|
||
coauthoring work, reused here on their own merits): baseline pinning, char-precise
|
||
attribution, the portable coauthoring sidecar, live turn progress, and editable
|
||
proposals (tweak-before-keep with mixed human/machine authorship).*
|