Skip to content

Add per-edit undo: revert one change before it ships - #21

Open
samschwartzfh wants to merge 1 commit into
petergyang:mainfrom
samschwartzfh:per-edit-undo
Open

Add per-edit undo: revert one change before it ships#21
samschwartzfh wants to merge 1 commit into
petergyang:mainfrom
samschwartzfh:per-edit-undo

Conversation

@samschwartzfh

Copy link
Copy Markdown

Why

Reviewing a long page, it's easy to make several changes between sends and then want just one of them back. Today the only recovery is Revert all, and Ctrl+Z can't see a chip-deleted block (it's removed via JS, outside the browser's undo stack). This PR adds per-edit undo, plus an immediate "Deleted ⟨block⟩ — Undo" toast for the most common accident.

What it does

  • Every row in Your edits gets a ↩ button while the change is still revertible. Text/formatting edits restore the block's original markup in place, deletes reinsert the exact detached node at its old position, moves return the block home. For HTML files the autosave then corrects the file on disk.
  • Deleting a block also offers an instant undo toast.
  • On feedback-only pages (markdown, localhost, self-rendering), undo removes the row — the only artifact there.

Design notes

  • Registry lives in the SDK, keyed by a per-block cid. Entries hold the actual nodes, so reinsertion preserves element identity (pinned labels, captured originals, comment marks all survive). Restores sync attributes + innerHTML onto the same element rather than replacing it.
  • Undo is only offered where it can deliver. The SDK advertises its live-undoable cids (eh:undoable); the chrome renders ↩ only for those (rows persisted from before a reload just don't get a button), and never while a batch is sent/pending — undoing a row the agent already holds would desync the two.
  • Strict transaction order (purge queued payload → revert DOM → save → drop row server-side, confirmed via eh:undone), so the 500ms edit flush can never resurrect an undone row.
  • Edit rows now carry a durable server id and the block's cid. Dedup is cid-first with a boot-timestamp-gated label+kind fallback: two live blocks that share a label stay separate rows, while a reloaded frame's fresh cid still merges into the block's existing row.
  • The agent-facing batch is unchangedcollectPages ships the same fields as before (there's a test asserting no id/cid leak).

Testing

  • New test/undo.test.js: store ids, cid dedup semantics, DELETE /api/page/:key/edit/:id, agent-contract cleanliness — all green alongside the existing suite.
  • Headless-Chrome E2E driving the real UI (20 checks): edit → undo → DOM + file restored; chip delete → toast → undo → block back in exact position; reload → row survives, undo button correctly withdrawn.

🤖 Generated with Claude Code

Every edit row in the sidebar gets an undo button while the change is still
undoable: the SDK keeps a per-document registry (element refs, detached
nodes, original positions) keyed by a per-block cid, reverts the DOM
in-place preserving element identity, and the row is dropped server-side
only after the frame confirms. Deleting a block also offers an immediate
'Deleted X — Undo' toast.

- edit rows carry a durable server id and the block's cid; dedup is
  cid-first with a boot-timestamp-gated label+kind fallback, so two live
  blocks sharing a label stay separate rows while reloads keep one row
- DELETE /api/page/:key/edit/:id undoes a single row
- undo is offered only for rows the live frame advertises as revertible
  (eh:undoable), never while a batch is pending; on feedback-only pages
  (markdown, localhost, self-rendering) undo is row-removal
- strict transaction order (purge queue -> revert -> save -> remove row)
  so a debounced flush can never resurrect an undone row
- agent-facing batches are unchanged

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@rpeck

rpeck commented Aug 10, 2026

Copy link
Copy Markdown

This looks great! Can one also edit individual comments? That's my biggest wishlist item.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants