diff --git a/specs/coauthoring-native-surfaces.md b/specs/coauthoring-native-surfaces.md index 0d5335e..054e875 100644 --- a/specs/coauthoring-native-surfaces.md +++ b/specs/coauthoring-native-surfaces.md @@ -1,7 +1,7 @@ --- status: draft --- -# Solution Design: Native-First Coauthoring — a VS Code-idiomatic redesign +# Solution Design: Coediting Markdown with a Machine — a Native VS Code Experience | | | | --- | --- | @@ -9,7 +9,7 @@ status: draft | **Reviewers / approvers** | Ben Stull | | **Status** | `draft` | | **Version** | v0.1.0 | -| **Source artifacts** | North-star architecture spec (design-only). Lineage: the shipped plugin Epic `benstull/vscode-cowriting-plugin#1` and its Features F1–F12 (the experience this re-homes). Reuses concepts from `coauthoring-inner-loop.md`, `coauthoring-attribution.md`, `coauthoring-propose-accept.md`, `coauthoring-diff-view.md`, `coauthoring-rendered-preview.md`, `coauthoring-cross-rung-format.md`, `coauthoring-live-progress.md`. Supersedes: none (design-only; does not commit a repo or migration). | +| **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** @@ -25,194 +25,215 @@ status: draft ### 1.1 Executive Summary -A writer working with a machine coauthor needs three things to feel native to -their editor: to **see what changed** against a known-good point, to **review and -accept or reject the machine's proposed changes** one at a time, and to **read a -clean rendered version** of the document with those changes legible. The existing -plugin (Epic #1, F1–F12) proved that experience is valuable — but it built each of -those three surfaces as bespoke UI (custom webviews, hand-rolled diff rendering, -editor decoration overlays) that the writer must *learn*. This design re-homes the -same experience onto the surfaces the writer **already knows** — VS Code's own diff -editor, Source Control gutter, multi-diff review, and Markdown preview — so the -coauthoring loop reads as a built-in capability of the editor rather than a foreign -panel grafted onto it. The value is lower cognitive cost for the writer and a -dramatically smaller surface for us to build, test, and maintain. +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 -The forge (Gitea / rfc-app) is the *outer* loop — durable review and record. The -*inner* loop — writing, keystroke by keystroke, with a machine coauthor — lives in -the editor. The shipped plugin delivered that inner loop as a standalone VS Code -extension on `@cline/sdk`, accreting twelve Features: region threads, char-precise -attribution, propose/accept, cross-rung persistence, a diff view, a rendered -track-changes preview, an interactive review panel, a preview toolbar, an inline -editor diff, and live turn progress. Each Feature solved a real need, but the diff, -the accept/reject, and the preview were each implemented as **custom rendering** — -webviews with bundled markdown-it/mermaid, sealed message channels, and -`TextEditorDecorationType` overlays — reinventing machinery VS Code already ships. -The accumulated UI is powerful but idiosyncratic: it does not look or behave like -the rest of the editor, and it is large to maintain. +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. -VS Code, meanwhile, exposes first-class APIs for exactly these three needs. The -strategic question this spec answers: *if we designed the plugin from scratch today -with "feels like a built-in feature" as the north star, how close to native VS Code -patterns could we get, and what would the architecture be?* +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) | | --- | --- | -| **Coauthor (human)** | A writer/engineer authoring prose, specs, or docs in their editor. Owns the document; decides what changes land. | -| **Coauthor (machine)** | The machine coauthor (Claude, via `@cline/sdk`). Proposes changes on the human's behalf; never lands them unilaterally. | +| **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 -The coauthoring experience is delivered through UI the writer must learn anew — -bespoke panels, custom-rendered diffs, and overlay decorations that do not match, -and do not cooperate with, the editor surfaces the writer uses every day. The -experience is *foreign to its host*. Separately, that bespoke UI is costly to -build and maintain: every surface (diff, accept/reject, preview) is a hand-written -renderer with its own bugs, its own test harness, and its own divergence from -platform conventions (theming, scroll-sync, accessibility, keyboard model). +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 diff/accept/preview surfaces don't look or behave like the rest of VS Code — new mental model to learn | Coauthor (human) | Every session; friction on every review | -| PP-2 | Custom webview/decoration rendering re-implements platform machinery (theming, scroll-sync, a11y, diff layout) and drifts from it | Maintainer | Ongoing; each Feature added more surface to own | -| PP-3 | Accept/reject lives in a bespoke panel rather than where the writer reviews changes natively (the diff) | Coauthor (human) | Every proposal review | -| PP-4 | The rendered preview is a sealed webview that can't cooperate with other Markdown extensions the writer already uses | Coauthor (human) | Every preview open | +| 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 | The coauthoring loop reads as a built-in editor capability (familiarity, lower learning cost) | Writer can complete a see-diff → review-proposal → read-preview loop using only native surfaces they already know | bespoke panels → native surfaces | The full inner-loop experience (diff, accept/reject, preview, threads, attribution, live progress) is preserved | Dogfooding walkthrough on the design's first build | -| BO-2 | The plugin is materially cheaper to build and maintain | Custom-rendering LOC retired in favor of platform APIs | large webview/decoration surface → thin controllers over native APIs | No capability dropped | Code-size + test-surface comparison at build time | +| 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:** a from-scratch architecture for the inner-loop coauthoring - experience that maximizes use of native VS Code surfaces for the three core needs - (see a diff, review proposed changes, read a rendered preview), plus the proven - supporting capabilities (region threads, authorship attribution, durable - cross-rung persistence, live turn progress, on-behalf-of identity). -- **Out of scope:** committing a specific repository, a migration plan from the - shipped plugin, or a marketplace release. This is a **design-only north-star** - (see §2): repo-and-migration decisions are deferred to a delivery decision, not - taken here. -- **Non-goals:** changing *what* the coauthoring experience is (the loop is - validated); a server or cloud backend (the inner loop stays serverless, - git-native); multi-file agent orchestration beyond the single-document loop. +- **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:** VS Code's stable APIs (SCM/QuickDiff, diff editor, multi-diff - editor, Markdown preview `markdown-it` contribution, Comments, decorations, - `TextDocumentContentProvider`, `FileSystemWatcher`) remain available; the machine - coauthor runs locally via `@cline/sdk` as in the shipped plugin. -- **Constraints:** serverless and git-native (no external store); no secret bytes - in artifacts or transcripts (§6.3-handbook); a VS Code extension has **no §9 - deploy pipeline stage** (it is not a webapp — see §7.2). -- **Dependencies:** `@cline/sdk` (loaded via runtime dynamic `import()` — never - bundled; a POC-validated fact carried forward). The shipped plugin's pure cores - (attribution, anchoring, merge, turn-progress reducer) are the reuse source. +- **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 human coauthor, I can see what changed in my document since a point I -chose, in a familiar diff, so that I can trust what I am about to keep.** +**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 — review changes against a chosen point - Given I marked the document as "good" at some earlier moment - And the document has changed since then +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 in the same diff experience I use for everything else + Then the changes are shown to me as a clear before/after I can read ``` -- **BUC-1 acceptance criteria:** the human sees an accurate before/after of the - document against the chosen point, in their editor's normal diff experience. +- **BUC-1 acceptance criteria:** the writer sees an accurate before/after of the + document against the chosen point. -**BUC-2 — As a human coauthor, I can review the machine's proposed changes and accept -or reject them one at a time, so that I stay in control of what lands.** +**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 — accept or reject proposed changes - Given the machine coauthor has proposed changes to my document +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 proposed change individually, or all at once - And nothing the machine proposed lands until I keep it + 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 - human keeps it; each change is independently keepable/discardable; an accept-all - and reject-all exist. + writer keeps it; each change is independently keepable/discardable; keep-all and + reject-all exist. -**BUC-3 — As a human coauthor, I can read a clean, rendered version of my document with -the changes and authorship made legible, so that I can judge the result as prose, -not as markup.** +**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 — read the rendered result - Given my document has changed and has mixed authorship - When I open its rendered preview with annotations on +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 the annotations off to read it clean + And I can turn annotations off to read it clean ``` -- **BUC-3 acceptance criteria:** the rendered document shows track-changes and - authorship when annotations are on, renders clean when off, in a preview that - behaves like the editor's normal Markdown preview. +- **BUC-3 acceptance criteria:** the rendered document shows changes and authorship + when annotations are on, renders clean when off. -**BUC-3a (unhappy) — As a human coauthor, when an anchored thread / attribution / -proposal can no longer be confidently placed after edits, I am shown it as orphaned +**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-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 brand-new VS Code extension that delivers the inner-loop -coauthoring experience almost entirely through native VS Code surfaces**, keeping -the shipped plugin's validated *experience* and its proven *pure cores* while -discarding its bespoke *rendering*. +**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 unifying idea: **model the coauthored document as a tiny Source Control.** The -last point the human marked "good" (the **pinned baseline**, carried from F6) is the -SCM *original*; the live document is the working tree; the machine's proposed edits -are *pending changes*. Once the relationship is framed as source control, VS Code's -own machinery supplies the three core surfaces for free: +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: -| Core need | Native surface | Replaces (shipped plugin) | +| What the writer needs | Native surface that delivers it | What it gives them, for free | | --- | --- | --- | -| See any diff | `QuickDiffProvider` gutter bars + the **native diff editor** (`vscode.diff`) against a `cowriting-baseline:` virtual document | F6 custom diff-view controller | -| Propose / accept / reject | the **multi-diff editor** with per-change **Keep/Undo** (the "AI edits" review feel), backed by the existing propose/accept model | F4/F10/F12 webview + decoration accept/reject UI | -| Rendered preview | **extend the built-in Markdown preview** via a `markdown-it` plugin contribution (authorship + track-changes injected into VS Code's own preview) | F7/F10 sealed webview renderer | -| Region threads | the **Comments API** (already native in F2 — kept as-is) | — (already native) | -| Live turn progress | notification activity-line + a shared **OutputChannel** + cancellable turn | #60 (kept; already native) | +| 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.** The do-nothing option (keep the shipped -plugin) leaves a foreign-feeling, maintenance-heavy UI. A "polish the webviews" -option spends effort making bespoke surfaces *look* native without getting the -platform's behavior (theming, scroll-sync, a11y, keyboard model, cooperation with -other extensions) — it pays the cost without the benefit. Re-homing onto native -surfaces gets the behavior for free and shrinks what we own. The cost is accepting -the platform's affordances where they differ from our bespoke ones, and a small -stable-API boundary on the per-change Keep/Undo overlay (§6.10, §13). +**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 new repo -vs. an in-place re-platform, nor to a data migration. Those are taken at delivery -(§7.1, Q1 in §13). +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 the native-first plugin, plus carry-forward of pinned -baseline, authorship attribution, cross-rung persistence, live progress, threads, -and on-behalf-of identity. *Out:* repo/migration choice, marketplace publish, -multi-file orchestration. *Non-goal:* re-deriving the coauthoring experience — it is -inherited from F1–F12. +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. --- @@ -220,59 +241,59 @@ inherited from F1–F12. | Product persona | In the plugin | Maps to business role(s) | | --- | --- | --- | -| **PP-1 Inner-loop writer** | Pins a baseline, asks the machine to edit, reviews proposed changes in the diff, reads the annotated preview, opens region threads. | Coauthor (human) | -| **PP-2 Machine coauthor (Claude)** | Runs a turn via `@cline/sdk`, streams progress, and emits **proposed** edits (never lands them); authored spans carry its provenance. | Coauthor (machine) | +| **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 pinned baseline (realizes BUC-1) - Given I run "Cowriting: Pin Review Baseline to Now" - When I later edit the document - Then the SCM gutter shows change bars against the baseline + 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 ``` ```gherkin -Scenario: PUC-2 — review and accept/reject proposed changes (realizes BUC-2) - Given I ask the machine coauthor to edit the document (or a selection) +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 Accept all / Reject all - And the document is unchanged until I keep a change + And toolbar actions offer Keep all / Reject all + And my document is unchanged until I keep a change ``` ```gherkin Scenario: PUC-3 — read the annotated rendered preview (realizes BUC-3) Given my document has changes and mixed authorship - When I open the built-in Markdown preview with Cowriting annotations enabled - Then track-changes (insert/delete) and authorship colors render inside the native preview - And toggling "cowriting.annotations" off renders the document clean + 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; carried from F2) - Given I select a region and add a coauthoring thread - When I edit around it or reload - Then the thread re-anchors; if it cannot be confidently placed it is shown orphaned, never silently moved +Scenario: PUC-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 turn progress (carried from #60) +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 the "Cowriting: Claude" OutputChannel streams the full text + And an OutputChannel streams the full text And I can cancel the turn ``` ```gherkin -Scenario: PUC-6 — reload / external change (realizes BUC-3a) +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, proposals, attributions, and threads re-resolve against current text - And anything that cannot be confidently placed is shown orphaned + 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 @@ -280,90 +301,89 @@ Scenario: PUC-6 — reload / external change (realizes BUC-3a) The experience is delivered through **native VS Code surfaces**; there is no bespoke panel to wireframe. Described in text (source of truth): -- **Source Control gutter & viewlet.** A dedicated `SourceControl` named - *"Cowriting"* coexists with the real Git SCM. Its `QuickDiffProvider` draws the - familiar change bars in the editor gutter against the pinned baseline. Its - resource group(s) list the document's pending coauthoring changes; "Open Changes" - uses the native diff editor. -- **Native diff editor.** `vscode.diff(baselineUri, docUri)` — the writer's normal - diff, with the platform's inline/side-by-side toggle, navigation, and a11y. +- **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 **Accept all / Reject all**. This is the accept/reject surface. + 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 Cowriting annotations on, authorship colors and track-changes marks appear - *inside* it (contributed `markdown-it` plugin + preview stylesheet). A command/ - setting toggles annotations. + 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), exactly as F2. -- **Notification + OutputChannel.** Live turn progress, with a Cancel action. + selection, reply, resolve). +- **Notification + OutputChannel.** Live machine progress, with a Cancel action. ### 5.1 Design tooling decision - **Decision:** **Code directly.** -- **Rationale:** the entire thesis is to use native VS Code chrome, so there is no - net-new bespoke visual surface to explore. Scoring the six-question rubric: - Q1 −1 (no net-new surface — native editor chrome), Q2 −1 (UI is specified at +- **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 (VS Code *is* the design system here), Q5 −1 (solo build), - Q6 −1 (logic-dominant — wiring pure cores to platform APIs). Total **−6 → Skip + 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 VS Code theme variables), which does not warrant Design. + 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 **orphaned** state surfaced in the Comments thread / proposal listing. +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 (carried from F2/INV-1):** an anchored artifact (thread, attribution, - proposal) is **never silently moved** to a wrong span; if it cannot be confidently - re-resolved it becomes **orphaned** (visible, flagged). -- **INV-2 (carried):** persistence is **git-native and serverless** — diffable, - human-resolvable JSON; **no server, no external store**. (The *baseline* is the - one exception: it is a throwaway local artifact stored **outside** the repo — - INV-7.) +- **INV-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 (carried):** the persisted artifact is **forward-compatible and - cross-rung**: `schemaVersion` present; `anchors`, `provenance`, `attributions`, - `proposals`, `threads` share one envelope; **unknown fields are preserved** on - read/merge round-trips (the F5 cross-rung contract). -- **INV-5 — proposals are pending until kept:** a proposed edit does **not** mutate - the document until the human keeps it (Keep in the multi-diff, or Accept). This is - the propose-by-default rule (carried from F4), now expressed through the SCM/diff - surface. +- **INV-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 turn-progress reducer are pure modules with no `vscode` import, - unit-tested in isolation; controllers are the only `vscode`-aware layer (the - shipped plugin's most valuable discipline — carried wholesale). -- **INV-7 (carried from F6):** the **pinned baseline** is the diff *original* and is - stored **outside** the working tree (global storage keyed by document URI hash), + 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` (human git identity, or agent with `onBehalfOf` the - human), reused across attributions, proposals, and threads (carried from - identity + F3 + F5). -- **INV-9 (carried from #60):** a machine turn is **observable and cancellable** — - it streams progress and honors an `AbortSignal`; the progress reducer is pure. + 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 -The shape mirrors the shipped plugin's most durable pattern: **pure, vscode-free -cores** + **thin controllers** that wire those cores to **native surfaces**. +**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
char-precise spans] - AN[Anchorer
fingerprint + offsets + orphan] + AN[Anchorer
fingerprint + offsets + displaced] MG[mergeArtifacts
union-by-id, unknown-field-safe] DF[Diff/hunk engine
word- & block-level] TP[turnProgress
reducer] @@ -372,11 +392,11 @@ flowchart LR subgraph Ctrls["Thin controllers (vscode-aware)"] SC[SourceControl + QuickDiffProvider] BP[Baseline provider
TextDocumentContentProvider] - PR[Proposal/review controller
multi-diff Keep/Undo] + PR[Review controller
multi-diff Keep/Undo] PV[Preview extension
extendMarkdownIt] TC[ThreadController
Comments API] LP[liveProgress relay] - ST[SidecarRouter + Store] + ST[Sidecar router + store] end subgraph Native["Native VS Code surfaces"] GUT[Gutter dirty-diff + SCM viewlet] @@ -386,7 +406,7 @@ flowchart LR COMM[Comments gutter/peek] NOTE[Notification + OutputChannel] end - SDK[["@cline/sdk (dynamic import)"]] + SDK[[Machine-coauthor runtime
dynamic import]] AN --> SC --> GUT BP --> DIFF @@ -400,40 +420,40 @@ flowchart LR ``` - **SourceControl + QuickDiffProvider** — owns the "Cowriting" source control and - the gutter dirty-diff; must never write to the user's document. + 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 doc URI from - the out-of-repo baseline store (INV-7). -- **Proposal/review controller** — turns the machine's emitted edits into pending - proposals, opens the multi-diff review, and applies Keep/Undo (Accept/Reject) by - delegating to the propose/accept model; must honor INV-5. + `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. -- **SidecarRouter + Store** — persistence (carried from F8): repo `.threads/` when - in-workspace, global-storage sidecar by URI-hash otherwise; cross-rung envelope. +- **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/.json` (in-workspace) or global-storage sidecar (out-of-workspace), per SidecarRouter | +| Coauthoring sidecar | the extension (sole writer) | `schemaVersion`, `document.path`, `anchors{}`, `provenance`, `attributions[]`, `proposals[]`, `threads[]`, unknown-field passthrough | git-committed `.threads/.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/agent), `id`, `email`, `onBehalfOf`, `agent{sdk,model,sessionId}` | embedded in sidecar (INV-8) | +| Provenance | the extension | `kind` (human/machine), `id`, `email`, `onBehalfOf`, `agent{runtime,model,sessionId}` | embedded in sidecar (INV-8) | -The envelope is the F5 **cross-rung contract** verbatim (v1.x, additive, -unknown-field-preserving) — the sidecar round-trips to the Gitea substrate -unchanged. The **baseline is deliberately not** in this envelope: it is local, -disposable review state (INV-7), not durable coauthoring metadata. +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 cross-rung envelope (carried from F5) +// .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": , "span": { "start": 0, "end": 12 } } ], "proposals": [ { "id": "p1", "anchorId": "a1", "status": "pending", // pending|accepted|rejected (INV-5) - "granularity": "block", // word|block (carried from #47) + "granularity": "block", // word|block — the decision unit "provenance": , "before": "…", "after": "…" } ], "threads": [ { "id": "t1", "anchorId": "a1", "status": "open", "messages": [ … ] } ] // unknown top-level fields preserved verbatim (INV-4) @@ -443,29 +463,30 @@ disposable review state (INV-7), not durable coauthoring metadata. ### 6.4 Interfaces & contracts - **Baseline provider** — `TextDocumentContentProvider` for `cowriting-baseline:`; - `provideTextDocumentContent(uri) → baselineText`. `BaselineStore.pin(docUri, - text, reason)` / `get(docUri) → {text, reason} | null` (out-of-repo, INV-7). -- **QuickDiffProvider** — `provideOriginalResource(docUri) → cowriting-baseline:` - URI (drives gutter bars). Registered on the "Cowriting" `SourceControl`. -- **Agent edit seam (carried from F3/F4)** — the single boundary where machine text - enters the model: `proposeAgentEdit(docUri, range, newText, provenance)` → - produces **pending** `proposals[]` entries (never mutates the doc — INV-5). - Block/word granularity via the pure diff engine (`diffToBlockHunks` / - `wordEditHunks`, carried from #47/F4). Word-diff splitting reconciled by - `matchEvent` (the F4 host fix) lives in the pure engine. -- **Proposal/review controller** — `openReview(docUri)` opens the multi-diff - (baseline⟷proposed); `keep(proposalId)` = Accept (applies the edit, attributes - changed words via the F5/F4 `acceptBlock` path, INV-40-style); `undo(proposalId)` - = Reject (drops it); `keepAll()` / `undoAll()` = Accept/Reject all (descending, - orphan-skip + report — carried from #46). + `provideTextDocumentContent(uri) → baselineText`. `BaselineStore.pin(docUri, text, + reason)` / `get(docUri) → {text, reason} | null` (out-of-repo, INV-7). +- **QuickDiffProvider** — `provideOriginalResource(docUri) → cowriting-baseline:` URI + (drives gutter bars). Registered on the "Cowriting" `SourceControl`. +- **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). - **Preview extension** — `extendMarkdownIt(md) → md.use(cowritingPlugin)`; the plugin invokes the pure `renderAnnotations(tokens, attributions, proposals, options)` transform, gated by the `cowriting.annotations` setting/command. -- **Turn runner (carried from #60)** — `runEditTurn(prompt, { onProgress, - signal, env }) → AgentResult`; pure `turnProgress` reducer; `liveProgress` relay - to notification + OutputChannel; `CLAUDE_CODE_AUTO_CONNECT_IDE=0` env carried (the - #59 Apple-Events fix). -- **ThreadController** (carried from F2) — create/reply/resolve via Comments API. +- **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. +- **ThreadController** — create/reply/resolve via the Comments API. ### 6.5 Per–Product-Use-Case design @@ -477,44 +498,44 @@ sequenceDiagram participant C as SCM/QuickDiff ctrl participant B as Baseline provider participant V as Native diff editor - U->>C: Pin baseline (cowriting.pinBaseline) + U->>C: Pin baseline C->>B: BaselineStore.pin(docUri, text) - U->>U: edits document + 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:** QuickDiffProvider points at the `cowriting-baseline:` URI; +- **Implementation:** the QuickDiffProvider points at the `cowriting-baseline:` URI; the platform renders gutter bars and powers "Open Changes" / `vscode.diff`. No - custom diff rendering (replaces F6). Honors INV-3, INV-7. + custom diff rendering. Honors INV-3, INV-7. -#### PUC-2 — review and accept/reject proposed changes +#### PUC-2 — keep/reject the machine's proposed changes ```mermaid sequenceDiagram actor U as Writer - participant R as Proposal/review ctrl - participant S as "@cline/sdk" + participant R as Review ctrl + participant S as Machine runtime participant M as Multi-diff editor - U->>R: Ask Claude to edit (cowriting.edit) + 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 · Accept all / Reject all + U->>M: Keep / Undo per change · Keep all / Reject all M->>R: keep(p)/undo(p) - R-->>U: document updated only on Keep; attribution recorded + R-->>U: document updated only on Keep; authorship recorded ``` - **Implementation:** the machine never edits the document; it emits **pending** - proposals (INV-5). The multi-diff editor is the review surface; Keep = Accept - (apply + attribute changed words), Undo = Reject. Accept-all/reject-all carried - from #46 (descending, orphan-skip + report). **Stable-API boundary:** the - multi-diff editor is stable; per-change inline **Keep/Undo overlays** as Copilot - Edits renders them lean on evolving chat-edit APIs — the **stable fallback** is - per-hunk **CodeLens** Accept/Reject within the diff (§6.10, §13-Q2). + 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 @@ -527,150 +548,138 @@ sequenceDiagram U->>P: Open Preview (annotations on) P->>E: render document tokens E->>T: transform(tokens, attributions, proposals) - T-->>E: tokens w/ authorship + track-changes marks + 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 (carried/adapted from F7/F9/F10 render engines) and - a small preview stylesheet using VS Code theme variables. Toggle via - `cowriting.annotations`. No webview (replaces F7/F10). Authorship coloring + - insert/delete marks; pinned-baseline-clean rule carried (#48): a freshly pinned, - zero-diff document renders clean. + `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, carried from F2/F8 unchanged. - On load / external change (FileSystemWatcher), every anchor re-resolves - (exact-unique → context-disambiguated → orphaned, INV-1). Proposals and - attributions ride the same `anchors` primitive, so they re-anchor and orphan by - the same ladder. +- **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 turn progress +#### PUC-5 — live machine progress -- **Implementation:** carried from #60 verbatim — pure `turnProgress` reducer, - `runEditTurn` `onProgress`/`AbortSignal`, `liveProgress` relay to a notification - activity-line + token count + the "Cowriting: Claude" OutputChannel; Cancel → - `agent.abort()`. `CLAUDE_CODE_AUTO_CONNECT_IDE=0` env (the #59 fix) carried. +- **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 - (§6.3-handbook). The machine coauthor runs locally; provenance records identity, - not credentials. The #59 fix (no spurious macOS automation prompt) is carried via - `CLAUDE_CODE_AUTO_CONNECT_IDE=0`. +- **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 an orphaned anchor +- **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 native surfaces — the diff editor, multi-diff, - Markdown preview, and Comments all carry VS Code's a11y. This is a primary - *reason* for the native-first choice (the bespoke webviews did not). +- **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 | custom webview diff (F6); decoration-only inline diff (F12) | Native gutter + diff editor for free: theming, inline/side toggle, a11y, navigation; zero custom rendering (INV-3) | -| Accept/reject surface | **Multi-diff editor with Keep/Undo** (operator-chosen), backed by the propose/accept model | SCM stage/unstage change-group; in-editor CodeLens Accept/Reject; bespoke webview ✓/✗ (F4/F10) | Closest to the modern "AI edits" review feel; reviews all changes in one native surface. Stable fallback: per-hunk CodeLens (§6.10) | -| Rendered preview | **Extend the built-in Markdown preview** (`markdown-it` contribution) | sealed custom webview (F7/F10); both surfaces | Native scroll-sync, theming, refresh, and cooperation with other Markdown extensions; deletes the webview (INV-3) | -| Baseline storage | out-of-repo global storage by URI hash (carried F6/INV-7) | commit baseline into repo; in-memory only | Never pollutes the repo; works for any file incl. untitled; disposable review state | -| Persistence envelope | the F5 cross-rung sidecar contract, unchanged | new format; inner-loop-only sidecar | Round-trips to the Gitea substrate; forward-compatible; already proven (INV-4) | -| Core/controller split | pure vscode-free cores + thin controllers (carried) | UI-coupled logic | The shipped plugin's highest-value discipline: unit-testable cores, swappable surfaces (INV-6) | -| Scope | design-only north star (no repo/migration commitment) | commit a v2 repo + migration now | Keeps the architecture decision separate from the delivery decision (§2, §7.1) | +| 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 carry their existing suites — - attribution spans, anchoring (fingerprint/resolve/orphan/offset shift), - `mergeArtifacts` (union-by-id, unknown-field preservation), diff engine - (`wordEditHunks` / `diffToBlockHunks`), `turnProgress` reducer, and the new - `renderAnnotations` markdown-it transform (token-in → annotated-token-out). +- **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 + attribution recorded; toggle annotations → assert preview - HTML marks; thread create → re-anchor → orphan. Where the harness cannot drive a - native surface's chrome directly (multi-diff Keep/Undo, Comments UI, preview DOM), - fall back to asserting controller/provider state and the pure transform output — - the F2/F6/F7 precedent. **No Playwright** — these are native surfaces, not a - webview. E2E are first-class plan tasks (handbook §4 / §6.8). + 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→accept-all; PUC-3 annotate-toggle; PUC-4/6 - thread→reanchor→orphan; PUC-5 progress→cancel. + 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 -- **Orphaned anchor** (INV-1) → shown orphaned at last-known `lineHint`, never moved - → auto-re-anchors if the text reappears → no alert (local). +- **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 - Accept/Reject in the diff editor (§6.10); the propose/accept *model* is unchanged. + 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` (carried #60). The whole capability is - additive — off by absence of a pinned baseline / sidecar. + `cowriting.liveProgress.revealOutput`. The whole capability is additive — off by + absence of a pinned baseline / sidecar. ### 6.10 Migration & compatibility -**Design-only — nothing is migrated by this spec.** It defines the target -architecture; the repo-and-migration decision is deferred (§2, §7.1, §13-Q1). +**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 committed here. The persistence envelope is the F5 - cross-rung contract **unchanged**, so existing `.threads/` sidecars are readable - as-is by a native-first build — making an in-place re-platform low-risk *if* - later chosen. The baseline (out-of-repo, disposable) needs no migration. -- **Backwards compatibility:** the sidecar contract stays v1.x additive - (unknown-field-preserving, INV-4); any current consumer keeps working. -- **Cutover:** N/A (design-only). If delivered as a v2 re-platform, the natural - order is surface-by-surface (diff → preview → accept/reject), each native surface - replacing its bespoke predecessor behind the unchanged cores. +- **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 as Copilot - Edits renders it uses **evolving** chat-edit APIs; the design's stable contract is - therefore "multi-diff review + per-hunk Accept/Reject CodeLens," with the - Copilot-style inline overlay as a progressive enhancement where the API allows. + 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 delivery decision (§13-Q1) -picks one of: - -- **New app, clean break** — a fresh repo + `app.json` (flotilla provisioning), - the shipped plugin frozen; build native-first from the reused cores. -- **v2 re-platform in place** — replace bespoke surfaces one at a time behind the - unchanged pure cores (diff → preview → accept/reject), reading existing sidecars - as-is (low-risk per §6.10). - -Either way the build order follows the surface mapping: stand up the pure cores -(mostly carried), then **the diff surface** (smallest, highest-confidence native -win), then **the preview extension**, then **the multi-diff accept/reject** -(largest API-boundary risk — do it last, with the CodeLens fallback ready). One -SPEC → one plan → one execution applies per delivered Feature once decomposed. +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) — like -the shipped plugin, "done" for a delivered Feature = acceptance met + unit and host -E2E green; there is no marketplace publish in scope. Rollout, if/when productized, -is a marketplace concern out of scope here. +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 chat-edit APIs | M / M | Stable contract = multi-diff + per-hunk CodeLens Accept/Reject; overlay is progressive enhancement (§6.10) | -| Two SourceControls (Git + Cowriting) confuse the SCM viewlet | L / M | Multiple SCM providers coexist by design; name it clearly; QuickDiff alone (no full SCM) is the minimal fallback | +| 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 (F2/F6/F7 precedent) | +| 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 @@ -679,8 +688,8 @@ is a marketplace concern out of scope here. | Outcome | Measured by (source) | Baseline captured how | Owner | Review cadence | | --- | --- | --- | --- | --- | -| BO-1 (familiarity) | Dogfooding walkthrough: complete diff→review→preview using only native surfaces | The shipped plugin's bespoke-panel flow as the before | Ben | At first native-first build | -| BO-2 (cheaper to maintain) | Custom-rendering LOC + test-surface retired vs. capability retained | Current webview/decoration LOC count | Ben | At first native-first build | +| 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 @@ -693,32 +702,32 @@ is a marketplace concern out of scope here. ## 9. Telemetry, Observability & Monitoring A local VS Code extension — **no telemetry service, no server monitoring.** The -user-facing turn log is the "Cowriting: Claude" OutputChannel (PUC-5). Deliberately -omitted: events pipeline, dashboards, alerts, SLOs (nothing is operated as a -service). N/A — TEL/SLO/ALR rows are not applicable to a serverless local -extension. +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 runbooks for a service. Routine maintenance is +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 plugin's Gitea tracker as today. One line: a bug -reaches us as a tracker issue and is triaged in a session. +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:** `@cline/sdk` and dev deps via the normal tracker/Patchwatch flow. +- **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 coauthor's token usage, - surfaced live (PUC-5). -- **Deprecation / sunset:** if delivered as a new app, the shipped plugin is frozen - (cutover §6.10); this design itself is superseded only by a later architecture spec. +- **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 @@ -728,17 +737,17 @@ reaches us as a tracker issue and is triaged in a session. | # | Document | Audience | Location | New / updated | | --- | --- | --- | --- | --- | | DOC-1 | This Solution Design | dev/operator | content repo `specs/coauthoring-native-surfaces.md` | new | -| DOC-2 | README "how the coauthoring loop maps to native surfaces" | dev/user | framework repo README | new (at delivery) | +| 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, PP-3 | BUC-2 | PUC-2 | host E2E: propose→review→keep/undo→accept-all | PM-2 / BO-1 | | PP-1 | BUC-1 | PUC-1 | host E2E: pin→QuickDiff→native diff | PM-1 / BO-1 | -| PP-1, PP-4 | BUC-3 | PUC-3 | unit (renderAnnotations) + host E2E: annotate toggle | PM-3 / BO-1 | -| PP-2 | — | PUC-1/2/3 | retired custom-rendering LOC | BO-2 | -| (orphaning) | BUC-3a | PUC-4/6 | unit (anchorer) + host E2E: reanchor→orphan | PM-1 | +| 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 @@ -746,7 +755,7 @@ reaches us as a tracker issue and is triaged in a session. | # | Question | Owner | Blocks | | --- | --- | --- | --- | -| Q1 | New app (clean break) vs. v2 re-platform in the current repo — the delivery decision | Ben | Any implementation plan | +| 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 | @@ -755,34 +764,36 @@ reaches us as a tracker issue and is triaged in a session. | # | Decision | Resolution | Date | | --- | --- | --- | --- | -| D1 | Scope / altitude | Design-only north star — architecture + surface mapping, no repo/migration commitment | 2026-06-27 | +| 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 | Carry-forward from the shipped plugin | Pinned baseline (F6), authorship attribution (F3), cross-rung sidecar (F5), live progress (#60), region threads (F2), on-behalf-of identity, and the pure-core/thin-controller discipline | 2026-06-27 | +| 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 human-chosen "good" snapshot used as the diff *original*; - stored out-of-repo, disposable (INV-7; from F6). +- **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 (the "AI edits" - review feel); here the accept/reject surface (Keep/Undo). -- **Cross-rung sidecar** — the F5 forward-compatible, unknown-field-preserving - coauthoring artifact that round-trips between the inner loop and the Gitea - substrate. -- **Provenance** — the author envelope (human git identity, or agent on-behalf-of a - human) reused across attributions, proposals, and threads (INV-8). -- **Orphaned** — an anchor that could not be confidently re-resolved; shown, never +- **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:** shipped plugin Epic #1 (F1–F12); specs `coauthoring-inner-loop.md`, - `coauthoring-attribution.md`, `coauthoring-propose-accept.md`, - `coauthoring-diff-view.md`, `coauthoring-rendered-preview.md`, - `coauthoring-cross-rung-format.md`, `coauthoring-live-progress.md`. `@cline/sdk` - (loaded via runtime dynamic `import()`). VS Code APIs: SCM/`QuickDiffProvider`, - diff editor (`vscode.diff`), multi-diff editor, Markdown preview - `markdown-it` contribution, Comments, `TextDocumentContentProvider`, decorations. +- **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.*