S6 (a1): display — the full picture under the grammar

- specs/ROW-GRAMMAR.md: the row grammar law (slot order, color law, mark
  law, mobile stacking, no-truncation), locked by tests/unit/rowGrammar.
  StatStrip violations fixed: MovementChip before the grade (market
  context before model output); ViabilityChips after the archetype
  (identity is one contiguous run).
- Line-movement sparklines: intradayRefreshService.trackHistory captures
  real {t,line} points per grade (seeded with the lock, deduped when
  flat, capped 24) inside the snapshot write-back; StatStrip.LineSparkline
  renders at >=3 points (green toward / amber against / dim flat).
- Last-10 dot strips: services/last10Dots (streaksService accessors) ->
  /api/snapshot/:sport attaches last10_dots from rosterlogs:{sport};
  StatStrip.DotStrip renders vs the LOCKED line, newest first.
- CLV distribution: getModelAggregate emits clv_distribution (7 signed
  buckets, outliers clamped) only past the centralized n>=20 gate;
  ledger MODEL header renders the bar strip.
- Global search: SearchModal (cmd-K via GlobalHosts + window.__search),
  players via /api/players/search per sport + static lib/teams.js
  (soccer deliberately absent); Nav search icon + Search first in the
  mobile More sheet. Explore tab untouched.
- Landing LCP: fade-up floored at opacity .6 (hero h1 contentful on
  first frame, S33 visible-floor rule) + IBM Plex Mono preload:false
  (4 decorative font files off the slow-4G critical path).

2654 -> 2698 tests (226 suites) green; web build exit 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kev
2026-07-11 20:08:24 -04:00
parent 1d46b446c9
commit d3637e7abd
26 changed files with 1408 additions and 25 deletions
+62 -1
View File
@@ -4,7 +4,7 @@
// real feed values; the refresh recaptures the close.
const { runIntradayRefresh, inSlateHours, __internals } = require('../../src/services/intradayRefreshService');
const { signedDelta } = __internals;
const { signedDelta, trackHistory, HISTORY_CAP } = __internals;
const NOW = '2026-07-11T20:00:00.000Z';
@@ -125,6 +125,67 @@ describe('runIntradayRefresh — directional handling', () => {
});
});
// S6 (A1 board) — sparkline fuel: {t, line} history capture on each grade's
// movement tracking, persisted inside the snapshot write-back (no new keys).
describe('trackHistory — real {t, line} points, seeded, deduped, capped', () => {
const LOCK_TS = '2026-07-11T14:00:00.000Z';
const g = (extra = {}) => ({ ...GRADE, gradedAt: { line: 0.5, odds: -115, timestamp: LOCK_TS }, ...extra });
test('first capture seeds the LOCKED line at its own timestamp, then the move', () => {
const h = trackHistory(g(), 1.5, NOW);
expect(h).toEqual([{ t: LOCK_TS, line: 0.5 }, { t: NOW, line: 1.5 }]);
});
test('a flat line never appends a duplicate point (a point means it MOVED)', () => {
const h = trackHistory(g(), 0.5, NOW); // current == locked
expect(h).toEqual([{ t: LOCK_TS, line: 0.5 }]);
const again = trackHistory(g({ history: h }), 0.5, '2026-07-11T21:00:00.000Z');
expect(again).toHaveLength(1);
});
test('an unparseable current line appends nothing (real points only)', () => {
expect(trackHistory(g(), null, NOW)).toBeUndefined();
const prior = [{ t: LOCK_TS, line: 0.5 }];
expect(trackHistory(g({ history: prior }), 'n/a', NOW)).toBe(prior);
});
test('capped at 24 points, newest kept', () => {
const prior = Array.from({ length: HISTORY_CAP }, (_, i) => ({ t: `t${i}`, line: i }));
const h = trackHistory(g({ history: prior }), 99, NOW);
expect(h).toHaveLength(HISTORY_CAP);
expect(h[HISTORY_CAP - 1]).toEqual({ t: NOW, line: 99 });
expect(h[0]).toEqual({ t: 't1', line: 1 }); // oldest point aged out
});
});
describe('runIntradayRefresh — history rides the snapshot write-back', () => {
test('a steam move persists history on the grade (locked point + current)', async () => {
const { deps, store } = harness({ grades: [GRADE], oddsProps: [prop(1.5)], analyzeResult: null });
await runIntradayRefresh('mlb', deps);
const snap = store.get('snapshot:mlb:latest');
expect(snap.grades[0].history).toEqual([
{ t: NOW, line: 0.5 }, // seeded from the lock (gradedAt.timestamp === NOW here)
{ t: NOW, line: 1.5 },
]);
// grades:{sport} carries the same enriched grades.
expect(store.get('grades:mlb').grades[0].history).toHaveLength(2);
});
test('a flat run keeps a single seeded point — the sparkline stays hidden (<3)', async () => {
const { deps, store } = harness({ grades: [GRADE], oddsProps: [prop(0.5)], analyzeResult: null });
await runIntradayRefresh('mlb', deps);
expect(store.get('snapshot:mlb:latest').grades[0].history).toEqual([{ t: NOW, line: 0.5 }]);
});
test('a prop gone from the feed leaves prior history untouched', async () => {
const withHist = { ...GRADE, stat_type: 'hits', history: [{ t: 'x', line: 1 }] };
const { deps, store } = harness({ grades: [withHist], oddsProps: [prop(1.5)], analyzeResult: null });
await runIntradayRefresh('mlb', deps);
const frozen = store.get('snapshot:mlb:latest').grades.find((g) => g.stat_type === 'hits');
expect(frozen.history).toEqual([{ t: 'x', line: 1 }]);
});
});
describe('inSlateHours — noonmidnight ET', () => {
test('3 PM ET yes, 4 AM ET no', () => {
expect(inSlateHours(new Date('2026-07-11T19:00:00Z'))).toBe(true); // 3 PM EDT