/* ═══════════════════════════════════════════════════════════════════
CALL UI · the pieces every desktop surface uses to show ONE Shadow call
(pt74, 2026-09-05) — moved here from pages/shadow/shadow/signals.jsx so
the trade page's Shadow Signal panel draws the same legs, the same
confidence bar and the same SIGNAL/PAPER pill as the Shadow page, from
the same rules. It had its own copies: a marker typed at 70%, a colour
ladder typed at 70/55, distances measured from the LIVE price, and no
word for a call the engine had filed as PAPER — one click from a real
order form.
Loaded as ../_fx/call-ui.jsx AFTER _kit/terminal.js (ST_num, ST_fmt,
ST_normPred) and BEFORE the page's own panels. Gated by BOTH
tools/check_d_signals.js and tools/check_d_tradesignal.js — a check
tagged `file: FX` mutates this file.
What each piece means is written above it. Read those before changing a
word: every sentence here is one a screen commits to.
═══════════════════════════════════════════════════════════════════ */
/* The numbers under ENTRY / NOW / TARGET / STOP, and what each is measured
FROM (pt72). Distances to the target and the stop are the CALL's own —
from its entry, the price at creation (`entry_price`) — never from the
live price: a call's bracket does not move because the tape did. With no
entry there is no distance ("—"), whatever the price now. The price now
gets its own leg, NOW, carrying the signed move since entry when both are
known, coloured by whether it moved the call's way. */
function ST_callLegs(p) {
const entry = ST_num(p.entry), price = ST_num(p.price), target = ST_num(p.target), stop = ST_num(p.stop);
const dist = (x) => (entry != null && entry !== 0 && x != null) ? Math.abs((x - entry) / entry * 100) : null;
const move = (entry != null && entry !== 0 && price != null) ? (price - entry) / entry * 100 : null;
return { entry, price, target, stop, tgtPct: dist(target), stopPct: dist(stop), move };
}
function ST_distPct(v, sign) { return v == null ? '—' : `${sign}${v.toFixed(1)}%`; }
function ST_movePct(v) { return v == null ? null : `${v >= 0 ? '▲' : '▼'} ${Math.abs(v).toFixed(2)}%`; }
function ST_moveColor(v, dir) {
if (v == null || v === 0 || !dir) return ST.ink3;
const toward = dir === 'LONG' ? v > 0 : v < 0; /* a SHORT wants the price DOWN */
return toward ? ST.green : ST.red;
}
function CallLegs({ L, dir, priceTs }) {
const noEntry = "the box did not send this call's entry price";
/* `current_price` carried no timestamp, so a tick the feed last wrote an
hour ago read as NOW (pt73). The router sends `price_ts` now; the leg
says on hover when its number was read, or says nothing. */
const read = priceTs != null ? ` · price read ${agoText(priceTs)}` : '';
return (
<>
{L.price != null && }
>
);
}
function agoText(ms) {
const s = Math.max(0, Math.round((Date.now() - ms) / 1000));
return s < 60 ? `${s}s ago` : s < 3600 ? `${Math.round(s / 60)}m ago` : `${(s / 3600).toFixed(1)}h ago`;
}
/* ── the confidence bar, drawn from the ENGINE's lines (pt73) ────────────
The marker used to sit at a typed 70 and the fill turned amber at a typed
65 — numbers that matched neither SHADOW.signal_threshold (the line that
files a call as SIGNAL rather than PAPER) nor the session ladder (the line
the governor trades from). Worse, the fill glowed amber over calls the
engine had ALREADY filed as PAPER: on 2026-09-05 every one of the 17 open
calls was paper — a gate (ev_gate, thesis, governor_veto, probation…) had
knocked each off the real path — and the page said "above threshold" about
calls the box had refused to alert on. Now the marker is `thresholds.signal`
from the payload or nothing, and the fill is amber only when the engine's
own `kind` says SIGNAL. */
function ConfBar({ conf, kind, thresholds, role, glow = false }) {
const pct = (v) => { const n = ST_num(v); return n == null ? null : (n <= 1 ? n * 100 : n); };
const thr = pct(thresholds && thresholds.signal);
const thrPct = thr == null ? null : Math.round(thr * 10) / 10;
const sessPct = pct(thresholds && thresholds.session);
const isSig = kind === 'signal';
const title = thrPct == null
? 'the box sent no threshold — no line is drawn'
: `Shadow files a call as SIGNAL from ${Math.round(thrPct)}% and as PAPER below it`
+ (sessPct != null ? ` · this session (${(thresholds && thresholds.session_name) || '—'}) trades nothing under ${Math.round(sessPct)}%` : '');
return (
{thrPct != null &&
}
{conf != null &&
}
);
}
/* The engine's verdict, in the engine's word (pt73). SIGNAL is alert-grade;
PAPER was kept off the real path by a named gate, and the hover names it —
the slug is the box's own (ev_gate, thesis, governor_veto, probation…),
never re-worded here. No kind → no pill: unknown is not "paper". */
function KindPill({ kind, why, role }) {
if (kind !== 'signal' && kind !== 'paper') return null;
const sig = kind === 'signal';
const c = sig ? ST.amber : ST.ink4;
const title = sig
? 'the box filed this call as a SIGNAL — alert-grade, above its own line after every gate'
: why === 'none'
? 'the box filed this call as PAPER — under its own SIGNAL line, no gate needed; recorded for the record, never alerted'
: why
? `the box filed this call as PAPER (gate: ${why}) — recorded for the record, never alerted`
: 'the box filed this call as PAPER — recorded for the record, never alerted; it sent no gate name';
return (
{sig ? 'SIGNAL' : 'PAPER'}
);
}
/* "· PAPER" in the OPEN SIGNALS title was typed (pt73): the engine has filed
296 all-time calls as SIGNAL, and one of them open would have sat under a
header calling it paper. Count the engine's own kinds; a call with no kind
is counted as unknown, folded into neither. */
/* The empty state's sentence (pt73). "No setup currently clears Shadow's
confidence threshold in this regime" was a RULE typed here — and a wrong
one: paper calls (below the signal line) ARE shown when they exist, so an
empty queue means nothing at or above the paper floor is open, not that
nothing clears the signal line. The numbers come from the payload, or the
sentence says less. */
function ThresholdLine({ thresholds, sym }) {
const pct = (v) => { const n = ST_num(v); return n == null ? null : Math.round(n <= 1 ? n * 100 : n); };
const pf = pct(thresholds && thresholds.paper_floor), sg = pct(thresholds && thresholds.signal);
/* `sym` names the coin on a per-coin surface (the trade page); without it
the sentence is the queue's. Either way the numbers are the payload's. */
const none = sym ? `No open call for ${sym} right now.` : 'No open call right now.';
if (pf == null || sg == null) return {none};
return (
{none} Shadow records a call from {`${pf}%`} confidence
and files it as a SIGNAL from {`${sg}%`}.
);
}
/* A leg whose number the box did not send reads "—" in the page's grey —
never "$0.00", never "$—" (pt72). */
function PriceLeg({ label, value, c, sub, subC, title }) {
const known = ST_num(value) != null;
return (
{label}
{known ? `$${ST_fmt.price(value)}` : '—'}
{sub && {sub}}
);
}
window.ST_callLegs = ST_callLegs;
window.ST_distPct = ST_distPct;
window.ST_movePct = ST_movePct;
window.ST_moveColor = ST_moveColor;
window.CallLegs = CallLegs;
window.agoText = agoText;
window.ConfBar = ConfBar;
window.KindPill = KindPill;
window.ThresholdLine = ThresholdLine;
window.PriceLeg = PriceLeg;