Files
vscode-cowriting-plugin-con…/specs/coauthoring-native-surfaces.md
T
BenStullsBets f6a86d6354 spec(0062): reframe around the human problem — coediting markdown with a machine
Reframe §1 away from a redesign-of-the-old-plugin narrative and root it in
the real-world problem: coediting a markdown document with a machine is hard
today (rewrite-not-a-diff, all-or-nothing, blurred authorship, no clean
rendered view, opaque machine work). Solution and technical substance stand on
their own; proven patterns reused on merit, not as old-plugin lineage.

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

44 KiB
Raw Blame History

status
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.1.0
Source artifacts North-star architecture spec (design-only) for human↔machine markdown coediting. Reference: VS Code's native review surfaces (SCM/QuickDiff, diff editor, multi-diff editor, Markdown preview, Comments). Supersedes: none.

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 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: committing a specific product packaging or release; multi-file / whole-project machine orchestration; a server or cloud backend. This is a design-only north star (see §2): packaging and delivery decisions are deferred.
  • 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); the writer's document is never mutated without their explicit acceptance; no secret bytes in any persisted artifact.
  • Dependencies: a local machine-coauthor runtime; the editor's stable extension APIs for the surfaces above.

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.

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.

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 alters my document until I keep it
  • BUC-2 acceptance criteria: no proposed change alters the document until the writer keeps it; each change is independently keepable/discardable; 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.

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.

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-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" (a pinned baseline) 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 changes (BUC-2) The multi-diff review editor with per-change Keep/Undo One native surface to sweep all of the machine's changes, accepting or discarding each
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 (supports BUC-1/3) The Comments API — threaded comments anchored to a region The familiar review-comment experience, in the gutter
Follow + stop the machine (BUC-4) A progress notification + a shared OutputChannel, with Cancel Live, stoppable, legible machine work

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, plus a small stable-API boundary on per-change Keep/Undo (§6.10, §13-Q2).

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 product packaging or a delivery sequence — those are taken later (§7.1, §13-Q1).

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

Scenario: PUC-1 — see the diff against the pinned baseline (realizes BUC-1)
  Given I run "Pin Review Baseline to Now"
  When I (or the machine) later change the document
  Then the source-control 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 — 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 the proposed changes open in a multi-diff review (baseline ⟷ proposed)
  And each change offers Keep / Undo (Accept / Reject)
  And toolbar actions offer Keep all / Reject all
  And my 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 annotations enabled
  Then insert/delete marks and authorship colors render inside the native preview
  And toggling annotations off renders the document clean
Scenario: PUC-4 — region thread (realizes BUC-3a)
  Given I select a passage and add a coauthoring thread
  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
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
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

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 source-control entry, "Cowriting," coexists with Git. Its quick-diff provider draws the familiar change bars in the editor gutter against the pinned baseline; its resource group lists the document's pending coauthoring changes; "Open Changes" uses the native diff editor.
  • Native diff editor. The writer's normal diff (baseline ⟷ live), with the platform's inline/side-by-side toggle, navigation, and accessibility.
  • 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 Keep all / Reject all. This is the accept/reject surface.
  • 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. Region threads via the native Comments API (create on selection, reply, resolve).
  • 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, multi-diff empty, 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 does not mutate the document until the writer keeps it (Keep in the multi-diff, or Accept). The machine proposes; the human disposes.
  • 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 lives outside the repo: the pinned baseline is the diff original and is stored in extension 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 (the human's git identity, or the machine acting onBehalfOf a human), reused across attributions, proposed changes, and threads.
  • INV-9 — machine turns are observable and cancellable: a turn streams progress and honors an AbortSignal; the progress reducer is pure.

6.2 High-level architecture

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 + 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[Review controller<br/>multi-diff Keep/Undo]
    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[Multi-diff review + Keep/Undo]
    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).
  • Review controller — turns the machine's emitted edits into pending proposed changes, opens the multi-diff review, and applies Keep/Undo by delegating to the proposed-change model; 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)
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/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.

