@sensiblestats/widget-react 0.48.0 → 0.48.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @sensiblestats/widget-react
2
2
 
3
+ ## 0.48.2
4
+
5
+ ### Patch Changes
6
+
7
+ - A match that finished more than three and a half hours ago now shows FT and its score instead of its kickoff time and a dash. The Today tab stopped asking about a match once a fixed window after kickoff elapsed, so on a cold load the earliest of the day's matches never had a state at all.
8
+
9
+ ## 0.48.1
10
+
11
+ ### Patch Changes
12
+
13
+ - A live match no longer loses its minute and drop out of the live section just because its score has not changed recently. The per-match staleness check read a last-changed timestamp as if it were a last-polled one.
14
+
3
15
  ## 0.48.0
4
16
 
5
17
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -1535,56 +1535,22 @@ function writeSchedule(operatorId, localDate, payload, nowMs) {
1535
1535
  }
1536
1536
 
1537
1537
  // src/dueSet.ts
1538
- var WINDOW_MS = 3.5 * 36e5;
1538
+ var TERMINAL_PHASES = /* @__PURE__ */ new Set(["fullTime", "postponed"]);
1539
1539
  function dueMatchIds(groups, states, nowMs) {
1540
1540
  const due = /* @__PURE__ */ new Set();
1541
1541
  for (const group of groups) {
1542
1542
  for (const match of group.matches) {
1543
- const sinceKickoff = nowMs - match.kickoffUnix * 1e3;
1544
- if (sinceKickoff >= 0 && sinceKickoff <= WINDOW_MS) due.add(match.matchId);
1545
- if (states[match.matchId]?.phase === "live") due.add(match.matchId);
1543
+ if (nowMs - match.kickoffUnix * 1e3 < 0) continue;
1544
+ const phase = states[match.matchId]?.phase;
1545
+ if (phase !== void 0 && TERMINAL_PHASES.has(phase)) continue;
1546
+ due.add(match.matchId);
1546
1547
  }
1547
1548
  }
1548
1549
  return [...due];
1549
1550
  }
1550
1551
 
1551
- // src/liveClock.ts
1552
- var STALE_READING_MS = 5 * 6e4;
1553
- function parseMinute(label) {
1554
- if (!label) return null;
1555
- const plus = /^(\d+)\+(\d+)'$/.exec(label);
1556
- if (plus) return Number(plus[1]) + Number(plus[2]);
1557
- const plain = /^(\d+)'$/.exec(label);
1558
- return plain ? Number(plain[1]) : null;
1559
- }
1560
- function readingAgeMs(state, nowMs) {
1561
- if (!state?.stateAsOfUtc) return null;
1562
- const t = Date.parse(state.stateAsOfUtc);
1563
- return Number.isNaN(t) ? null : Math.max(0, nowMs - t);
1564
- }
1565
- function isReadingStale(state, nowMs) {
1566
- if (state?.isHalfTime) return false;
1567
- const age = readingAgeMs(state, nowMs);
1568
- return age !== null && age > STALE_READING_MS;
1569
- }
1570
- function displayMinute(state, nowMs) {
1571
- if (!state || state.phase !== "live") return null;
1572
- if (state.isHalfTime) return null;
1573
- if (isReadingStale(state, nowMs)) return null;
1574
- const base = parseMinute(state.minuteLabel);
1575
- if (base === null) return null;
1576
- const age = readingAgeMs(state, nowMs);
1577
- if (age === null) return state.minuteLabel ?? null;
1578
- const advanced = base + Math.floor(age / 6e4);
1579
- return `${advanced}'`;
1580
- }
1581
- function isTreatedLive(state, pollStale, nowMs) {
1582
- return state?.phase === "live" && !pollStale && !isReadingStale(state, nowMs);
1583
- }
1584
-
1585
1552
  // src/useMatchDiscovery.ts
1586
1553
  var POLL_MS = 6e4;
1587
- var CLOCK_MS = 1e4;
1588
1554
  var ROLLOVER_CHECK_MS = 6e4;
1589
1555
  var STALE_AFTER_FAILURES = 2;
