F4: propose/accept diff flow — pending proposals, ✓/✗ review, seam-applied acceptance (Feature #12) #13

Merged
benstull merged 9 commits from feat/f4-propose-accept into main 2026-06-11 05:16:31 +00:00
4 changed files with 58 additions and 7 deletions
Showing only changes of commit 169abf06c8 - Show all commits
+26 -2
View File
@@ -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.
+8 -3
View File
@@ -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
+20
View File
@@ -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.
+4 -2
View File
@@ -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.