Complete S7 priced-line nudge: freshness on a long-open page + reversible gate
REPORT-FIRST correction. This order's premise — "S7 is a shell that doesn't update per selection" — is not what the code does. pricedForSelection is a useMemo on [pricedIndex, selectedPlayer, stat] and setSelectedPlayer/setStat fire on every user pick, so the chips already update per selection, and the prior session's verification of that stands. The genuine gap was FRESHNESS: the snapshot fetch depended on [sport] only, so pricedIndex was fetched once per sport-change and never refreshed. The pricing cron re-prices at five UTC hours, so a scanner left open across a cron boundary surfaced hour-stale priced lines. That is the real defect, and the only one fixed. FRESHNESS. The fetch is now a refreshPriced callback re-run when the held snapshot is older than PRICED_STALE_MS (30s, matching the /api/snapshot cache) at the moment of use — on selection change and on window focus — so a long-open page never shows a stale line. Sport change still clears the index first, so the old sport's lines never flash. STALE-TAP was already safe and is unchanged: the scan submit re-fetches the live snapshot server-side, so a chip that's gone stale between render and tap either lands on a real triplet (still priced) or degrades to the honest empty state (rotated away) — proven in the prior session and re-confirmed here (an off-snapshot line returns no market and shows the empty state). REVERSIBLE GATE. The whole nudge sits behind one PRICED_NUDGE_ENABLED flag: false empties the surfaced set, so the scanner falls back to S6's link-only empty state with the chips gone. Shipping enabled only after the cases are proven this session; the flag is the instant revert lever. DISPLAY-LAYER ONLY. Only scan/page.tsx changed. GradeResultCard, PriceTriplet, gradeAdapter, valueState, both scan routes and the pure pricedLines helper are byte-identical — Scan A and the scan-submit resolution are untouched, and the change is independently revertible. Tests 3765 passed / 303 suites, web build exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
This commit is contained in:
@@ -82,3 +82,33 @@ describe('the empty state + surfacer are independently reversible', () => {
|
||||
expect(page).toMatch(/result\.book_odds == null \|\| result\.fair_odds == null/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Session 80 — freshness + reversible gate (scan page wiring)', () => {
|
||||
const fs = require('fs');
|
||||
const src = fs.readFileSync(require.resolve('../../web/src/app/scan/page.tsx'), 'utf8');
|
||||
|
||||
it('re-fetches on selection change when the held snapshot is stale', () => {
|
||||
// The Session-79 gap: the fetch depended on [sport] only, so a long-open
|
||||
// page showed hour-stale priced lines. Now a stale index re-fetches at use.
|
||||
expect(src).toMatch(/Date\.now\(\) - pricedFetchedAt > PRICED_STALE_MS/);
|
||||
expect(src).toMatch(/\}, \[selectedPlayer, stat\]\)/);
|
||||
});
|
||||
|
||||
it('also refreshes on window focus for a long-open page', () => {
|
||||
expect(src).toMatch(/addEventListener\('focus', onFocus\)/);
|
||||
});
|
||||
|
||||
it('STALE_MS matches the /api/snapshot cache (no over-fetching)', () => {
|
||||
expect(src).toMatch(/PRICED_STALE_MS = 30_000/);
|
||||
});
|
||||
|
||||
it('the whole nudge is behind ONE reversible gate flag', () => {
|
||||
expect(src).toMatch(/const PRICED_NUDGE_ENABLED = true/);
|
||||
// the surfaced set is empty when the gate is off → S6 link-only fallback
|
||||
expect(src).toMatch(/PRICED_NUDGE_ENABLED && pricedIndex && selectedPlayer/);
|
||||
});
|
||||
|
||||
it('clears the index on sport change (never shows the old sport lines)', () => {
|
||||
expect(src).toMatch(/setPricedIndex\(null\);\s*\n\s*setPricedFetchedAt\(0\);\s*\n\s*refreshPriced\(\);/);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user