feat(client): undo button on the activity screen #357

Merged
james merged 1 commit from 356-undo-button-ui into main 2026-06-29 13:11:09 +00:00
Owner

Makes the undo backend (#354 / ADR-0031) user-facing — the last piece to fully satisfy epic #47's "undoable from the UI" exit criterion. Each reversible row of the activity history (#350) gets an undo affordance, with an inline confirmation before anything is reversed.

What's in it

  • @carol/api-clientuseUndoAuditEvent(): mutation over POST /api/agent/audit/{id}/undo, returns the inverse ProposalDto.
  • lib/activity/isUndoable.ts (pure, tested) — hides the undo affordance for the delete-family tools (delete_*/remove_*/merge_*), since deletes aren't reversible (ADR-0031). The server's 409 not_undoable stays as the backstop for anything the heuristic misses.
  • activity.tsx — an "undo" button (Lucide RotateCcw) on each undoable row → proposes the inverse → renders an inline confirmation card (summary + diff + apply/decline). Apply commits via useCommitProposal and invalidates the audit list (so the inverse appears in history); decline rejects; errors render inline via formatApiError. Reuses the chat card's diff-rendering + destructive-action styling conventions.
  • i18nactivity.undo + activity.confirm.*; updated the lede (was "nothing here can be undone").

How it flows

undo button → POST /undo returns a pending inverse proposal → confirmation card → commit/reject (the existing #51 endpoints) → audit list refreshes. The undo is itself confirmed + audited (consistent with the write-confirmation discipline), exactly as ADR-0031 intends.

Verification (local)

  • @carol/api-client: typecheck ✓ · lint ✓ · check ✓ (no spec change) · test ✓ 42
  • @carol/client: typecheck ✓ · lint ✓ · test ✓ 156 (+2 isUndoable cases)
  • en.json valid; semgrep (CI pack set) ✓ 0 findings on the new/changed files

⚠️ Needs maintainer verification (headless limit)

Client vitest is node-env — the visual undo button, confirmation card, and the commit→refresh interaction need a real PWA/device check.

Out of scope (follow-ups)

  • Undoing deletes (needs subtree-capture backend work, ADR-0031).

Part of epic #47.

Closes #356

🤖 Generated with Claude Code

Makes the undo backend (#354 / ADR-0031) user-facing — the last piece to fully satisfy epic #47's "undoable from the UI" exit criterion. Each reversible row of the activity history (#350) gets an undo affordance, with an inline confirmation before anything is reversed. ## What's in it - **`@carol/api-client`** — `useUndoAuditEvent()`: mutation over `POST /api/agent/audit/{id}/undo`, returns the inverse `ProposalDto`. - **`lib/activity/isUndoable.ts`** (pure, tested) — hides the undo affordance for the delete-family tools (`delete_*`/`remove_*`/`merge_*`), since deletes aren't reversible (ADR-0031). The server's 409 `not_undoable` stays as the backstop for anything the heuristic misses. - **`activity.tsx`** — an "undo" button (Lucide `RotateCcw`) on each undoable row → proposes the inverse → renders an inline confirmation card (summary + diff + apply/decline). Apply commits via `useCommitProposal` and invalidates the audit list (so the inverse appears in history); decline rejects; errors render inline via `formatApiError`. Reuses the chat card's diff-rendering + destructive-action styling conventions. - **i18n** — `activity.undo` + `activity.confirm.*`; updated the lede (was "nothing here can be undone"). ## How it flows undo button → `POST /undo` returns a pending inverse proposal → confirmation card → commit/reject (the existing #51 endpoints) → audit list refreshes. The undo is itself confirmed + audited (consistent with the write-confirmation discipline), exactly as ADR-0031 intends. ## Verification (local) - `@carol/api-client`: typecheck ✓ · lint ✓ · `check` ✓ (no spec change) · test ✓ 42 - `@carol/client`: typecheck ✓ · lint ✓ · test ✓ 156 (+2 `isUndoable` cases) - `en.json` valid; semgrep (CI pack set) ✓ 0 findings on the new/changed files ## ⚠️ Needs maintainer verification (headless limit) Client vitest is `node`-env — the **visual** undo button, confirmation card, and the commit→refresh interaction need a real PWA/device check. ## Out of scope (follow-ups) - Undoing deletes (needs subtree-capture backend work, ADR-0031). Part of epic #47. Closes #356 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(client): undo button on the activity screen
All checks were successful
Commits / Conventional Commits (pull_request) Successful in 6s
PR / Static analysis (pull_request) Successful in 3m20s
PR / Lint (pull_request) Successful in 3m30s
PR / pnpm audit (pull_request) Successful in 3m46s
PR / Client (web export smoke) (pull_request) Successful in 3m41s
PR / OpenAPI (pull_request) Successful in 4m6s
PR / Typecheck (pull_request) Successful in 4m14s
PR / OSV-Scanner (pull_request) Successful in 57s
PR / Test (postgres) (pull_request) Successful in 4m48s
PR / Build (pull_request) Successful in 5m2s
PR / Package age policy (soft) (pull_request) Successful in 1m7s
PR / Test (sqlite) (pull_request) Successful in 5m38s
Secrets / gitleaks (pull_request) Successful in 1m31s
PR / E2E (Playwright) (pull_request) Successful in 5m46s
PR / Trivy (image) (pull_request) Successful in 3m21s
PR / Coverage (soft) (pull_request) Successful in 3m11s
01cd2ff430
Makes the undo backend (#354 / ADR-0031) user-facing. Each reversible row
of the activity history (#350) gets an "undo" affordance that proposes the
inverse change and confirms it inline before anything is reversed.

- @carol/api-client: useUndoAuditEvent() — mutation over
  POST /api/agent/audit/{id}/undo, returns the inverse ProposalDto.
- lib/activity/isUndoable.ts (pure, tested): hide undo for the
  delete-family tools (delete_/remove_/merge_); the server 409
  not_undoable stays the backstop.
- activity.tsx: an undo button (RotateCcw) per undoable row → proposes
  the inverse → inline confirmation card (summary + diff + apply/decline).
  Apply commits via useCommitProposal + invalidates the audit list;
  decline rejects; errors render via formatApiError. Updated the lede.
- i18n: activity.undo + activity.confirm.*.

Closes #356

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

📊 Test coverage

Patch coverage: no testable lines changed.

Overall (app/, lib/, db/, excluding UI per ADR-0019):

Metric Value Soft target
Lines 79.3% ≥ 50%
Branches 71.1% ⚠️ ≥ 75%
Functions 80.5% informational

Soft thresholds per ADR-0019. Coverage is informational and does not block merge.

<!-- coverage-comment --> ## 📊 Test coverage **Patch coverage:** no testable lines changed. **Overall** (`app/`, `lib/`, `db/`, excluding UI per ADR-0019): | Metric | Value | Soft target | |---|---|---| | Lines | 79.3% ✅ | ≥ 50% | | Branches | 71.1% ⚠️ | ≥ 75% | | Functions | 80.5% | informational | Soft thresholds per [ADR-0019](docs/adr/0019-coverage-soft-targets.md). Coverage is informational and does not block merge.
james merged commit 58bde43eb7 into main 2026-06-29 13:11:09 +00:00
james deleted branch 356-undo-button-ui 2026-06-29 13:11:10 +00:00
Sign in to join this conversation.
No description provided.