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:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user