// .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 providerTextDocumentContentProvider for cowriting-baseline:; provideTextDocumentContent(uri) → baselineText. BaselineStore.pin(docUri, text, reason) / get(docUri) → {text, reason} | null (out-of-repo, INV-7).
  • QuickDiffProviderprovideOriginalResource(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 (never mutates the doc — 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.
  • Review controlleropenReview(docUri) opens the multi-diff (baseline ⟷ proposed); keep(proposalId) = Accept (applies the edit, attributes changed words); undo(proposalId) = Reject (drops it); keepAll() / undoAll() = Keep/Reject all (apply descending, skip + report any displaced change).
  • Preview extensionextendMarkdownIt(md) → md.use(cowritingPlugin); the plugin invokes the pure renderAnnotations(tokens, attributions, proposals, options) transform, gated by the cowriting.annotations setting/command.
  • Turn runnerrunEditTurn(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.
  • ThreadController — create/reply/resolve via the Comments API.

6.5 PerProduct-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
  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

sequenceDiagram
  actor U as Writer
  participant R as Review ctrl
  participant S as Machine runtime
  participant M as Multi-diff editor
  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->>M: openReview (baseline ⟷ proposed)
  U->>M: Keep / Undo per change · Keep all / Reject all
  M->>R: keep(p)/undo(p)
  R-->>U: document updated only on Keep; authorship recorded
  • Implementation: the machine never edits the document; it emits pending proposed changes (INV-5). The multi-diff editor is the review surface; Keep = Accept (apply + attribute changed words), Undo = Reject. Keep-all/reject-all apply descending and skip + report any displaced change. Stable-API boundary: the multi-diff editor is stable; per-change inline Keep/Undo overlays as some AI tools render them lean on evolving editor APIs — the stable fallback is per-hunk CodeLens Keep/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 + 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; a freshly pinned, zero-diff document renders clean.

PUC-4 / PUC-6 — threads & re-anchoring

  • Implementation: ThreadController + Anchorer. 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-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) 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 the native surfaces — the diff editor, multi-diff, 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 Multi-diff editor with Keep/Undo, backed by the proposed-change model source-control stage/unstage; in-editor CodeLens Keep/Reject; bespoke webview ✓/✗ Closest to a modern "review the machine's edits" feel; reviews all changes in one native surface. Stable fallback: per-hunk CodeLens (§6.10)
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 storage out-of-repo global storage by URI hash (INV-7) commit the baseline into the repo; in-memory only Never pollutes the repo; works for any file incl. untitled; disposable review state
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
Scope design-only north star (no packaging/migration commitment) commit a packaging + delivery sequence now Keeps the architecture decision separate from the delivery decision (§2, §7.1)

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 — pin baseline → assert QuickDiff original; ask-to-edit → assert pending proposals + multi-diff opened; Keep/Undo → assert document mutation only on Keep + authorship recorded; toggle annotations → assert preview HTML marks; thread create → re-anchor → displaced. Where the harness cannot drive a native surface's chrome directly (multi-diff Keep/Undo, Comments UI, preview 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).
  • Named E2E scenarios this design must pass: PUC-1 pin→diff; PUC-2 propose→review→keep/undo→keep-all; PUC-3 annotate-toggle; PUC-4/6 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).
  • 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 Keep/Reject in the diff editor (§6.10); the proposed-change model is unchanged.
  • Feature flags / settings: cowriting.annotations (preview annotations on/off), cowriting.liveProgress.revealOutput. The whole capability is additive — off by absence of a pinned baseline / sidecar.

6.10 Migration & compatibility

Greenfield — nothing to migrate. This is a from-scratch design for in-editor human↔machine markdown coediting; it does not replace an existing data format or contract that must be carried.

  • Data migration: none. The sidecar envelope is additive and unknown-field-preserving (INV-4) from v1, so future versions extend it without a break. The baseline (out-of-repo, disposable) needs no migration ever.
  • Backwards compatibility: N/A at v1 (nothing precedes it); the envelope's forward-compatibility rule (INV-4) protects future consumers.
  • Cutover: N/A (design-only; no packaging committed — §7.1).
  • 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 that some AI tools render uses evolving editor APIs; the design's stable contract is therefore "multi-diff review + per-hunk Keep/Reject CodeLens," with the inline overlay as a progressive enhancement where the API allows (§13-Q2).

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 build order follows the surface mapping — stand up the pure cores, 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). Each delivered increment is one SPEC → one plan → one execution once decomposed.

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
Per-change Keep/Undo overlay relies on evolving editor APIs M / M Stable contract = multi-diff + per-hunk CodeLens Keep/Reject; overlay is progressive enhancement (§6.10)
Two source controls (Git + Cowriting) confuse the SCM viewlet L / M Multiple SCM providers coexist by design; name it clearly; QuickDiff alone (no full SCM) is the minimal fallback
Built-in preview can't host interactive ✓/✗ inside the preview M / L Accept/reject lives in the multi-diff surface, not the preview; the preview is read/annotate only (a deliberate split)
Native surfaces are harder to E2E than a queryable webview M / L Assert controller/provider state + pure-transform output
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 (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 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 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

13. Open Questions & Decisions log

Open

# Question Owner Blocks
Q1 Product packaging & delivery sequence — the decision to act on this north star 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 packaging/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 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

14. Glossary & References

  • Pinned baseline — the writer-chosen "good" snapshot used as the diff original; stored out-of-repo, disposable (INV-7).
  • 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; here the accept/reject surface (Keep/Undo).
  • 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), multi-diff editor, Markdown preview markdown-it contribution, Comments, TextDocumentContentProvider, decorations. Prior art (patterns proven in earlier coauthoring work, reused here on their own merits): baseline pinning, char-precise attribution, the portable coauthoring sidecar, and live turn progress.