1590
1556
  function localDateOf(now) {
@@ -1597,7 +1563,6 @@ function useMatchDiscovery(opts) {
1597
1563
  const [groups, setGroups] = (0, import_react4.useState)([]);
1598
1564
  const [states, setStates] = (0, import_react4.useState)({});
1599
1565
  const [failures, setFailures] = (0, import_react4.useState)(0);
1600
- const [nowMs, setNowMs] = (0, import_react4.useState)(() => Date.now());
1601
1566
  const [expanded, setExpanded] = (0, import_react4.useState)({});
1602
1567
  const [touched, setTouched] = (0, import_react4.useState)({});
1603
1568
  const groupsRef = (0, import_react4.useRef)(groups);
@@ -1675,21 +1640,16 @@ function useMatchDiscovery(opts) {
1675
1640
  }, POLL_MS);
1676
1641
  return () => clearInterval(id);
1677
1642
  }, [opts.isOpen, tick, groups]);
1678
- (0, import_react4.useEffect)(() => {
1679
- if (!opts.isOpen) return;
1680
- const id = setInterval(() => setNowMs(Date.now()), CLOCK_MS);
1681
- return () => clearInterval(id);
1682
- }, [opts.isOpen]);
1683
1643
  const stale = failures >= STALE_AFTER_FAILURES;
1684
1644
  const liveCount = (0, import_react4.useMemo)(
1685
- () => stale ? 0 : Object.values(states).filter((s) => s.phase === "live" && !isReadingStale(s, nowMs)).length,
1686
- [states, stale, nowMs]
1645
+ () => stale ? 0 : Object.values(states).filter((s) => s.phase === "live").length,
1646
+ [states, stale]
1687
1647
  );
1688
1648
  const toggleGroup = (0, import_react4.useCallback)((competitionId) => {
1689
1649
  setTouched((prev) => ({ ...prev, [competitionId]: true }));
1690
1650
  setExpanded((prev) => ({ ...prev, [competitionId]: !prev[competitionId] }));
1691
1651
  }, []);
1692
- return { groups, states, nowMs, liveCount, stale, expanded, toggleGroup, _tick: tick };
1652
+ return { groups, states, liveCount, stale, expanded, toggleGroup, _tick: tick };
1693
1653
  }
1694
1654
 
1695
1655
  // src/eventLogContext.ts
@@ -3047,7 +3007,7 @@ function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
3047
3007
  return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
3048
3008
  }
3049
3009
  function useLiveClock(minute, clockRunning, receivedAtMs) {
3050
- const [displayMinute2, setDisplayMinute] = (0, import_react19.useState)(
3010
+ const [displayMinute, setDisplayMinute] = (0, import_react19.useState)(
3051
3011
  () => computeDisplayMinute(minute, clockRunning, receivedAtMs)
3052
3012
  );
3053
3013
  (0, import_react19.useEffect)(() => {
@@ -3058,7 +3018,7 @@ function useLiveClock(minute, clockRunning, receivedAtMs) {
3058
3018
  }, TICK_MS);
3059
3019
  return () => window.clearInterval(id);
3060
3020
  }, [minute, clockRunning, receivedAtMs]);
3061
- return displayMinute2;
3021
+ return displayMinute;
3062
3022
  }
3063
3023
 
3064
3024
  // src/components/dashboard/LiveScoreCard.tsx
@@ -3090,7 +3050,7 @@ function LiveScoreCard({
3090
3050
  if (!seedRef.current || seedRef.current.key !== seedKey) {
3091
3051
  seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
3092
3052
  }
3093
- const displayMinute2 = useLiveClock(data.minute, data.clockRunning, seedRef.current.receivedAtMs);
3053
+ const displayMinute = useLiveClock(data.minute, data.clockRunning, seedRef.current.receivedAtMs);
3094
3054
  const minuteSuffix = data.minuteLabel.replace(/^\d+/, "");
3095
3055
  const scorers = scorersLine(goalEvents);
3096
3056
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "ss-w-fandash-card ss-w-fandash-live-score", children: [
@@ -3100,7 +3060,7 @@ function LiveScoreCard({
3100
3060
  /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-dot", "aria-hidden": "true" }),
3101
3061
  data.statusLabel
3102
3062
  ] }),
3103
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-minute", children: `${displayMinute2}${minuteSuffix}` }),
3063
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-minute", children: `${displayMinute}${minuteSuffix}` }),
3104
3064
  data.competition ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-competition", children: data.competition }) : null
3105
3065
  ] }),
3106
3066
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "ss-w-fandash-live-main", children: [
@@ -3782,11 +3742,10 @@ function Side({ name, crest }) {
3782
3742
  /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "ss-w-drow-nm", children: name })
3783
3743
  ] });
3784
3744
  }
