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" ;
66import { FeatureGrid } from "@/components/feature-grid" ;
77import type { CellContext } from "@/components/feature-grid" ;
88import { 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