Trade pair click-switch crashes to ErrorBoundary: workspace prefetch seeds a flat page into the infinite limit-book query key #354

Closed
opened 2026-06-10 02:21:06 +00:00 by Brouie · 9 comments
Brouie commented 2026-06-10 02:21:06 +00:00 (Migrated from gitlab.com)

Summary

Clicking a different pair in the /trade pair search reliably crashes the whole trade view to the app ErrorBoundary ("Something went wrong") once the workspace prefetch resolves. The selector and navigation themselves work — the crash is in the order-book panel render that follows the switch.

Mechanism (source + console-stack layer)

  • onPairChange (TradePage.tsx:314) calls prefetchTradePairWorkspace BEFORE navigate; tradePairPrefetch.ts:33-43 prefetches the limit book with queryClient.prefetchQuery under ['limitBookPage', pair, side] — storing the FLAT IndexerLimitBookPageResponse { side, orders, has_more, next_after_order_id }.
  • useLimitBookInfinite (useLimitBookInfinite.ts:10-11) consumes the SAME key as useInfiniteQuery, which requires InfiniteData { pages, pageParams }.
  • When BookSideColumn (OrderBookPanel.tsx:221) renders within the shared 10s staleTime, TanStack v5's internal getNextPageParam wrapper destructures pages from the flat object and pages.length throws (infiniteQueryBehavior.ts:136) — synchronously during render, so the q.isError guard and the q.data?.pages optional chain never run; it unwinds to the app-level ErrorBoundary.
  • Page loads and deep links do NOT crash: the hook's own infinite fetch starts first and the prefetch dedupes onto it. The click path fires the flat prefetch in the event handler, so it wins the race — which is why this only bites on click-switch.
  • fetchJson throws on non-OK (client.ts:44-46), so an HTTP error on the book request actually AVOIDS the crash — it needs the prefetch to succeed. (We also hit real indexer 429s in the same session; filing that separately — co-symptom, not the cause.)
  • Latent since 67c0ea2 (#180) added the flat prefetch against the already-infinite consumer key; 23ffb21 (#194) extracted the hook and kept the collision. CI stays green because tradePairPrefetch.test only asserts the fetchers fire, and the useLimitBookInfinite tests never mount the hook against a prefetch-seeded cache.

Repro

  1. /trade on LocalTerra, open the pair search, click any other pair.
  2. Workspace starts switching, then "Something went wrong". Console: TypeError: Cannot read properties of undefined (reading 'length') at getNextPageParam ... at useLimitBookInfinite (useLimitBookInfinite.ts:10) at BookSideColumn (OrderBookPanel.tsx:221).

Fix direction

  • tradePairPrefetch.ts:34: queryClient.prefetchInfiniteQuery (initialPageParam undefined, single page) so the seeded entry is { pages, pageParams }.
  • Regression test that mounts useLimitBookInfinite against a QueryClient seeded by prefetchTradePairWorkspace.
  • Optional: debounce the hover-intent fan-out — every option the pointer crosses fires 5 parallel workspace requests, which also feeds the rate-limit issue.

Relation to #350

Likely the missing piece of what totdking hit on VP-4: selection commits, then the workspace fails. The type+Enter default-revert documented on #350 is still a separate, additional defect in the same control.

Severity: blocker-class for trade UX — pair switching by click cannot complete.

cc @PlasticDigits

### Summary Clicking a different pair in the /trade pair search reliably crashes the whole trade view to the app ErrorBoundary ("Something went wrong") once the workspace prefetch resolves. The selector and navigation themselves work — the crash is in the order-book panel render that follows the switch. ### Mechanism (source + console-stack layer) - `onPairChange` (TradePage.tsx:314) calls `prefetchTradePairWorkspace` BEFORE navigate; tradePairPrefetch.ts:33-43 prefetches the limit book with `queryClient.prefetchQuery` under `['limitBookPage', pair, side]` — storing the FLAT `IndexerLimitBookPageResponse { side, orders, has_more, next_after_order_id }`. - `useLimitBookInfinite` (useLimitBookInfinite.ts:10-11) consumes the SAME key as `useInfiniteQuery`, which requires `InfiniteData { pages, pageParams }`. - When `BookSideColumn` (OrderBookPanel.tsx:221) renders within the shared 10s staleTime, TanStack v5's internal getNextPageParam wrapper destructures `pages` from the flat object and `pages.length` throws (`infiniteQueryBehavior.ts:136`) — synchronously during render, so the `q.isError` guard and the `q.data?.pages` optional chain never run; it unwinds to the app-level ErrorBoundary. - Page loads and deep links do NOT crash: the hook's own infinite fetch starts first and the prefetch dedupes onto it. The click path fires the flat prefetch in the event handler, so it wins the race — which is why this only bites on click-switch. - `fetchJson` throws on non-OK (client.ts:44-46), so an HTTP error on the book request actually AVOIDS the crash — it needs the prefetch to succeed. (We also hit real indexer 429s in the same session; filing that separately — co-symptom, not the cause.) - Latent since 67c0ea2 (#180) added the flat prefetch against the already-infinite consumer key; 23ffb21 (#194) extracted the hook and kept the collision. CI stays green because tradePairPrefetch.test only asserts the fetchers fire, and the useLimitBookInfinite tests never mount the hook against a prefetch-seeded cache. ### Repro 1. /trade on LocalTerra, open the pair search, click any other pair. 2. Workspace starts switching, then "Something went wrong". Console: `TypeError: Cannot read properties of undefined (reading 'length') at getNextPageParam ... at useLimitBookInfinite (useLimitBookInfinite.ts:10) at BookSideColumn (OrderBookPanel.tsx:221)`. ### Fix direction - tradePairPrefetch.ts:34: `queryClient.prefetchInfiniteQuery` (initialPageParam undefined, single page) so the seeded entry is `{ pages, pageParams }`. - Regression test that mounts useLimitBookInfinite against a QueryClient seeded by prefetchTradePairWorkspace. - Optional: debounce the hover-intent fan-out — every option the pointer crosses fires 5 parallel workspace requests, which also feeds the rate-limit issue. ### Relation to #350 Likely the missing piece of what totdking hit on VP-4: selection commits, then the workspace fails. The type+Enter default-revert documented on #350 is still a separate, additional defect in the same control. Severity: blocker-class for trade UX — pair switching by click cannot complete. cc @PlasticDigits
Brouie commented 2026-06-10 02:22:59 +00:00 (Migrated from gitlab.com)

mentioned in issue #350

mentioned in issue #350
Brouie commented 2026-06-10 02:31:10 +00:00 (Migrated from gitlab.com)

mentioned in issue #337

mentioned in issue #337
PlasticDigits commented 2026-06-10 03:26:56 +00:00 (Migrated from gitlab.com)

mentioned in commit 35e38e54b2

mentioned in commit 35e38e54b2937344bc6573a67619a8e6efd43c53
PlasticDigits commented 2026-06-10 03:26:59 +00:00 (Migrated from gitlab.com)

mentioned in merge request !851

mentioned in merge request !851
PlasticDigits commented 2026-06-10 03:27:03 +00:00 (Migrated from gitlab.com)

Implementation

MR: !851 (local1/354-impl-prefetch-infinite)

prefetchTradePairWorkspace now uses prefetchInfiniteQuery for limit-book pages so the seeded cache shape matches useLimitBookInfinite. Added regression tests and documented the invariant in skills/docs.

Verification checklist

  • On /trade, click a different pair in the pair search — no ErrorBoundary crash
  • Order book bids/asks render for the switched pair
  • Hover-intent prefetch on other pairs still works
  • make test-frontend — tradePairPrefetch*.test* and useLimitBookInfinite.test.tsx pass

Follow-ups

None — click-switch path for #350 depends on this landing first.

## Implementation MR: !851 (`local1/354-impl-prefetch-infinite`) `prefetchTradePairWorkspace` now uses `prefetchInfiniteQuery` for limit-book pages so the seeded cache shape matches `useLimitBookInfinite`. Added regression tests and documented the invariant in skills/docs. ### Verification checklist - [ ] On `/trade`, click a different pair in the pair search — no ErrorBoundary crash - [ ] Order book bids/asks render for the switched pair - [ ] Hover-intent prefetch on other pairs still works - [ ] `make test-frontend` — `tradePairPrefetch*.test*` and `useLimitBookInfinite.test.tsx` pass ### Follow-ups None — click-switch path for #350 depends on this landing first.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-10 05:31:20 +00:00
PlasticDigits commented 2026-06-10 05:31:23 +00:00 (Migrated from gitlab.com)

mentioned in commit 76072064a0

mentioned in commit 76072064a078f08b495d2e8e4c8826b74e2f5334
Brouie commented 2026-06-10 06:29:45 +00:00 (Migrated from gitlab.com)

Verified the merged fix (!851, commit 35e38e5) at source + unit-test layer on e198dcb. tradePairPrefetch now uses prefetchInfiniteQuery with the same initialPageParam/getNextPageParam shape as useLimitBookInfinite, so the shared ['limitBookPage',pair,side] key is seeded as proper InfiniteData {pages,pageParams} instead of a flat page — the consumer no longer reads pages.length off a flat object during render. Proved it: the two new regression tests pass on HEAD and FAIL 2/2 against the pre-fix source (checked out at 35e38e5^ in a worktree) — exactly the crash path. Live click-switch browser re-test is the laptop layer, in progress.

Verified the merged fix (!851, commit 35e38e5) at source + unit-test layer on e198dcb. tradePairPrefetch now uses prefetchInfiniteQuery with the same initialPageParam/getNextPageParam shape as useLimitBookInfinite, so the shared ['limitBookPage',pair,side] key is seeded as proper InfiniteData {pages,pageParams} instead of a flat page — the consumer no longer reads pages.length off a flat object during render. Proved it: the two new regression tests pass on HEAD and FAIL 2/2 against the pre-fix source (checked out at 35e38e5^ in a worktree) — exactly the crash path. Live click-switch browser re-test is the laptop layer, in progress.
Brouie commented 2026-06-10 06:51:01 +00:00 (Migrated from gitlab.com)

mentioned in issue #357

mentioned in issue #357
Brouie commented 2026-06-11 02:14:11 +00:00 (Migrated from gitlab.com)

mentioned in merge request !863

mentioned in merge request !863
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#354
No description provided.