F4 — Propose/accept diff flow: inline diff + keep/reject on the applyAgentEdit seam #12

Closed
opened 2026-06-11 04:38:16 +00:00 by benstull · 0 comments
Owner

Summary

Propose/accept diff flow (propose-by-default): Claude's edits arrive as
proposals — rendered as an inline diff in the buffer — that the human
explicitly keeps or rejects. Accepting applies the edit through the
established applyAgentEdit seam, so accepted spans land Claude-attributed in
the F3 attribution substrate; rejecting leaves the document untouched. Pending
proposals persist git-natively in the sidecar's reserved proposals[] extension
point (reusing the shared anchors/provenance primitives from F2/F3).

Problem / pain

F3's machine-edit ingress applies edits directly: the moment a live Claude
turn completes, its text is already in the document, and the human reviews
after the fact via the attribution tint. There is no ratify-before-landing
gesture — no inline diff to inspect, no keep/reject decision point — so the
human's only recourse to an unwanted machine edit is undo. The epic's third
acceptance pillar — "accept / reject Claude's proposed diffs
(propose-by-default)"
— is unmet, and the trust model is backwards: the machine
writes first and the human objects later, instead of the machine proposing and
the human deciding.

Who feels it

A writer/engineer coauthoring prose or specs with Claude inside VS Code — one
human + one machine — who wants to see exactly what the machine intends to
change
before it becomes part of the document, and to keep or reject each
proposal with one gesture without losing the provenance record either way.

Desired outcome / value

Machine edits become propose-by-default: a live Claude turn produces a
visible inline diff (current text vs. proposed replacement) instead of a silent
buffer mutation; the human accepts (the edit applies through the seam and its
spans render Claude-attributed, exactly as F3 established) or rejects (the
buffer is untouched). Pending proposals are first-class, git-native state — they
survive save/reopen and re-anchor across edits like threads and attributions do.
This delivers the epic's third acceptance pillar, completes the inner-loop triad
(threads · attribution · propose/accept), and gives F5 a complete provenance
story to carry up the ladder (proposed → ratified → attributed).

