diff --git a/apps/web/src/routes/(app)/events/+page.server.ts b/apps/web/src/routes/(app)/events/+page.server.ts index cb8d7c6..787b2bb 100644 --- a/apps/web/src/routes/(app)/events/+page.server.ts +++ b/apps/web/src/routes/(app)/events/+page.server.ts @@ -3,6 +3,7 @@ import { getServerClient, listDiscoverableEventsFromContrail } from '$lib/contrail'; +import { parseCursor, tagCursor } from '$lib/contrail/cursor'; import type { PageServerLoad } from './$types'; const PAGE_SIZE = 20; @@ -10,7 +11,9 @@ const PAGE_SIZE = 20; export const load: PageServerLoad = async ({ url, platform }) => { const client = getServerClient(platform!.env.DB); const now = new Date().toISOString(); - const cursor = url.searchParams.get('cursor') ?? undefined; + // Untag any inbound cursor (deep link) before handing the opaque keyset to D1; + // legacy untagged cursors pass through unchanged (om-7dbs). + const cursor = parseCursor(url.searchParams.get('cursor')).raw ?? undefined; const isPopular = url.searchParams.get('filter') !== 'all'; const response = await listDiscoverableEventsFromContrail(client, { @@ -33,6 +36,8 @@ export const load: PageServerLoad = async ({ url, platform }) => { return { events: flattenEventRecords(response.records), handles, - cursor: response.cursor ?? null + // Tag the first-page cursor so load-more routes back to this same D1 + // discoverable pipeline instead of re-inferring a backend (om-7dbs). + cursor: tagCursor('d1', response.cursor ?? null) }; }; diff --git a/apps/web/src/routes/(app)/p/[actor]/hosting/+page.server.ts b/apps/web/src/routes/(app)/p/[actor]/hosting/+page.server.ts index 22c63ed..6486d82 100644 --- a/apps/web/src/routes/(app)/p/[actor]/hosting/+page.server.ts +++ b/apps/web/src/routes/(app)/p/[actor]/hosting/+page.server.ts @@ -5,6 +5,7 @@ import { getServerClient, listAuthoredEventsFromContrail } from '$lib/contrail'; +import { parseCursor, tagCursor } from '$lib/contrail/cursor'; import { isActorIdentifier } from '@atcute/lexicons/syntax'; import { error } from '@sveltejs/kit'; @@ -19,7 +20,8 @@ export async function load({ params, url, platform }) { if (!did) throw error(404, 'Actor not found'); - const cursor = url.searchParams.get('cursor') ?? undefined; + // Untag any inbound cursor before the D1 read; legacy untagged passes through. + const cursor = parseCursor(url.searchParams.get('cursor')).raw ?? undefined; const now = new Date().toISOString(); const [profile, response] = await Promise.all([ @@ -37,7 +39,8 @@ export async function load({ params, url, platform }) { return { events: response ? flattenEventRecords(response.records) : [], - cursor: response?.cursor ?? null, + // Tag so load-more stays on this D1 authored pipeline (om-7dbs). + cursor: tagCursor('d1', response?.cursor ?? null), actorProfile: profile, actor, actorDid: did diff --git a/apps/web/src/routes/(app)/p/[actor]/past-events/+page.server.ts b/apps/web/src/routes/(app)/p/[actor]/past-events/+page.server.ts index 9e08c64..56549b0 100644 --- a/apps/web/src/routes/(app)/p/[actor]/past-events/+page.server.ts +++ b/apps/web/src/routes/(app)/p/[actor]/past-events/+page.server.ts @@ -5,6 +5,7 @@ import { getServerClient, listAuthoredEventsFromContrail } from '$lib/contrail'; +import { parseCursor, tagCursor } from '$lib/contrail/cursor'; import { isActorIdentifier } from '@atcute/lexicons/syntax'; import { error } from '@sveltejs/kit'; @@ -19,7 +20,8 @@ export async function load({ params, url, platform }) { if (!did) throw error(404, 'Actor not found'); - const cursor = url.searchParams.get('cursor') ?? undefined; + // Untag any inbound cursor before the D1 read; legacy untagged passes through. + const cursor = parseCursor(url.searchParams.get('cursor')).raw ?? undefined; const now = new Date().toISOString(); const [profile, response] = await Promise.all([ @@ -42,7 +44,8 @@ export async function load({ params, url, platform }) { return { events, - cursor: response?.cursor ?? null, + // Tag so load-more stays on this D1 authored pipeline (om-7dbs). + cursor: tagCursor('d1', response?.cursor ?? null), actorProfile: profile, actor, actorDid: did diff --git a/apps/web/src/routes/(app)/search/+page.server.ts b/apps/web/src/routes/(app)/search/+page.server.ts index a93e668..95eeb5e 100644 --- a/apps/web/src/routes/(app)/search/+page.server.ts +++ b/apps/web/src/routes/(app)/search/+page.server.ts @@ -50,13 +50,13 @@ export const load: PageServerLoad = async ({ url, platform }) => { return { events: flattenEventRecords(response.records), handles, - // The D1 fallback is first-batch-only; don't hand its cursor back. - // loadMoreEvents has no search backend here, so it paginates via - // listRecords, which applies neither the discoverable filter nor the - // startsAtMin this page uses — later pages would drift into past and - // non-discoverable events. (When a configured backend errored the cursor - // is also unusable: it's a D1 cursor but loadMoreEvents re-routes to Meili - // whenever a backend is configured.) Drop it in both cases. + // The D1 fallback is first-batch-only; don't hand its cursor back. Even + // with self-describing cursors (om-7dbs), the search fetchParams carry no + // `pipeline`, so a d1-tagged cursor would route load-more through plain + // listRecords — dropping the discoverable filter and startsAtMin this page + // applies — and later pages would drift into past and non-discoverable + // events. Re-enabling consistent D1 pagination here would mean threading the + // discoverable pipeline + filters through; deferred. Drop the cursor. cursor: null, query: q }; diff --git a/apps/web/src/routes/(app)/topics/[slug]/+page.server.ts b/apps/web/src/routes/(app)/topics/[slug]/+page.server.ts index 2b4ae58..e7f9628 100644 --- a/apps/web/src/routes/(app)/topics/[slug]/+page.server.ts +++ b/apps/web/src/routes/(app)/topics/[slug]/+page.server.ts @@ -41,13 +41,14 @@ export const load: PageServerLoad = async ({ params, platform }) => { topic, events: flattenEventRecords(response?.records ?? []), handles, - // First-batch-only, like the search page's D1 fallback. This D1 FTS read has - // no cursor loadMoreEvents can resume: with a search backend configured it - // re-routes to Meili (whose offset cursor is incompatible with this D1 - // cursor), and without one it paginates via listRecords (dropping the - // discoverable + startsAtMin filters this page relies on). Either way later - // pages would drift, so don't hand back a cursor. Deep topic pagination is - // tracked separately (om-47ak). + // First-batch-only, like the search page's D1 fallback. Self-describing + // cursors (om-7dbs) now stop this page's D1 cursor from being mis-consumed + // as a Meili offset, but they don't make it resumable here: this fetchParams + // contract carries no `pipeline`, so a d1-tagged cursor would still route + // load-more through plain listRecords, dropping the discoverable + + // startsAtMin filters this page relies on. So don't hand back a cursor. Deep + // topic pagination (threading the discoverable pipeline through) is tracked + // separately (om-47ak). cursor: null, query };