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) <noreply@anthropic.com>
This commit is contained in:
BenStullsBets
2026-06-28 06:55:55 -07:00
parent f6a86d6354
commit aaf5fb46eb
+131 -7
View File
@@ -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 PerProduct-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