4ccba9ea93
Entering coediting sets the baseline to current content automatically;
'Pin Review Baseline to Now' resets it ('everything so far is settled; show
changes from here'). A coedited doc always has a baseline -> removed the
'no/missing baseline' empty state entirely; the only empty state is a
genuinely empty document. Updated INV-7, PUC-1/7, baseline provider, preview
(marks since checkpoint), failure modes, flags, glossary.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
975 lines
58 KiB
Markdown
975 lines
58 KiB
Markdown
---
|
||
status: draft
|
||
---
|
||
# Solution Design: Coediting Markdown with a Machine — a Native VS Code Experience
|
||
|
||
| | |
|
||
| --- | --- |
|
||
| **Author(s)** | Ben Stull (with Claude) |
|
||
| **Reviewers / approvers** | Ben Stull |
|
||
| **Status** | `draft` |
|
||
| **Version** | v0.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.**
|
||
|
||
```gherkin
|
||
Scenario: BUC-1 — see what changed
|
||
Given I marked my document as "good" at some earlier moment
|
||
And the machine has since revised it
|
||
When I ask to see what changed
|
||
Then the changes are shown to me as a clear before/after I can read
|
||
```
|
||
|
||
- **BUC-1 acceptance criteria:** the writer sees an accurate before/after of the
|
||
document against the chosen point.
|
||
|
||
**BUC-2 — As a writer, I can keep or reject the machine's proposed changes one at a
|
||
time, so that I stay in control of what lands.**
|
||
|
||
```gherkin
|
||
Scenario: BUC-2 — keep or reject, change by change
|
||
Given the machine has proposed changes to my document
|
||
When I review them
|
||
Then I can keep or discard each change individually, or all at once
|
||
And nothing the machine proposed 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; the writer can
|
||
**edit a proposed change before keeping it**, and those edits are attributed to the
|
||
writer; keep-all and reject-all exist.
|
||
|
||
**BUC-3 — As a writer, I can tell who wrote which parts of my document and read a
|
||
clean rendered version, so that I can judge the result as finished prose.**
|
||
|
||
```gherkin
|
||
Scenario: BUC-3 — authorship and rendered result
|
||
Given my document has mixed human/machine authorship and recent changes
|
||
When I open its rendered view with annotations on
|
||
Then I see the document rendered, with changes and authorship visibly marked
|
||
And I can turn annotations off to read it clean
|
||
```
|
||
|
||
- **BUC-3 acceptance criteria:** the rendered document shows changes and authorship
|
||
when annotations are on, renders clean when off.
|
||
|
||
**BUC-4 — As a writer, I can follow the machine's progress while it works and stop
|
||
it, so that it is never an opaque black box.**
|
||
|
||
```gherkin
|
||
Scenario: BUC-4 — visible, stoppable machine work
|
||
Given I asked the machine to revise my document
|
||
When it is working
|
||
Then I can see live progress
|
||
And I can cancel before it finishes
|
||
```
|
||
|
||
**BUC-5 — As a writer, I can leave a remark on a passage of a document I'm coediting
|
||
and have the machine respond and offer to make the change, so that a comment can
|
||
become an edit without me leaving the document.**
|
||
|
||
```gherkin
|
||
Scenario: BUC-5 — a comment summons a response and an offer to edit
|
||
Given I have invited the machine to coedit this document
|
||
When I leave a comment on a passage
|
||
Then the machine replies to my comment
|
||
And it offers to make the change I described
|
||
And if I accept, its edit arrives as a proposed change I review like any other
|
||
```
|
||
|
||
- **BUC-5 acceptance criteria:** on an invited document a comment elicits a machine
|
||
reply and an offer; accepting produces a reviewable proposed change (it never
|
||
edits directly); on a *non-invited* document nothing is summoned.
|
||
|
||
**BUC-3a (unhappy) — As a writer, when a comment, an attributed span, or a proposed
|
||
change can no longer be confidently placed after edits, I am shown it as displaced
|
||
rather than silently moved to the wrong place.**
|
||
|
||
---
|
||
|
||
## 2. Solution Proposal
|
||
|
||
**Build software: a VS Code extension that lets a writer coedit a markdown document
|
||
with a machine coauthor, delivering the whole loop through the editor's own native
|
||
review surfaces.**
|
||
|
||
The enabling idea: **model the coauthored document as a tiny source control.** The
|
||
point the writer last marked "good" (the **baseline checkpoint**, set on entering
|
||
coediting and re-pinnable) is the source-control
|
||
*original*; the live document is the working tree; the machine's proposed edits are
|
||
*pending changes*. Once the relationship is framed as source control, the editor's
|
||
own machinery supplies the experience the writer needs — and it is machinery they
|
||
already know:
|
||
|
||
| What the writer needs | Native surface that delivers it | What it gives them, for free |
|
||
| --- | --- | --- |
|
||
| See what changed (BUC-1) | Source-control gutter change bars + the **native diff editor** against a virtual *baseline* document | The exact diff experience they use for everything else: inline/side-by-side, navigation, accessibility |
|
||
| Keep/reject — and **tweak** — changes (BUC-2) | The **multi-diff review editor** with per-change **Keep/Undo** and a **writable proposed side** | One native surface to sweep all of the machine's changes — editing the proposal in place before keeping or discarding each (INV-12) |
|
||
| Authorship + rendered result (BUC-3) | The **built-in Markdown preview**, extended via a `markdown-it` plugin to inject authorship + change marks | Native rendering, scroll-sync, theming, and cooperation with the writer's other Markdown extensions |
|
||
| Discuss a passage — and have the machine respond (BUC-5) | The **Comments API** — threaded comments where, on a coedited document, the machine replies in-thread and offers an edit | The familiar review-comment experience, in the gutter, that can turn a remark into a proposed change |
|
||
| Follow + stop the machine (BUC-4) | A progress **notification** + a shared **OutputChannel**, with Cancel | Live, stoppable, legible machine work |
|
||
|
||
**Coediting is something a document opts into.** A writer turns a document into a
|
||
coedited document with one command — **"Coedit this Document with Claude"** — and
|
||
**only then** do the surfaces above attach to it: the SourceControl/QuickDiff entry,
|
||
the auto-responding comments, the annotation toggle, and the machine-edit entry
|
||
points. The extension registers its providers once but **scopes every surface to the
|
||
active coediting set** (INV-10), so it never hijacks Comments, Source Control, or the
|
||
preview for the writer's *other* markdown files. This gate is what makes the
|
||
auto-responding-comments behavior safe: a comment only summons the machine on a
|
||
document the writer has explicitly invited it into.
|
||
|
||
**Why this approach over the alternatives.** Doing nothing leaves writers in the
|
||
copy-paste-from-chat world (§1.2), which structurally cannot offer a diff,
|
||
selective accept, or authorship. Building a **bespoke coediting UI** (custom panels,
|
||
hand-rolled diff rendering, overlay decorations) can *imitate* these surfaces but
|
||
won't inherit the platform's behavior — theming, scroll-sync, accessibility, the
|
||
keyboard model, navigation, cooperation with other extensions — so it pays the cost
|
||
of building review machinery without getting the polish, and asks the writer to
|
||
learn a foreign surface. **Delivering through the editor's native surfaces** gets
|
||
that behavior for free, makes coediting feel built-in, and shrinks what must be
|
||
built and maintained to a thin layer of wiring over proven cores. The cost is
|
||
accepting the platform's affordances where they differ from a bespoke one, 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
|
||
|
||
```gherkin
|
||
Scenario: PUC-1 — see the diff against the baseline checkpoint (realizes BUC-1)
|
||
Given I am coediting a document (entering set the baseline to a clean checkpoint)
|
||
When I (or the machine) change the document
|
||
Then the source-control gutter shows change bars for what changed since the checkpoint
|
||
And "Open Changes" opens the native diff editor (baseline ⟷ live), inline or side-by-side at my choice
|
||
And running "Pin Review Baseline to Now" resets the checkpoint to the current content (the diff goes clean)
|
||
```
|
||
|
||
```gherkin
|
||
Scenario: PUC-2 — keep/reject the machine's proposed changes (realizes BUC-2)
|
||
Given I ask the machine to revise the document (or a selection)
|
||
When the turn completes
|
||
Then the proposed changes open in a multi-diff review (baseline ⟷ proposed)
|
||
And the proposed side is editable, so I can tweak the machine's wording before keeping it
|
||
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
|
||
And on keep, the machine's words are attributed to the machine and my tweaks to me
|
||
```
|
||
|
||
```gherkin
|
||
Scenario: PUC-3 — read the annotated rendered preview (realizes BUC-3)
|
||
Given my document has changes and mixed authorship
|
||
When I open the built-in Markdown preview with annotations enabled
|
||
Then insert/delete marks and authorship colors render inside the native preview
|
||
And toggling annotations off renders the document clean
|
||
```
|
||
|
||
```gherkin
|
||
Scenario: PUC-4 — region thread (realizes BUC-3a)
|
||
Given I select a passage 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
|
||
```
|
||
|
||
```gherkin
|
||
Scenario: PUC-5 — live machine progress (realizes BUC-4)
|
||
Given I started a machine turn
|
||
When the machine is working
|
||
Then a notification shows the live activity line and token count
|
||
And an OutputChannel streams the full text
|
||
And I can cancel the turn
|
||
```
|
||
|
||
```gherkin
|
||
Scenario: PUC-6 — reopen / external change (realizes BUC-3a)
|
||
Given I reopen the workspace or an external change lands (a git pull)
|
||
When the document loads
|
||
Then baseline, proposed changes, attributions, and threads re-resolve against current text
|
||
And anything that cannot be confidently placed is shown displaced
|
||
```
|
||
|
||
```gherkin
|
||
Scenario: PUC-7 — enter (and leave) coediting mode (realizes INV-10)
|
||
Given an ordinary markdown document
|
||
When I run "Coedit this Document with Claude" (command, editor title, or context menu)
|
||
Then this document gains the Cowriting source-control entry, the annotation toggle, and machine-edit/comment-response behavior
|
||
And the baseline is set to the document's current content (a clean checkpoint)
|
||
And my other markdown documents are unaffected
|
||
And running "Stop editing with Claude" (context menu) removes those surfaces (the saved sidecar is kept for next time)
|
||
```
|
||
|
||
```gherkin
|
||
Scenario: PUC-8 — comment summons a machine reply + offer to edit (realizes BUC-5)
|
||
Given I am coediting this document
|
||
When I add a comment on a passage (native Comments gutter)
|
||
Then a machine turn runs (with live progress, PUC-5) and replies in the thread
|
||
And it offers to make the change; accepting opens its edit as a pending proposed change (PUC-2)
|
||
And on a document I am not coediting, adding a comment summons nothing
|
||
```
|
||
|
||
## 5. UX Layout
|
||
|
||
The experience is delivered through **native VS Code surfaces**; there is no bespoke
|
||
panel to wireframe. Described in text (source of truth):
|
||
|
||
- **Entering coediting.** A **"Coedit this Document with Claude"** command (command
|
||
palette, editor title menu, and editor/explorer context menu, markdown-gated)
|
||
turns the active document into a coedited document; a **"Stop editing with Claude"**
|
||
command — in the same command palette **and the editor/explorer context menu** —
|
||
reverses it. A subtle status-bar item ("✦ Coediting") indicates when the active
|
||
document is in coediting mode. Until a document is entered, **none** of the
|
||
surfaces below attach to it (INV-10). **Entering also sets the baseline to the
|
||
document's current content** — a clean checkpoint to measure future changes
|
||
against (INV-7).
|
||
- **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). On a coedited document, the machine replies in-thread
|
||
and offers an edit (BUC-5 / PUC-8); the offer, when accepted, becomes a pending
|
||
proposed change reviewed in the multi-diff.
|
||
- **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 is the clean checkpoint, kept outside the repo:** the baseline
|
||
is the diff *original* — the last point the writer declared "clean." It is **set
|
||
automatically when a document enters coediting**, and **reset to the current
|
||
content by "Pin Review Baseline to Now"** (the "everything so far is settled; show
|
||
me changes from here" gesture). A coedited document therefore *always* has a
|
||
baseline — there is **no "missing baseline" state**; the only empty state is a
|
||
genuinely empty document. It is stored in extension global storage keyed by
|
||
document URI hash, so it never pollutes the repo and works for any file (incl.
|
||
untitled), and it is disposable review state, not durable metadata.
|
||
- **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.
|
||
- **INV-10 — coediting is opt-in per document:** every coauthoring surface (the
|
||
"Cowriting" SourceControl + QuickDiff, auto-responding comments, preview
|
||
annotations, the machine-edit entry points) activates **only** for a document the
|
||
writer has explicitly entered into coediting mode ("Coedit this Document with
|
||
Claude"). The extension registers its providers globally but **gates every
|
||
surface on membership in the active coediting set**, so it never hijacks
|
||
Comments/SCM/preview for documents the writer did not opt in. Exiting coediting
|
||
(or closing the workspace) removes the document from the set; the durable sidecar
|
||
remains, so re-entering restores state.
|
||
- **INV-11 — the native diff is author-blind:** the native diff editor shows *what*
|
||
changed against the baseline, not *who* changed it (the diff API has no per-author
|
||
color scoping — §6.7). Authorship (human vs. machine) is shown on a surface the
|
||
extension renders: the annotated preview (and optionally a decoration-based
|
||
track-changes editor), never by recoloring the native diff.
|
||
- **INV-12 — proposed changes are editable before they are kept:** the review
|
||
surface presents the machine's proposal on a **writable** side, so the writer can
|
||
tweak it in place before keeping it (the common "close, but a few human edits"
|
||
case). The live document stays untouched until Keep (INV-5 still holds); on Keep,
|
||
the words the machine proposed are attributed to the machine and the writer's
|
||
tweaks to the writer — so authorship is correct **at the moment the change is
|
||
made**, never reconstructed afterward. Editing happens at review time; once kept,
|
||
the text is just text (no lingering "trapped proposal" state to untangle).
|
||
|
||
### 6.2 High-level architecture
|
||
|
||
**Pure, vscode-free cores** + **thin controllers** that wire those cores to **native
|
||
surfaces**.
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph Cores["Pure cores (vscode-free, unit-tested)"]
|
||
AT[Attribution tracker<br/>char-precise spans]
|
||
AN[Anchorer<br/>fingerprint + offsets + displaced]
|
||
MG[mergeArtifacts<br/>union-by-id, unknown-field-safe]
|
||
DF[Diff/hunk engine<br/>word- & block-level]
|
||
TP[turnProgress<br/>reducer]
|
||
RA[renderAnnotations<br/>markdown-it transform]
|
||
end
|
||
subgraph Ctrls["Thin controllers (vscode-aware)"]
|
||
SC[SourceControl + QuickDiffProvider]
|
||
BP[Baseline provider<br/>TextDocumentContentProvider]
|
||
PR[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.
|
||
|
||
```jsonc
|
||
// .threads/docs/spec.md.json — the coauthoring envelope (INV-4)
|
||
{
|
||
"schemaVersion": 1,
|
||
"document": { "path": "docs/spec.md" },
|
||
"anchors": { "a1": { "fingerprint": { "text": "…", "before": "…", "after": "…", "lineHint": 42 } } },
|
||
"attributions": [ { "anchorId": "a1", "provenance": <Provenance>, "span": { "start": 0, "end": 12 } } ],
|
||
"proposals": [ { "id": "p1", "anchorId": "a1", "status": "pending", // pending|accepted|rejected (INV-5)
|
||
"granularity": "block", // word|block — the decision unit
|
||
"provenance": <Provenance>, "before": "…", "after": "…" } ],
|
||
"threads": [ { "id": "t1", "anchorId": "a1", "status": "open", "messages": [ … ] } ]
|
||
// unknown top-level fields preserved verbatim (INV-4)
|
||
}
|
||
```
|
||
|
||
### 6.4 Interfaces & contracts
|
||
|
||
- **Baseline provider** — `TextDocumentContentProvider` for `cowriting-baseline:`;
|
||
`provideTextDocumentContent(uri) → baselineText`. `BaselineStore.pin(docUri, text,
|
||
reason)` / `get(docUri) → {text, reason} | null` (out-of-repo, INV-7). `pin` is
|
||
called **automatically on `enterCoediting`** (reason `entered`) and on the explicit
|
||
**"Pin Review Baseline to Now"** (reason `pinned`); a coedited document always has a
|
||
baseline, so `get` returns null only for documents that are not being coedited.
|
||
- **QuickDiffProvider** — `provideOriginalResource(docUri) → cowriting-baseline:` URI
|
||
(drives gutter bars). Registered on the "Cowriting" `SourceControl`.
|
||
- **Machine-edit seam** — the single boundary where machine text enters the model:
|
||
`proposeAgentEdit(docUri, range, newText, provenance)` → produces **pending**
|
||
`proposals[]` entries (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 controller** — `openReview(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). The
|
||
**proposed side is a writable scratch document** (untitled / in-memory) seeded with
|
||
the machine's text (INV-12); the left side (baseline) is read-only. `keep` diffs
|
||
the **final** (possibly writer-tweaked) proposal against the baseline, applies it
|
||
to the live document, and attributes machine-proposed words to the machine and
|
||
writer tweaks to the writer by comparing *final ↔ the machine's original proposal*.
|
||
The live document is untouched until `keep` (INV-5).
|
||
- **Preview extension** — `extendMarkdownIt(md) → md.use(cowritingPlugin)`; the
|
||
plugin invokes the pure `renderAnnotations(tokens, attributions, proposals,
|
||
options)` transform, gated by the `cowriting.annotations` setting/command.
|
||
- **Turn runner** — `runEditTurn(prompt, { onProgress, signal, env }) →
|
||
AgentResult`; pure `turnProgress` reducer; `liveProgress` relay to a notification
|
||
+ OutputChannel; Cancel → `abort()`. The machine runtime is loaded via runtime
|
||
dynamic `import()` (it is ESM and must not be bundled), and spawned with IDE
|
||
auto-connect disabled so it never triggers an OS automation-permission prompt.
|
||
- **CoeditingRegistry** (the activation gate, INV-10) — `enter(docUri)` /
|
||
`exit(docUri)` / `isCoediting(docUri) → boolean` / `onDidChange`. Holds the active
|
||
coediting set (persisted across reloads). **Every** controller above checks
|
||
`isCoediting(docUri)` before attaching a surface: the QuickDiffProvider returns no
|
||
original for non-coedited docs, the comment-response controller ignores them, and
|
||
the annotation/edit commands are disabled (`when` clause `cowriting.isCoediting`).
|
||
- **ThreadController + comment-response** — create/reply/resolve via the Comments
|
||
API. On a coedited document only, a new comment fires `runEditTurn` to **reply in
|
||
the thread** and, when the remark implies a change, **offer an edit**; an accepted
|
||
offer routes through the machine-edit seam to a **pending** proposed change
|
||
(INV-5) — the comment never edits the document directly. The reply is posted as a
|
||
machine `Comment` carrying `onBehalfOf` provenance (INV-8).
|
||
|
||
### 6.5 Per–Product-Use-Case design
|
||
|
||
#### PUC-1 — see the diff against the baseline checkpoint
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
actor U as Writer
|
||
participant C as SCM/QuickDiff ctrl
|
||
participant B as Baseline provider
|
||
participant V as Native diff editor
|
||
U->>C: 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
|
||
|
||
```mermaid
|
||
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).
|
||
- **Editable before keep (INV-12):** the proposal renders on the **writable** side of
|
||
the review, so the writer can tweak the machine's wording ("close, but a few human
|
||
edits") before keeping. `keep` attributes the machine's words and the writer's
|
||
tweaks separately by diffing *final ↔ machine-original ↔ baseline*. Because editing
|
||
happens on the proposal (a scratch doc), not the live document, INV-5 holds and
|
||
authorship is correct at creation — the preview (Option 1, the authorship surface)
|
||
then shows clean green/blue with no post-hoc untangling. *Open detail (§13-Q7):*
|
||
how per-change Keep/Undo behaves once the writer has freely edited across hunks.
|
||
|
||
#### PUC-3 — annotated rendered preview
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
actor U as Writer
|
||
participant P as Built-in MD preview
|
||
participant E as Preview extension (extendMarkdownIt)
|
||
participant T as renderAnnotations (pure)
|
||
U->>P: Open Preview (annotations on)
|
||
P->>E: render document tokens
|
||
E->>T: transform(tokens, attributions, proposals)
|
||
T-->>E: tokens w/ authorship + change marks
|
||
E-->>P: annotated HTML (preview stylesheet)
|
||
P-->>U: native preview, annotated
|
||
```
|
||
|
||
- **Implementation:** a contributed `markdown-it` plugin runs the pure
|
||
`renderAnnotations` transform and a small preview stylesheet using the editor's
|
||
theme variables. Toggle via `cowriting.annotations`. No webview. Authorship
|
||
coloring + insert/delete marks are shown for changes since the baseline checkpoint;
|
||
a freshly entered or freshly pinned, zero-diff document renders clean.
|
||
|
||
#### PUC-4 / PUC-6 — threads & re-anchoring
|
||
|
||
- **Implementation:** ThreadController + Anchorer, **gated on `isCoediting(docUri)`**
|
||
(INV-10). On load / external change (FileSystemWatcher), every anchor re-resolves
|
||
(exact-unique → context-disambiguated → displaced, INV-1). Proposed changes and
|
||
attributions ride the same `anchors` primitive, so they re-anchor and displace by
|
||
the same ladder.
|
||
|
||
#### PUC-7 — enter / leave coediting
|
||
|
||
- **Implementation:** `cowriting.enterCoediting(docUri)` adds the URI to the
|
||
CoeditingRegistry's active set and persists it; `cowriting.exitCoediting` removes
|
||
it (the sidecar is kept). The registry fires `onDidChange`; controllers (QuickDiff,
|
||
comment-response, annotations, edit commands) attach/detach and the
|
||
`cowriting.isCoediting` context key flips so menu `when` clauses show/hide. Commands
|
||
are markdown-gated. No surface exists for a document until it is entered.
|
||
|
||
#### PUC-8 — comment summons a machine reply + offer
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
actor U as Writer
|
||
participant T as ThreadController
|
||
participant G as CoeditingRegistry
|
||
participant S as Machine runtime
|
||
participant R as Review ctrl
|
||
U->>T: add comment on a passage
|
||
T->>G: isCoediting(docUri)?
|
||
G-->>T: yes
|
||
T->>S: runEditTurn(comment + context, {onProgress, signal})
|
||
S-->>T: reply text (+ optional edit)
|
||
T-->>U: machine reply posted in thread (onBehalfOf provenance)
|
||
U->>T: accept the offered edit
|
||
T->>R: proposeAgentEdit → pending proposal (INV-5)
|
||
R-->>U: review in multi-diff (PUC-2)
|
||
```
|
||
|
||
- **Implementation:** gated on `isCoediting` (INV-10) — inert otherwise. The turn
|
||
runs with live progress (PUC-5). The reply is a machine `Comment`; an accepted
|
||
offer never edits the document directly, only proposes (INV-5). **While the
|
||
document is in coediting mode, every new comment fires a turn (D10)** — the opt-in
|
||
mode is the control, so there is no per-comment gate; "Stop editing with Claude"
|
||
is the off switch.
|
||
|
||
#### PUC-5 — live machine progress
|
||
|
||
- **Implementation:** pure `turnProgress` reducer; `runEditTurn`
|
||
`onProgress`/`AbortSignal`; `liveProgress` relay to a notification activity-line +
|
||
token count + an OutputChannel; Cancel → `abort()`. The machine runtime is spawned
|
||
with IDE auto-connect disabled so it never triggers an OS automation prompt.
|
||
|
||
### 6.6 Non-functional requirements & cross-cutting concerns
|
||
|
||
- **Security & privacy:** serverless; no secret bytes in sidecars or transcripts.
|
||
The machine runs locally; provenance records identity, not credentials. The
|
||
runtime is spawned with IDE auto-connect disabled (no spurious OS
|
||
automation-permission prompt).
|
||
- **Performance & scale:** anchor resolution is O(document) text search per anchor
|
||
on load/change — fine for human-scale documents; skip non-text/oversized files.
|
||
Native diff/preview rendering is the platform's, not ours.
|
||
- **Availability & resilience:** no service; the worst case is a displaced anchor
|
||
(handled, INV-1); a baseline always exists once coediting is entered (INV-7), so
|
||
there is no missing-baseline path.
|
||
- **Observability:** the OutputChannel stream (PUC-5) is the user-facing turn log;
|
||
no telemetry service (a local extension — §9).
|
||
- **Accessibility:** inherited from the native surfaces — the diff editor,
|
||
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 |
|
||
| Authorship surface | **Option 1** — the annotated preview is the authorship view (green/blue track-changes incl. deletions); native diff stays author-blind | Option 2 (in-editor decorations); Option 3 (owned author-colored track-changes diff that replaces native diff) | Preview shows author-colored changes for near-zero maintenance; editable proposals (INV-12) keep authorship clean *at creation*, so in-editor / post-hoc authorship rendering is unnecessary. Opts 2/3 deferred |
|
||
| Editing proposals | the proposed side of the review is **writable**; pre-Keep tweaks are absorbed and attributed (machine vs. writer) at Keep (INV-12) | machine proposal read-only (tweak only after accepting) | Most revisions are "close, but a few human edits"; editing at review time keeps authorship clean at creation and avoids messy post-acceptance untangling |
|
||
| Coediting activation | opt-in per document via "Coedit this Document with Claude"; every surface gated on the active coediting set (INV-10) | always-on for all markdown; opt-in per workspace/folder | Per-document opt-in is the only grain that avoids hijacking Comments/SCM/preview for the writer's other files while keeping single-file precision |
|
||
| Authorship in the diff | the native diff is **author-blind** (what changed); authorship is shown in the preview, optionally a decoration track-changes editor (INV-11) | recolor machine changes inside the native diff editor | **Not possible:** `diffEditor.*` colors are global theme tokens with no per-author scoping; `inlineEdit.*`/chat tokens differentiate accept-state not author; decorations over the diff pane composite over green/red and are unreliable (vanish on tab-switch). Verified against the VS Code API (§13-Q5) |
|
||
| Comment → machine response | on a coedited doc, a comment fires a turn that replies + offers an edit → pending proposal | comment is inert (human-only threads) | Turns the familiar review-comment gesture into a coediting action without a new surface; the opt-in gate (INV-10) keeps it from firing on other docs |
|
||
| 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).
|
||
- **Empty document** (genuinely no content) → the only true empty state → a gentle
|
||
"start writing, or ask Claude to draft something" affordance; not an error. A
|
||
coedited document always has a baseline (set on entry, INV-7), so there is **no
|
||
"missing baseline" state** to handle.
|
||
- **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 coediting mode for the document (INV-10).
|
||
|
||
### 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 |
|
||
| PP-1 (comment→edit) | BUC-5 | PUC-8 | host E2E: comment→reply→offer→pending proposal | BO-1 |
|
||
| (no hijack) | — | PUC-7 | host E2E: enter/exit gates every surface; non-coedited doc inert | BO-2 |
|
||
|
||
## 13. Open Questions & Decisions log
|
||
|
||
**Open**
|
||
|
||
| # | Question | Owner | Blocks |
|
||
| --- | --- | --- | --- |
|
||
| 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 |
|
||
| Q7 | Once the writer freely edits the proposal across hunks (INV-12), how do per-change Keep/Undo behave — do per-hunk actions survive arbitrary edits, or does free editing collapse the change set to a single Keep/Reject-all? | Ben | PUC-2 build |
|
||
|
||
**Resolved**
|
||
|
||
| # | 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 |
|
||
| D7 | Coediting is opt-in per document | A document enters coediting via "Coedit this Document with Claude"; every surface gates on the active coediting set (INV-10), so other markdown files are never hijacked | 2026-06-27 |
|
||
| D8 | Comments can summon the machine | On a coedited doc, a comment fires a turn that replies in-thread + offers an edit → pending proposal (BUC-5, INV-5); inert on non-coedited docs | 2026-06-27 |
|
||
| D9 | Native diff is author-blind | Verified the diff editor has no per-author color API; authorship lives in the preview (and optionally a decoration track-changes editor), not the native diff (INV-11) | 2026-06-27 |
|
||
| D10 | Comment trigger | While a document is in coediting mode, **every** new comment summons the machine (no per-comment gate); coediting mode is the control, "Stop editing with Claude" the off switch | 2026-06-27 |
|
||
| D11 | Authorship surface (Q5) | Option 1 — the annotated preview is the authorship view; native diff stays author-blind; in-editor decorations (opt 2) and an owned track-changes diff (opt 3) are out of scope | 2026-06-27 |
|
||
| D12 | Editable proposals | The review's proposed side is writable; the writer tweaks before Keep; machine words vs. writer tweaks are attributed at Keep (INV-12); live doc untouched until Keep (INV-5) | 2026-06-27 |
|
||
| D13 | Baseline semantics | Baseline = the clean checkpoint; set automatically on entering coediting and reset by "Pin Review Baseline to Now"; a coedited doc always has one. The only empty state is a genuinely empty document — no "missing baseline" state (INV-7) | 2026-06-27 |
|
||
|
||
## 14. Glossary & References
|
||
|
||
- **Baseline (checkpoint)** — the last point the writer declared "clean," used as the
|
||
diff *original*; set automatically when a document enters coediting and reset by
|
||
"Pin Review Baseline to Now"; 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, live turn progress, and editable
|
||
proposals (tweak-before-keep with mixed human/machine authorship).*
|