Document: loomworks-list-loading-cr-2-implementation-notes-v0_1
Version: v0.1
Status: complete — as-built record of CR-2's build
Date: 2026-06-27
CR: loomworks-list-loading-cr-2-frontend-load-more-v0_5 (final)
Engine contract: CR-1 merged at eeae9c2 / alembic 0096
Branches (built, not yet pushed at time of writing): engine list-loading-cr-2-page-size @ e5bbf05; OL list-loading-cr-2-frontend-load-more @ 376b098
The frontend half of list-loading: a reusable explicit load-more control consuming CR-1's keyset contract, applied to every list surface that exists today, plus per-surface page-size defaults and a voice-adjustable global override. Built across six steps, split per surface for blast-radius discipline. Two surfaces deferred to named engine dependencies.
| Step | Commit | What |
|---|---|---|
| 1 — engine setting | e5bbf05 (engine branch) | lists.page_size — nullable bounded [10,100] on the three-place pattern; shared numeric tune path made None-safe (zero regression). Engine suite 3041/0/63. |
| 2 — reusable spine | 5973ff8 | lib/pagination.ts + hooks/usePagedList.ts (append/prepend, dedup, caller-resolved pageSize) + components/lists/LoadMoreControl.tsx (explicit, has_more-gated, count shown). 12 isolation tests. |
| 3 — renders | 4eb517b | RenderingRoom on the spine. D-4 land-on-oldest (forward-only ASC keyset). 5 files / 28 tests. |
| 3 — Memory | d7fa2b6 | MemoryRoom: two usePagedList instances (held + settled) over one state-filtered endpoint (?state=held|committed). Confirm-act reloads both. 5 files / 25 tests. |
| 3 — engagements | — | DEFERRED — structural blocker (see below). |
| 4 — home lenses | e3f401d | needs_you + recent stop discarding next_cursor, wired to the spine; applySettingChange lists.page_size branch (the third settings place). |
| 5 — defaults + override | 5406c71 (with Step 4) | Per-surface defaults (needs_you 15, recent 40, Memory 15, Rendering 15) via useListsPageSize() ?? <default>; override uniformises + clears, test-pinned; store coerces garbage → null. |
| 6 — CenterPane | 20dcaf7 | conversation-history before → composite cursor; CenterPane adopts the shared spine (prepend mode); dedup guard kept as belt-and-braces (R2 now engine-handled). total_count surfaced against loadedCount not turns.length. |
| 6 follow-up — count suppress | 376b098 | LoadMoreControl gained showCount? (default true); CenterPane passes showCount={false} — conversation reads clean, the four list surfaces keep their count. |
Final OL suite: 79 files / 415 tests green. Final engine suite (Step 1 branch): 3041/0/63.
D-4 (renders direction) — land-on-oldest. The lean was land-newest/page-older. Build found the renders endpoint sorts created_at ASC and pages forward — a forward-only keyset cannot land on the newest page. So page 1 = oldest, load-more = newer. Matches what RenderingRoom showed before; no display change. Mechanics governed the lean.
D-3 (shared control) — extract, CenterPane adopts. Built the reusable control to the general list shape; CenterPane (the precedent it was extracted from) adopted it at Step 6 in prepend mode, keeping its dedup guard as a CenterPane-level concern. The precedent became the reusable thing.
Count line on conversation — suppressed. "Showing N of TOTAL" earns its place on lists (completeness is a real question); a conversation is a stream to read, so the count is suppressed there while kept on the four list surfaces.
CR-1 added the composite (created_at, id) cursor to close the boundary-tie skip the Axis 11 phase left open. CR-2 Step 6 is where it was proven on the surface: the new test shows load-earlier prepends older turns, dedups the boundary tie, and rides the real opaque composite cursor (&cursor=, never &before=, pinned by converse.test.ts). The dedup guard that used to do that work is now redundant, demoted to belt-and-braces. The R2 thread — open since the Axis 11 phase — is closed.
The home engagements list — cannot be paginated until an engine visibility='automatic' projection exists. The home "Your engagements" list is a client-side-derived zone: useHomeData fetches the whole /engagements set and classify() splits rows into zones (Personal, Shared/commons, Administrative-hidden, Yours) from per-row signals, needing the complete set. /engagements sorts DESC, so Personal/commons are oldest → would land on last pages → zones would mis-classify under pagination. No zone-scoped endpoint exists to page. Deferred; the home-list pagination is the waiting consumer of the visibility projection. 7 engagements today — no pressure.
The six roomless endpoints (considerations, compositions, shape-events, assertion-groups, manifestations, shapings) — no frontend consumer; their rooms are empty stubs. Paginate when the rooms are built. The reusable spine is ready, so each is a wire-up. Note: compositions uses item-key items (the Step-0 A outlier); the spine's item-key prop handles it.
usePagedList instances, no new primitive.lists.page_size override store is test-safe by design (caught bootstrap → null), so adding it touched no existing room/embedder mocks.total_count should render against the persisted depth (loadedCount), not the live array length, where a surface concatenates optimistic/local items (CenterPane) — otherwise the count flickers with the optimistic tail.LoadMoreControl's showCount defaults true; surfaces opt out, lists keep it.DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks list-loading — CR-2 implementation notes — v0.1 — 2026-06-27 As-built trace. CR-2 frontend load-more built at all buildable surfaces; engagements + six roomless endpoints deferred to named engine dependencies. Not a regression source for the remember-about-me confirmation-loop bug (separately scoped).