Skip to content

Discover responsive batching preview

Discover UI and responsive batching in codex/discovery-glass-preview. The user approved the interactive mock before release preparation. Verification below uses code, isolated tests, and DOM checks; no browser was opened.

Sizing policy

The clean Discover feed measures its grid width, resolved CSS column count, and scroll-owner height at request time. Before cards render, the loading skeleton supplies its tracks; unmeasurable/hidden grids use the same breakpoint fallback as the grid. Document scrolling uses window height, never the full document content height. Element scrolling uses the smaller of panel and window height.

Below 640px, request 10 cards. Larger layouts request one viewport of estimated rows plus two buffer rows, rounded to whole rows and bounded as follows:

Viewport / current gridBatch sizes
640–1023px / 3 columns12, 15, 18
1024–1279px / 5 columns20, 25, 30
1280px and up / 6 columns18, 24, 30

The shared row estimate is the existing grid estimate: card width × 4/3 for the cover plus 100px for text/spacing, with 16px column gaps. Actual resolved column counts take precedence over breakpoints. The 30-card cap bounds response/image work while providing up to six rows at five columns or five at six columns; the existing sentinel can continue loading longer viewports. Search, tag-filtered results, and other sorts retain their 24-card limit.

Continuity contract

  • The backend validates session.limit === request.limit. A successful cursor response therefore retains its request's size, even when resize occurred while it was pending. Appends, retries, and nonterminal refreshes keep that size.
  • Cursor-free baseline/starter requests measure again for the next page. Resizing does not change a React key, restart an effect, clear cards, or move the reading position.
  • New visits after exhaustion/expiry measure the current layout. An explicit successful refresh retains its existing behavior of moving to the new batch.
  • Offsets remain absolute: advance from the response offset (or requested offset) by delivered entry count, before deduplication or local exclusions. Never infer progress from the request limit or visible-card count. Server positions remain authoritative for attribution.
  • Checkpoints write version 2 with the successful request's pageSize. Valid version-1/12-card checkpoints remain resumable and migrate on the next successful response. A restored cursor uses its saved size; a restored offset uses the new layout but retains its saved offset and delivered IDs. Other version-1 sizes, missing/invalid version-2 sizes, and unknown versions are rejected. Existing scope, TTL, and ID bounds are unchanged.

Non-browser verification

From packages/app:

powershell
pnpm exec tsx --tsconfig tsconfig.app.json --test --test-timeout=120000 src/features/hub/discovery-layout.test.ts src/features/hub/discovery-visit.test.ts src/features/hub/discovery-grid.test.ts src/features/hub/rec-tab.test.ts src/lib/discovery-pagination.test.ts

Regression coverage includes responsive initial requests, resolved CSS tracks and real container width, panel/document height selection, in-flight resize, cursor retry/refresh invariance, baseline resize with retained DOM nodes and scroll, absolute offsets after deduplication, legacy/new checkpoint reloads, expired-cursor recovery, and resizing already-virtualized grids in both scroll modes. New pagination/checkpoint tests were first run against the fixed-size baseline: 21 failures, then passing after implementation.

Results: 72/72 targeted tests passed. The five grid tests also passed again after correcting a test-only readonly innerWidth assignment. Full pnpm typecheck passed (8 tasks), and full pnpm build passed (5 tasks; Vite reports chunk-size/mixed-import warnings). git diff --check passed for the owned tracked files. The separate edition-boundary check reports three pre-existing hosted imports in src/components/daily-supply-row.tsx; that file, its checker, and the export manifest are unchanged by this work.

Combined UI draft

The updated interest chooser retains all twelve category labels and the existing controller. Its scoped stylesheet matches the welcome screen's gold palette and frosted backdrop. Every choice has a small decorative line icon, a separate selected check and native aria-pressed state. Header, choices and guidance share one scrollable body; only the action row stays outside it, preserving access with enlarged text on small screens. Close and Skip remain usable during saving, and focus restoration still prevents scrolling.

Review fixes reserve a separate close-control row so scrolled choices cannot pass beneath its hit area, use the higher-contrast guidance color for the storage note, and reveal failures by scrolling only the dialog body to its error explanation. The existing save-failure regression was extended: it first failed with the body left at 80px, then passed after revealing the 900px content end. No page scroll or focus change is used for this reveal.

Combined chooser/controller/feed/layout/visit/grid/pagination run: 146/146 tests passed. The standalone interactive preview at output/discovery-preview/index.html bundles the actual chooser and CSS with fixture feed artwork. Its phone/tablet/laptop/large-desktop presets and five locale choices are review controls, not new product navigation. A separate DOM check verifies that the bundled component opens, offers all twelve choices/icons, saves a selection, reopens with it, and cancels. No local browser was opened or navigated for verification; responsive geometry was reviewed from CSS, not screenshots.

The user reviewed and approved the interactive mock before landing. Release preparation also fixes the existing edition-boundary failure: daily-supply-row is a hosted wallet component, so it is now classified consistently in the boundary checker and excluded from the open-source export, alongside daily-check-in-card. Its only consumer, credit-indicator, is already hosted-only. No wallet behavior or production setting changes are needed.

After syncing with main, the full CI export also caught the newly added music route importing the excluded hosted generation-storage helper from core. Its registration now lives in edition/hosted.ts, and its route is excluded from the local export. The hosted /api/music/* endpoints keep the same handlers and auth; the core edition no longer imports a route requiring hosted storage/billing. Generated-export assertions cover both boundary fixes and the new Discover stylesheet/layout helper.

The hosted billing tripwire also classified the new music transport as an unmetered caller. Its exact route owner already records usage and handles BYOK-aware deductions. The audit now recognizes this provider only after checking its sole importing owner, registered billing policy, usage log, provider-cost conversion, and deduction. Music call sites also count in the general audit; negative fixtures reject missing usage and aliased unregistered callers. This changes audit coverage, not charges or music handlers. Like the existing general audit, it is a static ownership contract, not proof that every error path records usage; music storage failures can still return before its usage log, a pre-existing music reconciliation issue outside this Discover change.