Acceptance — "done right"

  • A live Claude turn (the F3 edit-selection flow) produces a proposal, not a
    direct buffer mutation: the affected range and proposed replacement are
    rendered as an inline diff in the buffer, visibly distinct from both plain
    text and accepted Claude-attributed spans.
  • The human can accept a proposal: the edit applies through
    applyAgentEdit (the single machine-edit ingress, F3 INV-9), and the
    resulting spans are Claude-attributed in the existing attribution substrate —
    acceptance is the only path by which proposed text enters the document.
  • The human can reject a proposal: the document is untouched and the
    pending diff disappears cleanly.
  • Pending proposals persist git-natively and reload: closing and reopening
    the workspace restores them from the sidecar's proposals[]
    (schemaVersion honored, no format break — spec INV-4).
  • Pending proposals survive edits: their anchors re-resolve as surrounding
    text changes; a proposal whose anchor can't be confidently re-resolved is
    orphaned, never silently moved or applied (F2's INV-1 ladder applies).
  • Unit tests cover the proposal model, accept/reject state transitions, and
    the persistence round-trip; host E2E covers propose → render → accept →
    attributed → reload, propose → reject → untouched, and propose → reopen →
    still pending, per the F2/F3 test pattern (no LLM in CI — the seam and a
    programmatic proposal ingress drive the tests).

Scope / non-goals

In scope: the proposals[] shape on the shared anchors/provenance
primitives; turning the machine-edit flow propose-by-default; inline diff
rendering of pending proposals in the buffer; the keep/reject UX; apply-on-accept
through the applyAgentEdit seam (growing the seam's single-range contract to
multi-range if the design requires it — the F3 spec reserves exactly this);
persistence + reload + re-anchor/orphan of pending proposals.

Out of scope (deferred): F5 cross-rung persistence format + Gitea round-trip
(#46); hunk-level / partial acceptance beyond whatever granularity the design
settles (per-proposal accept/reject is the floor); chat-panel UX; multi-file
orchestration; turn-taking choreography; attribution of out-of-session edits;
any server.

Assumptions · constraints · dependencies

  • Parent: #1 (the coauthoring Epic). Blocked by: #6 (F3 — shipped;
    PR #7) and #4 (F2 — shipped; PR #5). Feeds: F5 (cross-rung format carries
    the full proposed→ratified→attributed provenance story up the ladder).
  • Builds on the graduated specs — this Feature fills reserved extension
    points, it does not re-litigate them:
    • specs/coauthoring-inner-loop.md §6.3 reserves proposals[] in the sidecar
      ("F4 extension point — reuses anchors[] + provenance") and §9 routes the
      exact shape "OPEN → F3/F4" (F3 resolved attributions[]; proposals[] is
      F4's half).
    • specs/coauthoring-attribution.md §9: "OPEN → F4: propose/accept drives
      the same seam; accepted diffs render into this attribution substrate. The
      seam's contract (single-range apply) may grow multi-range then."
  • The seam is the contract: applyAgentEdit(doc, range, newText, provenance, {expectedVersion?, turnId?}) is the ONLY machine-edit ingress (F3 INV-9). F4
    drives it on accept; it self-minimizes and attributes the full intended
    replacement (intent-honest, ratified).
  • Open design questions (the forks) — routed to the follow-on brainstorming
    session; this Feature consumes its reviewed Solution Design: (a) the exact
    proposals[] shape (pending vs. resolved records; is a rejected proposal
    kept as history or dropped?); (b) the inline-diff rendering primitive
    (decorations-based ghost text/strikethrough vs. VS Code's diff editor vs.
    CodeLens/Comments-style action surface — F2's Comments API and F3's
    Decorations API are the established in-buffer surfaces); (c) whether the F3
    edit-selection command flips wholesale to propose-by-default or keeps a
    direct-apply mode behind a setting; (d) whether the seam grows multi-range
    now (a turn proposing several disjoint edits) or stays single-range with one
    proposal per range; (e) accept/reject granularity (whole-proposal floor
    vs. per-hunk).
  • Live-turn base is established (F3): @cline/sdk Agent on the claude-code
    provider, zero extension-held credentials; result.runId is the provenance
    sessionId. F4 adds no new auth surface.

Source / signal

Epic #1 (coauthoring-plugin-on-cline) decomposition item F4 — Propose/accept
diff flow (inline-diff keep/reject)
; the graduated Solution Designs
vscode-cowriting-plugin-content/specs/coauthoring-inner-loop.md (§6.3 reserved
proposals[], §9 OPEN→F3/F4) and
vscode-cowriting-plugin-content/specs/coauthoring-attribution.md (§9 OPEN→F4);
architecture lineage ben.stull/rfc-app#48. Builds on F3 shipped in #6 (PR #7,
session vscode-cowriting-plugin-0007) and F2 shipped in #4 (PR #5). Captured in
session vscode-cowriting-plugin-0010.

Priority (WSJF)

Provisional: Value high (the epic's third acceptance pillar;
propose-by-default is the core trust gesture of the coauthoring surface) ·
Time-criticality medium · Opportunity-enablement medium-high (completes
the inner-loop triad; F5 needs the full provenance story to carry up the ladder)
÷ Size medium → P1.

Decomposition (Feature → slice checklist)

  • Resolve the design forks (proposal shape; rendering primitive;
    propose-by-default vs. retained direct mode; multi-range seam growth;
    accept/reject granularity) — consume the follow-on Solution Design.
  • proposals[] data model on the shared anchors/provenance primitives
    (schema extension, vscode-free, round-trip tests — per the F2/F3 SLICE-1
    pattern).
  • Proposal ingress: the live turn (and a programmatic test ingress) produces
    a pending proposal instead of a direct apply.
  • Inline diff rendering of pending proposals in the buffer.
  • Keep/reject UX + apply-on-accept through applyAgentEdit
    (Claude-attributed spans land exactly as F3 established).
  • Persistence + reload + re-anchor/orphan of pending proposals via the
    existing Store/Anchorer.
  • Unit tests (model, transitions, round-trip) + host E2E
    (propose → accept → attributed → reload; reject → untouched; pending survives
    reopen), no LLM in CI.

Solution notes (optional — NOT a design)

The reuse points are all in place: the sidecar's reserved proposals[], the
shared anchors/provenance primitives, CoauthorStore.update (all sidecar
writers go through it), the Anchorer's resolve/orphan ladder, and the
applyAgentEdit seam as the apply path on accept. Rendering candidates worth
weighing in the design session: decoration-based inline diff (strikethrough old

  • tinted proposed, the F3 Decorations pattern), the VS Code diff editor
    (heavyweight, leaves the buffer), or a Comments-API action thread on the
    proposal's anchor (the F2 pattern — keep/reject as thread actions). Host-only
    traps F3 logged (change events before dirty-flag flip; WorkspaceEdit
    diff-minimization) apply unchanged to F4's E2E. All non-binding — the forks
    above are the design session's to resolve.
<!-- Set the type/* label: type/feature --> ## Summary **Propose/accept diff flow (propose-by-default)**: Claude's edits arrive as **proposals** — rendered as an inline diff in the buffer — that the human explicitly **keeps or rejects**. Accepting applies the edit through the established `applyAgentEdit` seam, so accepted spans land Claude-attributed in the F3 attribution substrate; rejecting leaves the document untouched. Pending proposals persist git-natively in the sidecar's reserved `proposals[]` extension point (reusing the shared `anchors`/`provenance` primitives from F2/F3). ## Problem / pain F3's machine-edit ingress applies edits **directly**: the moment a live Claude turn completes, its text is already in the document, and the human reviews *after the fact* via the attribution tint. There is no **ratify-before-landing** gesture — no inline diff to inspect, no keep/reject decision point — so the human's only recourse to an unwanted machine edit is undo. The epic's third acceptance pillar — *"accept / reject Claude's proposed diffs (propose-by-default)"* — is unmet, and the trust model is backwards: the machine writes first and the human objects later, instead of the machine proposing and the human deciding. ## Who feels it A writer/engineer **coauthoring prose or specs with Claude inside VS Code** — one human + one machine — who wants to *see exactly what the machine intends to change* before it becomes part of the document, and to keep or reject each proposal with one gesture without losing the provenance record either way. ## Desired outcome / value Machine edits become **propose-by-default**: a live Claude turn produces a visible inline diff (current text vs. proposed replacement) instead of a silent buffer mutation; the human **accepts** (the edit applies through the seam and its spans render Claude-attributed, exactly as F3 established) or **rejects** (the buffer is untouched). Pending proposals are first-class, git-native state — they survive save/reopen and re-anchor across edits like threads and attributions do. This delivers the epic's third acceptance pillar, completes the inner-loop triad (threads · attribution · propose/accept), and gives F5 a complete provenance story to carry up the ladder (proposed → ratified → attributed). ## Acceptance — "done right" - A live Claude turn (the F3 edit-selection flow) produces a **proposal**, not a direct buffer mutation: the affected range and proposed replacement are rendered as an **inline diff** in the buffer, visibly distinct from both plain text and accepted Claude-attributed spans. - The human can **accept** a proposal: the edit applies through `applyAgentEdit` (the single machine-edit ingress, F3 INV-9), and the resulting spans are Claude-attributed in the existing attribution substrate — acceptance is the *only* path by which proposed text enters the document. - The human can **reject** a proposal: the document is untouched and the pending diff disappears cleanly. - Pending proposals **persist git-natively and reload**: closing and reopening the workspace restores them from the sidecar's `proposals[]` (`schemaVersion` honored, no format break — spec INV-4). - Pending proposals **survive edits**: their anchors re-resolve as surrounding text changes; a proposal whose anchor can't be confidently re-resolved is **orphaned, never silently moved or applied** (F2's INV-1 ladder applies). - **Unit tests** cover the proposal model, accept/reject state transitions, and the persistence round-trip; **host E2E** covers propose → render → accept → attributed → reload, propose → reject → untouched, and propose → reopen → still pending, per the F2/F3 test pattern (no LLM in CI — the seam and a programmatic proposal ingress drive the tests). ## Scope / non-goals **In scope:** the `proposals[]` shape on the shared `anchors`/`provenance` primitives; turning the machine-edit flow propose-by-default; inline diff rendering of pending proposals in the buffer; the keep/reject UX; apply-on-accept through the `applyAgentEdit` seam (growing the seam's single-range contract to multi-range if the design requires it — the F3 spec reserves exactly this); persistence + reload + re-anchor/orphan of pending proposals. **Out of scope (deferred):** F5 cross-rung persistence format + Gitea round-trip (#46); hunk-level / partial acceptance beyond whatever granularity the design settles (per-proposal accept/reject is the floor); chat-panel UX; multi-file orchestration; turn-taking choreography; attribution of out-of-session edits; any server. ## Assumptions · constraints · dependencies - **Parent:** #1 (the coauthoring Epic). **Blocked by:** #6 (F3 — shipped; PR #7) and #4 (F2 — shipped; PR #5). **Feeds:** F5 (cross-rung format carries the full proposed→ratified→attributed provenance story up the ladder). - **Builds on the graduated specs** — this Feature fills reserved extension points, it does not re-litigate them: - `specs/coauthoring-inner-loop.md` §6.3 reserves `proposals[]` in the sidecar ("F4 extension point — reuses `anchors[]` + provenance") and §9 routes the exact shape "OPEN → F3/F4" (F3 resolved `attributions[]`; `proposals[]` is F4's half). - `specs/coauthoring-attribution.md` §9: "**OPEN → F4:** propose/accept drives the same seam; accepted diffs render into this attribution substrate. The seam's contract (single-range apply) may grow multi-range then." - **The seam is the contract:** `applyAgentEdit(doc, range, newText, provenance, {expectedVersion?, turnId?})` is the ONLY machine-edit ingress (F3 INV-9). F4 drives it on accept; it self-minimizes and attributes the full intended replacement (intent-honest, ratified). - **Open design questions (the forks)** — routed to the follow-on brainstorming session; this Feature consumes its reviewed Solution Design: (a) the exact `proposals[]` shape (pending vs. resolved records; is a rejected proposal kept as history or dropped?); (b) the inline-diff **rendering primitive** (decorations-based ghost text/strikethrough vs. VS Code's diff editor vs. CodeLens/Comments-style action surface — F2's Comments API and F3's Decorations API are the established in-buffer surfaces); (c) whether the F3 edit-selection command flips **wholesale** to propose-by-default or keeps a direct-apply mode behind a setting; (d) whether the seam grows **multi-range** now (a turn proposing several disjoint edits) or stays single-range with one proposal per range; (e) accept/reject **granularity** (whole-proposal floor vs. per-hunk). - Live-turn base is established (F3): `@cline/sdk` `Agent` on the `claude-code` provider, zero extension-held credentials; `result.runId` is the provenance sessionId. F4 adds no new auth surface. ## Source / signal Epic #1 (`coauthoring-plugin-on-cline`) decomposition item **F4 — Propose/accept diff flow (inline-diff keep/reject)**; the graduated Solution Designs `vscode-cowriting-plugin-content/specs/coauthoring-inner-loop.md` (§6.3 reserved `proposals[]`, §9 OPEN→F3/F4) and `vscode-cowriting-plugin-content/specs/coauthoring-attribution.md` (§9 OPEN→F4); architecture lineage `ben.stull/rfc-app#48`. Builds on F3 shipped in #6 (PR #7, session `vscode-cowriting-plugin-0007`) and F2 shipped in #4 (PR #5). Captured in session `vscode-cowriting-plugin-0010`. ## Priority (WSJF) Provisional: **Value** high (the epic's third acceptance pillar; propose-by-default is the core trust gesture of the coauthoring surface) · **Time-criticality** medium · **Opportunity-enablement** medium-high (completes the inner-loop triad; F5 needs the full provenance story to carry up the ladder) ÷ **Size** medium → **P1**. ## Decomposition (Feature → slice checklist) - [ ] Resolve the design forks (proposal shape; rendering primitive; propose-by-default vs. retained direct mode; multi-range seam growth; accept/reject granularity) — consume the follow-on Solution Design. - [ ] `proposals[]` data model on the shared `anchors`/`provenance` primitives (schema extension, vscode-free, round-trip tests — per the F2/F3 SLICE-1 pattern). - [ ] Proposal ingress: the live turn (and a programmatic test ingress) produces a pending proposal instead of a direct apply. - [ ] Inline diff rendering of pending proposals in the buffer. - [ ] Keep/reject UX + apply-on-accept through `applyAgentEdit` (Claude-attributed spans land exactly as F3 established). - [ ] Persistence + reload + re-anchor/orphan of pending proposals via the existing Store/Anchorer. - [ ] Unit tests (model, transitions, round-trip) + host E2E (propose → accept → attributed → reload; reject → untouched; pending survives reopen), no LLM in CI. ## Solution notes (optional — NOT a design) The reuse points are all in place: the sidecar's reserved `proposals[]`, the shared `anchors`/`provenance` primitives, `CoauthorStore.update` (all sidecar writers go through it), the Anchorer's resolve/orphan ladder, and the `applyAgentEdit` seam as the apply path on accept. Rendering candidates worth weighing in the design session: decoration-based inline diff (strikethrough old + tinted proposed, the F3 Decorations pattern), the VS Code diff editor (heavyweight, leaves the buffer), or a Comments-API action thread on the proposal's anchor (the F2 pattern — keep/reject as thread actions). Host-only traps F3 logged (change events before dirty-flag flip; WorkspaceEdit diff-minimization) apply unchanged to F4's E2E. All non-binding — the forks above are the design session's to resolve.
benstull added the type/featurepriority/P1 labels 2026-06-11 04:38:16 +00:00
Sign in to join this conversation.