Files
vscode-cowriting-plugin-con…/specs/coauthoring-native-surfaces.md
T
BenStullsBets 74df46d078 spec(0062): native-first coauthoring — VS Code-idiomatic redesign (design-only north star)
Solution Design for a brand-new plugin that re-homes the inner-loop
coauthoring experience onto native VS Code surfaces: QuickDiffProvider +
native diff editor (any diff), multi-diff editor Keep/Undo (propose/accept),
built-in Markdown preview markdown-it extension (rendered preview), Comments
API (threads). Carries forward pinned baseline (F6), authorship attribution
(F3), cross-rung sidecar (F5), live progress (#60), on-behalf-of identity, and
the pure-core/thin-controller discipline. Design-only: no repo/migration
commitment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 05:43:22 -07:00

789 lines
44 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
status: draft
---
# Solution Design: Native-First Coauthoring — a VS Code-idiomatic redesign
| | |
| --- | --- |
| **Author(s)** | Ben Stull (with Claude) |
| **Reviewers / approvers** | Ben Stull |
| **Status** | `draft` |
| **Version** | v0.1.0 |
| **Source artifacts** | North-star architecture spec (design-only). Lineage: the shipped plugin Epic `benstull/vscode-cowriting-plugin#1` and its Features F1F12 (the experience this re-homes). Reuses concepts from `coauthoring-inner-loop.md`, `coauthoring-attribution.md`, `coauthoring-propose-accept.md`, `coauthoring-diff-view.md`, `coauthoring-rendered-preview.md`, `coauthoring-cross-rung-format.md`, `coauthoring-live-progress.md`. Supersedes: none (design-only; does not commit a repo or migration). |
**Change log**
| Date | Version | Change | By |
| --- | --- | --- | --- |
| 2026-06-27 | v0.1.0 | Initial draft — brainstorming session 0062 | Ben Stull + Claude |
---
## 1. Business Context
*The business lens — solution-agnostic throughout. No mechanism is proposed until §2.*
### 1.1 Executive Summary
A writer working with a machine coauthor needs three things to feel native to
their editor: to **see what changed** against a known-good point, to **review and
accept or reject the machine's proposed changes** one at a time, and to **read a
clean rendered version** of the document with those changes legible. The existing
plugin (Epic #1, F1F12) proved that experience is valuable — but it built each of
those three surfaces as bespoke UI (custom webviews, hand-rolled diff rendering,
editor decoration overlays) that the writer must *learn*. This design re-homes the
same experience onto the surfaces the writer **already knows** — VS Code's own diff
editor, Source Control gutter, multi-diff review, and Markdown preview — so the
coauthoring loop reads as a built-in capability of the editor rather than a foreign
panel grafted onto it. The value is lower cognitive cost for the writer and a
dramatically smaller surface for us to build, test, and maintain.
### 1.2 Background
The forge (Gitea / rfc-app) is the *outer* loop — durable review and record. The
*inner* loop — writing, keystroke by keystroke, with a machine coauthor — lives in
the editor. The shipped plugin delivered that inner loop as a standalone VS Code
extension on `@cline/sdk`, accreting twelve Features: region threads, char-precise
attribution, propose/accept, cross-rung persistence, a diff view, a rendered
track-changes preview, an interactive review panel, a preview toolbar, an inline
editor diff, and live turn progress. Each Feature solved a real need, but the diff,
the accept/reject, and the preview were each implemented as **custom rendering**
webviews with bundled markdown-it/mermaid, sealed message channels, and
`TextEditorDecorationType` overlays — reinventing machinery VS Code already ships.
The accumulated UI is powerful but idiosyncratic: it does not look or behave like
the rest of the editor, and it is large to maintain.
VS Code, meanwhile, exposes first-class APIs for exactly these three needs. The
strategic question this spec answers: *if we designed the plugin from scratch today
with "feels like a built-in feature" as the north star, how close to native VS Code
patterns could we get, and what would the architecture be?*
### 1.3 Business Actors / Roles
| Role | Responsible for (in the business) |
| --- | --- |
| **Coauthor (human)** | A writer/engineer authoring prose, specs, or docs in their editor. Owns the document; decides what changes land. |
| **Coauthor (machine)** | The machine coauthor (Claude, via `@cline/sdk`). Proposes changes on the human's behalf; never lands them unilaterally. |
### 1.4 Problem Statement
The coauthoring experience is delivered through UI the writer must learn anew —
bespoke panels, custom-rendered diffs, and overlay decorations that do not match,
and do not cooperate with, the editor surfaces the writer uses every day. The
experience is *foreign to its host*. Separately, that bespoke UI is costly to
build and maintain: every surface (diff, accept/reject, preview) is a hand-written
renderer with its own bugs, its own test harness, and its own divergence from
platform conventions (theming, scroll-sync, accessibility, keyboard model).
### 1.5 Pain Points
| # | Pain | Who feels it | Cost / frequency today |
| --- | --- | --- | --- |
| PP-1 | The diff/accept/preview surfaces don't look or behave like the rest of VS Code — new mental model to learn | Coauthor (human) | Every session; friction on every review |
| PP-2 | Custom webview/decoration rendering re-implements platform machinery (theming, scroll-sync, a11y, diff layout) and drifts from it | Maintainer | Ongoing; each Feature added more surface to own |
| PP-3 | Accept/reject lives in a bespoke panel rather than where the writer reviews changes natively (the diff) | Coauthor (human) | Every proposal review |
| PP-4 | The rendered preview is a sealed webview that can't cooperate with other Markdown extensions the writer already uses | Coauthor (human) | Every preview open |
### 1.6 Targeted Business Outcomes
| # | Outcome | Success metric | Baseline → Target | Guardrail (must not regress) | How / when measured |
| --- | --- | --- | --- | --- | --- |
| BO-1 | The coauthoring loop reads as a built-in editor capability (familiarity, lower learning cost) | Writer can complete a see-diff → review-proposal → read-preview loop using only native surfaces they already know | bespoke panels → native surfaces | The full inner-loop experience (diff, accept/reject, preview, threads, attribution, live progress) is preserved | Dogfooding walkthrough on the design's first build |
| BO-2 | The plugin is materially cheaper to build and maintain | Custom-rendering LOC retired in favor of platform APIs | large webview/decoration surface → thin controllers over native APIs | No capability dropped | Code-size + test-surface comparison at build time |
### 1.7 Scope (business)
- **In scope:** a from-scratch architecture for the inner-loop coauthoring
experience that maximizes use of native VS Code surfaces for the three core needs
(see a diff, review proposed changes, read a rendered preview), plus the proven
supporting capabilities (region threads, authorship attribution, durable
cross-rung persistence, live turn progress, on-behalf-of identity).
- **Out of scope:** committing a specific repository, a migration plan from the
shipped plugin, or a marketplace release. This is a **design-only north-star**
(see §2): repo-and-migration decisions are deferred to a delivery decision, not
taken here.
- **Non-goals:** changing *what* the coauthoring experience is (the loop is
validated); a server or cloud backend (the inner loop stays serverless,
git-native); multi-file agent orchestration beyond the single-document loop.
### 1.8 Assumptions · Constraints · Dependencies
- **Assumptions:** VS Code's stable APIs (SCM/QuickDiff, diff editor, multi-diff
editor, Markdown preview `markdown-it` contribution, Comments, decorations,
`TextDocumentContentProvider`, `FileSystemWatcher`) remain available; the machine
coauthor runs locally via `@cline/sdk` as in the shipped plugin.
- **Constraints:** serverless and git-native (no external store); no secret bytes
in artifacts or transcripts (§6.3-handbook); a VS Code extension has **no §9
deploy pipeline stage** (it is not a webapp — see §7.2).
- **Dependencies:** `@cline/sdk` (loaded via runtime dynamic `import()` — never
bundled; a POC-validated fact carried forward). The shipped plugin's pure cores
(attribution, anchoring, merge, turn-progress reducer) are the reuse source.
### 1.9 Business Use Cases
**BUC-1 — As a human coauthor, I can see what changed in my document since a point I
chose, in a familiar diff, so that I can trust what I am about to keep.**
```gherkin
Scenario: BUC-1 — review changes against a chosen point
Given I marked the document as "good" at some earlier moment
And the document has changed since then
When I ask to see what changed
Then the changes are shown in the same diff experience I use for everything else
```
- **BUC-1 acceptance criteria:** the human sees an accurate before/after of the
document against the chosen point, in their editor's normal diff experience.
**BUC-2 — As a human coauthor, I can review the machine's proposed changes and accept
or reject them one at a time, so that I stay in control of what lands.**
```gherkin
Scenario: BUC-2 — accept or reject proposed changes
Given the machine coauthor has proposed changes to my document
When I review them
Then I can keep or discard each proposed change individually, or all at once
And nothing the machine proposed lands until I keep it
```
- **BUC-2 acceptance criteria:** no proposed change alters the document until the
human keeps it; each change is independently keepable/discardable; an accept-all
and reject-all exist.
**BUC-3 — As a human coauthor, I can read a clean, rendered version of my document with
the changes and authorship made legible, so that I can judge the result as prose,
not as markup.**
```gherkin
Scenario: BUC-3 — read the rendered result
Given my document has changed and has mixed authorship
When I open its rendered preview with annotations on
Then I see the document rendered, with changes and authorship visibly marked
And I can turn the annotations off to read it clean
```
- **BUC-3 acceptance criteria:** the rendered document shows track-changes and
authorship when annotations are on, renders clean when off, in a preview that
behaves like the editor's normal Markdown preview.
**BUC-3a (unhappy) — As a human coauthor, when an anchored thread / attribution /
proposal can no longer be confidently placed after edits, I am shown it as orphaned
rather than silently moved to the wrong place.**
---
## 2. Solution Proposal
**Build software: a brand-new VS Code extension that delivers the inner-loop
coauthoring experience almost entirely through native VS Code surfaces**, keeping
the shipped plugin's validated *experience* and its proven *pure cores* while
discarding its bespoke *rendering*.
The unifying idea: **model the coauthored document as a tiny Source Control.** The
last point the human marked "good" (the **pinned baseline**, carried from F6) is the
SCM *original*; the live document is the working tree; the machine's proposed edits
are *pending changes*. Once the relationship is framed as source control, VS Code's
own machinery supplies the three core surfaces for free:
| Core need | Native surface | Replaces (shipped plugin) |
| --- | --- | --- |
| See any diff | `QuickDiffProvider` gutter bars + the **native diff editor** (`vscode.diff`) against a `cowriting-baseline:` virtual document | F6 custom diff-view controller |
| Propose / accept / reject | the **multi-diff editor** with per-change **Keep/Undo** (the "AI edits" review feel), backed by the existing propose/accept model | F4/F10/F12 webview + decoration accept/reject UI |
| Rendered preview | **extend the built-in Markdown preview** via a `markdown-it` plugin contribution (authorship + track-changes injected into VS Code's own preview) | F7/F10 sealed webview renderer |
| Region threads | the **Comments API** (already native in F2 — kept as-is) | — (already native) |
| Live turn progress | notification activity-line + a shared **OutputChannel** + cancellable turn | #60 (kept; already native) |
**Why this approach over the alternatives.** The do-nothing option (keep the shipped
plugin) leaves a foreign-feeling, maintenance-heavy UI. A "polish the webviews"
option spends effort making bespoke surfaces *look* native without getting the
platform's behavior (theming, scroll-sync, a11y, keyboard model, cooperation with
other extensions) — it pays the cost without the benefit. Re-homing onto native
surfaces gets the behavior for free and shrinks what we own. The cost is accepting
the platform's affordances where they differ from our bespoke ones, and a small
stable-API boundary on the per-change Keep/Undo overlay (§6.10, §13).
**Decision posture:** this is a **design-only north star**. It commits to the
architecture and the native-surface mapping; it does **not** commit to a new repo
vs. an in-place re-platform, nor to a data migration. Those are taken at delivery
(§7.1, Q1 in §13).
**Solution scope.** *In:* the architecture, surface mapping, data model, contracts,
and per-use-case design for the native-first plugin, plus carry-forward of pinned
baseline, authorship attribution, cross-rung persistence, live progress, threads,
and on-behalf-of identity. *Out:* repo/migration choice, marketplace publish,
multi-file orchestration. *Non-goal:* re-deriving the coauthoring experience — it is
inherited from F1F12.
---
## 3. Product Personas
| Product persona | In the plugin | Maps to business role(s) |
| --- | --- | --- |
| **PP-1 Inner-loop writer** | Pins a baseline, asks the machine to edit, reviews proposed changes in the diff, reads the annotated preview, opens region threads. | Coauthor (human) |
| **PP-2 Machine coauthor (Claude)** | Runs a turn via `@cline/sdk`, streams progress, and emits **proposed** edits (never lands them); authored spans carry its provenance. | Coauthor (machine) |
## 4. Product Use Cases
```gherkin
Scenario: PUC-1 — see the diff against the pinned baseline (realizes BUC-1)
Given I run "Cowriting: Pin Review Baseline to Now"
When I later edit the document
Then the SCM gutter shows change bars against the baseline
And "Open Changes" opens the native diff editor (baseline live), inline or side-by-side at my choice
```
```gherkin
Scenario: PUC-2 — review and accept/reject proposed changes (realizes BUC-2)
Given I ask the machine coauthor to edit the document (or a selection)
When the turn completes
Then the proposed changes open in a multi-diff review (baseline proposed)
And each change offers Keep / Undo (Accept / Reject)
And toolbar actions offer Accept all / Reject all
And the document is unchanged until I keep a change
```
```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 Cowriting annotations enabled
Then track-changes (insert/delete) and authorship colors render inside the native preview
And toggling "cowriting.annotations" off renders the document clean
```
```gherkin
Scenario: PUC-4 — region thread (realizes BUC-3a; carried from F2)
Given I select a region and add a coauthoring thread
When I edit around it or reload
Then the thread re-anchors; if it cannot be confidently placed it is shown orphaned, never silently moved
```
```gherkin
Scenario: PUC-5 — live turn progress (carried from #60)
Given I started a machine turn
When the machine is working
Then a notification shows the live activity line and token count
And the "Cowriting: Claude" OutputChannel streams the full text
And I can cancel the turn
```
```gherkin
Scenario: PUC-6 — reload / external change (realizes BUC-3a)
Given I reopen the workspace or an external change lands (a git pull)
When the document loads
Then baseline, proposals, attributions, and threads re-resolve against current text
And anything that cannot be confidently placed is shown orphaned
```
## 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):
- **Source Control gutter & viewlet.** A dedicated `SourceControl` named
*"Cowriting"* coexists with the real Git SCM. Its `QuickDiffProvider` draws the
familiar change bars in the editor gutter against the pinned baseline. Its
resource group(s) list the document's pending coauthoring changes; "Open Changes"
uses the native diff editor.
- **Native diff editor.** `vscode.diff(baselineUri, docUri)` — the writer's normal
diff, with the platform's inline/side-by-side toggle, navigation, and a11y.
- **Multi-diff review editor.** When the machine proposes edits, a multi-diff editor
opens showing baseline⟷proposed, each change carrying **Keep/Undo**; a toolbar
carries **Accept all / Reject all**. This is the accept/reject surface.
- **Built-in Markdown preview.** The writer's normal "Open Preview to the Side";
with Cowriting annotations on, authorship colors and track-changes marks appear
*inside* it (contributed `markdown-it` plugin + preview stylesheet). A command/
setting toggles annotations.
- **Comments gutter.** Region threads via the native Comments API (create on
selection, reply, resolve), exactly as F2.
- **Notification + OutputChannel.** Live turn progress, with a Cancel action.
### 5.1 Design tooling decision
- **Decision:** **Code directly.**
- **Rationale:** the entire thesis is to use 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 is specified at
wireframe-level prose against known platform surfaces), Q3 1 (no multi-page
visual language), Q4 1 (VS Code *is* the design system here), Q5 1 (solo build),
Q6 1 (logic-dominant — wiring pure cores to platform APIs). Total **6 → Skip
Design**. The only "designed" pixels are the preview annotation stylesheet (a
small CSS theme using VS Code 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, multi-diff empty, preview loading/error) plus the
coauthoring **orphaned** state surfaced in the Comments thread / proposal listing.
## 6. Technical Design
### 6.1 Invariants
- **INV-1 (carried from F2/INV-1):** an anchored artifact (thread, attribution,
proposal) is **never silently moved** to a wrong span; if it cannot be confidently
re-resolved it becomes **orphaned** (visible, flagged).
- **INV-2 (carried):** persistence is **git-native and serverless** — diffable,
human-resolvable JSON; **no server, no external store**. (The *baseline* is the
one exception: it is a throwaway local artifact stored **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 (carried):** the persisted artifact is **forward-compatible and
cross-rung**: `schemaVersion` present; `anchors`, `provenance`, `attributions`,
`proposals`, `threads` share one envelope; **unknown fields are preserved** on
read/merge round-trips (the F5 cross-rung contract).
- **INV-5 — proposals are pending until kept:** a proposed edit does **not** mutate
the document until the human keeps it (Keep in the multi-diff, or Accept). This is
the propose-by-default rule (carried from F4), now expressed through the SCM/diff
surface.
- **INV-6 — pure cores stay vscode-free:** attribution, anchoring, merge, diffing,
and the turn-progress reducer are pure modules with no `vscode` import,
unit-tested in isolation; controllers are the only `vscode`-aware layer (the
shipped plugin's most valuable discipline — carried wholesale).
- **INV-7 (carried from F6):** the **pinned baseline** is the diff *original* and is
stored **outside** the working tree (global storage keyed by document URI hash),
so it never pollutes the repo and works for any file (including untitled).
- **INV-8 — provenance carries on-behalf-of identity:** every authored span /
message records `provenance` (human git identity, or agent with `onBehalfOf` the
human), reused across attributions, proposals, and threads (carried from
identity + F3 + F5).
- **INV-9 (carried from #60):** a machine turn is **observable and cancellable**
it streams progress and honors an `AbortSignal`; the progress reducer is pure.
### 6.2 High-level architecture
The shape mirrors the shipped plugin's most durable pattern: **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 + orphan]
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[Proposal/review controller<br/>multi-diff Keep/Undo]
PV[Preview extension<br/>extendMarkdownIt]
TC[ThreadController<br/>Comments API]
LP[liveProgress relay]
ST[SidecarRouter + Store]
end
subgraph Native["Native VS Code surfaces"]
GUT[Gutter dirty-diff + SCM viewlet]
DIFF[Native diff editor]
MDIFF[Multi-diff review + Keep/Undo]
MDPREV[Built-in Markdown preview]
COMM[Comments gutter/peek]
NOTE[Notification + OutputChannel]
end
SDK[["@cline/sdk (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 user's document.
- **Baseline provider** — a `TextDocumentContentProvider` for the
`cowriting-baseline:` scheme; serves the pinned baseline text for any doc URI from
the out-of-repo baseline store (INV-7).
- **Proposal/review controller** — turns the machine's emitted edits into pending
proposals, opens the multi-diff review, and applies Keep/Undo (Accept/Reject) by
delegating to the propose/accept model; must honor INV-5.
- **Preview extension** — contributes `markdown-it` so the pure `renderAnnotations`
transform runs inside the built-in preview; owns no webview.
- **SidecarRouter + Store** — persistence (carried from F8): repo `.threads/` when
in-workspace, global-storage sidecar by URI-hash otherwise; cross-rung envelope.
### 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), per SidecarRouter |
| Pinned baseline | the extension | doc URI hash → baseline text + pin reason/time | **out-of-repo** global storage (INV-7) — throwaway, not committed |
| Provenance | the extension | `kind` (human/agent), `id`, `email`, `onBehalfOf`, `agent{sdk,model,sessionId}` | embedded in sidecar (INV-8) |
The envelope is the F5 **cross-rung contract** verbatim (v1.x, additive,
unknown-field-preserving) — the sidecar round-trips to the Gitea substrate
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 cross-rung envelope (carried from F5)
{
"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 (carried from #47)
"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 provider** — `TextDocumentContentProvider` for `cowriting-baseline:`;
`provideTextDocumentContent(uri) → baselineText`. `BaselineStore.pin(docUri,
text, reason)` / `get(docUri) → {text, reason} | null` (out-of-repo, INV-7).
- **QuickDiffProvider** — `provideOriginalResource(docUri) → cowriting-baseline:`
URI (drives gutter bars). Registered on the "Cowriting" `SourceControl`.
- **Agent edit seam (carried from F3/F4)** — the single boundary where machine text
enters the model: `proposeAgentEdit(docUri, range, newText, provenance)`
produces **pending** `proposals[]` entries (never mutates the doc — INV-5).
Block/word granularity via the pure diff engine (`diffToBlockHunks` /
`wordEditHunks`, carried from #47/F4). Word-diff splitting reconciled by
`matchEvent` (the F4 host fix) lives in the pure engine.
- **Proposal/review controller** — `openReview(docUri)` opens the multi-diff
(baseline⟷proposed); `keep(proposalId)` = Accept (applies the edit, attributes
changed words via the F5/F4 `acceptBlock` path, INV-40-style); `undo(proposalId)`
= Reject (drops it); `keepAll()` / `undoAll()` = Accept/Reject all (descending,
orphan-skip + report — carried from #46).
- **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 (carried from #60)** — `runEditTurn(prompt, { onProgress,
signal, env }) → AgentResult`; pure `turnProgress` reducer; `liveProgress` relay
to notification + OutputChannel; `CLAUDE_CODE_AUTO_CONNECT_IDE=0` env carried (the
#59 Apple-Events fix).
- **ThreadController** (carried from F2) — create/reply/resolve via Comments API.
### 6.5 PerProduct-Use-Case design
#### PUC-1 — see the diff against the pinned baseline
```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: Pin baseline (cowriting.pinBaseline)
C->>B: BaselineStore.pin(docUri, text)
U->>U: edits document
C-->>U: gutter dirty-diff bars (provideOriginalResource)
U->>V: Open Changes
V->>B: read cowriting-baseline:docUri
V-->>U: native diff (baseline ⟷ live)
```
- **Implementation:** QuickDiffProvider points at the `cowriting-baseline:` URI;
the platform renders gutter bars and powers "Open Changes" / `vscode.diff`. No
custom diff rendering (replaces F6). Honors INV-3, INV-7.
#### PUC-2 — review and accept/reject proposed changes
```mermaid
sequenceDiagram
actor U as Writer
participant R as Proposal/review ctrl
participant S as "@cline/sdk"
participant M as Multi-diff editor
U->>R: Ask Claude to edit (cowriting.edit)
R->>S: runEditTurn(prompt, {onProgress, signal, env})
S-->>R: proposed text
R->>R: proposeAgentEdit → pending proposals[] (INV-5)
R->>M: openReview (baseline ⟷ proposed)
U->>M: Keep / Undo per change · Accept all / Reject all
M->>R: keep(p)/undo(p)
R-->>U: document updated only on Keep; attribution recorded
```
- **Implementation:** the machine never edits the document; it emits **pending**
proposals (INV-5). The multi-diff editor is the review surface; Keep = Accept
(apply + attribute changed words), Undo = Reject. Accept-all/reject-all carried
from #46 (descending, orphan-skip + report). **Stable-API boundary:** the
multi-diff editor is stable; per-change inline **Keep/Undo overlays** as Copilot
Edits renders them lean on evolving chat-edit APIs — the **stable fallback** is
per-hunk **CodeLens** Accept/Reject within the diff (§6.10, §13-Q2).
#### 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 + track-changes marks
E-->>P: annotated HTML (preview stylesheet)
P-->>U: native preview, annotated
```
- **Implementation:** a contributed `markdown-it` plugin runs the pure
`renderAnnotations` transform (carried/adapted from F7/F9/F10 render engines) and
a small preview stylesheet using VS Code theme variables. Toggle via
`cowriting.annotations`. No webview (replaces F7/F10). Authorship coloring +
insert/delete marks; pinned-baseline-clean rule carried (#48): a freshly pinned,
zero-diff document renders clean.
#### PUC-4 / PUC-6 — threads & re-anchoring
- **Implementation:** ThreadController + Anchorer, carried from F2/F8 unchanged.
On load / external change (FileSystemWatcher), every anchor re-resolves
(exact-unique → context-disambiguated → orphaned, INV-1). Proposals and
attributions ride the same `anchors` primitive, so they re-anchor and orphan by
the same ladder.
#### PUC-5 — live turn progress
- **Implementation:** carried from #60 verbatim — pure `turnProgress` reducer,
`runEditTurn` `onProgress`/`AbortSignal`, `liveProgress` relay to a notification
activity-line + token count + the "Cowriting: Claude" OutputChannel; Cancel →
`agent.abort()`. `CLAUDE_CODE_AUTO_CONNECT_IDE=0` env (the #59 fix) carried.
### 6.6 Non-functional requirements & cross-cutting concerns
- **Security & privacy:** serverless; no secret bytes in sidecars or transcripts
(§6.3-handbook). The machine coauthor runs locally; provenance records identity,
not credentials. The #59 fix (no spurious macOS automation prompt) is carried via
`CLAUDE_CODE_AUTO_CONNECT_IDE=0`.
- **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 an orphaned anchor
(handled, INV-1) or a missing baseline (degrade to "no diff yet").
- **Observability:** the OutputChannel stream (PUC-5) is the user-facing turn log;
no telemetry service (a local extension — §9).
- **Accessibility:** inherited from native surfaces — the diff editor, multi-diff,
Markdown preview, and Comments all carry VS Code's a11y. This is a primary
*reason* for the native-first choice (the bespoke webviews did not).
### 6.7 Key decisions & alternatives considered
| Decision | Chosen | Alternatives considered | Why chosen |
| --- | --- | --- | --- |
| Diff surface | `QuickDiffProvider` + native diff editor against a `cowriting-baseline:` virtual doc | custom webview diff (F6); decoration-only inline diff (F12) | 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** (operator-chosen), backed by the propose/accept model | SCM stage/unstage change-group; in-editor CodeLens Accept/Reject; bespoke webview ✓/✗ (F4/F10) | Closest to the modern "AI edits" review feel; reviews all changes in one native surface. Stable fallback: per-hunk CodeLens (§6.10) |
| Rendered preview | **Extend the built-in Markdown preview** (`markdown-it` contribution) | sealed custom webview (F7/F10); both surfaces | Native scroll-sync, theming, refresh, and cooperation with other Markdown extensions; deletes the webview (INV-3) |
| Baseline storage | out-of-repo global storage by URI hash (carried F6/INV-7) | commit baseline into repo; in-memory only | Never pollutes the repo; works for any file incl. untitled; disposable review state |
| Persistence envelope | the F5 cross-rung sidecar contract, unchanged | new format; inner-loop-only sidecar | Round-trips to the Gitea substrate; forward-compatible; already proven (INV-4) |
| Core/controller split | pure vscode-free cores + thin controllers (carried) | UI-coupled logic | The shipped plugin's highest-value discipline: unit-testable cores, swappable surfaces (INV-6) |
| Scope | design-only north star (no repo/migration commitment) | commit a v2 repo + migration now | Keeps the architecture decision separate from the delivery decision (§2, §7.1) |
### 6.8 Testing strategy
- **Unit (vitest, vscode-free):** the pure cores carry their existing suites —
attribution spans, anchoring (fingerprint/resolve/orphan/offset shift),
`mergeArtifacts` (union-by-id, unknown-field preservation), diff engine
(`wordEditHunks` / `diffToBlockHunks`), `turnProgress` reducer, and the new
`renderAnnotations` markdown-it transform (token-in → annotated-token-out).
- **Integration / E2E (`@vscode/test-electron`):** drive the controllers in the
Extension Development Host — pin baseline → assert QuickDiff original; ask-to-edit
→ assert pending proposals + multi-diff opened; Keep/Undo → assert document
mutation only on Keep + attribution recorded; toggle annotations → assert preview
HTML marks; thread create → re-anchor → orphan. Where the harness cannot drive a
native surface's chrome directly (multi-diff Keep/Undo, Comments UI, preview DOM),
fall back to asserting controller/provider state and the pure transform output —
the F2/F6/F7 precedent. **No Playwright** — these are native surfaces, not a
webview. E2E are first-class plan tasks (handbook §4 / §6.8).
- **Named E2E scenarios this design must pass:** PUC-1 pin→diff; PUC-2
propose→review→keep/undo→accept-all; PUC-3 annotate-toggle; PUC-4/6
thread→reanchor→orphan; PUC-5 progress→cancel.
### 6.9 Failure modes, rollback & flags
- **Orphaned anchor** (INV-1) → shown orphaned at last-known `lineHint`, never moved
→ auto-re-anchors if the text reappears → no alert (local).
- **Missing baseline** → no QuickDiff original → degrade to "pin a baseline to see
changes"; no diff shown, no error.
- **Multi-diff Keep/Undo API unavailable/changed** → fall back to per-hunk CodeLens
Accept/Reject in the diff editor (§6.10); the propose/accept *model* is unchanged.
- **Feature flags / settings:** `cowriting.annotations` (preview annotations on/off),
`cowriting.liveProgress.revealOutput` (carried #60). The whole capability is
additive — off by absence of a pinned baseline / sidecar.
### 6.10 Migration & compatibility
**Design-only — nothing is migrated by this spec.** It defines the target
architecture; the repo-and-migration decision is deferred (§2, §7.1, §13-Q1).
- **Data migration:** none committed here. The persistence envelope is the F5
cross-rung contract **unchanged**, so existing `.threads/` sidecars are readable
as-is by a native-first build — making an in-place re-platform low-risk *if*
later chosen. The baseline (out-of-repo, disposable) needs no migration.
- **Backwards compatibility:** the sidecar contract stays v1.x additive
(unknown-field-preserving, INV-4); any current consumer keeps working.
- **Cutover:** N/A (design-only). If delivered as a v2 re-platform, the natural
order is surface-by-surface (diff → preview → accept/reject), each native surface
replacing its bespoke predecessor behind the unchanged cores.
- **Stable-API boundary (recorded risk):** the native diff editor, SCM/QuickDiff,
multi-diff editor, Markdown-preview `markdown-it` contribution, Comments, and
decorations are **stable**. The per-change inline **Keep/Undo** overlay as Copilot
Edits renders it uses **evolving** chat-edit APIs; the design's stable contract is
therefore "multi-diff review + per-hunk Accept/Reject CodeLens," with the
Copilot-style inline overlay as a progressive enhancement where the API allows.
## 7. Delivery Plan
### 7.1 Approach / strategy
This is a **design-only north star**: it produces no implementation plan and ships
no code. When the operator decides to act on it, the delivery decision (§13-Q1)
picks one of:
- **New app, clean break** — a fresh repo + `app.json` (flotilla provisioning),
the shipped plugin frozen; build native-first from the reused cores.
- **v2 re-platform in place** — replace bespoke surfaces one at a time behind the
unchanged pure cores (diff → preview → accept/reject), reading existing sidecars
as-is (low-risk per §6.10).
Either way the build order follows the surface mapping: stand up the pure cores
(mostly carried), then **the diff surface** (smallest, highest-confidence native
win), then **the preview extension**, then **the multi-diff accept/reject**
(largest API-boundary risk — do it last, with the CodeLens fallback ready). One
SPEC → one plan → one execution applies per delivered Feature once decomposed.
### 7.2 Rollout / launch plan
A VS Code extension has **no §9 webapp deploy pipeline** (localhost/PPE/prod) — like
the shipped plugin, "done" for a delivered Feature = acceptance met + unit and host
E2E green; there is no marketplace publish in scope. Rollout, if/when productized,
is a marketplace concern out of scope here.
### 7.3 Risks & mitigations
| Risk | Likelihood / impact | Mitigation |
| --- | --- | --- |
| Per-change Keep/Undo overlay relies on evolving chat-edit APIs | M / M | Stable contract = multi-diff + per-hunk CodeLens Accept/Reject; overlay is progressive enhancement (§6.10) |
| Two SourceControls (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) |
| Native surfaces are harder to E2E than a queryable webview | M / L | Assert controller/provider state + pure-transform output (F2/F6/F7 precedent) |
| Design-only spec stalls (never delivered) | M / L | §13-Q1 names the delivery decision and owner; the architecture stands regardless |
## 8. Success Metrics & Measurement
### 8.1 Business success metrics
| Outcome | Measured by (source) | Baseline captured how | Owner | Review cadence |
| --- | --- | --- | --- | --- |
| BO-1 (familiarity) | Dogfooding walkthrough: complete diff→review→preview using only native surfaces | The shipped plugin's bespoke-panel flow as the before | Ben | At first native-first build |
| BO-2 (cheaper to maintain) | Custom-rendering LOC + test-surface retired vs. capability retained | Current webview/decoration LOC count | Ben | At first native-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 document mutation until Keep; each change independently keep/undo-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 "Cowriting: Claude" 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 runbooks for a service. Routine maintenance is
dependency patching (§10.3).
### 10.2 Support & incident response
Pre-v1; issues are filed on the plugin's Gitea tracker as today. One line: a bug
reaches us as a tracker issue and is triaged in a session.
### 10.3 Maintenance & lifecycle
- **Dependency patching:** `@cline/sdk` and dev deps via the normal tracker/Patchwatch flow.
- **Backup / restore:** N/A — sidecars are git-committed; the baseline is disposable.
- **Capacity & cost:** local; the only cost is the machine coauthor's token usage,
surfaced live (PUC-5).
- **Deprecation / sunset:** if delivered as a new app, the shipped plugin is frozen
(cutover §6.10); this design itself 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 the coauthoring loop maps to native surfaces" | dev/user | framework repo README | new (at delivery) |
## 12. Traceability matrix
| Pain | Business UC | Product UC | Tests | Measured by |
| --- | --- | --- | --- | --- |
| PP-1, PP-3 | BUC-2 | PUC-2 | host E2E: propose→review→keep/undo→accept-all | PM-2 / BO-1 |
| PP-1 | BUC-1 | PUC-1 | host E2E: pin→QuickDiff→native diff | PM-1 / BO-1 |
| PP-1, PP-4 | BUC-3 | PUC-3 | unit (renderAnnotations) + host E2E: annotate toggle | PM-3 / BO-1 |
| PP-2 | — | PUC-1/2/3 | retired custom-rendering LOC | BO-2 |
| (orphaning) | BUC-3a | PUC-4/6 | unit (anchorer) + host E2E: reanchor→orphan | PM-1 |
## 13. Open Questions & Decisions log
**Open**
| # | Question | Owner | Blocks |
| --- | --- | --- | --- |
| Q1 | New app (clean break) vs. v2 re-platform in the current repo — the delivery decision | Ben | Any implementation plan |
| 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) |
| Q3 | One "Cowriting" SourceControl vs. QuickDiff-only (no full SCM viewlet entry) — how much SCM presence is wanted | Ben | PUC-1 build |
| Q4 | Does any need genuinely require a webview the native surfaces can't serve (INV-3 escape hatch)? | Ben | Per-surface build |
**Resolved**
| # | Decision | Resolution | Date |
| --- | --- | --- | --- |
| D1 | Scope / altitude | Design-only north star — architecture + surface mapping, no repo/migration commitment | 2026-06-27 |
| D2 | Accept/reject primary surface | Multi-diff editor with Keep/Undo (CodeLens stable fallback) | 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 | Carry-forward from the shipped plugin | Pinned baseline (F6), authorship attribution (F3), cross-rung sidecar (F5), live progress (#60), region threads (F2), on-behalf-of identity, and the pure-core/thin-controller discipline | 2026-06-27 |
| D6 | Design tooling | Code directly (rubric 6); no bespoke UI surface | 2026-06-27 |
## 14. Glossary & References
- **Pinned baseline** — the human-chosen "good" snapshot used as the diff *original*;
stored out-of-repo, disposable (INV-7; from F6).
- **QuickDiffProvider** — the VS Code API that supplies gutter dirty-diff bars from a
virtual *original* document.
- **Multi-diff editor** — VS Code's native multi-change review surface (the "AI edits"
review feel); here the accept/reject surface (Keep/Undo).
- **Cross-rung sidecar** — the F5 forward-compatible, unknown-field-preserving
coauthoring artifact that round-trips between the inner loop and the Gitea
substrate.
- **Provenance** — the author envelope (human git identity, or agent on-behalf-of a
human) reused across attributions, proposals, and threads (INV-8).
- **Orphaned** — an anchor that could not be confidently re-resolved; shown, never
silently moved (INV-1).
- **Pure core / thin controller** — the vscode-free, unit-tested logic vs. the
vscode-aware wiring split (INV-6).
- **References:** shipped plugin Epic #1 (F1F12); specs `coauthoring-inner-loop.md`,
`coauthoring-attribution.md`, `coauthoring-propose-accept.md`,
`coauthoring-diff-view.md`, `coauthoring-rendered-preview.md`,
`coauthoring-cross-rung-format.md`, `coauthoring-live-progress.md`. `@cline/sdk`
(loaded via runtime dynamic `import()`). VS Code APIs: SCM/`QuickDiffProvider`,
diff editor (`vscode.diff`), multi-diff editor, Markdown preview
`markdown-it` contribution, Comments, `TextDocumentContentProvider`, decorations.