Skip to content

refactor(events): self-contained pagination envelope + backend resumer registry#61

Draft
tompscanlan wants to merge 5 commits into
flo-bit:mainfrom
tompscanlan:refactor/om-gvrh-cursor-envelope-registry
Draft

refactor(events): self-contained pagination envelope + backend resumer registry#61
tompscanlan wants to merge 5 commits into
flo-bit:mainfrom
tompscanlan:refactor/om-gvrh-cursor-envelope-registry

Conversation

@tompscanlan

Copy link
Copy Markdown
Collaborator

Follow-up to #57. That fix stopped D1/Meili cursors from being cross-consumed; this retires the machinery that made the mistake possible.

  • Continuation envelope: the load-more cursor is now a self-contained token (named query + backend tag + raw cursor) minted server-side and echoed back opaquely by the client. The fetchParams query-reconstruction path is gone.
  • Backend resumer registry: backend selection is a registry lookup (tag → resumer) instead of a hand-written conditional chain — adding a search backend means registering a resumer, not editing branches in runLoadMoreEvents.
  • Server-owned filters: the visibility filter no longer rides on a client-echoed string; each paginated page (events, search, topics, hosting, past-events) builds its envelope server-side, which also gives deep-linked pages consistent pagination.

Verification: pnpm -C apps/web check (0 errors) and pnpm -C apps/web test (258 passed) on top of current main after rebasing over #60; also deployed to a test worker and exercised live load-more across the events, search, topics, hosting, and past-events pages.

Add a self-describing base64url(JSON {v,q,args?,raw}) continuation envelope
alongside the existing tagCursor/parseCursor. The token names a server-side
query and wraps the opaque backend cursor; no filters bag. decodeCursor never
throws and returns null on malformed/non-JSON/wrong-shape/unknown-q/oversized
input, fail-safing every legacy meili:/d1: tag. rawForQuery enforces the
deep-link query-match rule. Keeps tagCursor/parseCursor for the search/near-me
offset path.
runLoadMoreEvents decodes the envelope and dispatches through a flat registry
keyed by query name (events/hosting/past-events/topic/search-d1/search-meili) —
no routeMeili/if-else, no pipeline switch. Every filter value is
server-authoritative in the registry entry; the client supplies none. The
plain unlisted-inclusive listRecords pipeline has no entry, so no decoded
envelope can reach it (a tampered cursor cannot widen visibility, by
construction). listEventsInput now reads only { cursor, q }; legacy extra
fields are dropped. Shared orQueryFromSlug helper added to topics.ts so page-1
and load-more derive the topic search identically.
Server loads build the first envelope from their own server-authoritative
filters (events/hosting/past-events/search/topics) and consume inbound ?cursor=
via rawForQuery (deep-link query-match). EventList drops the fetchParams prop +
echo loop and POSTs only { cursor } (plus the search term q on the search page).
search D1-fallback and topics now return a real search-d1 / topic envelope
instead of cursor:null, so both paginate beyond page 1 with discoverable +
startsAtMin intact — closing the earlier "results stop after the first batch"
gap. near-me and query.ts untouched.
@tompscanlan tompscanlan force-pushed the refactor/om-gvrh-cursor-envelope-registry branch from 298e967 to 38f0875 Compare July 8, 2026 11:55
…ference it

Move the load-more continuation model — the cross-backend bug it solves, the
self-describing envelope, the resumer registry, and the tamper-safety property —
into a new README "load-more pagination" section, and thin the design-essay
comments in cursor.ts / events-load-more.ts to point at it while keeping the
local invariants. Also sharpen the parseCursor note to separate its permanent
use (the Meilisearch offset codec for search + near-me) from the temporary
pre-envelope-cursor fail-safe, which can be dropped once those cursors drain.
Remove the events-load-more "maps profile handles" test (a trivial did->handle
mapping) and the pass-through assertions that only re-checked a mock's own
return value (toHaveLength on mocked output, a flattenEventRecords call count).
The security, per-query filter-construction, and route-branching tests — the
ones that would actually catch a regression — are untouched.
@tompscanlan tompscanlan force-pushed the refactor/om-gvrh-cursor-envelope-registry branch from bd1653f to 37d11a2 Compare July 8, 2026 13:41
@tompscanlan tompscanlan marked this pull request as draft July 8, 2026 14:21
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