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>
44 KiB
status
| 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 F1–F12 (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, F1–F12) 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-itcontribution, Comments, decorations,TextDocumentContentProvider,FileSystemWatcher) remain available; the machine coauthor runs locally via@cline/sdkas 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 dynamicimport()— 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.
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.
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.
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 F1–F12.
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
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
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
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
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
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
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
SourceControlnamed "Cowriting" coexists with the real Git SCM. ItsQuickDiffProviderdraws 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-itplugin + 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:
schemaVersionpresent;anchors,provenance,attributions,proposals,threadsshare 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
vscodeimport, unit-tested in isolation; controllers are the onlyvscode-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 withonBehalfOfthe 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.
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
TextDocumentContentProviderfor thecowriting-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-itso the purerenderAnnotationstransform 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.
// .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 —
TextDocumentContentProviderforcowriting-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 pendingproposals[]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 bymatchEvent(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/F4acceptBlockpath, 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 purerenderAnnotations(tokens, attributions, proposals, options)transform, gated by thecowriting.annotationssetting/command. - Turn runner (carried from #60) —
runEditTurn(prompt, { onProgress, signal, env }) → AgentResult; pureturnProgressreducer;liveProgressrelay to notification + OutputChannel;CLAUDE_CODE_AUTO_CONNECT_IDE=0env carried (the #59 Apple-Events fix). - ThreadController (carried from F2) — create/reply/resolve via Comments API.
6.5 Per–Product-Use-Case design
PUC-1 — see the diff against the pinned baseline
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
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
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-itplugin runs the purerenderAnnotationstransform (carried/adapted from F7/F9/F10 render engines) and a small preview stylesheet using VS Code theme variables. Toggle viacowriting.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
anchorsprimitive, so they re-anchor and orphan by the same ladder.
PUC-5 — live turn progress
- Implementation: carried from #60 verbatim — pure
turnProgressreducer,runEditTurnonProgress/AbortSignal,liveProgressrelay to a notification activity-line + token count + the "Cowriting: Claude" OutputChannel; Cancel →agent.abort().CLAUDE_CODE_AUTO_CONNECT_IDE=0env (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),turnProgressreducer, and the newrenderAnnotationsmarkdown-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-itcontribution, 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/sdkand 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 (F1–F12); 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 dynamicimport()). VS Code APIs: SCM/QuickDiffProvider, diff editor (vscode.diff), multi-diff editor, Markdown previewmarkdown-itcontribution, Comments,TextDocumentContentProvider, decorations.