From aaf5fb46eb41ffe79fa537d6be09a0c9b86ba998 Mon Sep 17 00:00:00 2001 From: BenStullsBets Date: Sun, 28 Jun 2026 06:55:55 -0700 Subject: [PATCH] spec(0062): opt-in coediting mode + comment auto-response; native diff is author-blind (verified) - INV-10: coediting is opt-in per document ('Coedit this Document with Claude'); every surface (SCM/QuickDiff, comments, preview annotations, edit entry points) gates on the active coediting set so other markdown files are never hijacked. Adds PUC-7 (enter/leave), CoeditingRegistry. - BUC-5/PUC-8/D8: on a coedited doc a comment fires a turn that replies in-thread and offers an edit -> pending proposal (never edits directly). - INV-11/D9: verified against the VS Code API that the native diff editor cannot recolor changes per-author (diffEditor.* tokens are global; inlineEdit./chat tokens are accept-state not author; diff-pane decorations composite + are unreliable). Authorship lives in the preview (optionally a decoration track-changes editor). New open Qs: Q5 (authorship surface), Q6 (comment trigger). Co-Authored-By: Claude Opus 4.8 (1M context) --- specs/coauthoring-native-surfaces.md | 138 +++++++++++++++++++++++++-- 1 file changed, 131 insertions(+), 7 deletions(-) diff --git a/specs/coauthoring-native-surfaces.md b/specs/coauthoring-native-surfaces.md index 054e875..a3f8f6f 100644 --- a/specs/coauthoring-native-surfaces.md +++ b/specs/coauthoring-native-surfaces.md @@ -184,6 +184,23 @@ Scenario: BUC-4 — visible, stoppable machine work 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.** @@ -208,9 +225,19 @@ already know: | 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 | +| 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, @@ -296,11 +323,35 @@ Scenario: PUC-6 — reopen / external change (realizes BUC-3a) 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 my other markdown documents are unaffected + And running "Stop Coediting" 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 Coediting"** command + 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). - **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 @@ -315,7 +366,9 @@ panel to wireframe. Described in text (source of truth): 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). + 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 @@ -373,6 +426,20 @@ coauthoring **displaced** state surfaced in the thread / proposed-change listing `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. ### 6.2 High-level architecture @@ -486,7 +553,18 @@ state (INV-7), not durable coauthoring metadata. + 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. +- **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 @@ -561,10 +639,46 @@ sequenceDiagram #### PUC-4 / PUC-6 — threads & re-anchoring -- **Implementation:** ThreadController + Anchorer. On load / external change - (FileSystemWatcher), every anchor re-resolves (exact-unique → context- - disambiguated → displaced, INV-1). Proposed changes and attributions ride the same - `anchors` primitive, so they re-anchor and displace by the same ladder. +- **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). Whether *every* + comment fires a turn or only an explicit "Ask Claude" affordance on the thread is + open (§13-Q6). #### PUC-5 — live machine progress @@ -601,6 +715,9 @@ sequenceDiagram | 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 | +| 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 @@ -748,6 +865,8 @@ a bug reaches us as a tracker issue. | 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 @@ -759,6 +878,8 @@ a bug reaches us as a tracker issue. | 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 | +| Q5 | Authorship (human vs. machine) cannot live in the native diff (INV-11, verified). Which surface carries it — the annotated preview only, or **also** a decoration-based track-changes editor pane? | Ben | PUC-3 build / whether a track-changes editor is in scope | +| Q6 | Comment auto-response: should it run automatically on every new comment, or only when the writer's comment is phrased as a request / via an explicit "Ask Claude" affordance on the thread? | Ben | PUC-8 build | **Resolved** @@ -770,6 +891,9 @@ a bug reaches us as a tracker issue. | 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 | ## 14. Glossary & References