| E subprocess.CalledProcessError: Command '['node', '-e', 'const _CH_STATE_WORD = /(^|_)(oversold|overbought|stretched|extreme)(_|$)/;\nconst _CH_NAMES_DIR = /(^|_)(up|down|long|short|bull|bear|buy|sell)(_|$)/;\nfunction _chShort(s, n){ s = String(s==null?\'\':s); return s.length>n ? s.slice(0,n-1)+\'…\' : s; }\nfunction _chFillsFor(rows, events){\n const mine = new Set((rows||[]).map(r => r && r.coid).filter(Boolean).map(String));\n if(!mine.size) return [];\n return (events||[]).filter(e => e && e.type===\'trade\' && e.coid && mine.has(String(e.coid)));\n }\nfunction _chExec(dRows, fills){\n if(fills && fills.length){\n const f = fills[0];\n const qty = fills.reduce((a,x)=>a+(+x.qty||0),0);\n return { state:\'fired\', label:`FIRED ${f.symbol||\'\'}`.trim(),\n facts:`${qty||f.qty||\'\'} @ $${f.price!=null?f.price:\'—\'}${fills.length>1?` · ${fills.length} fills`:\'\'}`, dead:false };\n }\n const fired = (dRows||[]).find(r=>r.row_kind===\'execution\' && r.fill_price!=null);\n if(fired) return { state:\'fired\', label:\'FIRED\', facts:`${fired.shares||\'\'} @ $${fired.fill_price}`, dead:false };\n const vetoed = (dRows||[]).find(r=>r.row_kind===\'veto\' || r.row_kind===\'governor_demotion\');\n if(vetoed){\n const why = String(vetoed.engine_reason||(vetoed.demoted_by||[]).join(\'+\')||\'vetoed\');\n return /^llm:no_trade/.test(why)\n ? { state:\'declined\', label:\'no order\', facts:\'the reasoner declined\', dead:true }\n : { state:\'vetoed\', label:\'no order\', facts:_chShort(why,40), dead:true };\n }\n const skip = (dRows||[]).find(r=>r.row_kind===\'allowlist_skip\');\n if(skip) return { state:\'skipped\', label:\'no order\', facts:\'never consulted\', dead:true };\n const close = (dRows||[]).find(r=>r.row_kind===\'macro_silent_close\');\n if(close) return { state:\'manual\', label:\'MACRO CLOSE\', facts:`${close.exit_symbol||\'\'} ${close.exit_shares||\'\'}`.trim(), dead:false };\n return { state:\'skipped\', label:\'no order\', facts:\'chain ended before execution\', dead:true };\n }\nfunction _chDirPhrase(dir){\n const d = String(dir||\'\').toLowerCase();\n if(/^(long|up|buy|bull)$/.test(d)) return \'BUY / go LONG\';\n if(/^(short|down|sell|bear)$/.test(d)) return \'SELL / go SHORT\';\n return \'\'; // unknown direction — say nothing rather than guess\n }\nfunction _chSignalLabel(arrow, k, dir){\n const nice = String(k||\'\').replace(/_+/g,\' \').trim();\n const phrase = _chDirPhrase(dir);\n if(!phrase || _CH_NAMES_DIR.test(String(k||\'\'))) return `${arrow} ${nice}`;\n const m = String(k||\'\').match(_CH_STATE_WORD);\n const state = m ? m[2] : \'\';\n const base = (state ? String(k).replace(_CH_STATE_WORD, \'$1$3\') : String(k))\n .replace(/_+/g,\' \').trim() || nice;\n return `${arrow} ${base} — ${phrase}${state ? ` (was ${state})` : \'\'}`;\n }\nfunction _chSignal(et, kind, evs, dRows){\n // Never let an `engine_routed_*` row supply the signal text: its `detail` is a dumped python dict\n // — exactly the boilerplate wall the operator rejected.\n const evsClean = (evs||[]).filter(e => !/^engine_routed_/.test(String(e.type||\'\')));\n const lead = evsClean.find(e => /^(level_|rollover_)/.test(String(e.type||\'\'))) || evsClean[0] || {};\n const k = String(kind || lead.marker_kind || lead.type || \'event\').replace(/^level_/,\'\');\n const dir = (dRows||[]).map(r=>r.direction).find(Boolean) || \'\';\n // Whole tokens only: a loose /up/ matched "s-UP-port_break" and drew a bullish arrow on a breakdown.\n const hay = `${dir} ${k}`.toLowerCase();\n const isUp = /(^|[^a-z])(up|long|bull|reclaim|bounce)([^a-z]|$)/.test(hay) || /_reclaim|rollover_up|cross_up/.test(hay);\n const isDn = /(^|[^a-z])(down|short|bear|below)([^a-z]|$)/.test(hay) || /_break|breakdown|rollover_down|liquidation/.test(hay);\n const arrow = isUp && !isDn ? \'▲\' : (isDn && !isUp ? \'▼\' : (isUp ? \'▲\' : \'•\'));\n const cls = arrow===\'▲\' ? \'up\' : (arrow===\'▼\' ? \'down\' : \'\');\n let facts = lead.detail || (dRows||[]).map(r=>r.skip_reason_code).find(Boolean) || \'\';\n if(/^decision=|^\\{|reason=.*status=/.test(String(facts))) facts = \'\';\n return { label:_chSignalLabel(arrow, k, dir), cls, facts:String(facts), pattern:lead.label||\'\' };\n }\n\n // ---- stage 3: the two brains + the conclusion ---------------------------------------------------\n // THE RM CONTRACT (feature/rm-veto e9f5c7c25f): the decision row carries an additive `risk` key:\n // {tier:\'rm\'|\'mechanical-fallback\', forbidden[], rationale, slug, day_type, doc, doc_sha,\n // elapsed_s, why_fallback, readings[{name, available, sentence, detail{}}]}\n // `risk` is NULL when the tier did not run (switch off / test origin). That is a THIRD state and\n // the panel must show it differently from "ran and allowed" — only the null distinguishes them.\n function _chRisk(d){ return (d && typeof d.risk === \'object\') ? d.risk : null; }\n\n function _chDecision(dRows){\n const rows = dRows||[];\n const d = rows.find(r=>r.decision) || rows.find(r=>r.skip_reason_code) || rows[0] || {};\n const rv = _chRisk(d);\n const skip = rows.find(r=>r.skip_reason_code);\n const engine = String(d.engine_reason || rows.map(r=>r.engine_reason).find(Boolean) || \'\');\n const demoted = (d.demoted_by||[]);\n\n let trader;\n if(d.decision){\n const conf = (typeof d.consult_confidence===\'number\' && typeof d.confidence===\'number\')\n ? `${(d.consult_confidence*100).toFixed(0)}→${(d.confidence*100).toFixed(0)}%`\n : (typeof d.confidence===\'number\' ? `${(d.confidence*100).toFixed(0)}%` : \'\');\n const scale = (typeof d.scale===\'number\') ? ` ×${d.scale}` : \'\';\n trader = { text:`${d.decision} ${conf}${scale}`.trim(), cls:d.decision };\n } else trader = { text: skip ? \'not consulted\' : \'—\', cls:\'none\' };\n\n const isLlmNoTrade = /^llm:no_trade/.test(engine);\n let risk;\n if(rv){\n const blocked = (rv.forbidden||[]).length > 0;\n const tier = rv.tier === \'rm\' ? \'🧑\u200d⚖️ RM judge (LLM)\'\n : `mechanical fallback${rv.why_fallback?` — ${rv.why_fallback}`:\'\'}`;\n risk = { text: blocked ? `⛔ ${_chShort(rv.slug || rv.rationale, 30)}` : \'allow\',\n cls: blocked ? \'block\' : \'allow\', tier, ran:true, rv };\n } else if(demoted.length){\n risk = { text:`⛔ ${demoted.join(\'+\')}`, cls:\'block\', tier:\'mechanical guard\', ran:true };\n } else if(engine && !isLlmNoTrade && !/^allowlist/.test(engine) && d.fill_price==null && d.decision){\n risk = { text:`⛔ ${_chShort(engine,28)}`, cls:\'block\', tier:\'mechanical guard\', ran:true };\n } else if(d.decision){\n // No RM ruling recorded and no guard fired. Say "did not run" — NOT "allow". They are\n // different states and conflating them would claim a review that never happened.\n risk = { text:\'not run\', cls:\'none\', tier:\'risk tier did not run\', ran:false };\n } else risk = { text:\'—\', cls:\'none\', tier:\'risk tier did not run\', ran:false };\n\n let outcome;\n if(skip && !d.decision) outcome = { text:\'NOT CONSULTED — policy\', cls:\'none\' };\n else if(risk.cls===\'block\') outcome = { text:`VETOED — ${_chShort((rv&&(rv.slug||rv.rationale))||demoted.join(\'+\')||engine,26)}`, cls:\'vetoed\' };\n else if(isLlmNoTrade || /NO_TRADE/i.test(String(d.decision||\'\')))\n outcome = { text:\'DECLINED — no trade\', cls:\'declined\' };\n else if(d.decision) outcome = { text:\'APPROVED → execute\', cls:\'approved\' };\n else outcome = { text:\'—\', cls:\'none\' };\n\n return { trader, risk, outcome, row:d, rv,\n latency:(typeof d.consult_elapsed_s===\'number\') ? `${d.consult_elapsed_s.toFixed(0)}s` : \'\' };\n }\n\n function _chDecNone(){\n return { trader:{text:\'—\',cls:\'none\'}, risk:{text:\'—\',cls:\'none\',tier:\'risk tier did not run\',ran:false},\n outcome:{text:\'—\',cls:\'none\'}, row:{}, rv:null, latency:\'\' };\n }\n\n // ---- stage 4: what reached the BROKER, or where the chain died ---------------------------------\n function _chExec(dRows, fills){\n if(fills && fills.length){\n const f = fills[0];\n const qty = fills.reduce((a,x)=>a+(+x.qty||0),0);\n return { state:\'fired\', label:`FIRED ${f.symbol||\'\'}`.trim(),\n facts:`${qty||f.qty||\'\'} @ $${f.price!=null?f.price:\'—\'}${fills.length>1?` · ${fills.length} fills`:\'\'}`, dead:false };\n }\n const fired = (dRows||[]).find(r=>r.row_kind===\'execution\' && r.fill_price!=null);\n if(fired) return { state:\'fired\', label:\'FIRED\', facts:`${fired.shares||\'\'} @ $${fired.fill_price}`, dead:false };\n const vetoed = (dRows||[]).find(r=>r.row_kind===\'veto\' || r.row_kind===\'governor_demotion\');\n if(vetoed){\n const why = String(vetoed.engine_reason||(vetoed.demoted_by||[]).join(\'+\')||\'vetoed\');\n return /^llm:no_trade/.test(why)\n ? { state:\'declined\', label:\'no order\', facts:\'the reasoner declined\', dead:true }\n : { state:\'vetoed\', label:\'no order\', facts:_chShort(why,40), dead:true };\n }\n const skip = (dRows||[]).find(r=>r.row_kind===\'allowlist_skip\');\n if(skip) return { state:\'skipped\', label:\'no order\', facts:\'never consulted\', dead:true };\n const close = (dRows||[]).find(r=>r.row_kind===\'macro_silent_close\');\n if(close) return { state:\'manual\', label:\'MACRO CLOSE\', facts:`${close.exit_symbol||\'\'} ${close.exit_shares||\'\'}`.trim(), dead:false };\n return { state:\'skipped\', label:\'no order\', facts:\'chain ended before execution\', dead:true };\n }\n\n // ---- LIVE ATTENTION: is this chain still in flight? ---------------------------------------------\n // Only for TODAY and only for a YOUNG event: a chain that never terminated hours ago is not "in\n // progress", it is a chain that died silently, and calling that live is the lie this panel ends.\n function _chLive(c){\n if(!isToday(curDay)) return null;\n const p = new Date().toLocaleTimeString(\'en-US\',{timeZone:\'America/New_York\',hour12:false}).split(\':\').map(Number);\n const ageMin = ((p[0]*3600+p[1]*60+(p[2]||0)) - c.tsec) / 60;\n if(!(ageMin >= 0 && ageMin <= 15)) return null;\n const row = (c.dec && c.dec.row) || {};\n if(c.exe && (c.exe.state===\'fired\' || c.exe.state===\'manual\')) return null;\n if(row.skip_reason_code) return null;\n if(!row.decision) return \'decision\'; // consult running\n if(!row.route_status || row.route_phase===\'ordering\') return \'execution\'; // ordering in flight\n return null;\n }\n\n // ---- which fills belong to THIS chain — BY IDENTITY, never by time proximity -------------------\n // The old rule was "any trade within 180s after the onset", and proximity is not ownership. On\n // 2026-07-29 the operator\'s two manual TNA fills (09:45:13 buy, 09:51:43 sell) landed near five\n // unrelated onsets and were rendered as \'FIRED TNA\' on every one of them — including chains whose\n // own outcome was VETOED — rail_guard, DECLINED — no trade and allowlist_excluded. A chain cannot\n // be vetoed and fired at the same time; the column was reporting trades the chain never made.\n //\n // A chain\'s rows already carry the ONLY correct answer: the decisions payload resolves each route\'s\n // flip coid against AlpacaOrders server-side, so ``row.coid`` is the order this chain actually\n // placed. Matching that against the fill\'s own coid is the identity join, and three properties fall\n // straight out of it with no special-casing:\n // - a manual / protection fill (mktstate-bull, mktstate-exit, a genome stop) matches no engine\n // chain, stays unclaimed, and is picked up by the manual-chain loop below as its own\n // \'✋ direct order\' row — which is where the original spec always said it belonged;\n // - an engine chain shows a fill ONLY when its own route reached an executed terminal, because\n // that is the only way a coid gets onto its rows;\n // - a vetoed / declined / skipped chain has no coid at all, so its execution cell can never\n // show a fill. Pinned by tests/test_tide_wave_chain_fill_identity.py.\n // No coid-prefix list here on purpose: classifying manual orders by parsing their names would be a\n // second source of truth that drifts the day someone mints a new prefix.\n function _chFillsFor(rows, events){\n const mine = new Set((rows||[]).map(r => r && r.coid).filter(Boolean).map(String));\n if(!mine.size) return [];\n return (events||[]).filter(e => e && e.type===\'trade\' && e.coid && mine.has(String(e.coid)));\n }\n\n // ---- assemble every chain for the day ----------------------------------------------------------\n function _chBuild(events, cutoff){\n const dRows = _chain.decisions || [];\n const finite = isFinite(cutoff);\n const byEt = new Map(), evByEt = new Map();\n for(const r of dRows){ const et=r.event_et||\'\'; if(!byEt.has(et)) byEt.set(et,[]); byEt.get(et).push(r); }\n for(const e of (events||[])){ const et=String(e.et||\'\'); if(!evByEt.has(et)) evByEt.set(et,[]); evByEt.get(et).push(e); }\n const used = new Set(), chains = [];\n const _secs = et => { const p=String(et||\'\').split(\':\').map(Number); return (p[0]||0)*3600+(p[1]||0)*60+(p[2]||0); };\n\n for(const [et, rows] of byEt){\n const evs = evByEt.get(et) || [];\n evs.forEach(e => used.add(e));\n const fills = _chFillsFor(rows, events);\n fills.forEach(f => used.add(f));\n const kind = rows.map(r=>r.kind).find(Boolean) || \'\';\n chains.push({ et, tsec:_secs(et), sig:_chSignal(et, kind, evs, rows), dec:_chDecision(rows),\n exe:_chExec(rows, fills), accts:[...new Set(rows.map(r=>r.account).filter(Boolean))],\n lag:rows.map(r=>r.claim_lag_min).find(v=>typeof v===\'number\'), rows, fills });\n }\n for(const e of (events||[])){\n if(used.has(e)) continue;\n const et = String(e.et||\'\'), type = String(e.type||\'\');\n if(/^engine_routed_/.test(type)) continue;\n let exe, sig, dec = _chDecNone();\n if(type===\'trade\'){\n exe = { state:\'manual\', label:`FILL ${e.symbol||\'\'}`.trim(),\n facts:`${e.qty||\'\'} @ $${e.price!=null?e.price:\'—\'} · ${_chShort(e.strategy||e.detail||\'\',26)}`, dead:false };\n sig = { label:\'✋ direct order\', cls:\'\', facts:_chShort(e.detail||\'\',48), pattern:e.label||\'\' };\n } else if(/manual_/.test(type)){\n exe = { state:\'manual\', label:(e.label||\'MANUAL\'), facts:_chShort(e.detail||\'\',44), dead:false };\n sig = { label:\'✋ operator action\', cls:\'\', facts:\'\', pattern:e.label||\'\' };\n } else if(/stop_out|auto_exit|genome/.test(type)){\n exe = { state:\'fired\', label:(e.label||type).replace(/_/g,\' \'), facts:_chShort(e.detail||\'\',44), dead:false };\n sig = { label:\'🛡 protection\', cls:\'\', facts:_chShort(e.detail||\'\',48), pattern:e.label||\'\' };\n } else {\n const r = e.reasoning || null;\n sig = _chSignal(et, type, [e], []);\n if(r && r.decision){\n const declined = /no_trade/i.test(String(r.decision));\n const c2 = typeof r.confidence===\'number\' ? `${(r.confidence*100).toFixed(0)}%` : \'\';\n exe = declined ? { state:\'declined\', label:\'no order\', facts:\'the reasoner declined\', dead:true }\n : { state:\'skipped\', label:\'no order\', facts:\'decided, never routed\', dead:true };\n dec = { trader:{ text:`${r.decision} ${c2}`.trim(), cls:r.decision },\n risk:{ text:\'not run\', cls:\'none\', tier:\'risk tier did not run\', ran:false },\n outcome: declined ? {text:\'DECLINED — no trade\',cls:\'declined\'} : {text:\'APPROVED → execute\',cls:\'approved\'},\n row:{ rationale:r.rationale, kind:type, event_et:et }, rv:null,\n latency: typeof r.elapsed_s===\'number\' ? `${r.elapsed_s.toFixed(0)}s` : \'\' };\n } else {\n exe = { state:\'skipped\', label:\'no order\', facts:\'no decision recorded\', dead:true };\n }\n }\n chains.push({ et, tsec:_secs(et), sig, dec, exe, accts:e.account?[e.account]:[],\n lag:null, rows:[], fills:[] });\n }\n let out = chains;\n for(const c of out){\n c.live = _chLive(c);\n c.noise = (!c.live && c.exe.state===\'skipped\');\n }\n if(finite){\n const hh = new Date(Math.min(cutoff, replayHi())).toLocaleTimeString(\'en-US\',{timeZone:\'America/New_York\',hour12:false});\n const lim = _secs(hh);\n out = out.filter(x => x.tsec <= lim);\n }\n out.sort((a,b)=> _chain.desc ? (b.tsec-a.tsec) : (a.tsec-b.tsec));\n out.sort((a,b)=> (a.live?0:1) - (b.live?0:1)); // in-flight pinned to the top, order preserved\n return out;\n }\n\n // ---- INLINE DETAIL — grows the chart downward; no modal, ever ----------------------------------\n function _chDetail(c, i){\n const row = c.dec.row || {}, rv = c.dec.rv;\n const pills = [];\n if(c.accts.length) pills.push(c.accts.join(\', \'));\n if(c.dec.latency) pills.push(`consult ${c.dec.latency}`);\n if(typeof c.lag===\'number\') pills.push(`claim lag ${c.lag.toFixed(1)}m`);\n if((row.kinds||[]).length>1) pills.push(`stages: ${row.kinds.join(\' + \')}`);\n if(rv && typeof rv.elapsed_s===\'number\') pills.push(`risk ${rv.elapsed_s.toFixed(1)}s`);\n if(rv && rv.day_type) pills.push(`day: ${rv.day_type}`);\n let h = `<div class="tw-cdet">`;\n if(pills.length) h += `<div class="pills">`+pills.map(p=>`<span class="pill">${_chEsc(p)}</span>`).join(\'\')+`</div>`;\n h += `<div class="sec"><div class="sk">🧠 Trader — why we wanted this trade</div>`+\n `<div class="sv">${_chEsc(row.rationale || \'(no rationale recorded — the reasoner was not consulted for this event)\')}</div></div>`;\n // RISK — three distinct states: the RM judge ruled / a mechanical guard ruled / the tier never ran\n let riskBody;\n if(rv){\n const bits = [rv.rationale || \'(no sentence recorded)\'];\n if((rv.forbidden||[]).length) bits.push(`\\nsides forbidden: ${rv.forbidden.join(\', \')}`);\n if(rv.doc) bits.push(`profile: ${rv.doc}${rv.doc_sha?` @ ${rv.doc_sha}`:\'\'}`);\n if(rv.why_fallback) bits.push(`fell back to mechanical: ${rv.why_fallback}`);\n const reads = (rv.readings||[]).map(x=> x.available===false\n ? ` ${x.name}: measurement unavailable` : (x.sentence || ` ${x.name}`)).join(\'\\n\');\n if(reads) bits.push(`\\nreadings it weighed:\\n${reads}`);\n riskBody = bits.join(\'\\n\');\n } else {\n const bits = [];\n if(row.engine_reason) bits.push(`guard / reason: ${row.engine_reason}`);\n if((row.demoted_by||[]).length) bits.push(`demoted by: ${row.demoted_by.join(\', \')}`);\n for(const g of (row.governors||[])) bits.push(`${g.governor}: ${g.decision_in||\'?\'} → ${g.decision_out||\'?\'}${g.reason?(\' — \'+g.reason):\'\'}`);\n if(row.rail_guard && row.rail_guard.reason) bits.push(`rail_guard: ${row.rail_guard.reason}`);\n if(row.skip_reason_text) bits.push(row.skip_reason_text);\n if(row.route_status) bits.push(`route status: ${row.route_status}`);\n bits.push(bits.length ? \'\\n(mechanical guard chain — the RM judge did not rule on this one)\'\n : \'The risk tier did not run for this decision (switch off, or no consult happened).\');\n riskBody = bits.join(\'\\n\');\n }\n h += `<div class="sec"><div class="sk">🛡 Risk — ${_chEsc(c.dec.risk.tier)}</div><div class="sv">${_chEsc(riskBody)}</div></div>`;\n if(c.rows.length){\n h += `<div class="sec"><div class="sk">Per-account fate</div><div class="sv">`+\n _chEsc(c.rows.map(r=>`${r.account||\'—\'}: ${r.route_status||\'—\'}`+\n `${r.engine_reason?(\' · \'+r.engine_reason):\'\'}${r.fill_price!=null?(\' · filled $\'+r.fill_price):\'\'}`).join(\'\\n\'))+`</div></div>`;\n }\n if(c.fills.length){\n h += `<div class="sec"><div class="sk">Broker fills</div><div class="sv">`+\n _chEsc(c.fills.map(x=>`${x.et} ${x.side||\'\'} ${x.symbol||\'\'} ×${x.qty||\'\'} @ $${x.price} · ${x.strategy||\'\'}`).join(\'\\n\'))+`</div></div>`;\n }\n h += _chOverride(c, i);\n return h + `</div>`;\n }\n\n // ---- INLINE OVERRIDE — the operator\'s own judgment, on the chain, two taps --------------------\n // Same server plumbing as every other operator action (Phase 2 tide_veto_override →\n // run_manual_control → flip_account, MANUAL_ORIGIN, TideManualActions, compare-and-set idempotency,\n // ATR drift refusal, server-enforced window). This is a SECOND ENTRY POINT, never a second path.\n // SIZING: overriding to the brain\'s OWN vetoed/declined side uses that decision\'s computed scale;\n // forcing the OPPOSITE side is a fresh operator judgment and uses the manual-roll pct machinery.\n function _chOverride(c, i){\n const out = c.dec.outcome.cls;\n if(out!==\'vetoed\' && out!==\'declined\') return \'\'; // nothing to override\n if(c.exe.state===\'fired\' || c.exe.state===\'manual\') return \'\';\n const dl = (c.rows||[]).map(r=>r.override_deadline).find(Boolean);\n const already = (c.rows||[]).map(r=>r.operator_override).find(Boolean);\n if(already) return `<div class="sec"><div class="sk">Operator override</div><div class="sv">`+\n _chEsc(`already decided at ${already.at||\'?\'} by ${already.by||\'?\'} — ${already.status||\'\'}`)+`</div></div>`;\n // THREE distinct states, never conflated: the feature has not shipped / the window has closed /\n // the window is open. Saying "closed" when the server never offered a window at all would be a\n // guess dressed as a fact — the same class of lie this panel exists to end.\n if(!dl) return `<div class="sec"><div class="sk">Operator override</div><div class="sv">`+\n `not available — this build carries no override window for the chain.\\n`+\n `(the server stamps override_deadline once MARKET_TURN_VETO_OVERRIDE_ENABLED ships; until then `+\n `a veto is final and nothing here can place an order)</div></div>`;\n const open = Date.parse(dl) > Date.now();\n if(!open) return `<div class="sec"><div class="sk">Operator override</div><div class="sv">`+\n `the disapprove window has closed — the veto stands</div></div>`;\n const own = String(c.dec.trader.cls||\'\').toUpperCase(); // the side the brain wanted\n const mk = side => {\n const isOwn = side===own;\n const armedKey = _chKey(c)+\'|\'+side;\n const armed = _chain.armed === armedKey;\n const label = armed ? `CONFIRM ${side}?` : `OVERRIDE → ${side}`;\n const why = isOwn ? `execute the ${side} the brain wanted, at its own size (×${(c.dec.row||{}).scale||1})`\n : `force the OPPOSITE side — a fresh operator judgment, sized by the manual-roll pct`;\n return `<button class="${side.toLowerCase()}${armed?\' armed\':\'\'}" data-ovr="${side}" data-ci="${i}" title="${_chEsc(why)}">${label}</button>`;\n };\n const secs = Math.max(0, Math.round((Date.parse(dl)-Date.now())/1000));\n return `<div class="sec"><div class="sk">Operator override — ${_chEsc(own||\'—\')} was ${out}</div>`+\n `<div class="tw-ovr">${mk(\'LONG\')}${mk(\'SHORT\')}`+\n `<span class="note">window closes in ${secs}s · tap once to arm, again to confirm · `+\n `own side uses the decision\'s size, opposite side uses the manual-roll pct</span></div></div>`;\n }\n\n // ---- one chain -> DOM --------------------------------------------------------------------------\n function _chHtml(c, i){\n const acctTxt = c.accts.length>1 ? `${c.accts.length} acct` : (c.accts[0]||\'\');\n const lagTxt = (typeof c.lag===\'number\' && c.lag>=1) ? ` · ⏱+${c.lag.toFixed(1)}m` : \'\';\n const D = c.dec, open = _chain.expanded.has(_chKey(c));\n const liveCls = c.live ? \' is-live\' : \'\';\n const lv = s => (c.live===s ? \' live\' : \'\');\n return (\n `<div class="tw-chain-box is-${c.exe.state}${liveCls}" data-ci="${i}">`+\n `<div class="tw-chain" data-ci="${i}">`+\n `<div class="tw-blk b-time"><span class="k">time</span>`+\n `<span class="v">${c.live?\'<span class="tw-live-dot"></span>\':\'\'}${_chEsc(c.et)}</span>`+\n `<span class="s">${_chEsc(acctTxt)}</span></div>`+\n `<div class="tw-arw">→</div>`+\n `<div class="tw-blk b-signal"><span class="k">event / signal</span>`+\n `<span class="v ${c.sig.cls}">${_chEsc(c.sig.label)}</span>`+\n `<span class="s">${_chEsc(c.sig.facts)}</span></div>`+\n `<div class="tw-arw">→</div>`+\n `<div class="tw-blk b-decision${lv(\'decision\')}"><span class="k">decision</span>`+\n `<div class="tw-dsub"><span class="who">🧠 TRADER</span>`+\n `<span class="val ${D.trader.cls}">${_chEsc(D.trader.text)}</span>`+\n (D.latency?`<span class="who">${_chEsc(D.latency)}</span>`:\'\')+`</div>`+\n `<div class="tw-dsub"><span class="who">🛡 RISK</span>`+\n `<span class="val ${D.risk.cls}">${_chEsc(D.risk.text)}</span></div>`+\n `<div class="tw-dout ${D.outcome.cls}">${_chEsc(D.outcome.text)}</div></div>`+\n `<div class="tw-arw${c.exe.dead?\' dead\':\'\'}">${c.exe.dead?\'⊘\':\'→\'}</div>`+\n `<div class="tw-blk b-exec ${c.exe.state}${lv(\'execution\')}"><span class="k">execution</span>`+\n `<span class="v">${_chEsc(c.exe.label)}</span>`+\n `<span class="s">${_chEsc(c.exe.facts)}${_chEsc(lagTxt)}</span></div>`+\n `<div class="tw-blk" style="flex:0 0 auto;justify-content:center">`+\n `<span class="tw-info-i" data-ci="${i}" title="${open?\'Collapse\':\'Expand this chain — the trader\\\'s reasoning, the risk ruling, per-account fate, and the override controls\'}">${open?\'▴\':\'i\'}</span></div>`+\n `</div>`+\n (open ? _chDetail(c, i) : \'\')+\n `</div>`\n );\n }\n\n function renderChains(){\n const box = el(\'tw-events\'); if(!box) return;\n const all = _chBuild(window._twChainEvents || [], window._twChainCutoff);\n const shown = _chain.noise ? all : all.filter(c=>!c.noise);\n const hidden = all.length - shown.length;\n window._twChains = all;\n if(!all.length){\n box.innerHTML = \'<div class="tw-chain-empty">No signals recorded for this day.</div>\';\n } else {\n const idx = new Map(all.map((c,i)=>[c,i]));\n let h = \'<div class="tw-chain-wrap">\' + shown.map(c=>_chHtml(c, idx.get(c))).join(\'\');\n if(hidden>0 && !_chain.noise)\n h += `<div class="tw-chain-noisebar" id="tw-chain-shownoise">+ ${hidden} routine skip${hidden===1?\'\':\'s\'} / marker${hidden===1?\'\':\'s\'} hidden — show all</div>`;\n box.innerHTML = h + \'</div>\';\n const sn = el(\'tw-chain-shownoise\');\n if(sn) sn.onclick = () => { _chain.noise = true; renderChains(); };\n }\n const cnt = el(\'tw-evcount\');\n const nLive = all.filter(c=>c.live).length;\n if(cnt) cnt.textContent = `(${shown.length}${hidden>0?` +${hidden} quiet`:\'\'}${nLive?` · ${nLive} live`:\'\'})`;\n // DEEP LINK — `?open=HH:MM:SS` expands that signal\'s chain on load, so a decision can be linked\n // to directly ("look at 15:21:14") instead of described. Runs once, and only after the decisions\n // payload has arrived, so the link never opens a chain built from the event side alone.\n if(!window._twOpenDone && (_chain.decisions||[]).length){\n const want = new URLSearchParams(location.search).get(\'open\');\n if(want){\n const hit = all.find(c => c.et === want) || all.find(c => String(c.et).startsWith(want));\n if(hit){ window._twOpenDone = true; _chain.expanded.add(_chKey(hit)); renderChains(); }\n }\n }\n }\n\n // ---- the override POST (Phase 2 endpoint; per-account, each independently idempotent) ----------\n async function _chDoOverride(c, side){\n const targets = (c.rows||[]).filter(r => r.override_deadline && Date.parse(r.override_deadline) > Date.now());\n const list = targets.length ? targets : (c.rows||[]);\n const msgs = []; let ok = 0;\n for(const t of list){\n const p = new URLSearchParams();\n p.set(\'date\', curDay); p.set(\'event_et\', c.et); p.set(\'account\', t.account||\'\');\n p.set(\'side\', side);\n if (DEBUG_Q) p.set(\'debug\', DEBUG_Q);\n try{\n const r = await fetch(\'/api/tide-wave/veto-override?\'+p.toString(), {method:\'POST\'});\n const d = await r.json();\n if(d.ok) ok++;\n msgs.push(`${t.account||\'?\'}: ${d.message || d.error || (d.ok?\'ok\':\'refused\')}`);\n }catch(err){ msgs.push(`${t.account||\'?\'}: ${err}`); }\n }\n _chain.armed = null;\n alert(`override → ${side}\\n${ok}/${list.length} accepted\\n\\n`+msgs.join(\'\\n\'));\n loadDecisionsPanel().catch(()=>{});\n }\n\n async function loadDecisionsPanel(){\n try{\n const r = await fetch(decisionsApi(curDay));\n const d = await r.json();\n _chain.decisions = (d && d.events) || [];\n }catch(err){ _chain.decisions = []; }\n renderChains();\n }\n function stopPoll(){ if(pollTimer){ clearInterval(pollTimer); pollTimer=null; } }\n\n // ---- Camera: export the current TIDE-wave chart (overlays included) as a PNG ----\n // Chart.js paints on a TRANSPARENT canvas, so a naive canvas.toDataURL() yields a see-through\n // (renders black) PNG. Draw the chart onto an offscreen canvas pre-filled with the panel\n // background first → solid, theme-matched PNG. Uses the canvas\' device-pixel dimensions so the\n // export keeps full chart resolution and every active overlay (🌙 premarket band, prev-day S/R,\n // POC, ROC, state zones, trade markers, replay cursor) that the overlay plugin drew on-canvas.\n function captureChartPNG(){\n if(!chart){ return; }\n const src = chart.canvas;\n const off = document.createElement(\'canvas\');\n off.width = src.width; off.height = src.height;\n const octx = off.getContext(\'2d\');\n octx.fillStyle = \'#0e131c\'; // --panel (theme bg) → non-transparent PNG\n octx.fillRect(0, 0, off.width, off.height);\n octx.drawImage(src, 0, 0);\n const a = document.createElement(\'a\');\n a.href = off.toDataURL(\'image/png\');\n a.download = `tide-wave-${curDay || \'day\'}.png`;\n document.body.appendChild(a); a.click(); a.remove();\n }\n\n // wiring\n el(\'tw-date\').addEventListener(\'change\', e=>{ unpinEtfSim(); el(\'tw-etfsim-badge\').innerHTML=\'\'; load(e.target.value, true); });\n el(\'tw-prev\').addEventListener(\'click\', e=>{ const d=e.target.dataset.day; if(d){ unpinEtfSim(); el(\'tw-etfsim-badge\').innerHTML=\'\'; load(d, true); } });\n el(\'tw-next\').addEventListener(\'click\', e=>{ const d=e.target.dataset.day; if(d){ unpinEtfSim(); el(\'tw-etfsim-badge\').innerHTML=\'\'; load(d, true); } });\n el(\'tw-today\').addEventListener(\'click\', ()=>{ unpinEtfSim(); el(\'tw-etfsim-badge\').innerHTML=\'\'; load(\'\', true); });\n el(\'tw-rth\').addEventListener(\'click\', ()=>{ rthFocus = !rthFocus; applyCutoff(R.active?R.cutoff:Infinity); });\n el(\'tw-camera\').addEventListener(\'click\', captureChartPNG);\n el(\'tw-play\').addEventListener(\'click\', ()=> R.playing ? pause() : play());\n el(\'tw-reset\').addEventListener(\'click\', reset);\n el(\'tw-liveBtn\').addEventListener(\'click\', goLive);\n el(\'tw-etfsim-run\').addEventListener(\'click\', loadEtfSim);\n el(\'tw-etfsim-live\').addEventListener(\'click\', exitEtfSim);\n // Persisted-run picker (bug 371d487cd2): select a persisted SignalSimStudy for the day, or clear\n // the choice. The change handler either clears state (empty) or asks loadPersistedSimRun to\n // fetch + render the chosen run via the existing pinEtfSimResult envelope. The refresh\n // button re-fetches the day\'s run list, preserving the operator\'s current selection when it\n // survives. History is updated via history.replaceState so the choice is shareable via URL.\n if (el(\'tw-sim-run-picker\')) {\n el(\'tw-sim-run-picker\').addEventListener(\'change\', e=>{\n const v = e.target.value;\n if(!v){ persistedSimRunId=null; persistedSimPayload=null; updateSimRunUrl(curDay, null); return; }\n loadPersistedSimRun(v);\n });\n el(\'tw-sim-run-refresh\').addEventListener(\'click\', ()=> loadPersistedSimRuns(persistedSimRunId));\n }\n // engine toggle: ⚙️ Mechanic vs 🧠 LLM\n el(\'tw-engine-toggle\').addEventListener(\'click\', e=>{\n const b = e.target.closest(\'.tw-eng\'); if(!b) return;\n etfEngine = b.getAttribute(\'data-eng\');\n el(\'tw-eng-mech\').classList.toggle(\'active\', etfEngine===\'mechanic\');\n el(\'tw-eng-llm\').classList.toggle(\'active\', etfEngine===\'llm\');\n el(\'tw-etfsim-run\').textContent = etfEngine===\'llm\' ? \'🧠 LLM sim ▶\' : \'🎢 ETF sim ▶\';\n });\n // reveal-mode toggle: ▶ Pre-play (batch) vs 🔬 Step (trigger-by-trigger monitor). Orthogonal to engine.\n el(\'tw-sim-mode\').addEventListener(\'change\', e=>{ etfSimMode = e.target.value; });\n // 🧠 LLM auto-drive (SIM/display autonomy ONLY — never wired to live order execution). Confirm BOTH ways.\n function _reflectAutoDrive(){\n const b = el(\'tw-autodrive\'); if(!b) return;\n b.classList.toggle(\'on\', etfAutoDrive); b.classList.toggle(\'off\', !etfAutoDrive);\n b.textContent = etfAutoDrive ? \'🧠 auto-drive ON\' : \'🧠 auto-drive OFF\';\n }\n el(\'tw-autodrive\').addEventListener(\'click\', ()=>{\n if(etfAutoDrive){\n if(!confirm(\'Turn off LLM auto-drive? You will approve each decision manually.\')) return;\n etfAutoDrive = false;\n } else {\n if(!confirm(\'Re-enable LLM auto-drive? The LLM will act on its own for the rest of the day.\')) return;\n etfAutoDrive = true;\n }\n localStorage.setItem(\'tw_autodrive\', etfAutoDrive ? \'1\' : \'0\');\n _reflectAutoDrive();\n // if a step-monitor is live, reflect the new autonomy on its controls immediately\n if(_etfStep && !_etfStep.done){\n if(!etfAutoDrive) etfStepPause();\n const pb = el(\'tw-etfstep-play\'); if(pb){ pb.disabled = !etfAutoDrive; }\n }\n });\n _reflectAutoDrive();\n // step-monitor controls (Mode 2)\n el(\'tw-etfstep-play\').addEventListener(\'click\', ()=>{ if(_etfStep && _etfStep.playing) etfStepPause(); else etfStepPlay(); });\n el(\'tw-etfstep-next\').addEventListener(\'click\', ()=>etfStepNext());\n el(\'tw-etfstep-finish\').addEventListener(\'click\', finishEtfStep);\n el(\'tw-etfstep-dwell\').addEventListener(\'change\', e=>{ if(_etfStep) _etfStep.dwellMs = parseInt(e.target.value,10)||1500; });\n // operator manual entry (hermetic SIM overlay — never halts the LLM walk, never touches the broker)\n el(\'tw-etfstep-manlong\').addEventListener(\'click\', ()=>injectManualEntry(\'up\'));\n el(\'tw-etfstep-manshort\').addEventListener(\'click\', ()=>injectManualEntry(\'down\'));\n // per-account long/short config: click ON/OFF/def → POST to Redis (live source of truth)\n el(\'tw-cfg-body\').addEventListener(\'click\', e=>{\n const b = e.target.closest(\'.tw-flagbtn\'); if(!b) return;\n const acct = b.getAttribute(\'data-acct\'), side = b.getAttribute(\'data-side\'), v = b.getAttribute(\'data-v\');\n if(v===\'off\'){ if(!confirm(`Disable ${side.toUpperCase()} for ${acct}? The LIVE sleeve will stop opening ${side} legs for this account (flatten-to-cash instead) on the next rollover.`)) return; }\n setEtfFlag(acct, side, v);\n });\n loadEtfFlags();\n // per-account equity-% for the SIZED sleeve buttons — fetched once at boot (read-only, no broker) so\n // the confirm sheet + the button tooltips always quote the CURRENT settings, never a literal.\n loadManualSizing();\n\n // ── BROKER EXECUTION PAUSE (bug 7d6f6039f442fa13eae436f3f4998a9f5cbc417e) ──\n // One-click halt: flips PAUSE_BROKER_EXECUTION_REMAINING_DAY (Redis-backed, hot switch). When ON:\n // every broker-write path short-circuits to a logged SKIP; the model keeps thinking + deciding so\n // the dashboard can show what would-have-done. Auto-clears at 16:00 ET so it never bleeds into\n // tomorrow. The button is the operator\'s only path — no PM daemon writes back.\n let _pauseState = {paused: false, skip_count_today: 0};\n function renderPauseBtn(){\n const label = el(\'tw-pause-label\'); const btn = el(\'tw-pause-btn\');\n const stats = el(\'tw-pause-stats\');\n if(!label || !btn) return;\n if(_pauseState.paused){\n label.innerHTML = \'⏸️ <span style="color:#ff6f6f;font-weight:700">BROKER EXECUTION · PAUSED — observe only</span>\';\n btn.textContent = \'▶️ RESUME BROKER\'; btn.style.background=\'#3a1010\'; btn.style.borderColor=\'#ff6f6f\'; btn.style.color=\'#ff6f6f\';\n } else {\n label.innerHTML = \'🟢 <span style="color:#3ddc84;font-weight:700">BROKER EXECUTION · RUNNING</span>\';\n btn.textContent = \'⏸️ PAUSE BROKER\'; btn.style.background=\'#1f2a44\'; btn.style.borderColor=\'#3ddc84\'; btn.style.color=\'#3ddc84\';\n }\n stats.textContent = `skips today: ${_pauseState.skip_count_today || 0} · auto-clears at 16:00 ET`;\n }\n async function loadPauseState(){\n try{\n const p = new URLSearchParams(); if (DEBUG_Q) p.set(\'debug\', DEBUG_Q);\n const r = await fetch(\'/api/tide-wave/pause-broker?\'+p.toString());\n const d = await r.json();\n if(d.ok){ _pauseState = {paused: !!d.paused, skip_count_today: d.skip_count_today || 0}; renderPauseBtn(); }\n }catch(_){ /* keep the cached state — the button is idempotent */ }\n }\n async function flipPause(){\n const next = _pauseState.paused ? \'resume\' : \'pause\';\n if(next === \'pause\' && !confirm(\'PAUSE broker execution for the rest of the trading day? Every buy / sell / cover / flip / cancel will be short-circuited to a logged SKIP. The model keeps thinking so you can observe decisions without capital risk. Auto-clears at 16:00 ET.\')) return;\n try{\n const p = new URLSearchParams(); p.set(\'action\', next);\n if (DEBUG_Q) p.set(\'debug\', DEBUG_Q);\n const r = await fetch(\'/api/tide-wave/pause-broker?\'+p.toString(), {method:\'POST\'});\n const d = await r.json();\n if(d.ok){ _pauseState = {paused: !!d.paused, skip_count_today: d.skip_count_today || 0}; renderPauseBtn(); }\n else { alert(\'pause-broker error: \'+(d.error||\'?\')); }\n }catch(err){ alert(\'pause-broker request failed: \'+err); }\n }\n const _pauseBtn = el(\'tw-pause-btn\'); if(_pauseBtn) _pauseBtn.addEventListener(\'click\', flipPause);\n loadPauseState();\n // poll every 30s so the SKIP count + any cross-process flip is reflected live\n setInterval(loadPauseState, 30000);\n // 🧠 reasoned-onset ⓘ (incl. NO_TRADE) → open the LLM decision + rationale + facts/premarket it read\n el(\'tw-etfsim-decisions\').addEventListener(\'click\', e=>{\n const btn = e.target.closest(\'.tw-dec-info\'); if(!btn) return;\n const d = (window._twLlmDecs||[])[parseInt(btn.getAttribute(\'data-i\'),10)]; if(!d) return;\n const lab = d.trigger_kind===\'level\'\n ? \'EVAL-ONLY · \'+String(d.kind||\'level\').replace(/_/g,\' \') // level event: surfaced, not traded\n : (d.taken?\'TAKEN\':\'declined\')+\' · \'+(d.onset_dir===\'up\'?\'roll-up\':\'roll-down\');\n openReasonModal({decision:d.decision, confidence:d.confidence, scale:d.scale, rationale:d.rationale,\n struct_context:d.struct_context, story:d.story}, {label:lab, et:(d.et||\'\')});\n });\n // per-trade ⓘ info button → open the LLM reasoning + CAUSAL struct-context it saw\n el(\'tw-etf-trades\').addEventListener(\'click\', e=>{\n const btn = e.target.closest(\'.tw-etf-info\'); if(!btn) return;\n e.stopPropagation(); // don\'t also open the row\'s price chart\n const i = parseInt(btn.getAttribute(\'data-i\'),10);\n const r = (window._twEtfRows && window._twEtfRows[i]) || null;\n if(!r) return;\n openReasonModal({decision:r.llm_decision, confidence:r.llm_confidence, scale:r.llm_scale,\n rationale:r.llm_rationale, struct_context:r.struct_context, story:r.story},\n {label:(r.account||\'\'), et:(r.entry_et||\'\')});\n });\n el(\'tw-reason-run\').addEventListener(\'click\', loadTideReason);\n // PER-ACCOUNT PICKER for every sleeve trade/marker button (operator 2026-07-24: "allow manual roll\n // down or up on a specific account... current is for all, i would like from UI to do it per account").\n // \'all\' (default) or one nick; the backend ?account= param (status.py api_tide_wave_manual) validates\n // the nick and scopes run_manual_control\'s fan-out. Cancel (Esc / backdrop / Cancel) aborts without a\n // POST. Locks stay fleet-wide on purpose (they are the operator\'s account-wide veto; a per-account\n // lock is a different feature, not this one).\n //\n // The scope now comes from a TAP SHEET, not window.prompt (operator 2026-07-30): the native prompt\n // required TYPING a nick — unusable one-handed on a phone — and showed no size. The sheet lists each\n // sleeve account with the % of equity the action commits (backend-sourced) BEFORE the tap. The\n // subsequent manualControl() confirm is UNCHANGED, so every action keeps at least as many\n // deliberate steps as it had, and the blast-radius warning is still the last thing before the POST.\n const pickAccount = async (action, label, danger)=>{\n const acct = await askManualScope(action, label, danger);\n if(!acct) return; // cancelled\n const s = manualSizeFor(action, acct); // one-account pick → name the size in the confirm\n const sizeNote = s ? (\' · \'+_pctTxt(s.x1_pct)+\' of equity\') : \'\';\n manualControl(action, label+\' · account=\'+acct+sizeNote, 1, false, acct);\n };\n el(\'tw-manual-exit\').addEventListener(\'click\', ()=>pickAccount(\'exit\', \'⛔ EXIT → cash\', true));\n el(\'tw-manual-up\').addEventListener(\'click\', ()=>pickAccount(\'roll_up\', \'△ MANUAL ROLL-UP → bull\'));\n el(\'tw-manual-down\').addEventListener(\'click\', ()=>pickAccount(\'roll_down\', \'▽ MANUAL ROLL-DOWN → bear\', true));\n el(\'tw-manual-boost\').addEventListener(\'click\', ()=>pickAccount(\'boost\', \'➕ EXECUTION BOOST\'));\n el(\'tw-manual-trim\').addEventListener(\'click\', ()=>pickAccount(\'trim\', \'➖ EXECUTION TRIM\', true));\n el(\'tw-manual-lock\').addEventListener(\'click\', ()=>manualControl(\'lock_all\', \'🔒 INTRADAY LOCK → sleeve cannot sell intraday (EOD flatten still runs)\'));\n el(\'tw-manual-unlock\').addEventListener(\'click\', ()=>manualControl(\'unlock_all\', \'🔓 INTRADAY UNLOCK → sleeve manages again\'));\n el(\'tw-manual-permlock\').addEventListener(\'click\', ()=>manualControl(\'perm_lock_all\', \'🔐 PERM LOCK → NOTHING sells this leg (not even EOD flatten / next-morning sale) until you PERM UNLOCK. Carries overnight.\'));\n el(\'tw-manual-permunlock\').addEventListener(\'click\', ()=>manualControl(\'perm_unlock_all\', \'🗝 PERM UNLOCK → hard lock force-cleared; full sleeve management incl. EOD flatten resumes\'));\n el(\'tw-manual-holdovn\').addEventListener(\'click\', ()=>pickAccount(\'hold_overnight\', \'🌙 HOLD OVERNIGHT → EOD flatten skips\'));\n el(\'tw-manual-unholdovn\').addEventListener(\'click\', ()=>pickAccount(\'unhold_overnight\', \'🌤 UNHOLD → EOD flatten resumes\'));\n // click-to-chart modal close (button, backdrop click, Esc)\n const closeEtfModal = ()=>{ el(\'tw-etf-modal\').style.display=\'none\';\n if(etfModalChart){ etfModalChart.destroy(); etfModalChart=null; } };\n el(\'tw-etf-modal-close\').addEventListener(\'click\', closeEtfModal);\n el(\'tw-etf-modal\').addEventListener(\'click\', e=>{ if(e.target===el(\'tw-etf-modal\')) closeEtfModal(); });\n document.addEventListener(\'keydown\', e=>{ if(e.key===\'Escape\'){ closeEtfModal(); closeReasonModal(); } });\n // full-reasoning modal: ℹ button on a rollover row (event delegation — rows are rebuilt each cutoff)\n el(\'tw-events\').addEventListener(\'click\', e=>{\n // OVERRIDE — two taps, inline: the first ARMS the button, the second FIRES. Handled BEFORE the\n // expand toggle so a click on a button never collapses the panel out from under the operator.\n const ovr = e.target.closest(\'button[data-ovr]\');\n if(ovr){\n e.stopPropagation();\n const c2 = (window._twChains||[])[parseInt(ovr.getAttribute(\'data-ci\'),10)];\n if(!c2) return;\n const side = ovr.getAttribute(\'data-ovr\'), key = _chKey(c2)+\'|\'+side;\n if(_chain.armed === key) _chDoOverride(c2, side); // second tap = confirm\n else { _chain.armed = key; renderChains(); } // first tap = arm\n return;\n }\n // Clicking the chain (or its ⓘ) EXPANDS IT IN PLACE — no popup. A finger on a phone should not\n // have to find a small target, so the whole row is the hit area.\n const info = e.target.closest(\'.tw-info-i\') || e.target.closest(\'.tw-chain\');\n if(info){\n const ci = parseInt(info.getAttribute(\'data-ci\'), 10);\n const c = (window._twChains||[])[ci];\n if(c){\n // EXPAND IN PLACE — no modal. Second click collapses.\n const k = _chKey(c);\n if(_chain.expanded.has(k)) _chain.expanded.delete(k); else _chain.expanded.add(k);\n _chain.armed = null; // collapsing/expanding disarms any primed override\n renderChains();\n return;\n }\n }\n const btn = e.target.closest(\'.tw-rz-info\'); if(!btn) return;\n const rid = parseInt(btn.getAttribute(\'data-rid\'), 10);\n const ev = fullData && fullData._events && fullData._events[rid];\n if(ev && ev.reasoning) openReasonModal(ev.reasoning, ev);\n });\n // chain toolbar: sort order + noise toggle\n el(\'tw-chain-sort\').addEventListener(\'click\', ()=>{\n _chain.desc = !_chain.desc;\n el(\'tw-chain-sort\').textContent = _chSortLabel();\n renderChains();\n });\n el(\'tw-chain-noise\').addEventListener(\'click\', ()=>{\n _chain.noise = !_chain.noise;\n el(\'tw-chain-noise\').textContent = _chain.noise ? \'hide noise\' : \'show noise\';\n renderChains();\n });\n el(\'tw-reason-modal-close\').addEventListener(\'click\', closeReasonModal);\n el(\'tw-reason-modal\').addEventListener(\'click\', e=>{ if(e.target===el(\'tw-reason-modal\')) closeReasonModal(); });\n el(\'tw-speed\').addEventListener(\'change\', e=>{ R.speed = parseFloat(e.target.value)||30; });\n el(\'tw-scrub\').addEventListener(\'input\', e=>{\n startReplayIfNeeded();\n R.cutoff = replayLo() + (parseInt(e.target.value,10)/1000)*(replayHi()-replayLo());\n applyCutoff(R.cutoff);\n });\n document.querySelectorAll(\'.tw-f\').forEach(cb=>cb.addEventListener(\'change\', ()=>{\n hidden.clear(); document.querySelectorAll(\'.tw-f\').forEach(x=>{ if(!x.checked) hidden.add(x.value); });\n applyCutoff(R.active ? R.cutoff : Infinity);\n }));\n // reference-overlay toggles: prev-day support/ceiling, POC, ROC (redraw at the current cutoff)\n (function(){\n const wire = (id, set) => { const cb = el(id); if(cb) cb.addEventListener(\'change\', ()=>{\n set(cb.checked); applyCutoff(R.active ? R.cutoff : Infinity); }); };\n wire(\'tw-ov-levels\', v=>{ ovShowSR = v; });\n wire(\'tw-ov-poc\', v=>{ ovShowPoc = v; });\n wire(\'tw-ov-roc\', v=>{ ovShowRoc = v; });\n wire(\'tw-ov-tna\', v=>{ ovShowTna = v; });\n wire(\'tw-ov-energy\', v=>{ ovShowEnergy = v; }); // ⚡ energy blocks shaded on the oscillator panel\n // premarket toggle also re-frames the x-axis (04:00 left edge on / 09:30 off), so re-apply the window\n const pcb = el(\'tw-ov-premkt\');\n if(pcb) pcb.addEventListener(\'change\', ()=>{ ovShowPremkt = pcb.checked; applyRthWindow();\n applyCutoff(R.active ? R.cutoff : Infinity); });\n })();\n\n // additive: sound mute toggle (default OFF) + lazily unlock the AudioContext on any control gesture\n (function(){\n const sb = el(\'tw-sound\');\n if(sb){\n const paint = ()=>{ const m = twSound.isMuted();\n sb.textContent = m ? \'🔇\' : \'🔊\';\n sb.title = m ? \'Audio cues off — click to enable sound\' : \'Audio cues on — click to mute\';\n sb.style.color = m ? \'\' : \'var(--accent)\'; };\n paint();\n sb.addEventListener(\'click\', ()=>{ twSound.ensure(); twSound.toggle(); paint(); });\n }\n [\'tw-play\',\'tw-reset\',\'tw-liveBtn\'].forEach(id=>{ const n = el(id); if(n) n.addEventListener(\'click\', ()=>twSound.ensure()); });\n const sc = el(\'tw-scrub\'); if(sc) sc.addEventListener(\'pointerdown\', ()=>twSound.ensure());\n })();\n\n // First data load. A #flywheel write-up link carries `?sim=llm` (or `?sim=etf`) so the operator lands\n // on the REASONER\'s simulated entries/exits — NOT the live-account trades the page shows by default\n // (bug acbfeaf9527f). After the live/historic payload lands, switch the engine and auto-run the sim ONCE.\n // Display-only + hermetic: the sim endpoints write nothing live, `?sim=llm` sizes off CACHED decisions\n // (instant, no LLM call), and the page\'s ✕/Live buttons revert to the live view. Fail-open — a missing\n // param, an empty cache, or a sim error leaves the normal live/historic dashboard fully intact.\n const _boot = load(curDay);\n const _simQ = (new URLSearchParams(location.search).get(\'sim\') || \'\').trim().toLowerCase();\n if (_simQ === \'llm\' || _simQ === \'etf\' || _simQ === \'mechanic\') {\n const _eng = _simQ === \'llm\' ? \'llm\' : \'mechanic\';\n Promise.resolve(_boot).then(() => {\n try {\n etfEngine = _eng; // reflect the ⚙️ Mechanic / 🧠 LLM engine toggle\n const em = el(\'tw-eng-mech\'), elm = el(\'tw-eng-llm\');\n if (em) em.classList.toggle(\'active\', _eng === \'mechanic\');\n if (elm) elm.classList.toggle(\'active\', _eng === \'llm\');\n const rb = el(\'tw-etfsim-run\'); if (rb) rb.textContent = _eng === \'llm\' ? \'🧠 LLM sim ▶\' : \'🎢 ETF sim ▶\';\n loadEtfSim(); // no event arg → cached (instant) reasoner overlay\n } catch (e) { /* fail-open: keep the live/historic view intact */ }\n });\n }\n\n // Persisted-run deep link (bug 371d487cd2): `?date=YYYY-MM-DD&sim_run=<run_id>` cold-loads the\n // named SignalSimStudy for the day, mirroring the `?sim=...` deep-link pattern above. After\n // the day payload lands, the list-loader auto-fetches the named run when it\'s in the day\'s\n // list, or surfaces the "unknown persisted run X" named state and clears the URL. Fail-open:\n // a missing picker (flag OFF), a missing run, or any 404 leaves the live/historic view intact.\n const _deepRun = (new URLSearchParams(location.search).get(\'sim_run\') || \'\').trim();\n if (_deepRun && el(\'tw-sim-run-picker\')) {\n Promise.resolve(_boot).then(() => {\n try { loadPersistedSimRuns(_deepRun); } catch(e) { /* fail-open */ }\n });\n }\n}\n\nconst __out = (function(){\n\n const contradictory = [{event_et:\'14:51:00\', direction:\'short\', account:\'cuixia\'}];\n return { label:_chSignal(\'14:51:00\',\'tna_tide_oversold\', [], contradictory).label,\n noDir:_chSignal(\'09:46:00\',\'tna_tide_oversold\', [], [{event_et:\'09:46:00\'}]).label,\n phrases:[_chDirPhrase(\'long\'), _chDirPhrase(\'up\'), _chDirPhrase(\'short\'),\n _chDirPhrase(\'down\'), _chDirPhrase(\'\'), _chDirPhrase(\'sideways\')] };\n \n})();\nconsole.log(JSON.stringify(__out));\n']' returned non-zero exit status 1.
|