Skip to content

Commit 07abb37

Browse files
committed
fix(showcase): thread shared now + periodic staleness re-render in dashboard page
1 parent 9522099 commit 07abb37

1 file changed

Lines changed: 59 additions & 24 deletions

File tree

  • showcase/shell-dashboard/src/app

showcase/shell-dashboard/src/app/page.tsx

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Feature matrix: composable overlay-driven 2-tab layout.
33
// Matrix tab: overlay toggles control which visual layers render.
44
// Ops tab: probe status grid (unchanged from legacy).
5-
import { useCallback, useMemo } from "react";
5+
import { useCallback, useEffect, useMemo, useState } from "react";
66
import { FeatureGrid } from "@/components/feature-grid";
77
import type { CellContext } from "@/components/feature-grid";
88
import { StatusTab } from "@/components/status-tab";
@@ -45,6 +45,27 @@ export default function Page() {
4545

4646
const connection = allStatus.status;
4747

48+
// Wall-clock tick: forces staleness re-evaluation even when no SSE delta
49+
// arrives. Without this, a wedged pipeline (no new rows) would never
50+
// re-sample time, so a frozen-green cell could never downgrade to stale.
51+
// One interval, 60s cadence (well under the staleness window), cleared on
52+
// unmount. Bumping `tick` only invalidates the `now` memo below, so the
53+
// re-render is cheap — the same recompute that already runs on every SSE
54+
// delta.
55+
const [tick, setTick] = useState(0);
56+
useEffect(() => {
57+
const id = setInterval(() => setTick((t) => t + 1), 60_000);
58+
return () => clearInterval(id);
59+
}, []);
60+
61+
// Single reference time shared by EVERY staleness-deriving call below
62+
// (healthStats, depthDistribution, d6Stats, renderCell). Re-sampled when
63+
// live-status changes OR the 60s `tick` fires, so chips and stats agree on
64+
// which green rows are stale instead of each defaulting to its own
65+
// `Date.now()` that may have crossed a window boundary milliseconds later.
66+
// Mirrors the single-`now` discipline in cell-matrix.tsx.
67+
const now = useMemo(() => Date.now(), [liveStatus, tick]);
68+
4869
// R2-D.1: real probe wiring. `useProbes` polls the ops API every 10s and
4970
// feeds the schedule grid; `useTriggerProbe` POSTs to /trigger with the
5071
// operator token. If the token is unset, the trigger callback throws
@@ -85,12 +106,16 @@ export default function Page() {
85106
for (const cell of catalogData.cells) {
86107
if (cell.status !== "wired" || cell.feature === null) continue;
87108
// "wired" status implies supported — no need to check unsupported here
88-
const model = buildCellModel(liveStatus, {
89-
slug: cell.integration,
90-
featureId: cell.feature,
91-
isSupported: true,
92-
isWired: true,
93-
});
109+
const model = buildCellModel(
110+
liveStatus,
111+
{
112+
slug: cell.integration,
113+
featureId: cell.feature,
114+
isSupported: true,
115+
isWired: true,
116+
},
117+
now,
118+
);
94119
switch (model.chipColor) {
95120
case "green":
96121
green++;
@@ -107,7 +132,7 @@ export default function Page() {
107132
}
108133
}
109134
return { green, amber, red };
110-
}, [liveStatus]);
135+
}, [liveStatus, now]);
111136

112137
// Compute parity tier counts
113138
const parityStats = useMemo(() => {
@@ -163,17 +188,21 @@ export default function Page() {
163188
for (const cell of catalogData.cells) {
164189
if (cell.status !== "wired" || cell.feature === null) continue;
165190
// "wired" status implies supported — no need to check unsupported here
166-
const model = buildCellModel(liveStatus, {
167-
slug: cell.integration,
168-
featureId: cell.feature,
169-
isSupported: true,
170-
isWired: true,
171-
});
191+
const model = buildCellModel(
192+
liveStatus,
193+
{
194+
slug: cell.integration,
195+
featureId: cell.feature,
196+
isSupported: true,
197+
isWired: true,
198+
},
199+
now,
200+
);
172201
const key = `d${model.achievedDepth}` as keyof DepthDistribution;
173202
dist[key]++;
174203
}
175204
return dist;
176-
}, [liveStatus]);
205+
}, [liveStatus, now]);
177206

178207
// Compute D6 (parity-vs-reference) rollup counts across wired cells.
179208
const d6Stats = useMemo((): D6Stats => {
@@ -182,7 +211,9 @@ export default function Page() {
182211
let red = 0;
183212
for (const cell of catalogData.cells) {
184213
if (cell.status !== "wired" || cell.feature === null) continue;
185-
const state = resolveCell(liveStatus, cell.integration, cell.feature);
214+
const state = resolveCell(liveStatus, cell.integration, cell.feature, {
215+
now,
216+
});
186217
switch (state.d6.tone) {
187218
case "green":
188219
green++;
@@ -196,7 +227,7 @@ export default function Page() {
196227
}
197228
}
198229
return { green, gray, red };
199-
}, [liveStatus]);
230+
}, [liveStatus, now]);
200231

201232
// renderCell callback wrapping UnifiedCell + buildCellModel.
202233
// buildCellModel is the single source of truth for cell state — it handles
@@ -207,15 +238,19 @@ export default function Page() {
207238
ctx.integration.not_supported_features?.includes(ctx.feature.id) ??
208239
false
209240
);
210-
const model = buildCellModel(ctx.liveStatus, {
211-
slug: ctx.integration.slug,
212-
featureId: ctx.feature.id,
213-
isSupported,
214-
isWired: true, // renderCell only called when demo exists
215-
});
241+
const model = buildCellModel(
242+
ctx.liveStatus,
243+
{
244+
slug: ctx.integration.slug,
245+
featureId: ctx.feature.id,
246+
isSupported,
247+
isWired: true, // renderCell only called when demo exists
248+
},
249+
now,
250+
);
216251
return <UnifiedCell ctx={ctx} model={model} overlays={overlays} />;
217252
},
218-
[overlays],
253+
[overlays, now],
219254
);
220255

221256
return (

0 commit comments

Comments
 (0)