diff --git a/README.md b/README.md index e6bb5da..99c5805 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,8 @@ catalog (a pure, key-free SDK call) in a notification and the "Cowriting (Cline SDK)" output channel. Features shipped so far: F2 region-anchored threads (Feature #4), F3 live -human/Claude attribution (Feature #6). +human/Claude attribution (Feature #6), F4 propose/accept diff flow +(Feature #12). ## Architecture @@ -67,13 +68,36 @@ Attribution" output channel) rather than silently moved or discarded. - **`Cowriting: Ask Claude to Edit Selection`** — select text → enter an instruction → a live `@cline/sdk` turn runs on the built-in `claude-code` provider (rides your local Claude Code Pro/Max login; the extension stores no - credentials). The replacement lands in the buffer as a Claude-attributed span. + credentials). As of F4 the turn ends in a **proposal** (see below); accepted + text lands as a Claude-attributed span. - **`Cowriting: Toggle Attribution`** — show/hide attribution decorations. - `cowriting.applyAgentEdit` _(palette-hidden)_ — the single machine-edit ingress seam. Tests drive this directly so CI requires no LLM. Design: `vscode-cowriting-plugin-content/specs/coauthoring-attribution.md`. +## F4 — Propose/accept diff flow (Feature #12) + +Claude's edits arrive as **pending proposals** — propose-by-default, the +document never changes without your say-so. A proposal renders two ways at +once: an **amber tint** on the target range, and a "Claude proposes" comment +thread showing a fenced `diff` of current → proposed text with two actions: + +- **✓ Accept Proposal** — applies the replacement through the `applyAgentEdit` + seam, so it lands Claude-attributed (F3) — and the proposal disappears. +- **✗ Reject Proposal** — the document is untouched; the proposal disappears. + +Pending proposals persist git-natively in the same sidecar (`proposals[]`, +sharing the F2/F3 `anchors` fingerprints), survive reload, and re-anchor as +surrounding text changes. If the **target text itself** changes, the proposal +goes **stale** (status-bar count, accept disabled — never applied by guess); +undo the change and it becomes decidable again. `cowriting.proposeAgentEdit` +_(palette-hidden)_ is the programmatic propose ingress E2E drives — no LLM in +CI. + +Design: `vscode-cowriting-plugin-content/specs/coauthoring-propose-accept.md`. +Live smoke: [`docs/MANUAL-SMOKE-F4.md`](docs/MANUAL-SMOKE-F4.md). + ## Develop - `npm run watch` — rebuild on change. diff --git a/docs/MANUAL-SMOKE-F3.md b/docs/MANUAL-SMOKE-F3.md index fe82cfe..89df7cb 100644 --- a/docs/MANUAL-SMOKE-F3.md +++ b/docs/MANUAL-SMOKE-F3.md @@ -1,5 +1,10 @@ # F3 manual smoke — live `claude-code` turn (spec §6.8) +> **As of F4 (#12)** the edit-selection turn ends in a **proposal** — the +> direct-apply behavior in §2 step 4 now happens on **✓ Accept Proposal**. The +> full propose→accept smoke is [`MANUAL-SMOKE-F4.md`](./MANUAL-SMOKE-F4.md); +> §1's scripted smoke is unchanged (it drives the SDK turn only, no editor). + The live turn is deliberately NOT in CI (unit + host E2E drive the seam). It gets this documented smoke, run once per machine that has Claude Code installed and signed in (Pro/Max). The extension itself holds no credentials @@ -37,9 +42,9 @@ the model id, a non-empty `sessionId`, and exits 0. with a clear error (`runEditTurn` throws on any non-`completed` run status); in-editor, the command shows an error notification and NO edit is applied. -- Buffer edited mid-turn: start an edit-selection turn, type elsewhere in the - document before it completes → warning notification "document changed", no - partial application (stale `expectedVersion`, spec §6.9). +- Buffer edited mid-turn: superseded by F4 — the turn ends in a proposal, so a + mid-turn edit can't race an application; the proposal simply renders where + its target re-resolves (or stale if the target itself changed — F4 INV-11). ## Smoke log diff --git a/docs/MANUAL-SMOKE-F4.md b/docs/MANUAL-SMOKE-F4.md new file mode 100644 index 0000000..5956fa1 --- /dev/null +++ b/docs/MANUAL-SMOKE-F4.md @@ -0,0 +1,20 @@ +# Manual smoke — F4 propose/accept (live turn) + +Pre-req: Claude Code installed + signed in (the `claude-code` provider rides +that login — the extension holds no credentials, INV-8). Run on a real machine, +not CI. + +1. `npm run build`, then F5 (the EDH opens the committed `sandbox/` playground). +2. Open `playground.md`, select a sentence → right-click → **Ask Claude to Edit + Selection** → give an instruction. +3. ✅ A **proposal** appears: the selection gets an amber tint and a "Claude + proposes" comment thread shows a `diff` of current → proposed. **The document + text is unchanged** (INV-10). +4. Click **✓ Accept Proposal** in the thread title. ✅ The replacement lands and + renders Claude-tinted (the F3 attribution substrate); the proposal disappears. +5. Repeat with another selection and click **✗ Reject Proposal**. ✅ The document + is untouched; the proposal disappears. +6. Propose again, then save and reload the window. ✅ The pending proposal is + restored at its re-resolved range. +7. Failure path: edit the proposed-on text, then try Accept. ✅ Refused with a + warning ("target text changed"); undo your edit → Accept works. diff --git a/sandbox/playground.md b/sandbox/playground.md index 234efdf..23024fd 100644 --- a/sandbox/playground.md +++ b/sandbox/playground.md @@ -8,8 +8,10 @@ open in your dev window (#8). Play here: 1. Type a sentence below — it renders with the human left-border as you type. -2. Highlight it → right-click → **Ask Claude to Edit Selection** — the - replacement lands Claude-tinted. +2. Highlight it → right-click → **Ask Claude to Edit Selection** — Claude + **proposes** a diff (amber tint + a "Claude proposes" thread); click + **✓ Accept Proposal** and it lands Claude-tinted, or **✗ Reject Proposal** + to discard. 3. Edit inside the tinted span — it splits character-precisely. 4. Save, then peek at `sandbox/.threads/playground.md.json` — the git-native attribution record. Reopen the file: spans re-resolve.