3785
- function FixtureRow({ match, state, stale, nowMs, ui, monitor }) {
3786
- const readingStale = isReadingStale(state, nowMs);
3787
- const isLive = state?.phase === "live" && !stale && !readingStale;
3745
+ function FixtureRow({ match, state, stale, ui, monitor }) {
3746
+ const isLive = state?.phase === "live" && !stale;
3788
3747
  const hasScore = (state?.phase === "live" || state?.phase === "fullTime") && typeof state.homeScore === "number" && typeof state.awayScore === "number";
3789
- const ticked = displayMinute(state, nowMs);
3748
+ const ticked = state?.phase === "live" ? state.minuteLabel ?? null : null;
3790
3749
  const isFinished = state?.phase === "fullTime";
3791
3750
  const when = state?.isHalfTime && !stale ? ui.halfTime : isLive ? ticked ?? "\u2014" : state?.phase === "live" ? "\u2014" : isFinished ? ui.fullTime : kickoffLabel(match.kickoffUnix);
3792
3751
  const pin = { kind: "match", matchId: match.matchId };
@@ -3802,10 +3761,15 @@ function FixtureRow({ match, state, stale, nowMs, ui, monitor }) {
3802
3761
  ] });
3803
3762
  }
3804
3763
 
3764
+ // src/liveClock.ts
3765
+ function isTreatedLive(state, pollStale) {
3766
+ return state?.phase === "live" && !pollStale;
3767
+ }
3768
+
3805
3769
  // src/components/CompetitionGroup.tsx
3806
3770
  var import_jsx_runtime50 = require("react/jsx-runtime");
3807
- function CompetitionGroup({ group, open, onToggle, states, stale, nowMs, ui, monitor, disambiguate = false }) {
3808
- const own = group.matches.filter((m) => !isTreatedLive(states[m.matchId], stale, nowMs));
3771
+ function CompetitionGroup({ group, open, onToggle, states, stale, ui, monitor, disambiguate = false }) {
3772
+ const own = group.matches.filter((m) => !isTreatedLive(states[m.matchId], stale));
3809
3773
  const hasLive = own.length !== group.matches.length;
3810
3774
  if (own.length === 0) {
3811
3775
  return null;
@@ -3824,15 +3788,15 @@ function CompetitionGroup({ group, open, onToggle, states, stale, nowMs, ui, mon
3824
3788
  ] }),
3825
3789
  /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { className: "ss-w-grp-count", children: own.length })
3826
3790
  ] }),
3827
- open && /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: "ss-w-grp-body", children: own.map((m) => /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(FixtureRow, { match: m, state: states[m.matchId], stale, nowMs, ui, monitor }, m.matchId)) })
3791
+ open && /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: "ss-w-grp-body", children: own.map((m) => /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(FixtureRow, { match: m, state: states[m.matchId], stale, ui, monitor }, m.matchId)) })
3828
3792
  ] });
3829
3793
  }
3830
3794
 
3831
3795
  // src/components/TodayPanel.tsx
3832
3796
  var import_jsx_runtime51 = require("react/jsx-runtime");
3833
3797
  function TodayPanel({ discovery, ui, monitor }) {
3834
- const { groups, states, nowMs, stale, expanded, toggleGroup } = discovery;
3835
- const liveRows = stale ? [] : groups.flatMap((group) => group.matches.filter((m) => isTreatedLive(states[m.matchId], stale, nowMs)).map((match) => ({ match, rank: group.rank }))).sort((a, b) => a.rank - b.rank);
3798
+ const { groups, states, stale, expanded, toggleGroup } = discovery;
3799
+ const liveRows = stale ? [] : groups.flatMap((group) => group.matches.filter((m) => isTreatedLive(states[m.matchId], stale)).map((match) => ({ match, rank: group.rank }))).sort((a, b) => a.rank - b.rank);
3836
3800
  const nameCounts = groups.reduce((acc, g) => {
3837
3801
  acc[g.competitionName] = (acc[g.competitionName] ?? 0) + 1;
3838
3802
  return acc;
@@ -3850,7 +3814,6 @@ function TodayPanel({ discovery, ui, monitor }) {
3850
3814
  match,
3851
3815
  state: states[match.matchId],
3852
3816
  stale,
3853
- nowMs,
3854
3817
  ui,
3855
3818
  monitor
3856
3819
  },
@@ -3866,7 +3829,6 @@ function TodayPanel({ discovery, ui, monitor }) {
3866
3829
  disambiguate: (nameCounts[group.competitionName] ?? 0) > 1,
3867
3830
  states,
3868
3831
  stale,
3869
- nowMs,
3870
3832
  ui,
3871
3833
  monitor
3872
3834
  },