feat(client): render proposal diffs as readable field-level changes (#363) #367

Merged
james merged 1 commit from 363-readable-proposal-diffs into main 2026-06-29 13:51:36 +00:00
Owner

The chat confirmation card showed a proposed change's diff as raw pretty-printed JSON. Replace it with a readable, human field-level diff, extracted into a shared component.

What's in it

  • lib/agent/parseProposalDiff.ts — pure, node-tested interpreter that reads the ProposedChange.diff shapes by structure (defensive against the unknown wire type): { fields } → update old → new rows; { after } → create field rows; { before } → delete rows; anything else (merge/unknown/string) → the JSON fallback (preserving old behavior).
  • lib/agent/ProposalDiff.tsx — shared presentational component (theme.tokens, destructive tinting, old values muted + strikethrough, new emphasized).
  • chat.tsxProposalCard now uses <ProposalDiff>; removed the local renderDiff / DESTRUCTIVE_ACTIONS / diff styles. Change confined to the card — the conversation switcher, message bubbles, composer, and streaming are untouched.
  • i18nchat.confirm.diff.{removing,noChanges}.

Note

The agent found the activity undo card (#356/#357) isn't merged yet, so it doesn't exist on this branch's base — the shared component is wired into chat only. Once #357 lands, dropping <ProposalDiff> into the activity card satisfies the "both places" intent (tiny follow-up).

Verification (local, re-run in the worktree)

  • typecheck ✓ · lint ✓ · test ✓ 162 (+8 parseProposalDiff cases) · semgrep ✓ 0 findings · en.json valid

⚠️ Needs maintainer verification

Visual rendering (create/update/delete + fallback, light + dark, strikethrough/arrow alignment, danger tinting) — client tests are node-env.

Closes #363

🤖 Generated with Claude Code

The chat confirmation card showed a proposed change's `diff` as raw pretty-printed JSON. Replace it with a readable, human field-level diff, extracted into a shared component. ## What's in it - **`lib/agent/parseProposalDiff.ts`** — pure, node-tested interpreter that reads the `ProposedChange.diff` shapes **by structure** (defensive against the `unknown` wire type): `{ fields }` → update `old → new` rows; `{ after }` → create field rows; `{ before }` → delete rows; anything else (merge/unknown/string) → the JSON fallback (preserving old behavior). - **`lib/agent/ProposalDiff.tsx`** — shared presentational component (`theme.tokens`, destructive tinting, old values muted + strikethrough, new emphasized). - **`chat.tsx`** — `ProposalCard` now uses `<ProposalDiff>`; removed the local `renderDiff` / `DESTRUCTIVE_ACTIONS` / diff styles. Change confined to the card — the conversation switcher, message bubbles, composer, and streaming are untouched. - **i18n** — `chat.confirm.diff.{removing,noChanges}`. ## Note The agent found the **activity undo card (#356/#357) isn't merged yet**, so it doesn't exist on this branch's base — the shared component is wired into chat only. Once #357 lands, dropping `<ProposalDiff>` into the activity card satisfies the "both places" intent (tiny follow-up). ## Verification (local, re-run in the worktree) - `typecheck` ✓ · `lint` ✓ · `test` ✓ 162 (+8 `parseProposalDiff` cases) · semgrep ✓ 0 findings · `en.json` valid ## ⚠️ Needs maintainer verification Visual rendering (create/update/delete + fallback, light + dark, strikethrough/arrow alignment, danger tinting) — client tests are `node`-env. Closes #363 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(client): render proposal diffs as readable field-level changes (#363)
All checks were successful
Commits / Conventional Commits (pull_request) Successful in 33s
PR / Lint (pull_request) Successful in 2m22s
PR / OpenAPI (pull_request) Successful in 3m15s
PR / Static analysis (pull_request) Successful in 3m24s
PR / Client (web export smoke) (pull_request) Successful in 3m45s
PR / Typecheck (pull_request) Successful in 4m7s
PR / Build (pull_request) Successful in 4m26s
PR / OSV-Scanner (pull_request) Successful in 1m5s
PR / pnpm audit (pull_request) Successful in 1m38s
PR / Package age policy (soft) (pull_request) Successful in 58s
Secrets / gitleaks (pull_request) Successful in 1m2s
PR / Test (postgres) (pull_request) Successful in 3m10s
PR / E2E (Playwright) (pull_request) Successful in 5m49s
PR / Coverage (soft) (pull_request) Successful in 3m0s
PR / Trivy (image) (pull_request) Successful in 3m52s
PR / Test (sqlite) (pull_request) Successful in 6m18s
2aa3158fc1
The chat confirmation card showed a proposed change's diff as raw
pretty-printed JSON. Replace it with a readable, human diff and extract
the rendering into a shared component the activity undo card can reuse.

- lib/agent/parseProposalDiff.ts: pure, node-tested interpreter that
  reads the ProposedChange.diff shapes by structure (defensive against
  the unknown wire type) — {fields} → update old→new rows, {after} →
  create field rows, {before} → delete rows, anything else → JSON
  fallback.
- lib/agent/ProposalDiff.tsx: shared presentational component (theme
  tokens, destructive tinting, strikethrough on old values).
- chat.tsx ProposalCard uses <ProposalDiff>; removed the local
  renderDiff/DESTRUCTIVE_ACTIONS/diff styles (change confined to the
  card — switcher/bubbles/composer/streaming untouched).
- i18n: chat.confirm.diff.{removing,noChanges}.

The activity undo card (once #356 lands) drops in <ProposalDiff> to
satisfy the "both places" intent.

Closes #363

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 357a8b880a into main 2026-06-29 13:51:36 +00:00
james deleted branch 363-readable-proposal-diffs 2026-06-29 13:51:36 +00:00
Sign in to join this conversation.
No description provided.