Skip to content

feat(ui): Version History block/word-level diff (Compare) (PVH-6)#176

Merged
eliotlim merged 3 commits into
mainfrom
feat/pvh6-block-diff
Jul 14, 2026
Merged

feat(ui): Version History block/word-level diff (Compare) (PVH-6)#176
eliotlim merged 3 commits into
mainfrom
feat/pvh6-block-diff

Conversation

@eliotlim

Copy link
Copy Markdown
Owner

What & why

The last Version History slice (PVH-6): a Compare mode in the history side pane that shows a real block/word-level diff of a captured version against the current document — not just two read-only previews.

  • Block alignment is an order-preserving LCS over top-level blocks keyed by stable block id: an aligned pair is unchanged/changed, an old-only block is removed, a new-only block is added.
  • Changed text blocks get an intra-block word-level diff (kept/added/removed runs) rendered with semantic <ins>/<del> (never colour-only), tinted emerald / destructive / amber.
  • Non-text blocks (image, chart, table, embed…) are reported opaquely as a "{Type} changed" badge rather than pretending to diff their internals.
  • Long unchanged-context runs collapse behind a toggle. Pure engine (lib/blockDiff.ts, plain BlockJSON in / data out), unit-tested; no diff dependency added (the word-LCS is ~20 lines).

Before / After

Before = base main (the history pane's disabled Compare (coming soon) placeholder). After = this branch.

Behaviour Before After
Compare a version vs current: added + removed + inline word-level changed + an opaque non-text chip, together
read-only preview only; Compare disabled

quickslow ins/del · removed row struck through · Image changed chip · added row
Dark mode — emerald / destructive / amber tint contrast
Preview | Compare toggle (light / dark) text: single disabled "Compare (coming soon)" button
No-changes state (identical / empty-vs-empty version)
"No changes — this version matches the current document."

Review fixes applied (Quinn + Devon — batched)

  1. Empty-doc false positive (Quinn). decodeSnapshotensureNotEmpty injects a lone empty paragraph with a fresh random id into each decoded-empty doc, so empty-vs-empty diffed as removed+added. The engine now drops a lone trailing bare default paragraph from each side before aligning → truly empty-vs-empty reads "No changes" (blockDiff.ts, stripTrailingBareParagraph).
  2. Blank "changed" row for formatting-only changes (Quinn Q2 / Devon Rebrand to OpenBook #1). The "(empty)" fallback was gated on oldBlock (always truthy for changed), so a text block with identical text but changed props/children rendered a blank row with a lone ~. Now gated on the word runs actually containing an ins/del; all-kept runs render a "Formatting changed" hint (new i18n key) (VersionDiff.tsx).
  3. Opaque chip over-promise (Devon Load workspace from filesystem #2). Downgraded the copy so nothing claims an "old→new" pair for opaque changes — a "{Type} changed" badge is the honest representation (module doc + VersionDiff comments/copy).
  4. aria-label collision (Devon Navigation should update the currently open page #3). The toggle wrapper and the diff content root both used aria-label="Compare". The toggle wrapper now uses a dedicated "View mode" label; "Compare" is reserved for the button + diff content (HistoryPaneBody.tsx).
  5. Dead copy/branches (Devon Novel has multiple unresolved vulnerabilities #4/Feature: use blocknote to replace novel #5). Removed the unused history.diffShowUnchanged key and the unreachable singular UnchangedRun branch + its singular diffUnchangedRun string (the run only renders for count ≥3 → plural only).
  6. Tests (Quinn). Added locking tests: empty-doc-vs-empty → no change; trailing bare paragraph is not a change; and the MAX_WORD_TOKENS whole-block +/− fallback.

(Devon #6 — the "Computing changes…" flash on every Preview↔Compare toggle — left as-is: VersionDiff unmounts on toggle and refetches the live page, so a safe cache would risk staleness for marginal gain. Noted for follow-up.)

By-design (reviewer product-confirm flags)

  • Nested-container edits (a change inside a table/columns/group) surface as a single opaque "{Type} changed" chip on the container, not a drill-down diff — intentional at this granularity.
  • Word-diff granularity is unicode word/whitespace/punctuation tokens (grapheme-level, \p{L}\p{N}); it does not diff below the token.
  • Reorders surface as remove+add (block moves are not tracked — owner listed move-tracking as optional).

Verify

build:libs · ui typecheck · ui build · ui lint · ui vitest all green (observed exit 0). Vitest: 1099 passed (incl. 14 in blockDiff.test.ts). Captures driven through the web e2e harness (seed a page whose captured version differs from current; Compare in light + dark).

Gates: design (Devon) + code (Quinn) — both CLEAR WITH NITS; all nits applied in the fix commit on this branch.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KFk2T9k3p7ghCjdzMfkA5w

eliotlim and others added 3 commits July 14, 2026 18:18
Pure, tested engine: LCS block alignment by stable id (added/removed/
unchanged/changed) plus an intra-block word-level LCS diff for changed
text blocks. Non-text blocks (image/chart/table/embed) are reported
opaquely at block granularity. No diff dependency added — the word-LCS
is self-contained (same shape as textMerge.ts).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KFk2T9k3p7ghCjdzMfkA5w
Wire the reserved "Compare (coming soon)" placeholder into a working
Preview/Compare toggle. Compare renders VersionDiff — added blocks tint
green (+), removed red with strikethrough (−), changed text blocks show
inline word-level <ins>/<del>, non-text changes show old→new opaquely,
and long unchanged runs collapse. Markers + sr-only labels keep it
accessible (not colour-only). Current row stays preview-only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KFk2T9k3p7ghCjdzMfkA5w
…/dead-code cleanup (review)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KFk2T9k3p7ghCjdzMfkA5w
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app.book.pub Ready Ready Preview, Comment Jul 14, 2026 10:47am

Request Review

@eliotlim eliotlim merged commit a5c4a51 into main Jul 14, 2026
10 checks passed
@eliotlim eliotlim deleted the feat/pvh6-block-diff branch July 14, 2026 11:02
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.

1 participant