Files
beardrive/internal/webapp
820978cd76 fix(history): page the History API and view so old changes are reachable (BEA-46) (#99)
* fix(history): page the API so old changes stop being unreachable (BEA-46)

GET /api/p/<id>/history capped at ?n= and said nothing about what it was
hiding, so every change older than the cap was unreachable — a project's
early history sat in the journals and blob store with no way to display it.

The display order used to come from two mechanisms: a stable time sort over
a slice built in reverse-journal order, so the tie-break was implicit in the
construction and a cursor could not re-derive it. histLess makes it one
function — newest wall-clock first, ties in reverse journal.Less — used for
both the sort and the skip-past-cursor step, so paging cannot disagree with
the feed. The cursor is server-minted and opaque because it has to be:
HistoryEntry.time is formatted to whole seconds and carries no lamport/seq,
so a client-computed cursor would be lossy across same-second ops.

?n= alone returns exactly the entries it always did (the tie-break IS
reverse-Less); it just gains a next_cursor key when more exist. A cursor is
a position in an ordering, not a snapshot: an offline device pushing
mid-scroll lands ops mid-feed by timestamp and the reader sees them on
refresh — pinning would mean server state for the life of a scroll.

BenchmarkHistoryPage over 5000 ops: page 1 14.4ms, page 20 15.8ms — every
page re-lists and re-parses the journals, so the ceiling is gone but the
per-page work is not. No cache needed at this scale.

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

* fix(history): follow the cursor in the History view, with a Load more (BEA-46)

The view hard-coded n=200 and rendered whatever came back, so a project past
200 changes showed a list that simply stopped. useInfiniteQuery now follows
next_cursor at 100 a page, and the foot of the list says "Load more" while
older changes exist — a button, not an IntersectionObserver, so it is
keyboard-reachable and states out loud that there is more.

Pages accumulate into one array, which is what makes the rest free:
groupRuns already groups across the whole window (a run straddling a page
boundary becomes one card when its second page lands — verified live: 7
files on page 1, 12 after Load more) and prevBlob already returns undefined
past the end, so the oldest loaded row shows no diff base rather than
diffing against the wrong predecessor.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 18:09:30 +09:00
..