From 44431c4e66bf339899d7d42325b19247dcfd1f17 Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Sat, 30 May 2026 02:34:28 -0400 Subject: [PATCH] expand ci quality gates --- .beads/issues.jsonl | 1 + .forgejo/workflows/ci.yml | 9 + .../app/api/admin/synthetic/control/route.ts | 11 +- .../app/api/admin/synthetic/routes.test.ts | 5 +- apps/web/app/dashboard-mocks.tsx | 63 +- apps/web/app/globals.css | 155 ++- apps/web/app/terminal.test.ts | 128 ++- apps/web/app/terminal.tsx | 929 ++++++++++++------ apps/web/tsconfig.json | 11 +- biome.json | 93 ++ bun.lock | 19 + deployment/docker/workspace-root/bun.lock | 19 + deployment/docker/workspace-root/package.json | 5 + .../docker/workspace-root/tsconfig.base.json | 4 +- .../2026-05-30-expand-ci-quality-gates.html | 137 +++ package.json | 5 + packages/bus/src/jetstream.ts | 45 +- packages/bus/src/streams.ts | 4 +- packages/bus/src/synthetic-control.ts | 30 +- packages/bus/tests/jetstream.test.ts | 17 +- packages/config/src/alpaca.ts | 14 +- packages/storage/src/alerts.ts | 8 +- packages/storage/src/clickhouse.ts | 104 +- packages/storage/tests/alerts.test.ts | 5 +- packages/storage/tests/flow-packets.test.ts | 6 +- packages/storage/tests/news.test.ts | 13 +- packages/storage/tests/option-prints.test.ts | 6 +- packages/types/src/events.ts | 118 ++- packages/types/src/live.ts | 15 +- packages/types/src/options-flow.ts | 34 +- packages/types/src/sp500.ts | 4 +- packages/types/src/synthetic-market.ts | 108 +- packages/types/tests/live.test.ts | 4 +- scripts/check-docker-workspace.ts | 36 +- scripts/check-public-api-routes.ts | 9 +- scripts/deploy.ts | 91 +- scripts/generate-docs-index.mjs | 4 +- scripts/sync-docker-workspace.ts | 7 +- scripts/typecheck.ts | 22 +- services/api/src/index.ts | 70 +- services/api/src/live.ts | 185 +++- services/api/src/synthetic-control.ts | 6 +- services/api/tests/alert-context.test.ts | 4 +- services/api/tests/live.test.ts | 86 +- services/candles/src/index.ts | 17 +- services/compute/src/alert-scoring.ts | 1 - services/compute/src/classifiers.ts | 19 +- services/compute/src/equity-joins.ts | 5 +- services/compute/src/index.ts | 164 +++- services/compute/src/parent-events.ts | 90 +- services/compute/src/rolling-stats.ts | 4 +- .../compute/src/smart-money-evaluation.ts | 53 +- services/compute/src/structure-packets.ts | 17 +- services/compute/src/structures.ts | 7 +- services/compute/tests/classifiers.test.ts | 1 - services/compute/tests/helpers.ts | 23 +- .../compute/tests/structure-packets.test.ts | 4 +- .../ingest-equities/src/adapters/alpaca.ts | 45 +- .../ingest-equities/src/adapters/synthetic.ts | 32 +- services/ingest-equities/src/index.ts | 5 +- services/ingest-news/src/index.ts | 8 +- .../ingest-options/src/adapters/alpaca.ts | 19 +- .../ingest-options/src/adapters/databento.ts | 3 +- services/ingest-options/src/adapters/ibkr.ts | 4 +- .../ingest-options/src/adapters/synthetic.ts | 142 +-- services/ingest-options/src/enrichment.ts | 6 +- services/ingest-options/src/index.ts | 34 +- services/refdata/src/event-calendar.ts | 44 +- services/refdata/src/index.ts | 15 +- services/replay/src/index.ts | 20 +- tsconfig.base.json | 4 +- 71 files changed, 2262 insertions(+), 1173 deletions(-) create mode 100644 biome.json create mode 100644 docs/turns/2026-05-30-expand-ci-quality-gates.html diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index d26574c..c0fa90a 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -24,6 +24,7 @@ {"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"islandflow-cig","title":"Expand CI quality gates","description":"Add a more robust CI workflow for the Bun/TypeScript monorepo, including formatting, linting, type checking, builds, and tests where appropriate.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-30T06:29:33Z","created_by":"dirtydishes","updated_at":"2026-05-30T06:34:11Z","started_at":"2026-05-30T06:29:41Z","closed_at":"2026-05-30T06:34:11Z","close_reason":"Expanded CI quality gates with Biome formatting/linting, public API route checks, Docker snapshot validation, tests, typecheck, and web build validation.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-3l6","title":"fix ci typecheck bun path resolution","description":"Forgejo CI fails in scripts/typecheck.ts because the script shells out to bunx, which expects bun on PATH. The runner installs Bun by absolute path, so the typecheck helper should use the current Bun executable instead of PATH lookup.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-30T05:34:55Z","created_by":"dirtydishes","updated_at":"2026-05-30T06:00:31Z","started_at":"2026-05-30T05:35:02Z","closed_at":"2026-05-30T06:00:31Z","close_reason":"Fixed the Forgejo CI terminal import mismatch by switching the terminal client component to a namespace import; verified locally and on Forgejo run #56.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-wtg","title":"Harden drawer dialog focus behavior","description":"Fix terminal drawers so they expose modal dialog semantics, trap keyboard focus while open, and restore focus to the invoking control after close.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-29T22:55:25Z","created_by":"dirtydishes","updated_at":"2026-05-29T23:09:45Z","started_at":"2026-05-29T22:56:22Z","closed_at":"2026-05-29T23:09:45Z","close_reason":"Implemented modal dialog semantics, focus trapping, Escape dismissal, focus restoration, validation, and turn documentation.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-833","title":"Improve narrow options table responsiveness","description":"Adapt the Options route for narrow screens so dense tape tables remain contained in their panes, preserve row identity while horizontally panning, and keep the mobile ticker/filter controls readable.","acceptance_criteria":"Options tape panes have bounded heights on narrow screens; table body scrolls internally; first table column remains visible while panning; mobile topbar and filter controls have adequate spacing; web production build passes.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-29T22:34:05Z","created_by":"dirtydishes","updated_at":"2026-05-29T22:36:20Z","started_at":"2026-05-29T22:34:24Z","closed_at":"2026-05-29T22:36:20Z","close_reason":"Implemented narrow-screen options pane containment, sticky row context, touch-scroll affordances, and mobile control spacing. Validated with web build and in-browser narrow viewport checks.","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 2717c84..01724f6 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -36,12 +36,21 @@ jobs: - name: Install dependencies run: ~/.bun/bin/bun install --frozen-lockfile + - name: Check formatting + run: ~/.bun/bin/bun run fmt:check + + - name: Run lint + run: ~/.bun/bin/bun run lint + - name: Run typecheck run: ~/.bun/bin/bun run typecheck - name: Run tests run: ~/.bun/bin/bun test + - name: Check public API routes + run: ~/.bun/bin/bun run check:public-api-routes + - name: Check Docker workspace snapshot run: ~/.bun/bin/bun run check:docker-workspace diff --git a/apps/web/app/api/admin/synthetic/control/route.ts b/apps/web/app/api/admin/synthetic/control/route.ts index 09f5629..578df3a 100644 --- a/apps/web/app/api/admin/synthetic/control/route.ts +++ b/apps/web/app/api/admin/synthetic/control/route.ts @@ -9,11 +9,8 @@ export async function GET(): Promise { } export async function PUT(req: Request): Promise { - return proxySyntheticAdminRequest( - "/admin/synthetic/control", - { - method: "PUT", - body: await req.text() - } - ); + return proxySyntheticAdminRequest("/admin/synthetic/control", { + method: "PUT", + body: await req.text() + }); } diff --git a/apps/web/app/api/admin/synthetic/routes.test.ts b/apps/web/app/api/admin/synthetic/routes.test.ts index eec575d..ee50525 100644 --- a/apps/web/app/api/admin/synthetic/routes.test.ts +++ b/apps/web/app/api/admin/synthetic/routes.test.ts @@ -1,8 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test"; -import { - getSyntheticAdminProxyConfig, - isSyntheticAdminFeatureEnabled -} from "./shared"; +import { getSyntheticAdminProxyConfig, isSyntheticAdminFeatureEnabled } from "./shared"; const originalFetch = globalThis.fetch; diff --git a/apps/web/app/dashboard-mocks.tsx b/apps/web/app/dashboard-mocks.tsx index 101141c..1c23bb1 100644 --- a/apps/web/app/dashboard-mocks.tsx +++ b/apps/web/app/dashboard-mocks.tsx @@ -18,25 +18,29 @@ const variants: Record< > = { mock1: { title: "Command Deck", - premise: "Closest to the reference: left navigation, ticker ribbon, dense evidence panes, replay rail.", + premise: + "Closest to the reference: left navigation, ticker ribbon, dense evidence panes, replay rail.", mode: "Dense ops", layout: "classic" }, mock2: { title: "Investigation Stack", - premise: "A calmer analyst layout with the selected symbol story in the center and context wrapped around it.", + premise: + "A calmer analyst layout with the selected symbol story in the center and context wrapped around it.", mode: "Forensic", layout: "focus" }, mock3: { title: "Signal Wall", - premise: "Prioritizes alert triage and cross-symbol scanning before a user drills into price action.", + premise: + "Prioritizes alert triage and cross-symbol scanning before a user drills into price action.", mode: "Triage", layout: "signals" }, mock4: { title: "Replay Lab", - premise: "A replay-first structure with timeline, event tape, and causality context always visible.", + premise: + "A replay-first structure with timeline, event tape, and causality context always visible.", mode: "Replay", layout: "replay" } @@ -93,7 +97,10 @@ export function DashboardMock({ variant }: DashboardMockProps) { const config = variants[variant]; return ( -
+
{variant === "mock1" ? : null} @@ -277,7 +284,11 @@ function OptionTape({ condensed = false }: { condensed?: boolean }) { function ChartPanel({ compact = false }: { compact?: boolean }) { return ( - +
194.88 +2.34 (+1.22%) @@ -306,16 +317,24 @@ function ChartPanel({ compact = false }: { compact?: boolean }) { function SignalPanel({ hero = false }: { hero?: boolean }) { return ( - +
{signals.map(([time, title, symbol, value, tag]) => (
{title} - {symbol} / {value} + + {symbol} / {value} +
- + {tag}
@@ -332,7 +351,9 @@ function FeedHealth() { {feedHealth.map(([feed, status, lag, rate]) => (
{feed} - {status} + + {status} + {lag} {rate}/s
@@ -350,7 +371,9 @@ function DarkFlow() {
{time} {symbol} - {side} + + {side} + {size} {notional} {type} @@ -402,7 +425,11 @@ function EventContext() { function ReplayRail({ compact = false }: { compact?: boolean }) { return ( - +
@@ -430,8 +457,9 @@ function SymbolBrief() { +1.22%

- Dark sweep pressure aligns with short-window momentum and a fresh news catalyst. Context confidence is high, but - the largest block remains off-exchange and should be checked against next print behavior. + Dark sweep pressure aligns with short-window momentum and a fresh news catalyst. Context + confidence is high, but the largest block remains off-exchange and should be checked against + next print behavior.

Bullish @@ -444,7 +472,12 @@ function SymbolBrief() { function Sparkline({ direction }: { direction: string }) { return ( - + span { @@ -1761,17 +1817,39 @@ h3 { font-variant-numeric: tabular-nums; } -.classifier-green { --classifier-rgb: 37, 193, 122; } -.classifier-red { --classifier-rgb: 255, 107, 95; } -.classifier-amber { --classifier-rgb: 245, 166, 35; } -.classifier-copper { --classifier-rgb: 198, 122, 75; } -.classifier-blue { --classifier-rgb: 77, 163, 255; } -.classifier-teal { --classifier-rgb: 64, 210, 190; } -.classifier-yellowgreen { --classifier-rgb: 174, 210, 78; } -.classifier-violet { --classifier-rgb: 170, 130, 255; } -.classifier-cyan { --classifier-rgb: 94, 214, 255; } -.classifier-magenta { --classifier-rgb: 255, 92, 205; } -.classifier-neutral { --classifier-rgb: 192, 200, 210; } +.classifier-green { + --classifier-rgb: 37, 193, 122; +} +.classifier-red { + --classifier-rgb: 255, 107, 95; +} +.classifier-amber { + --classifier-rgb: 245, 166, 35; +} +.classifier-copper { + --classifier-rgb: 198, 122, 75; +} +.classifier-blue { + --classifier-rgb: 77, 163, 255; +} +.classifier-teal { + --classifier-rgb: 64, 210, 190; +} +.classifier-yellowgreen { + --classifier-rgb: 174, 210, 78; +} +.classifier-violet { + --classifier-rgb: 170, 130, 255; +} +.classifier-cyan { + --classifier-rgb: 94, 214, 255; +} +.classifier-magenta { + --classifier-rgb: 255, 92, 205; +} +.classifier-neutral { + --classifier-rgb: 192, 200, 210; +} .contract, .drawer-row-title { @@ -1921,7 +1999,9 @@ h3 { opacity: 0; pointer-events: none; transform: translateY(8px); - transition: opacity 0.15s ease, transform 0.15s ease; + transition: + opacity 0.15s ease, + transform 0.15s ease; z-index: 5; } @@ -2047,7 +2127,10 @@ h3 { color: var(--text-dim); box-shadow: 0 10px 28px rgba(0, 0, 0, 0.28); z-index: 45; - transition: border-color 0.16s ease, background-color 0.16s ease, color 0.16s ease; + transition: + border-color 0.16s ease, + background-color 0.16s ease, + color 0.16s ease; } .synthetic-control-gear:hover, @@ -2213,7 +2296,9 @@ h3 { background: oklch(0.18 0.012 250 / 0.6); color: var(--text); text-align: left; - transition: border-color 150ms ease, background 150ms ease; + transition: + border-color 150ms ease, + background 150ms ease; } .news-row:hover { @@ -2520,7 +2605,11 @@ h3 { @media (max-width: 720px) { .terminal-shell { - background-size: 24px 24px, 24px 24px, 100% 100%, auto; + background-size: + 24px 24px, + 24px 24px, + 100% 100%, + auto; } .terminal-nav-drawer { @@ -2877,9 +2966,7 @@ h3 { width: 34px; height: 34px; border-radius: 9px; - background: - linear-gradient(135deg, oklch(0.68 0.14 246), oklch(0.68 0.12 164)), - var(--blue-soft); + background: linear-gradient(135deg, oklch(0.68 0.14 246), oklch(0.68 0.12 164)), var(--blue-soft); box-shadow: inset 0 0 0 1px oklch(0.94 0.02 240 / 0.24); } diff --git a/apps/web/app/terminal.test.ts b/apps/web/app/terminal.test.ts index 062ea35..d396602 100644 --- a/apps/web/app/terminal.test.ts +++ b/apps/web/app/terminal.test.ts @@ -311,12 +311,16 @@ describe("live manifest", () => { }); it("includes news subscriptions on home and /news", () => { - expect(getLiveManifest("/", "SPY", 60000, buildDefaultFlowFilters()).map((subscription) => subscription.channel)).toContain( - "news" - ); - expect(getLiveManifest("/news", "SPY", 60000, buildDefaultFlowFilters()).map((subscription) => subscription.channel)).toEqual([ - "news" - ]); + expect( + getLiveManifest("/", "SPY", 60000, buildDefaultFlowFilters()).map( + (subscription) => subscription.channel + ) + ).toContain("news"); + expect( + getLiveManifest("/news", "SPY", 60000, buildDefaultFlowFilters()).map( + (subscription) => subscription.channel + ) + ).toEqual(["news"]); }); it("scopes /charts subscriptions to chart channels only", () => { @@ -520,12 +524,36 @@ describe("route feature map", () => { describe("fixed tape virtualization config", () => { it("uses expected fixed row heights and overscan by table", () => { - expect(getTapeVirtualConfig("options")).toEqual({ rowHeight: 36, overscan: 44, debugLabel: "options" }); - expect(getTapeVirtualConfig("equities")).toEqual({ rowHeight: 36, overscan: 36, debugLabel: "equities" }); - expect(getTapeVirtualConfig("flow")).toEqual({ rowHeight: 44, overscan: 24, debugLabel: "flow" }); - expect(getTapeVirtualConfig("alerts")).toEqual({ rowHeight: 44, overscan: 24, debugLabel: "alerts" }); - expect(getTapeVirtualConfig("classifier")).toEqual({ rowHeight: 44, overscan: 24, debugLabel: "classifier" }); - expect(getTapeVirtualConfig("dark")).toEqual({ rowHeight: 44, overscan: 24, debugLabel: "dark" }); + expect(getTapeVirtualConfig("options")).toEqual({ + rowHeight: 36, + overscan: 44, + debugLabel: "options" + }); + expect(getTapeVirtualConfig("equities")).toEqual({ + rowHeight: 36, + overscan: 36, + debugLabel: "equities" + }); + expect(getTapeVirtualConfig("flow")).toEqual({ + rowHeight: 44, + overscan: 24, + debugLabel: "flow" + }); + expect(getTapeVirtualConfig("alerts")).toEqual({ + rowHeight: 44, + overscan: 24, + debugLabel: "alerts" + }); + expect(getTapeVirtualConfig("classifier")).toEqual({ + rowHeight: 44, + overscan: 24, + debugLabel: "classifier" + }); + expect(getTapeVirtualConfig("dark")).toEqual({ + rowHeight: 44, + overscan: 24, + debugLabel: "dark" + }); }); }); @@ -712,7 +740,11 @@ describe("live tape history helpers", () => { }); it("promotes hot-window overflow into the history tail", () => { - const currentHot = [makeItem("hot-3", 3, 300), makeItem("hot-2", 2, 200), makeItem("hot-1", 1, 100)]; + const currentHot = [ + makeItem("hot-3", 3, 300), + makeItem("hot-2", 2, 200), + makeItem("hot-1", 1, 100) + ]; const incoming = [makeItem("hot-4", 4, 400)]; const { kept, evicted } = mergeNewestWithOverflow(incoming, currentHot, 3); @@ -727,7 +759,11 @@ describe("live tape history helpers", () => { let history: Array> = []; for (let seq = 1; seq <= 5; seq += 1) { - const { kept, evicted } = mergeNewestWithOverflow([makeItem(`row-${seq}`, seq, seq * 100)], hot, 2); + const { kept, evicted } = mergeNewestWithOverflow( + [makeItem(`row-${seq}`, seq, seq * 100)], + hot, + 2 + ); hot = kept; history = appendHistoryTail(history, evicted, hot, 5000); } @@ -762,13 +798,24 @@ describe("live tape history helpers", () => { }); it("dedupes the seam between promoted overflow and fetched history", () => { - const currentHot = [makeItem("hot-3", 3, 300), makeItem("hot-2", 2, 200), makeItem("hot-1", 1, 100)]; + const currentHot = [ + makeItem("hot-3", 3, 300), + makeItem("hot-2", 2, 200), + makeItem("hot-1", 1, 100) + ]; const { kept, evicted } = mergeNewestWithOverflow([makeItem("hot-4", 4, 400)], currentHot, 3); const promoted = appendHistoryTail([], evicted, kept, 5000); - const merged = appendHistoryTail(promoted, [makeItem("hot-1", 1, 100), makeItem("older", 0, 50)], kept, 5000); + const merged = appendHistoryTail( + promoted, + [makeItem("hot-1", 1, 100), makeItem("older", 0, 50)], + kept, + 5000 + ); expect(merged.map((item) => item.trace_id)).toEqual(["hot-1", "older"]); - expect(new Set([...kept, ...merged].map((item) => item.trace_id)).size).toBe(kept.length + merged.length); + expect(new Set([...kept, ...merged].map((item) => item.trace_id)).size).toBe( + kept.length + merged.length + ); }); it("trims the history tail to the soft cap", () => { @@ -821,10 +868,9 @@ describe("live tape history helpers", () => { makeItem("hist-2", 2, 200) ]; - expect(mergeHeldTapeHistory(displayed, incoming, frozenLive).map((item) => item.trace_id)).toEqual([ - "hist-3", - "hist-2" - ]); + expect( + mergeHeldTapeHistory(displayed, incoming, frozenLive).map((item) => item.trace_id) + ).toEqual(["hist-3", "hist-2"]); }); it("appends truly older lazy-loaded rows to the held history tail", () => { @@ -837,12 +883,9 @@ describe("live tape history helpers", () => { makeItem("older-0", 0, 50) ]; - expect(mergeHeldTapeHistory(displayed, incoming, frozenLive).map((item) => item.trace_id)).toEqual([ - "hist-3", - "hist-2", - "older-1", - "older-0" - ]); + expect( + mergeHeldTapeHistory(displayed, incoming, frozenLive).map((item) => item.trace_id) + ).toEqual(["hist-3", "hist-2", "older-1", "older-0"]); }); it("resyncs buffered live history by replacing the held segment after resume", () => { @@ -855,7 +898,12 @@ describe("live tape history helpers", () => { const resynced = appendHistoryTail([], [makeItem("overflow-newer", 6, 600), ...held], [], 0); expect(held.map((item) => item.trace_id)).toEqual(["hist-3", "hist-2", "older-1"]); - expect(resynced.map((item) => item.trace_id)).toEqual(["overflow-newer", "hist-3", "hist-2", "older-1"]); + expect(resynced.map((item) => item.trace_id)).toEqual([ + "overflow-newer", + "hist-3", + "hist-2", + "older-1" + ]); }); }); @@ -935,9 +983,21 @@ describe("classifier row decoration helpers", () => { it("selects primary hits by confidence, source timestamp, then seq", () => { const hit = selectPrimaryClassifierHit([ - { ...makeAlert({ classifier_id: "old", confidence: 0.9, source_ts: 1_000, seq: 1 }), direction: "bullish", explanations: [] }, - { ...makeAlert({ classifier_id: "new", confidence: 0.9, source_ts: 2_000, seq: 1 }), direction: "bullish", explanations: [] }, - { ...makeAlert({ classifier_id: "low", confidence: 0.5, source_ts: 3_000, seq: 9 }), direction: "bullish", explanations: [] } + { + ...makeAlert({ classifier_id: "old", confidence: 0.9, source_ts: 1_000, seq: 1 }), + direction: "bullish", + explanations: [] + }, + { + ...makeAlert({ classifier_id: "new", confidence: 0.9, source_ts: 2_000, seq: 1 }), + direction: "bullish", + explanations: [] + }, + { + ...makeAlert({ classifier_id: "low", confidence: 0.5, source_ts: 3_000, seq: 9 }), + direction: "bullish", + explanations: [] + } ]); expect(hit?.classifier_id).toBe("new"); @@ -1010,9 +1070,9 @@ describe("signals helpers", () => { ) ).toBe("bearish"); - expect(deriveAlertDirection(makeAlert({ hits: [{ direction: "weird", confidence: 0.4 }] }))).toBe( - "neutral" - ); + expect( + deriveAlertDirection(makeAlert({ hits: [{ direction: "weird", confidence: 0.4 }] })) + ).toBe("neutral"); expect(deriveAlertDirection(makeAlert({ hits: [] }))).toBe("neutral"); }); diff --git a/apps/web/app/terminal.tsx b/apps/web/app/terminal.tsx index 4c6082f..d7afe6e 100644 --- a/apps/web/app/terminal.tsx +++ b/apps/web/app/terminal.tsx @@ -54,7 +54,12 @@ import { matchesFlowPacketFilters, matchesOptionPrintFilters } from "@islandflow/types"; -import { createChart, type IChartApi, type SeriesMarker, type UTCTimestamp } from "lightweight-charts"; +import { + createChart, + type IChartApi, + type SeriesMarker, + type UTCTimestamp +} from "lightweight-charts"; const parseBoundedInt = ( value: string | undefined, @@ -656,8 +661,9 @@ const frontendTapeDebugMetrics: Record = { const bumpTapeDebugMetric = (key: TapeDebugMetricKey, count = 1): void => { frontendTapeDebugMetrics[key] += count; if (DEV_TAPE_DEBUG && typeof window !== "undefined") { - (window as typeof window & { __IF_TAPE_DEBUG__?: Record }).__IF_TAPE_DEBUG__ = - frontendTapeDebugMetrics; + ( + window as typeof window & { __IF_TAPE_DEBUG__?: Record } + ).__IF_TAPE_DEBUG__ = frontendTapeDebugMetrics; } }; @@ -1047,9 +1053,8 @@ const buildApiUrl = (path: string): string => { return `${httpProtocol}://${host}${path}`; }; -export const isSyntheticAdminVisible = ( - value = process.env.NEXT_PUBLIC_SYNTHETIC_ADMIN -): boolean => value === "1"; +export const isSyntheticAdminVisible = (value = process.env.NEXT_PUBLIC_SYNTHETIC_ADMIN): boolean => + value === "1"; type SyntheticAdminStatusResponse = { enabled: boolean; @@ -1082,10 +1087,7 @@ const SYNTHETIC_PROFILE_ORDER: Array = { +const SYNTHETIC_PROFILE_LABELS: Record = { institutional_directional: "Institutional Directional", retail_whale: "Retail Whale", event_driven: "Event Driven", @@ -1266,10 +1268,17 @@ export const formatNewsTimestamp = (ts: number, now = Date.now()): string => { const date = new Date(ts); return isSameLocalDay(ts, now) ? date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }) - : date.toLocaleString([], { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" }); + : date.toLocaleString([], { + month: "short", + day: "numeric", + hour: "numeric", + minute: "2-digit" + }); }; -const sanitizeNewsHtml = (value: string): { html: string; fallbackText: string; sanitized: boolean } => { +const sanitizeNewsHtml = ( + value: string +): { html: string; fallbackText: string; sanitized: boolean } => { const fallbackText = value .replace(//gi, " ") .replace(//gi, " ") @@ -1283,7 +1292,10 @@ const sanitizeNewsHtml = (value: string): { html: string; fallbackText: string; .replace(//gi, "") .replace(/\son\w+=(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, "") .replace(/\shref=(["'])javascript:[\s\S]*?\1/gi, ' href="#"') - .replace(/<(?!\/?(p|div|section|article|span|strong|em|b|i|ul|ol|li|br|a|h1|h2|h3|h4|blockquote)\b)[^>]*>/gi, ""); + .replace( + /<(?!\/?(p|div|section|article|span|strong|em|b|i|ul|ol|li|br|a|h1|h2|h3|h4|blockquote)\b)[^>]*>/gi, + "" + ); return { html: sanitized, fallbackText, sanitized: true }; } catch { return { html: "", fallbackText, sanitized: false }; @@ -1350,9 +1362,11 @@ export const deriveAlertDirection = (alert: AlertEvent): "bullish" | "bearish" | totals[direction].confidence += Number.isFinite(hit.confidence) ? hit.confidence : 0; } - const ranked = (Object.entries(totals) as Array< - ["bullish" | "bearish" | "neutral", { count: number; confidence: number }] - >).sort((a, b) => { + const ranked = ( + Object.entries(totals) as Array< + ["bullish" | "bearish" | "neutral", { count: number; confidence: number }] + > + ).sort((a, b) => { if (b[1].count !== a[1].count) { return b[1].count - a[1].count; } @@ -1366,7 +1380,10 @@ export const getAlertWindowAnchorTs = (alerts: AlertEvent[], fallbackNow = Date. if (alerts.length === 0) { return fallbackNow; } - return alerts.reduce((max, alert) => Math.max(max, alert.source_ts), alerts[0]?.source_ts ?? fallbackNow); + return alerts.reduce( + (max, alert) => Math.max(max, alert.source_ts), + alerts[0]?.source_ts ?? fallbackNow + ); }; const extractUnderlying = (contractId: string): string => { @@ -1510,14 +1527,13 @@ export const buildDefaultFlowFilters = (): OptionFlowFilters => ({ nbboSides: DEFAULT_FLOW_SIDES, optionTypes: DEFAULT_FLOW_OPTION_TYPES, minNotional: - FLOW_FILTER_PRESET === "all" - ? undefined - : FLOW_FILTER_PRESET === "balanced" - ? 5_000 - : undefined + FLOW_FILTER_PRESET === "all" ? undefined : FLOW_FILTER_PRESET === "balanced" ? 5_000 : undefined }); -const sameFilterValues = (left: T[] | undefined, right: T[] | undefined): boolean => { +const sameFilterValues = ( + left: T[] | undefined, + right: T[] | undefined +): boolean => { const leftValues = [...(left ?? [])].sort(); const rightValues = [...(right ?? [])].sort(); if (leftValues.length !== rightValues.length) { @@ -1716,7 +1732,7 @@ export const classifierToneForFamily = (classifierId: string): string => CLASSIFIER_FAMILY_TONES[classifierId] ?? "neutral"; export const smartMoneyToneForProfile = (profileId: SmartMoneyProfileId | null): string => - profileId ? SMART_MONEY_PROFILE_TONES[profileId] ?? "neutral" : "neutral"; + profileId ? (SMART_MONEY_PROFILE_TONES[profileId] ?? "neutral") : "neutral"; export const smartMoneyProfileLabel = (profileId: SmartMoneyProfileId | null): string => profileId ? humanizeClassifierId(profileId) : "Abstained"; @@ -1755,7 +1771,10 @@ export const getOptionTableSnapshot = ( ): { spot: string; iv: string; side: string; details: string; value: string } => { const side = print.execution_nbbo_side ?? print.nbbo_side ?? fallbackSide ?? "--"; return { - spot: typeof print.execution_underlying_spot === "number" ? formatPrice(print.execution_underlying_spot) : "--", + spot: + typeof print.execution_underlying_spot === "number" + ? formatPrice(print.execution_underlying_spot) + : "--", iv: typeof print.execution_iv === "number" ? formatPct(print.execution_iv) : "--", side, details: `${formatSize(print.size)}@${formatPrice(print.price)}_${side}`, @@ -1879,7 +1898,9 @@ const useScrollAnchor = ( } | null>(null); const readRenderedRows = useCallback((element: HTMLDivElement) => { - return Array.from(element.querySelectorAll("[data-tape-key][data-row-start][data-row-size]")) + return Array.from( + element.querySelectorAll("[data-tape-key][data-row-start][data-row-size]") + ) .map((node) => { const key = node.dataset.tapeKey; const start = Number(node.dataset.rowStart); @@ -2164,9 +2185,7 @@ type TapeConfig = { hotWindowLimit?: number; }; -const useTape = ( - config: TapeConfig -): TapeState => { +const useTape = (config: TapeConfig): TapeState => { const { mode, wsPath, replayPath, expectedType, latestPath, onNewItems, captureScroll } = config; const batchSize = config.batchSize ?? 40; const pollMs = config.pollMs ?? 1000; @@ -2712,20 +2731,16 @@ const usePausableTapeView = ( }; }; -const useLiveStream = ( - config: { - enabled: boolean; - wsPath: string; - expectedType: MessageType; - onNewItems?: (count: number) => void; - captureScroll?: () => void; - shouldHold?: () => boolean; - resumeSignal?: number; - } -): TapeState => { - const [status, setStatus] = useState( - config.enabled ? "connecting" : "disconnected" - ); +const useLiveStream = (config: { + enabled: boolean; + wsPath: string; + expectedType: MessageType; + onNewItems?: (count: number) => void; + captureScroll?: () => void; + shouldHold?: () => boolean; + resumeSignal?: number; +}): TapeState => { + const [status, setStatus] = useState(config.enabled ? "connecting" : "disconnected"); const [items, setItems] = useState([]); const [lastUpdate, setLastUpdate] = useState(null); const [replayTime] = useState(null); @@ -2784,8 +2799,7 @@ const useLiveStream = ( return; } - const nextBatch = - holdRef.current.length > 0 ? [...holdRef.current, ...buffered] : buffered; + const nextBatch = holdRef.current.length > 0 ? [...holdRef.current, ...buffered] : buffered; holdRef.current = []; setItems((prev) => @@ -3002,7 +3016,10 @@ const LIVE_HISTORY_ENDPOINTS: Partial { +const appendOptionFlowFilters = ( + params: URLSearchParams, + filters: OptionFlowFilters | undefined +): void => { if (!filters) { return; } @@ -3119,7 +3136,10 @@ export const shouldClearOptionFocusSeed = ( }; const appendLiveScopeParams = (params: URLSearchParams, subscription: LiveSubscription): void => { - if ((subscription.channel === "options" || subscription.channel === "equities") && subscription.underlying_ids?.length) { + if ( + (subscription.channel === "options" || subscription.channel === "equities") && + subscription.underlying_ids?.length + ) { params.set("underlying_ids", subscription.underlying_ids.join(",")); } if (subscription.channel === "options" && subscription.option_contract_id) { @@ -3157,7 +3177,7 @@ export const getLiveManifest = ( filters: optionScope?.option_contract_id && optionPrintFilters === undefined ? undefined - : optionPrintFilters ?? flowFilters, + : (optionPrintFilters ?? flowFilters), ...optionScope, snapshot_limit: LIVE_OPTIONS_HEAD_LIMIT }); @@ -3412,7 +3432,8 @@ const useLiveSession = ( return; } - const subscription = message.op === "snapshot" ? message.snapshot.subscription : message.subscription; + const subscription = + message.op === "snapshot" ? message.snapshot.subscription : message.subscription; const items = message.op === "snapshot" ? message.snapshot.items : [message.item]; const subscriptionKey = getLiveSubscriptionKey(subscription); const updateAt = Date.now(); @@ -3520,10 +3541,16 @@ const useLiveSession = ( }); break; case "inferred-dark": - mergeItems(setInferredDark, inferredDarkRef, items as InferredDarkEvent[], LIVE_HOT_WINDOW, { - setter: setInferredDarkHistory, - ref: inferredDarkHistoryRef - }); + mergeItems( + setInferredDark, + inferredDarkRef, + items as InferredDarkEvent[], + LIVE_HOT_WINDOW, + { + setter: setInferredDarkHistory, + ref: inferredDarkHistoryRef + } + ); break; case "equity-candles": mergeItems(setChartCandles, chartCandlesRef, items as EquityCandle[]); @@ -3895,7 +3922,9 @@ const TapeStatus = ({ const pausedLabel = paused && dropped > 0 ? `+${dropped} queued` : ""; return ( -
+
{label} {mode === "replay" ? ( @@ -3903,7 +3932,9 @@ const TapeStatus = ({ Replay time {replayTime ? formatTime(replayTime) : "—"} ) : null} - + {pausedLabel || "+000 queued"}
@@ -3919,7 +3950,14 @@ type TapeControlsProps = { onJump: () => void; }; -const TapeControls = ({ mode, paused, onTogglePause, isAtTop, missed, onJump }: TapeControlsProps) => { +const TapeControls = ({ + mode, + paused, + onTogglePause, + isAtTop, + missed, + onJump +}: TapeControlsProps) => { const active = !isAtTop && missed > 0; return (
@@ -3931,7 +3969,10 @@ const TapeControls = ({ mode, paused, onTogglePause, isAtTop, missed, onJump }: - + +{missed} new
@@ -4120,11 +4161,7 @@ const CandleChart = ({ ? "#c46f2a" : "rgba(111, 91, 57, 0.9)", shape: - direction === "bullish" - ? "arrowUp" - : direction === "bearish" - ? "arrowDown" - : "circle", + direction === "bullish" ? "arrowUp" : direction === "bearish" ? "arrowDown" : "circle", text: event.abstained ? "ABS" : event.primary_profile_id @@ -4381,9 +4418,7 @@ const CandleChart = ({ const response = await fetch(url.toString()); if (!response.ok) { const detail = await readErrorDetail(response); - throw new Error( - `Candle fetch failed (${response.status})${detail ? `: ${detail}` : ""}` - ); + throw new Error(`Candle fetch failed (${response.status})${detail ? `: ${detail}` : ""}`); } const payload = (await response.json()) as { data?: EquityCandle[] }; if (!active || !seriesRef.current) { @@ -4416,7 +4451,6 @@ const CandleChart = ({ } }; - const ensureOverlayListener = () => { if (!chartRef.current) { return; @@ -4563,7 +4597,7 @@ const CandleChart = ({ return; } - const sortedCandles = [...liveCandles].sort((a, b) => (a.ts - b.ts) || (a.seq - b.seq)); + const sortedCandles = [...liveCandles].sort((a, b) => a.ts - b.ts || a.seq - b.seq); if (sortedCandles.length > 0) { seriesRef.current.setData(sortedCandles.map(toChartCandle)); const last = sortedCandles.at(-1); @@ -4768,9 +4802,7 @@ export const collectAlertContextEvidence = ( return { packets, prints }; }; -export const getAlertFlowPacketRefs = ( - alert: Pick -): string[] => { +export const getAlertFlowPacketRefs = (alert: Pick): string[] => { return alert.evidence_refs.filter((ref) => ref.startsWith("flowpacket:")); }; @@ -4839,7 +4871,10 @@ const AlertDrawer = ({ alert, flowPacket, evidence, contextStatus, onClose }: Al {isContextLoading ? Loading context : null}
{isContextLoading ? ( -
+
@@ -4880,7 +4915,12 @@ const AlertDrawer = ({ alert, flowPacket, evidence, contextStatus, onClose }: Al {String(flowPacket.features.option_contract_id ?? flowPacket.id ?? "Flow packet")}
- {formatFlowMetric(parseNumber(flowPacket.features.count, flowPacket.members.length))} prints + + {formatFlowMetric( + parseNumber(flowPacket.features.count, flowPacket.members.length) + )}{" "} + prints + {formatFlowMetric(parseNumber(flowPacket.features.total_size, 0))} size Notional $ @@ -4906,7 +4946,9 @@ const AlertDrawer = ({ alert, flowPacket, evidence, contextStatus, onClose }: Al

Evidence prints

{evidencePrints.length === 0 ? ( -

Persisted evidence prints are not available for this alert.

+

+ Persisted evidence prints are not available for this alert. +

) : (
{evidencePrints.slice(0, 6).map((item) => ( @@ -4916,7 +4958,9 @@ const AlertDrawer = ({ alert, flowPacket, evidence, contextStatus, onClose }: Al ${formatPrice(item.print.price)} {formatSize(item.print.size)}x {item.print.exchange} - {item.print.execution_nbbo_side ? Side {item.print.execution_nbbo_side} : null} + {item.print.execution_nbbo_side ? ( + Side {item.print.execution_nbbo_side} + ) : null} {formatOptionalMs(item.print.execution_nbbo_age_ms) ? ( Quote {formatOptionalMs(item.print.execution_nbbo_age_ms)} ) : null} @@ -4953,7 +4997,9 @@ const AlertDrawer = ({ alert, flowPacket, evidence, contextStatus, onClose }: Al
)} {unknownCount > 0 ? ( -

+{unknownCount} evidence refs unresolved in persisted context.

+

+ +{unknownCount} evidence refs unresolved in persisted context. +

) : null} {missingRefs.length > 0 ? (

Missing refs: {missingRefs.slice(0, 4).join(", ")}

@@ -4979,7 +5025,9 @@ const NewsDrawer = ({ story, onClose }: NewsDrawerProps) => {

{story.headline}

{story.source} · Published {formatDateTime(story.published_ts)} - {story.updated_ts !== story.published_ts ? ` · Updated ${formatDateTime(story.updated_ts)}` : ""} + {story.updated_ts !== story.published_ts + ? ` · Updated ${formatDateTime(story.updated_ts)}` + : ""}

@@ -5384,13 +5443,19 @@ const useTerminalState = () => { const [selectedAlert, setSelectedAlert] = useState(null); const [selectedNewsStory, setSelectedNewsStory] = useState(null); const [selectedDarkEvent, setSelectedDarkEvent] = useState(null); - const [selectedClassifierHit, setSelectedClassifierHit] = useState(null); - const [selectedSmartMoneyEvent, setSelectedSmartMoneyEvent] = useState(null); + const [selectedClassifierHit, setSelectedClassifierHit] = useState( + null + ); + const [selectedSmartMoneyEvent, setSelectedSmartMoneyEvent] = useState( + null + ); const [selectedInstrument, setSelectedInstrument] = useState(null); const [optionFocusSeed, setOptionFocusSeed] = useState | null>(null); const [equityFocusSeed, setEquityFocusSeed] = useState | null>(null); const [filterInput, setFilterInput] = useState(""); - const [flowFilters, setFlowFilters] = useState(() => buildDefaultFlowFilters()); + const [flowFilters, setFlowFilters] = useState(() => + buildDefaultFlowFilters() + ); const [chartIntervalMs, setChartIntervalMs] = useState(CANDLE_INTERVALS[0].ms); const activeTickers = useMemo(() => parseTickerFilterInput(filterInput), [filterInput]); const tickerSet = useMemo(() => new Set(activeTickers), [activeTickers]); @@ -5398,8 +5463,9 @@ const useTerminalState = () => { const isOptionContractFocused = selectedInstrument?.kind === "option-contract"; const focusedOptionContractId = selectedInstrument?.kind === "option-contract" ? selectedInstrument.contractId : null; - const optionFocusScopeKey = - focusedOptionContractId ? `option-contract:${focusedOptionContractId}` : null; + const optionFocusScopeKey = focusedOptionContractId + ? `option-contract:${focusedOptionContractId}` + : null; const equityFocusScopeKey = selectedInstrument?.kind === "equity" ? `equity:${selectedInstrument.underlyingId.toUpperCase()}` @@ -5414,7 +5480,12 @@ const useTerminalState = () => { ); const equityScope = useMemo( () => ({ - underlying_ids: activeTickers.length > 0 ? activeTickers : instrumentUnderlying ? [instrumentUnderlying] : undefined + underlying_ids: + activeTickers.length > 0 + ? activeTickers + : instrumentUnderlying + ? [instrumentUnderlying] + : undefined }), [activeTickers, instrumentUnderlying] ); @@ -5479,7 +5550,13 @@ const useTerminalState = () => { }, [mode]); useEffect(() => { - if (!selectedAlert && !selectedNewsStory && !selectedClassifierHit && !selectedDarkEvent && !selectedSmartMoneyEvent) { + if ( + !selectedAlert && + !selectedNewsStory && + !selectedClassifierHit && + !selectedDarkEvent && + !selectedSmartMoneyEvent + ) { return; } @@ -5511,7 +5588,13 @@ const useTerminalState = () => { document.removeEventListener("mousedown", handlePointerDown); document.removeEventListener("keydown", handleKeyDown); }; - }, [selectedAlert, selectedNewsStory, selectedClassifierHit, selectedDarkEvent, selectedSmartMoneyEvent]); + }, [ + selectedAlert, + selectedNewsStory, + selectedClassifierHit, + selectedDarkEvent, + selectedSmartMoneyEvent + ]); const optionsScroll = useListScroll(); const equitiesScroll = useListScroll(); @@ -5525,10 +5608,7 @@ const useTerminalState = () => { const flowAnchor = useScrollAnchor(flowScroll.listRef, flowScroll.isAtTopRef); const darkAnchor = useScrollAnchor(darkScroll.listRef, darkScroll.isAtTopRef); const alertsAnchor = useScrollAnchor(alertsScroll.listRef, alertsScroll.isAtTopRef); - const classifierAnchor = useScrollAnchor( - classifierScroll.listRef, - classifierScroll.isAtTopRef - ); + const classifierAnchor = useScrollAnchor(classifierScroll.listRef, classifierScroll.isAtTopRef); const disableReplayGrouping = useCallback(() => null, []); const optionQueryParams = useMemo>( () => buildOptionTapeQueryParams(effectiveOptionPrintFilters, optionScope), @@ -5664,12 +5744,18 @@ const useTerminalState = () => { getReplayKey: disableReplayGrouping }); - const optionsChannelStatus = getHotChannelFeedStatus(liveSession.status, liveSession.channelHealth.options); + const optionsChannelStatus = getHotChannelFeedStatus( + liveSession.status, + liveSession.channelHealth.options + ); const equitiesChannelStatus = getHotChannelFeedStatus( liveSession.status, liveSession.channelHealth.equities ); - const flowChannelStatus = getHotChannelFeedStatus(liveSession.status, liveSession.channelHealth.flow); + const flowChannelStatus = getHotChannelFeedStatus( + liveSession.status, + liveSession.channelHealth.flow + ); const liveOptions = usePausableTapeView({ enabled: mode === "live", @@ -5725,8 +5811,7 @@ const useTerminalState = () => { [equityFocusScopeKey, equityFocusSeed, liveEquities.historyItems, liveEquities.liveItems] ); - const optionsFeed = - mode === "live" ? { ...liveOptions, items: seededLiveOptionsItems } : options; + const optionsFeed = mode === "live" ? { ...liveOptions, items: seededLiveOptionsItems } : options; const nbboFeed = mode === "live" ? toStaticTapeState( @@ -5868,10 +5953,12 @@ const useTerminalState = () => { error: null }); const [optionSupportSmartMoney, setOptionSupportSmartMoney] = useState([]); - const [optionSupportClassifierHits, setOptionSupportClassifierHits] = useState([]); - const [historicalNbboByTraceId, setHistoricalNbboByTraceId] = useState>( - () => new Map() - ); + const [optionSupportClassifierHits, setOptionSupportClassifierHits] = useState< + ClassifierHitEvent[] + >([]); + const [historicalNbboByTraceId, setHistoricalNbboByTraceId] = useState< + Map + >(() => new Map()); const resolvedOptionPrintMap = useMemo(() => { const merged = new Map(); @@ -6365,11 +6452,16 @@ const useTerminalState = () => { } return { kind: "unknown", id }; }); - }, [resolvedFlowPacketMap, resolvedOptionPrintMap, selectedClassifierHit, selectedClassifierPacketId]); + }, [ + resolvedFlowPacketMap, + resolvedOptionPrintMap, + selectedClassifierHit, + selectedClassifierPacketId + ]); const selectedSmartMoneyFlowPacket = useMemo(() => { const packetId = selectedSmartMoneyEvent?.packet_ids[0]; - return packetId ? resolvedFlowPacketMap.get(packetId) ?? null : null; + return packetId ? (resolvedFlowPacketMap.get(packetId) ?? null) : null; }, [resolvedFlowPacketMap, selectedSmartMoneyEvent]); const selectedSmartMoneyEvidence = useMemo((): EvidenceItem[] => { @@ -6390,12 +6482,16 @@ const useTerminalState = () => { return; } - const missingPacketIds = selectedSmartMoneyEvent.packet_ids.filter((id) => !resolvedFlowPacketMap.has(id)); + const missingPacketIds = selectedSmartMoneyEvent.packet_ids.filter( + (id) => !resolvedFlowPacketMap.has(id) + ); if (missingPacketIds.length > 0) { incrementRetentionMetric("pinnedFetchMisses", missingPacketIds.length); void Promise.all( missingPacketIds.map(async (packetId) => { - const response = await fetch(buildApiUrl(`/flow/packets/${encodeURIComponent(packetId)}`)); + const response = await fetch( + buildApiUrl(`/flow/packets/${encodeURIComponent(packetId)}`) + ); if (!response.ok) { throw new Error(await readErrorDetail(response)); } @@ -6420,7 +6516,9 @@ const useTerminalState = () => { }); } - const missingPrintIds = selectedSmartMoneyEvent.member_print_ids.filter((id) => !resolvedOptionPrintMap.has(id)); + const missingPrintIds = selectedSmartMoneyEvent.member_print_ids.filter( + (id) => !resolvedOptionPrintMap.has(id) + ); if (missingPrintIds.length === 0) { return; } @@ -6475,7 +6573,12 @@ const useTerminalState = () => { return null; }, - [extractPacketContract, extractUnderlyingFromTrace, resolvedFlowPacketMap, resolvedOptionPrintMap] + [ + extractPacketContract, + extractUnderlyingFromTrace, + resolvedFlowPacketMap, + resolvedOptionPrintMap + ] ); const matchesTicker = useCallback( @@ -6510,7 +6613,9 @@ const useTerminalState = () => { const filteredEquities = useMemo(() => { if (tickerSet.size === 0) { if (instrumentUnderlying) { - return equitiesFeed.items.filter((print) => print.underlying_id.toUpperCase() === instrumentUnderlying); + return equitiesFeed.items.filter( + (print) => print.underlying_id.toUpperCase() === instrumentUnderlying + ); } return equitiesFeed.items; } @@ -6548,7 +6653,11 @@ const useTerminalState = () => { setEquityFocusSeed(null); return; } - const composedBaseItems = composeTapeItems([], liveEquities.liveItems ?? [], liveEquities.historyItems ?? []); + const composedBaseItems = composeTapeItems( + [], + liveEquities.liveItems ?? [], + liveEquities.historyItems ?? [] + ); const liveKeys = new Set(composedBaseItems.map((item) => getTapeItemKey(item))); if (equityFocusSeed.items.every((item) => liveKeys.has(getTapeItemKey(item)))) { setEquityFocusSeed(null); @@ -6559,7 +6668,11 @@ const useTerminalState = () => { (print: OptionPrint) => { const contractId = normalizeContractId(print.option_contract_id); const parsed = parseOptionContractId(contractId); - const underlyingId = (print.underlying_id ?? parsed?.root ?? extractUnderlying(contractId)).toUpperCase(); + const underlyingId = ( + print.underlying_id ?? + parsed?.root ?? + extractUnderlying(contractId) + ).toUpperCase(); const scopeKey = `option-contract:${contractId}`; const subscriptionKey = getLiveSubscriptionKey({ channel: "options", @@ -6568,7 +6681,9 @@ const useTerminalState = () => { }); const seedItems = composeTapeItems( [print], - filteredOptions.filter((candidate) => normalizeContractId(candidate.option_contract_id) === contractId), + filteredOptions.filter( + (candidate) => normalizeContractId(candidate.option_contract_id) === contractId + ), [] ); setOptionFocusSeed({ scopeKey, subscriptionKey, items: seedItems }); @@ -6593,7 +6708,9 @@ const useTerminalState = () => { const scopeKey = `equity:${underlyingId}`; const seedItems = composeTapeItems( [print], - filteredEquities.filter((candidate) => candidate.underlying_id.toUpperCase() === underlyingId), + filteredEquities.filter( + (candidate) => candidate.underlying_id.toUpperCase() === underlyingId + ), [] ); setEquityFocusSeed({ scopeKey, items: seedItems }); @@ -6707,7 +6824,9 @@ const useTerminalState = () => { if (tickerSet.size === 0) { return newsFeed.items; } - return newsFeed.items.filter((story) => story.resolved_symbols.some((symbol) => matchesTicker(symbol))); + return newsFeed.items.filter((story) => + story.resolved_symbols.some((symbol) => matchesTicker(symbol)) + ); }, [matchesTicker, newsFeed.items, routeFeatures.news, routeFeatures.showNewsPane, tickerSet]); const visibleAlerts = useMemo(() => { @@ -6731,7 +6850,11 @@ const useTerminalState = () => { }, [visibleAlerts]); useEffect(() => { - if (!routeFeatures.needsAlertEvidencePrefetch || mode !== "live" || visibleAlerts.length === 0) { + if ( + !routeFeatures.needsAlertEvidencePrefetch || + mode !== "live" || + visibleAlerts.length === 0 + ) { return; } @@ -6744,7 +6867,9 @@ const useTerminalState = () => { incrementRetentionMetric("pinnedFetchMisses", missingPacketIds.length); void Promise.all( missingPacketIds.map(async (packetId) => { - const response = await fetch(buildApiUrl(`/flow/packets/${encodeURIComponent(packetId)}`)); + const response = await fetch( + buildApiUrl(`/flow/packets/${encodeURIComponent(packetId)}`) + ); if (!response.ok) { throw new Error(await readErrorDetail(response)); } @@ -6855,7 +6980,12 @@ const useTerminalState = () => { keys.add(id); } return keys; - }, [selectedAlert, selectedClassifierFlowPacket, selectedSmartMoneyEvent, visibleAlertEvidenceRefs]); + }, [ + selectedAlert, + selectedClassifierFlowPacket, + selectedSmartMoneyEvent, + visibleAlertEvidenceRefs + ]); const activePinnedJoinKeys = useMemo(() => { const keys = new Set(); @@ -6974,7 +7104,8 @@ const useTerminalState = () => { const desiredTrace = `alert:${packetId}`; return ( alertsFeed.items.find( - (item) => item.trace_id === desiredTrace || getAlertFlowPacketRefs(item).includes(packetId) + (item) => + item.trace_id === desiredTrace || getAlertFlowPacketRefs(item).includes(packetId) ) ?? null ); }, @@ -7045,15 +7176,20 @@ const useTerminalState = () => { if (routeFeatures.alerts || routeFeatures.showAlertsPane) { updates.push(alertsFeed.lastUpdate); } - if (routeFeatures.smartMoney || routeFeatures.showClassifierPane || routeFeatures.showChartPane || routeFeatures.showFocusPane) { + if ( + routeFeatures.smartMoney || + routeFeatures.showClassifierPane || + routeFeatures.showChartPane || + routeFeatures.showFocusPane + ) { updates.push(smartMoneyFeed.lastUpdate); } if (routeFeatures.classifierHits || routeFeatures.showClassifierPane) { updates.push(classifierHitsFeed.lastUpdate); } - return updates - .filter((value): value is number => value !== null) - .sort((a, b) => b - a)[0] ?? null; + return ( + updates.filter((value): value is number => value !== null).sort((a, b) => b - a)[0] ?? null + ); }, [ routeFeatures.options, routeFeatures.showOptionsPane, @@ -7212,13 +7348,7 @@ type FlowFilterPopoverProps = { onChange: Dispatch>; }; -const FlowFilterSection = ({ - title, - children -}: { - title: string; - children: ReactNode; -}) => { +const FlowFilterSection = ({ title, children }: { title: string; children: ReactNode }) => { return (
{title}
@@ -7265,7 +7395,8 @@ export const FlowFilterPopover = ({ filters, onChange }: FlowFilterPopoverProps) onChange((prev) => ({ ...prev, view, - securityTypes: view === "raw" ? undefined : prev.securityTypes ?? DEFAULT_FLOW_SECURITY_TYPES, + securityTypes: + view === "raw" ? undefined : (prev.securityTypes ?? DEFAULT_FLOW_SECURITY_TYPES), nbboSides: view === "raw" ? undefined : prev.nbboSides, optionTypes: view === "raw" ? undefined : prev.optionTypes, minNotional: view === "raw" ? undefined : prev.minNotional @@ -7316,11 +7447,7 @@ export const FlowFilterPopover = ({ filters, onChange }: FlowFilterPopoverProps) {open ? ( -
+
Flow Filters
@@ -7488,16 +7615,25 @@ type OptionsPaneProps = { const OptionsPane = memo(({ state, limit }: OptionsPaneProps) => { const items = limit ? state.filteredOptions.slice(0, limit) : state.filteredOptions; - const virtual = useTapeVirtualList(items, state.optionsScroll.listRef, getTapeVirtualConfig("options")); + const virtual = useTapeVirtualList( + items, + state.optionsScroll.listRef, + getTapeVirtualConfig("options") + ); const optionHistorySubscription = state.liveSession.manifest.find( (subscription) => subscription.channel === "options" ); - const optionHistoryKey = optionHistorySubscription ? getLiveSubscriptionKey(optionHistorySubscription) : null; + const optionHistoryKey = optionHistorySubscription + ? getLiveSubscriptionKey(optionHistorySubscription) + : null; const optionHistoryError = optionHistoryKey ? state.liveSession.historyErrors[optionHistoryKey] : null; - useVirtualHistoryGate(state.mode === "live" && !limit, items.length, virtual.virtualItems.at(-1)?.index ?? -1, () => - void state.liveSession.loadOlder("options") + useVirtualHistoryGate( + state.mode === "live" && !limit, + items.length, + virtual.virtualItems.at(-1)?.index ?? -1, + () => void state.liveSession.loadOlder("options") ); return ( @@ -7572,7 +7708,9 @@ const OptionsPane = memo(({ state, limit }: OptionsPaneProps) => { const contractId = normalizeContractId(print.option_contract_id); const parsed = parseOptionContractId(contractId); const contractDisplay = formatOptionContractLabel(contractId); - const quote = state.historicalNbboByTraceId.get(print.trace_id) ?? state.nbboMap.get(contractId); + const quote = + state.historicalNbboByTraceId.get(print.trace_id) ?? + state.nbboMap.get(contractId); const hasPreservedNbbo = typeof print.execution_nbbo_side === "string"; const nbboSide = print.execution_nbbo_side ?? @@ -7602,42 +7740,72 @@ const OptionsPane = memo(({ state, limit }: OptionsPaneProps) => { }; const cells = ( <> - {formatTime(print.ts)} + + {formatTime(print.ts)} + - - - - - {typeof spot === "number" ? formatPrice(spot) : "--"} + + {typeof spot === "number" ? formatPrice(spot) : "--"} + {formatSize(print.size)}@{formatPrice(print.price)}_{nbboSide ?? "--"} {print.option_type ?? "--"} - ${formatCompactUsd(notional)} + + ${formatCompactUsd(notional)} + {nbboSide ? ( - {nbboSide} + + {nbboSide} + ) : ( "--" )} - {typeof iv === "number" ? formatPct(iv) : "--"} - {decor ? humanizeClassifierId(decor.family) : "--"} + + {typeof iv === "number" ? formatPct(iv) : "--"} + + + {decor ? humanizeClassifierId(decor.family) : "--"} + ); @@ -7689,9 +7857,16 @@ type EquitiesPaneProps = { const EquitiesPane = memo(({ state, limit }: EquitiesPaneProps) => { const items = limit ? state.filteredEquities.slice(0, limit) : state.filteredEquities; - const virtual = useTapeVirtualList(items, state.equitiesScroll.listRef, getTapeVirtualConfig("equities")); - useVirtualHistoryGate(state.mode === "live" && !limit, items.length, virtual.virtualItems.at(-1)?.index ?? -1, () => - void state.liveSession.loadOlder("equities") + const virtual = useTapeVirtualList( + items, + state.equitiesScroll.listRef, + getTapeVirtualConfig("equities") + ); + useVirtualHistoryGate( + state.mode === "live" && !limit, + items.length, + virtual.virtualItems.at(-1)?.index ?? -1, + () => void state.liveSession.loadOlder("equities") ); return ( @@ -7759,7 +7934,9 @@ const EquitiesPane = memo(({ state, limit }: EquitiesPaneProps) => { data-tape-key={key} style={{ transform: `translateY(${start}px)` }} > - {formatTime(print.ts)} + + {formatTime(print.ts)} + - ${formatPrice(print.price)} - {formatSize(print.size)}x + + ${formatPrice(print.price)} + + + {formatSize(print.size)}x + {print.exchange} - {print.offExchangeFlag ? "Off-Ex" : "Lit"} + + {print.offExchangeFlag ? "Off-Ex" : "Lit"} +
))}
@@ -7794,8 +7977,11 @@ type FlowPaneProps = { const FlowPane = memo(({ state, limit, title = "Flow" }: FlowPaneProps) => { const items = limit ? state.filteredFlow.slice(0, limit) : state.filteredFlow; const virtual = useTapeVirtualList(items, state.flowScroll.listRef, getTapeVirtualConfig("flow")); - useVirtualHistoryGate(state.mode === "live" && !limit, items.length, virtual.virtualItems.at(-1)?.index ?? -1, () => - void state.liveSession.loadOlder("flow") + useVirtualHistoryGate( + state.mode === "live" && !limit, + items.length, + virtual.virtualItems.at(-1)?.index ?? -1, + () => void state.liveSession.loadOlder("flow") ); return ( @@ -7866,18 +8052,26 @@ const FlowPane = memo(({ state, limit, title = "Flow" }: FlowPaneProps) => { typeof features.structure_type === "string" ? features.structure_type : ""; const structureLegs = parseNumber(features.structure_legs, 0); const structureRights = - typeof features.structure_rights === "string" ? features.structure_rights : ""; + typeof features.structure_rights === "string" + ? features.structure_rights + : ""; const structureStrikes = parseNumber(features.structure_strikes, 0); const nbboBid = parseNumber(features.nbbo_bid, Number.NaN); const nbboAsk = parseNumber(features.nbbo_ask, Number.NaN); const nbboMid = parseNumber(features.nbbo_mid, Number.NaN); const nbboSpread = parseNumber(features.nbbo_spread, Number.NaN); - const aggressiveBuyRatio = parseNumber(features.nbbo_aggressive_buy_ratio, Number.NaN); + const aggressiveBuyRatio = parseNumber( + features.nbbo_aggressive_buy_ratio, + Number.NaN + ); const aggressiveSellRatio = parseNumber( features.nbbo_aggressive_sell_ratio, Number.NaN ); - const aggressiveCoverage = parseNumber(features.nbbo_coverage_ratio, Number.NaN); + const aggressiveCoverage = parseNumber( + features.nbbo_coverage_ratio, + Number.NaN + ); const insideRatio = parseNumber(features.nbbo_inside_ratio, Number.NaN); const nbboAge = parseNumber(packet.join_quality.nbbo_age_ms, Number.NaN); const nbboStale = parseNumber(packet.join_quality.nbbo_stale, 0) > 0; @@ -7885,21 +8079,26 @@ const FlowPane = memo(({ state, limit, title = "Flow" }: FlowPaneProps) => { const structureLabel = structureType ? `${structureType.replace(/_/g, " ")}${structureRights ? ` ${structureRights}` : ""}${structureLegs > 0 ? ` ${structureLegs}L` : ""}${structureStrikes > 0 ? ` ${structureStrikes}K` : ""}` : "--"; - const nbboLabel = Number.isFinite(nbboBid) && Number.isFinite(nbboAsk) - ? `${formatPrice(nbboBid)} x ${formatPrice(nbboAsk)}` - : Number.isFinite(nbboMid) - ? `Mid ${formatPrice(nbboMid)}` - : "--"; + const nbboLabel = + Number.isFinite(nbboBid) && Number.isFinite(nbboAsk) + ? `${formatPrice(nbboBid)} x ${formatPrice(nbboAsk)}` + : Number.isFinite(nbboMid) + ? `Mid ${formatPrice(nbboMid)}` + : "--"; const qualityLabel = [ Number.isFinite(aggressiveCoverage) && aggressiveCoverage > 0 ? `Agg ${formatPct(aggressiveBuyRatio)}/${formatPct(aggressiveSellRatio)} ${formatPct(aggressiveCoverage)} cov` : null, - Number.isFinite(insideRatio) && insideRatio > 0 ? `In ${formatPct(insideRatio)}` : null, + Number.isFinite(insideRatio) && insideRatio > 0 + ? `In ${formatPct(insideRatio)}` + : null, Number.isFinite(nbboSpread) ? `Spr ${formatPrice(nbboSpread)}` : null, Number.isFinite(nbboAge) ? `${Math.round(nbboAge)}ms` : null, nbboStale ? "Stale" : null, nbboMissing ? "Missing" : null - ].filter(Boolean).join(" | "); + ] + .filter(Boolean) + .join(" | "); return (
{ data-tape-key={key} style={{ transform: `translateY(${start}px)` }} > - {formatTime(startTs)} → {formatTime(endTs)} + + {formatTime(startTs)} → {formatTime(endTs)} + {contract} - {formatFlowMetric(count)} - {formatFlowMetric(totalSize)} - ${formatUsd(notional)} - {windowMs > 0 ? formatFlowMetric(windowMs, "ms") : "--"} + + {formatFlowMetric(count)} + + + {formatFlowMetric(totalSize)} + + + ${formatUsd(notional)} + + + {windowMs > 0 ? formatFlowMetric(windowMs, "ms") : "--"} + {structureLabel} {nbboLabel} {qualityLabel || "--"} @@ -7942,9 +8151,16 @@ type AlertsPaneProps = { const AlertsPane = memo(({ state, limit, withStrip = false, className }: AlertsPaneProps) => { const items = limit ? state.filteredAlerts.slice(0, limit) : state.filteredAlerts; - const virtual = useTapeVirtualList(items, state.alertsScroll.listRef, getTapeVirtualConfig("alerts")); - useVirtualHistoryGate(state.mode === "live" && !limit, items.length, virtual.virtualItems.at(-1)?.index ?? -1, () => - void state.liveSession.loadOlder("alerts") + const virtual = useTapeVirtualList( + items, + state.alertsScroll.listRef, + getTapeVirtualConfig("alerts") + ); + useVirtualHistoryGate( + state.mode === "live" && !limit, + items.length, + virtual.virtualItems.at(-1)?.index ?? -1, + () => void state.liveSession.loadOlder("alerts") ); return ( @@ -8020,13 +8236,23 @@ const AlertsPane = memo(({ state, limit, withStrip = false, className }: AlertsP state.setSelectedAlert(alert); }} > - {formatTime(alert.source_ts)} - {primary ? humanizeClassifierId(primary.classifier_id) : "Alert"} + + {formatTime(alert.source_ts)} + + + {primary ? humanizeClassifierId(primary.classifier_id) : "Alert"} + {severity} - {Math.round(alert.score)} - {alert.hits.length} + + {Math.round(alert.score)} + + + {alert.hits.length} + {direction} - {primary?.explanations?.[0] ?? "--"} + + {primary?.explanations?.[0] ?? "--"} + ); })} @@ -8068,7 +8294,11 @@ const NewsPane = memo(({ state, limit, className }: NewsPaneProps) => { } actions={ canLoadOlder ? ( - ) : null @@ -8078,7 +8308,9 @@ const NewsPane = memo(({ state, limit, className }: NewsPaneProps) => {
News is live-only in v1.
) : items.length === 0 ? (
- {state.tickerSet.size > 0 ? "No news stories match the current filter." : "Waiting for live news stories."} + {state.tickerSet.size > 0 + ? "No news stories match the current filter." + : "Waiting for live news stories."}
) : (
@@ -8124,7 +8356,9 @@ type ClassifierPaneProps = { }; const ClassifierPane = memo(({ state, limit, className }: ClassifierPaneProps) => { - const smartMoneyItems = limit ? state.filteredSmartMoneyEvents.slice(0, limit) : state.filteredSmartMoneyEvents; + const smartMoneyItems = limit + ? state.filteredSmartMoneyEvents.slice(0, limit) + : state.filteredSmartMoneyEvents; const legacyItems = smartMoneyItems.length === 0 ? limit @@ -8133,11 +8367,20 @@ const ClassifierPane = memo(({ state, limit, className }: ClassifierPaneProps) = : []; const items: Array = smartMoneyItems.length > 0 ? smartMoneyItems : legacyItems; - const virtual = useTapeVirtualList(items, state.classifierScroll.listRef, getTapeVirtualConfig("classifier")); - useVirtualHistoryGate(state.mode === "live" && !limit, items.length, virtual.virtualItems.at(-1)?.index ?? -1, () => { - void state.liveSession.loadOlder("smart-money"); - void state.liveSession.loadOlder("classifier-hits"); - }); + const virtual = useTapeVirtualList( + items, + state.classifierScroll.listRef, + getTapeVirtualConfig("classifier") + ); + useVirtualHistoryGate( + state.mode === "live" && !limit, + items.length, + virtual.virtualItems.at(-1)?.index ?? -1, + () => { + void state.liveSession.loadOlder("smart-money"); + void state.liveSession.loadOlder("classifier-hits"); + } + ); const showingSmartMoney = smartMoneyItems.length > 0; return ( @@ -8177,7 +8420,11 @@ const ClassifierPane = memo(({ state, limit, className }: ClassifierPaneProps) =
) : (
-
+
TIME PROFILE @@ -8187,60 +8434,75 @@ const ClassifierPane = memo(({ state, limit, className }: ClassifierPaneProps) =
- {showingSmartMoney ? virtual.virtualItems.map(({ item, key, index, start, size }) => { - const event = item as SmartMoneyEvent; - const primaryScore = - event.profile_scores.find((score) => score.profile_id === event.primary_profile_id) ?? - event.profile_scores[0]; - const direction = normalizeDirection(event.primary_direction); - return ( - - ); - }) : virtual.virtualItems.map(({ item, key, index, start, size }) => { - const hit = item as ClassifierHitEvent; - const direction = normalizeDirection(hit.direction); - return ( - - ); - })} + {showingSmartMoney + ? virtual.virtualItems.map(({ item, key, index, start, size }) => { + const event = item as SmartMoneyEvent; + const primaryScore = + event.profile_scores.find( + (score) => score.profile_id === event.primary_profile_id + ) ?? event.profile_scores[0]; + const direction = normalizeDirection(event.primary_direction); + return ( + + ); + }) + : virtual.virtualItems.map(({ item, key, index, start, size }) => { + const hit = item as ClassifierHitEvent; + const direction = normalizeDirection(hit.direction); + return ( + + ); + })}
@@ -8260,8 +8522,11 @@ type DarkPaneProps = { const DarkPane = memo(({ state, limit, className }: DarkPaneProps) => { const items = limit ? state.filteredInferredDark.slice(0, limit) : state.filteredInferredDark; const virtual = useTapeVirtualList(items, state.darkScroll.listRef, getTapeVirtualConfig("dark")); - useVirtualHistoryGate(state.mode === "live" && !limit, items.length, virtual.virtualItems.at(-1)?.index ?? -1, () => - void state.liveSession.loadOlder("inferred-dark") + useVirtualHistoryGate( + state.mode === "live" && !limit, + items.length, + virtual.virtualItems.at(-1)?.index ?? -1, + () => void state.liveSession.loadOlder("inferred-dark") ); return ( @@ -8334,12 +8599,20 @@ const DarkPane = memo(({ state, limit, className }: DarkPaneProps) => { state.setSelectedDarkEvent(event); }} > - {formatTime(event.source_ts)} + + {formatTime(event.source_ts)} + {humanizeClassifierId(event.type)} {underlying ?? "Unknown"} - {formatConfidence(event.confidence)} - {evidenceCount} - {underlying ? "--" : "Underlying not in current join cache."} + + {formatConfidence(event.confidence)} + + + {evidenceCount} + + + {underlying ? "--" : "Underlying not in current join cache."} + ); })} @@ -8359,7 +8632,6 @@ type ChartPaneProps = { }; const ChartPane = memo(({ state, title = "Chart" }: ChartPaneProps) => { - return ( { } for (const print of state.filteredOptions.slice(0, 80)) { const parsed = parseOptionContractId(normalizeContractId(print.option_contract_id)); - const symbol = (print.underlying_id ?? parsed?.root ?? extractUnderlying(print.option_contract_id))?.toUpperCase(); + const symbol = ( + print.underlying_id ?? + parsed?.root ?? + extractUnderlying(print.option_contract_id) + )?.toUpperCase(); if (symbol) { symbols.add(symbol); } @@ -8432,31 +8708,39 @@ const buildCommandDeckTickers = (state: TerminalState): CommandDeckTicker[] => { symbols.add(state.chartTicker.toUpperCase()); } - return Array.from(symbols).slice(0, 10).map((symbol) => { - const equityPrints = state.filteredEquities - .filter((print) => print.underlying_id.toUpperCase() === symbol) - .slice(0, 2); - const price = equityPrints[0]?.price ?? null; - const previous = equityPrints[1]?.price ?? null; - const move = price !== null && previous !== null && previous !== 0 ? (price - previous) / previous : null; - const options = state.filteredOptions - .slice(0, 120) - .filter((print) => { + return Array.from(symbols) + .slice(0, 10) + .map((symbol) => { + const equityPrints = state.filteredEquities + .filter((print) => print.underlying_id.toUpperCase() === symbol) + .slice(0, 2); + const price = equityPrints[0]?.price ?? null; + const previous = equityPrints[1]?.price ?? null; + const move = + price !== null && previous !== null && previous !== 0 + ? (price - previous) / previous + : null; + const options = state.filteredOptions.slice(0, 120).filter((print) => { const parsed = parseOptionContractId(normalizeContractId(print.option_contract_id)); - const underlying = (print.underlying_id ?? parsed?.root ?? extractUnderlying(print.option_contract_id))?.toUpperCase(); + const underlying = ( + print.underlying_id ?? + parsed?.root ?? + extractUnderlying(print.option_contract_id) + )?.toUpperCase(); return underlying === symbol; }).length; - const alerts = state.filteredAlerts - .slice(0, 80) - .filter((alert) => alert.trace_id.toUpperCase().includes(symbol)).length; - return { symbol, price, move, options, alerts }; - }); + const alerts = state.filteredAlerts + .slice(0, 80) + .filter((alert) => alert.trace_id.toUpperCase().includes(symbol)).length; + return { symbol, price, move, options, alerts }; + }); }; const CommandDeckHeader = ({ state }: { state: TerminalState }) => { const focus = state.activeTickers.length > 0 ? state.activeTickers.join(", ") : state.chartTicker; const selected = state.selectedInstrumentLabel ?? "No contract lock"; - const connectionLabel = state.mode === "live" ? statusLabel(state.liveSession.status, false, state.mode) : "Replay"; + const connectionLabel = + state.mode === "live" ? statusLabel(state.liveSession.status, false, state.mode) : "Replay"; return (
@@ -8476,7 +8760,9 @@ const CommandDeckHeader = ({ state }: { state: TerminalState }) => { {state.mode === "live" ? "Live" : "Replay"}: {connectionLabel} - Last {state.lastSeen ? formatTime(state.lastSeen) : "waiting"} + + Last {state.lastSeen ? formatTime(state.lastSeen) : "waiting"} + @@ -8493,16 +8779,22 @@ const TickerRail = ({ state }: { state: TerminalState }) => {
{tickers.map((ticker) => { const direction = ticker.move === null ? "flat" : ticker.move >= 0 ? "up" : "down"; - const equity = state.filteredEquities.find((print) => print.underlying_id.toUpperCase() === ticker.symbol); + const equity = state.filteredEquities.find( + (print) => print.underlying_id.toUpperCase() === ticker.symbol + ); return (
Cursor - {replayTime ? formatTime(replayTime) : state.lastSeen ? formatTime(state.lastSeen) : "waiting"} + + {replayTime + ? formatTime(replayTime) + : state.lastSeen + ? formatTime(state.lastSeen) + : "waiting"} +
Chart - {state.chartTicker} / {formatIntervalLabel(state.chartIntervalMs)} + + {state.chartTicker} / {formatIntervalLabel(state.chartIntervalMs)} +
Scope - {state.activeTickers.length > 0 ? state.activeTickers.join(", ") : "All symbols"} + + {state.activeTickers.length > 0 ? state.activeTickers.join(", ") : "All symbols"} +
@@ -8696,7 +9016,9 @@ const FocusPane = memo(({ state }: { state: TerminalState }) => {
{smartMoneyProfileLabel(hit.primary_profile_id)}
- + {normalizeDirection(hit.primary_direction)} {formatTime(hit.source_ts)} @@ -8744,7 +9066,11 @@ const ReplayConsole = memo(({ state }: { state: TerminalState }) => { + } @@ -8900,9 +9226,7 @@ function SyntheticControlDock() { const disabled = !status?.enabled; const derived = status?.derived; - const updateControl = ( - patch: SyntheticControlPatch - ) => { + const updateControl = (patch: SyntheticControlPatch) => { dirtyRef.current = true; setDraft((current) => createSyntheticControlDraft(current ?? buildDefaultSyntheticControl(), patch) @@ -8989,9 +9313,7 @@ function SyntheticControlDock() {