/* ═══════════════════════════════════════════════════════════════════════════
GATE BAR — replaces the Market Clock (2026-07-30; audit cut #3 + idea #5).
The old strip was 1,549 lines of animated equity-hub clock faces for a
market that never closes. This one answers the question the dashboard
could not answer anywhere: CAN SHADOW ACT RIGHT NOW, AND WHAT STOPS IT?
Four cells, all tri-state — a failed read renders UNKNOWN, never a guess
(the invariant this codebase keeps re-learning):
1. KILL SWITCH /api/paper/summary → kill_switch
2. REAL MODE /api/real/status → enabled
3. SESSION client clock (mirrors session_manager's UTC ladder —
display only; the governor enforces server-side)
4. F1/F4 WINDOW /api/shadow/window → registered end date + n progress
IIFE-wrapped like every no-build screen file. Depends on kit globals
(ST, ST_fetch, ST_poll).
═══════════════════════════════════════════════════════════════════════ */
(() => {
const SESSIONS = [
/* [fromHourUTC, toHourUTC, name, threshold, color-key] — session_manager's ladder */
[22, 24, 'DEAD ZONE', 0.65, 'ink3'],
[0, 7, 'ASIAN', 0.55, 'green'],
[7, 13, 'EU', 0.60, 'amber'],
[13, 17, 'EU/US OVERLAP', 0.75, 'red'],
[17, 22, 'US', 0.60, 'amber'],
];
function sessionNow() {
const h = new Date().getUTCHours();
for (const [a, b, name, thr, ck] of SESSIONS)
if (h >= a && h < b) return { name, thr, ck };
return { name: '?', thr: null, ck: 'ink3' };
}
function Cell({ label, value, color, sub }) {
return (
{label}
{value}
{sub && {sub}}
);
}
function GateBar() {
const [kill, setKill] = React.useState(undefined); /* undefined=loading, null=UNKNOWN, bool=known */
const [real, setReal] = React.useState(undefined);
const [win, setWin] = React.useState(undefined);
const [, tick] = React.useState(0); /* minute tick re-derives the session */
React.useEffect(() => {
const load = () => {
ST_fetch('/paper/summary').then(d => setKill(!!d.kill_switch)).catch(() => setKill(null));
ST_fetch('/real/status').then(d => setReal(!!d.enabled)).catch(() => setReal(null));
ST_fetch('/shadow/window').then(setWin).catch(() => setWin(null));
};
load();
const id = ST_poll(load, 30000);
const mi = setInterval(() => tick(t => t + 1), 60000); /* clock only, no backend */
return () => { clearInterval(id); clearInterval(mi); };
}, []);
const s = sessionNow();
const killVal = kill === undefined ? '…' : kill === null ? 'UNKNOWN' : kill ? 'HALTED' : 'ENTRIES OPEN';
const killCol = kill === undefined ? ST.ink4 : kill === null ? ST.amber : kill ? ST.red : ST.green;
const realVal = real === undefined ? '…' : real === null ? 'UNKNOWN' : real ? 'ARMED' : 'OFF';
const realCol = real === undefined ? ST.ink4 : real === null ? ST.amber : real ? ST.red : ST.ink3;
let winVal = '…', winSub = null, winCol = ST.ink3;
if (win === null) { winVal = 'UNKNOWN'; winCol = ST.amber; }
else if (win) {
const end = win.registered && win.registered.end_date;
const days = end ? Math.max(0, Math.ceil((new Date(end + 'T00:00:00Z') - Date.now()) / 86400000)) : null;
const m = win.measured || {};
winVal = days != null ? `${days}d left` : '—';
winSub = (m.n_resolved != null && win.registered && win.registered.n_target)
? `${m.n_resolved}/${win.registered.n_target} resolved` : null;
winCol = ST.ink2;
}
return (
|
|
|
|
can Shadow act right now? — every cell UNKNOWN before it guesses
);
}
window.GateBar = GateBar;
})();