Fixes the compare-page refresh bug and removes a redundant per-page comparison fetch.
Bug: blank compare page on refresh
On mount the comparison basket is empty for one render before it hydrates from the URL. The fetch effect nulled comparisonData on that transient empty urnKey, and the one-shot "SSR already covers this set" skip then suppressed the refetch — so a reload showed the SSR-rendered data for a flash, then blanked.
Fix: the fetch effect is gated on isInitialized, never blanks on an empty set (the render already shows the empty state when nothing is selected), and decides fetch-vs-skip by whether it already holds each requested school's data (from SSR or a prior fetch). Bonus: removing a school no longer triggers a refetch.
A regression test reproduces the refresh scenario (SSR data + basket hydrating from empty) and fails against the old code.
Perf: dead per-page /api/compare fetch removed
useComparison() ran a useSWR('/api/compare?urns=…') whose result nothing consumed. Because Navigation and ComparisonToast mount it globally, that fetch fired on every page whenever the basket was non-empty — and duplicated ComparisonView's own fetch on the compare page itself. The hook now exposes basket state only.
Tests
78 frontend tests (1 new, verified to fail on the pre-fix code) · tsc clean.
Measured (staging)
/api/national-averages (the PR #36 mart fix) is now ~60ms (was ~250ms). The remaining /api/compare cost scales ~37ms per school — the per-URN supplementary DB round-trips — see the follow-up note in the PR discussion.
Fixes the compare-page refresh bug and removes a redundant per-page comparison fetch.
## Bug: blank compare page on refresh
On mount the comparison basket is empty for one render before it hydrates from the URL. The fetch effect nulled `comparisonData` on that transient empty `urnKey`, and the one-shot "SSR already covers this set" skip then suppressed the refetch — so a reload showed the SSR-rendered data for a flash, then blanked.
Fix: the fetch effect is gated on `isInitialized`, never blanks on an empty set (the render already shows the empty state when nothing is selected), and decides fetch-vs-skip by whether it already holds each requested school's data (from SSR or a prior fetch). Bonus: removing a school no longer triggers a refetch.
A regression test reproduces the refresh scenario (SSR data + basket hydrating from empty) and fails against the old code.
## Perf: dead per-page `/api/compare` fetch removed
`useComparison()` ran a `useSWR('/api/compare?urns=…')` whose result **nothing consumed**. Because `Navigation` and `ComparisonToast` mount it globally, that fetch fired on *every* page whenever the basket was non-empty — and duplicated `ComparisonView`'s own fetch on the compare page itself. The hook now exposes basket state only.
## Tests
78 frontend tests (1 new, verified to fail on the pre-fix code) · tsc clean.
## Measured (staging)
`/api/national-averages` (the PR #36 mart fix) is now ~60ms (was ~250ms). The remaining `/api/compare` cost scales ~37ms per school — the per-URN supplementary DB round-trips — see the follow-up note in the PR discussion.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB
Refresh bug: on mount the basket is empty for a beat before it hydrates
from the URL. The fetch effect nulled comparisonData on that transient
empty urnKey, then the one-shot 'SSR covers it' skip suppressed the
refetch — leaving the page blank on reload. The effect is now gated on
isInitialized, never blanks on empty (the render already shows the empty
state when nothing is selected), and decides fetch-vs-skip by whether it
already holds each requested school's data (SSR or a prior fetch).
Perf: useComparison ran a useSWR('/api/compare') whose result nothing
consumed — dead weight that fired on every page (Navigation + Toast are
global) whenever the basket was non-empty, and duplicated ComparisonView's
own fetch on the compare page. Removed; the hook now exposes basket state
only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB
tudor
merged commit abc03a0dd3 into main2026-07-14 21:39:47 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Fixes the compare-page refresh bug and removes a redundant per-page comparison fetch.
Bug: blank compare page on refresh
On mount the comparison basket is empty for one render before it hydrates from the URL. The fetch effect nulled
comparisonDataon that transient emptyurnKey, and the one-shot "SSR already covers this set" skip then suppressed the refetch — so a reload showed the SSR-rendered data for a flash, then blanked.Fix: the fetch effect is gated on
isInitialized, never blanks on an empty set (the render already shows the empty state when nothing is selected), and decides fetch-vs-skip by whether it already holds each requested school's data (from SSR or a prior fetch). Bonus: removing a school no longer triggers a refetch.A regression test reproduces the refresh scenario (SSR data + basket hydrating from empty) and fails against the old code.
Perf: dead per-page
/api/comparefetch removeduseComparison()ran auseSWR('/api/compare?urns=…')whose result nothing consumed. BecauseNavigationandComparisonToastmount it globally, that fetch fired on every page whenever the basket was non-empty — and duplicatedComparisonView's own fetch on the compare page itself. The hook now exposes basket state only.Tests
78 frontend tests (1 new, verified to fail on the pre-fix code) · tsc clean.
Measured (staging)
/api/national-averages(the PR #36 mart fix) is now ~60ms (was ~250ms). The remaining/api/comparecost scales ~37ms per school — the per-URN supplementary DB round-trips — see the follow-up note in the PR discussion.🤖 Generated with Claude Code
https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB
Refresh bug: on mount the basket is empty for a beat before it hydrates from the URL. The fetch effect nulled comparisonData on that transient empty urnKey, then the one-shot 'SSR covers it' skip suppressed the refetch — leaving the page blank on reload. The effect is now gated on isInitialized, never blanks on empty (the render already shows the empty state when nothing is selected), and decides fetch-vs-skip by whether it already holds each requested school's data (SSR or a prior fetch). Perf: useComparison ran a useSWR('/api/compare') whose result nothing consumed — dead weight that fired on every page (Navigation + Toast are global) whenever the basket was non-empty, and duplicated ComparisonView's own fetch on the compare page. Removed; the hook now exposes basket state only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB