rebuild terminal routes around dense command views
This commit is contained in:
parent
f9f732c3d1
commit
0320533628
6 changed files with 2279 additions and 248 deletions
|
|
@ -139,7 +139,7 @@ const LIVE_SESSION_HOT_CHANNELS = new Set<LiveSubscription["channel"]>([
|
|||
"equity-overlay"
|
||||
]);
|
||||
|
||||
type TapeVirtualPane = "options" | "equities" | "flow" | "alerts" | "classifier" | "dark";
|
||||
type TapeVirtualPane = "options" | "equities" | "flow" | "alerts" | "classifier" | "dark" | "news";
|
||||
|
||||
type TapeVirtualListConfig = {
|
||||
rowHeight: number;
|
||||
|
|
@ -153,7 +153,8 @@ const TAPE_VIRTUAL_CONFIG: Record<TapeVirtualPane, TapeVirtualListConfig> = {
|
|||
flow: { rowHeight: 44, overscan: 24, debugLabel: "flow" },
|
||||
alerts: { rowHeight: 44, overscan: 24, debugLabel: "alerts" },
|
||||
classifier: { rowHeight: 44, overscan: 24, debugLabel: "classifier" },
|
||||
dark: { rowHeight: 44, overscan: 24, debugLabel: "dark" }
|
||||
dark: { rowHeight: 44, overscan: 24, debugLabel: "dark" },
|
||||
news: { rowHeight: 52, overscan: 28, debugLabel: "news" }
|
||||
};
|
||||
|
||||
export const getTapeVirtualConfig = (pane: TapeVirtualPane): TapeVirtualListConfig =>
|
||||
|
|
@ -5602,6 +5603,7 @@ const useTerminalState = () => {
|
|||
const darkScroll = useListScroll();
|
||||
const alertsScroll = useListScroll();
|
||||
const classifierScroll = useListScroll();
|
||||
const newsScroll = useListScroll();
|
||||
|
||||
const optionsAnchor = useScrollAnchor(optionsScroll.listRef, optionsScroll.isAtTopRef);
|
||||
const equitiesAnchor = useScrollAnchor(equitiesScroll.listRef, equitiesScroll.isAtTopRef);
|
||||
|
|
@ -5609,6 +5611,7 @@ const useTerminalState = () => {
|
|||
const darkAnchor = useScrollAnchor(darkScroll.listRef, darkScroll.isAtTopRef);
|
||||
const alertsAnchor = useScrollAnchor(alertsScroll.listRef, alertsScroll.isAtTopRef);
|
||||
const classifierAnchor = useScrollAnchor(classifierScroll.listRef, classifierScroll.isAtTopRef);
|
||||
const newsAnchor = useScrollAnchor(newsScroll.listRef, newsScroll.isAtTopRef);
|
||||
const disableReplayGrouping = useCallback(() => null, []);
|
||||
const optionQueryParams = useMemo<Record<string, string | undefined>>(
|
||||
() => buildOptionTapeQueryParams(effectiveOptionPrintFilters, optionScope),
|
||||
|
|
@ -5791,6 +5794,18 @@ const useTerminalState = () => {
|
|||
shouldHold: () => !flowScroll.isAtTopRef.current,
|
||||
resumeSignal: flowScroll.resumeTick
|
||||
});
|
||||
const liveNews = usePausableTapeView<NewsStory>({
|
||||
enabled: mode === "live",
|
||||
sourceStatus: liveSession.status,
|
||||
sourceItems: liveSession.news,
|
||||
historyTail: liveSession.newsHistory,
|
||||
lastUpdate: liveSession.lastUpdate,
|
||||
retentionLimit: LIVE_OPTIONS_HEAD_LIMIT,
|
||||
captureScroll: newsAnchor.capture,
|
||||
onNewItems: newsScroll.onNewItems,
|
||||
shouldHold: () => !newsScroll.isAtTopRef.current,
|
||||
resumeSignal: newsScroll.resumeTick
|
||||
});
|
||||
|
||||
const seededLiveOptionsItems = useMemo(
|
||||
() =>
|
||||
|
|
@ -5831,14 +5846,7 @@ const useTerminalState = () => {
|
|||
)
|
||||
: equityJoins;
|
||||
const flowFeed = mode === "live" ? liveFlow : flow;
|
||||
const newsFeed =
|
||||
mode === "live"
|
||||
? toStaticTapeState(
|
||||
liveSession.status,
|
||||
composeTapeItems([], liveSession.news, liveSession.newsHistory),
|
||||
liveSession.lastUpdate
|
||||
)
|
||||
: toStaticTapeState("disconnected", [], null);
|
||||
const newsFeed = mode === "live" ? liveNews : toStaticTapeState("disconnected", [], null);
|
||||
const alertsFeed =
|
||||
mode === "live"
|
||||
? toStaticTapeState(
|
||||
|
|
@ -5896,6 +5904,10 @@ const useTerminalState = () => {
|
|||
classifierAnchor.apply();
|
||||
}, [smartMoneyFeed.items, classifierHitsFeed.items, classifierAnchor.apply]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
newsAnchor.apply();
|
||||
}, [newsFeed.items, newsAnchor.apply]);
|
||||
|
||||
const nbboMap = useMemo(() => {
|
||||
const map = new Map<string, OptionNBBO>();
|
||||
for (const quote of nbboFeed.items) {
|
||||
|
|
@ -7248,6 +7260,7 @@ const useTerminalState = () => {
|
|||
darkScroll,
|
||||
alertsScroll,
|
||||
classifierScroll,
|
||||
newsScroll,
|
||||
options: optionsFeed,
|
||||
equities: equitiesFeed,
|
||||
equityJoins: equityJoinsFeed,
|
||||
|
|
@ -7320,22 +7333,30 @@ const useTerminal = (): TerminalState => {
|
|||
};
|
||||
|
||||
export const NAV_ITEMS = [
|
||||
{ href: "/", label: "Home" },
|
||||
{ href: "/", label: "Dashboard" },
|
||||
{ href: "/options", label: "Options" },
|
||||
{ href: "/news", label: "News" }
|
||||
] as const;
|
||||
|
||||
type PageFrameVariant = "default" | "dashboard" | "options" | "news";
|
||||
|
||||
type PageFrameProps = {
|
||||
title: string;
|
||||
eyebrow?: string;
|
||||
variant?: PageFrameVariant;
|
||||
actions?: ReactNode;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const PageFrame = ({ title, actions, children }: PageFrameProps) => {
|
||||
const PageFrame = ({ title, eyebrow, variant = "default", actions, children }: PageFrameProps) => {
|
||||
const classes = ["page-shell", `page-shell-${variant}`].join(" ");
|
||||
return (
|
||||
<div className="page-shell">
|
||||
<div className={classes} data-route-variant={variant}>
|
||||
<header className="page-header">
|
||||
<h1 className="page-title">{title}</h1>
|
||||
<div className="page-heading">
|
||||
{eyebrow ? <span className="page-eyebrow">{eyebrow}</span> : null}
|
||||
<h1 className="page-title">{title}</h1>
|
||||
</div>
|
||||
{actions ? <div className="page-actions">{actions}</div> : null}
|
||||
</header>
|
||||
{children}
|
||||
|
|
@ -7611,9 +7632,11 @@ const ShellMetricStrip = () => {
|
|||
type OptionsPaneProps = {
|
||||
state: TerminalState;
|
||||
limit?: number;
|
||||
title?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const OptionsPane = memo(({ state, limit }: OptionsPaneProps) => {
|
||||
const OptionsPane = memo(({ state, limit, title = "Options", className }: OptionsPaneProps) => {
|
||||
const items = limit ? state.filteredOptions.slice(0, limit) : state.filteredOptions;
|
||||
const virtual = useTapeVirtualList(
|
||||
items,
|
||||
|
|
@ -7638,7 +7661,8 @@ const OptionsPane = memo(({ state, limit }: OptionsPaneProps) => {
|
|||
|
||||
return (
|
||||
<Pane
|
||||
title="Options"
|
||||
className={className}
|
||||
title={title}
|
||||
status={
|
||||
<TapeStatus
|
||||
status={state.options.status}
|
||||
|
|
@ -7972,9 +7996,10 @@ type FlowPaneProps = {
|
|||
state: TerminalState;
|
||||
limit?: number;
|
||||
title?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const FlowPane = memo(({ state, limit, title = "Flow" }: FlowPaneProps) => {
|
||||
const FlowPane = memo(({ state, limit, title = "Flow", className }: FlowPaneProps) => {
|
||||
const items = limit ? state.filteredFlow.slice(0, limit) : state.filteredFlow;
|
||||
const virtual = useTapeVirtualList(items, state.flowScroll.listRef, getTapeVirtualConfig("flow"));
|
||||
useVirtualHistoryGate(
|
||||
|
|
@ -7986,6 +8011,7 @@ const FlowPane = memo(({ state, limit, title = "Flow" }: FlowPaneProps) => {
|
|||
|
||||
return (
|
||||
<Pane
|
||||
className={className}
|
||||
title={title}
|
||||
status={
|
||||
<TapeStatus
|
||||
|
|
@ -8272,79 +8298,156 @@ type NewsPaneProps = {
|
|||
className?: string;
|
||||
};
|
||||
|
||||
const formatNewsSymbolsLabel = (story: NewsStory): string => {
|
||||
if (story.resolved_symbols.length === 0) {
|
||||
return story.symbol_resolution === "none" ? "unmapped" : "market";
|
||||
}
|
||||
const visible = story.resolved_symbols.slice(0, 4);
|
||||
const extra = story.resolved_symbols.length - visible.length;
|
||||
return extra > 0 ? `${visible.join(", ")} +${extra}` : visible.join(", ");
|
||||
};
|
||||
|
||||
const getNewsWireStatus = (story: NewsStory): "updated" | "mapped" | "unmapped" => {
|
||||
if (story.updated_ts > story.published_ts) {
|
||||
return "updated";
|
||||
}
|
||||
return story.resolved_symbols.length > 0 ? "mapped" : "unmapped";
|
||||
};
|
||||
|
||||
const openNewsStory = (state: TerminalState, story: NewsStory): void => {
|
||||
state.setSelectedNewsStory(null);
|
||||
state.setSelectedAlert(null);
|
||||
state.setSelectedClassifierHit(null);
|
||||
state.setSelectedSmartMoneyEvent(null);
|
||||
state.setSelectedDarkEvent(null);
|
||||
state.setSelectedNewsStory(story);
|
||||
};
|
||||
|
||||
const NewsPane = memo(({ state, limit, className }: NewsPaneProps) => {
|
||||
const items = limit ? state.filteredNews.slice(0, limit) : state.filteredNews;
|
||||
const canLoadOlder = state.mode === "live" && !limit && items.length > 0;
|
||||
const virtual = useTapeVirtualList(items, state.newsScroll.listRef, getTapeVirtualConfig("news"));
|
||||
const newsHistorySubscription = state.liveSession.manifest.find(
|
||||
(subscription) => subscription.channel === "news"
|
||||
);
|
||||
const newsHistoryKey = newsHistorySubscription
|
||||
? getLiveSubscriptionKey(newsHistorySubscription)
|
||||
: null;
|
||||
const newsHistoryLoading = newsHistoryKey
|
||||
? Boolean(state.liveSession.historyLoading[newsHistoryKey])
|
||||
: false;
|
||||
const newsHistoryError = newsHistoryKey ? state.liveSession.historyErrors[newsHistoryKey] : null;
|
||||
useVirtualHistoryGate(
|
||||
state.mode === "live" && !limit,
|
||||
items.length,
|
||||
virtual.virtualItems.at(-1)?.index ?? -1,
|
||||
() => void state.liveSession.loadOlder("news")
|
||||
);
|
||||
|
||||
return (
|
||||
<Pane
|
||||
className={className}
|
||||
title="News Wire"
|
||||
status={
|
||||
<TapeStatus
|
||||
status={state.news.status}
|
||||
lastUpdate={state.news.lastUpdate}
|
||||
replayTime={state.news.replayTime}
|
||||
replayComplete={state.news.replayComplete}
|
||||
paused={state.news.paused}
|
||||
dropped={state.news.dropped}
|
||||
mode={state.mode}
|
||||
/>
|
||||
}
|
||||
actions={
|
||||
limit ? (
|
||||
<Link className="terminal-button terminal-link-button" href="/news">
|
||||
View all
|
||||
</Link>
|
||||
) : (
|
||||
<div className="status-inline status-connected">
|
||||
<span className="status-dot" />
|
||||
<span>{state.mode === "live" ? "Live wire" : "Live-only in v1"}</span>
|
||||
</div>
|
||||
<TapeControls
|
||||
mode={state.mode}
|
||||
paused={state.news.paused}
|
||||
onTogglePause={state.news.togglePause}
|
||||
isAtTop={state.newsScroll.isAtTop}
|
||||
missed={state.newsScroll.missed}
|
||||
onJump={state.newsScroll.jumpToTop}
|
||||
/>
|
||||
)
|
||||
}
|
||||
actions={
|
||||
canLoadOlder ? (
|
||||
<button
|
||||
className="terminal-button"
|
||||
type="button"
|
||||
onClick={() => void state.liveSession.loadOlder("news")}
|
||||
>
|
||||
Older
|
||||
</button>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
{state.mode === "replay" ? (
|
||||
<div className="empty">News is live-only in v1.</div>
|
||||
) : items.length === 0 ? (
|
||||
<div className="empty">
|
||||
{state.tickerSet.size > 0
|
||||
? "No news stories match the current filter."
|
||||
: "Waiting for live news stories."}
|
||||
</div>
|
||||
) : (
|
||||
<div className="news-list" role="list" aria-label="News stories">
|
||||
{items.map((story) => (
|
||||
<button
|
||||
className="news-row"
|
||||
key={`${story.trace_id}:${story.updated_ts}:${story.seq}`}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
state.setSelectedNewsStory(null);
|
||||
state.setSelectedAlert(null);
|
||||
state.setSelectedClassifierHit(null);
|
||||
state.setSelectedSmartMoneyEvent(null);
|
||||
state.setSelectedDarkEvent(null);
|
||||
state.setSelectedNewsStory(story);
|
||||
}}
|
||||
>
|
||||
<div className="news-row-head">
|
||||
<h3>{story.headline}</h3>
|
||||
<span className="news-row-time">{formatNewsTimestamp(story.published_ts)}</span>
|
||||
<div className="data-table-shell news-wire-shell">
|
||||
{state.mode === "live" && newsHistoryError ? (
|
||||
<div className="history-load-warning" role="status">
|
||||
Older news history failed to load: {newsHistoryError}
|
||||
</div>
|
||||
) : null}
|
||||
{state.mode === "live" && newsHistoryLoading ? (
|
||||
<div className="history-load-warning history-load-muted" role="status">
|
||||
Loading older wire history.
|
||||
</div>
|
||||
) : null}
|
||||
{state.mode === "replay" ? (
|
||||
<div className="empty">News is live only in v1.</div>
|
||||
) : items.length === 0 ? (
|
||||
<div className="empty">
|
||||
{state.tickerSet.size > 0
|
||||
? "No news stories match the current filter."
|
||||
: "Waiting for live news stories."}
|
||||
</div>
|
||||
) : (
|
||||
<div className="data-table-wrap">
|
||||
<div className="data-table data-table-news" role="table" aria-label="News wire">
|
||||
<div className="data-table-head" role="row">
|
||||
<span className="data-table-cell">TIME</span>
|
||||
<span className="data-table-cell">SOURCE</span>
|
||||
<span className="data-table-cell">SYMBOLS</span>
|
||||
<span className="data-table-cell">STATE</span>
|
||||
<span className="data-table-cell">HEADLINE</span>
|
||||
<span className="data-table-cell">SUMMARY</span>
|
||||
</div>
|
||||
<div className="news-row-meta">
|
||||
<span>{story.source}</span>
|
||||
{story.resolved_symbols.map((symbol) => (
|
||||
<span className="drawer-chip" key={`${story.trace_id}-${symbol}`}>
|
||||
{symbol}
|
||||
</span>
|
||||
))}
|
||||
<div className="data-table-scroll" ref={state.newsScroll.setListRef}>
|
||||
<div
|
||||
className="data-table-body"
|
||||
style={{ height: `${virtual.totalSize}px` }}
|
||||
aria-hidden={virtual.virtualItems.length === 0}
|
||||
>
|
||||
{virtual.virtualItems.map(({ item: story, key, index, start, size }) => {
|
||||
const wireStatus = getNewsWireStatus(story);
|
||||
return (
|
||||
<button
|
||||
className={`data-table-row data-table-row-button data-table-row-news data-table-virtual-row${index % 2 === 1 ? " is-even" : ""} news-wire-row-${wireStatus}`}
|
||||
key={key}
|
||||
type="button"
|
||||
data-index={index}
|
||||
data-row-start={String(start)}
|
||||
data-row-size={String(size)}
|
||||
data-tape-key={key}
|
||||
style={{ transform: `translateY(${start}px)` }}
|
||||
onClick={() => openNewsStory(state, story)}
|
||||
>
|
||||
<span className="data-table-cell data-table-cell-number">
|
||||
{formatNewsTimestamp(story.published_ts)}
|
||||
</span>
|
||||
<span className="data-table-cell">{story.source}</span>
|
||||
<span className="data-table-cell">{formatNewsSymbolsLabel(story)}</span>
|
||||
<span className="data-table-cell">
|
||||
<span className={`news-state news-state-${wireStatus}`}>
|
||||
{wireStatus}
|
||||
</span>
|
||||
</span>
|
||||
<span className="data-table-cell news-headline-cell">{story.headline}</span>
|
||||
<span className="data-table-cell news-summary-cell">
|
||||
{story.summary || story.provider}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{!limit && story.summary ? <p className="drawer-note">{story.summary}</p> : null}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Pane>
|
||||
);
|
||||
});
|
||||
|
|
@ -8736,6 +8839,275 @@ const buildCommandDeckTickers = (state: TerminalState): CommandDeckTicker[] => {
|
|||
});
|
||||
};
|
||||
|
||||
type CommandPriorityState = "confirm" | "watch" | "hold" | "reject" | "info";
|
||||
|
||||
type CommandPriorityRow = {
|
||||
key: string;
|
||||
ts: number;
|
||||
symbol: string;
|
||||
packet: string;
|
||||
read: string;
|
||||
score: number;
|
||||
invalidation: string;
|
||||
state: CommandPriorityState;
|
||||
onOpen: () => void;
|
||||
};
|
||||
|
||||
const clampCommandScore = (value: number): number => {
|
||||
if (!Number.isFinite(value)) {
|
||||
return 0;
|
||||
}
|
||||
return Math.max(0, Math.min(100, Math.round(value)));
|
||||
};
|
||||
|
||||
const commandStateFromDirection = (direction: string): CommandPriorityState => {
|
||||
const normalized = normalizeDirection(direction);
|
||||
if (normalized === "bullish") {
|
||||
return "confirm";
|
||||
}
|
||||
if (normalized === "bearish") {
|
||||
return "reject";
|
||||
}
|
||||
return "watch";
|
||||
};
|
||||
|
||||
const inferCommandSymbolFromTrace = (traceId: string): string | null => {
|
||||
const token = traceId
|
||||
.toUpperCase()
|
||||
.split(/[^A-Z0-9]+/)
|
||||
.find((part) => /^[A-Z]{1,6}$/.test(part) && !["ALERT", "FLOW", "SMART"].includes(part));
|
||||
return token ?? null;
|
||||
};
|
||||
|
||||
const buildCommandPriorityRows = (state: TerminalState): CommandPriorityRow[] => {
|
||||
const rows: CommandPriorityRow[] = [];
|
||||
|
||||
for (const event of state.filteredSmartMoneyEvents.slice(0, 8)) {
|
||||
const primaryScore =
|
||||
event.profile_scores.find((score) => score.profile_id === event.primary_profile_id) ??
|
||||
event.profile_scores[0];
|
||||
const read =
|
||||
primaryScore?.reasons[0] ??
|
||||
(event.primary_profile_id
|
||||
? smartMoneyProfileLabel(event.primary_profile_id)
|
||||
: event.event_kind);
|
||||
rows.push({
|
||||
key: `smart-${event.event_id}-${event.seq}`,
|
||||
ts: event.source_ts,
|
||||
symbol: event.underlying_id.toUpperCase(),
|
||||
packet: event.packet_ids[0] ?? event.event_id,
|
||||
read,
|
||||
score: clampCommandScore((primaryScore?.probability ?? 0) * 100),
|
||||
invalidation:
|
||||
event.packet_ids.length > 0
|
||||
? `${event.packet_ids.length} packet${event.packet_ids.length === 1 ? "" : "s"}`
|
||||
: `${formatFlowMetric(event.features.print_count)} prints`,
|
||||
state: event.abstained ? "hold" : commandStateFromDirection(event.primary_direction),
|
||||
onOpen: () => state.openFromSmartMoneyEvent(event)
|
||||
});
|
||||
}
|
||||
|
||||
for (const alert of state.filteredAlerts.slice(0, 8)) {
|
||||
const primary = alert.hits[0];
|
||||
const direction = deriveAlertDirection(alert);
|
||||
const severity = normalizeAlertSeverity(alert);
|
||||
rows.push({
|
||||
key: `alert-${alert.trace_id}-${alert.seq}`,
|
||||
ts: alert.source_ts,
|
||||
symbol: inferCommandSymbolFromTrace(alert.trace_id) ?? "ALERT",
|
||||
packet: getAlertFlowPacketRefs(alert)[0] ?? alert.trace_id,
|
||||
read: primary?.explanations?.[0] ?? primary?.classifier_id ?? "Classifier alert",
|
||||
score: clampCommandScore(alert.score),
|
||||
invalidation: `${alert.evidence_refs.length} refs`,
|
||||
state:
|
||||
severity === "high"
|
||||
? commandStateFromDirection(direction)
|
||||
: severity === "medium"
|
||||
? "watch"
|
||||
: "hold",
|
||||
onOpen: () => {
|
||||
state.setSelectedNewsStory(null);
|
||||
state.setSelectedDarkEvent(null);
|
||||
state.setSelectedClassifierHit(null);
|
||||
state.setSelectedSmartMoneyEvent(null);
|
||||
state.setSelectedAlert(alert);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (const packet of state.filteredFlow.slice(0, 6)) {
|
||||
const contract = String(packet.features.option_contract_id ?? packet.id);
|
||||
const symbol = extractUnderlying(contract);
|
||||
const notional = parseNumber(packet.features.total_notional, 0);
|
||||
rows.push({
|
||||
key: `flow-${packet.id}-${packet.seq}`,
|
||||
ts: packet.source_ts,
|
||||
symbol,
|
||||
packet: packet.id,
|
||||
read:
|
||||
typeof packet.features.structure_type === "string"
|
||||
? packet.features.structure_type.replace(/_/g, " ")
|
||||
: "Flow packet",
|
||||
score: clampCommandScore(parseNumber(packet.join_quality.nbbo_coverage_ratio, 0) * 100),
|
||||
invalidation: notional > 0 ? `$${formatCompactUsd(notional)}` : "packet fit",
|
||||
state: "watch",
|
||||
onOpen: () => state.setFilterInput(symbol)
|
||||
});
|
||||
}
|
||||
|
||||
for (const story of state.filteredNews.slice(0, 4)) {
|
||||
rows.push({
|
||||
key: `news-${story.trace_id}-${story.seq}`,
|
||||
ts: story.published_ts,
|
||||
symbol: story.resolved_symbols[0]?.toUpperCase() ?? "WIRE",
|
||||
packet: story.source,
|
||||
read: story.headline,
|
||||
score: story.resolved_symbols.length > 0 ? 55 : 25,
|
||||
invalidation: getNewsWireStatus(story),
|
||||
state: "info",
|
||||
onOpen: () => openNewsStory(state, story)
|
||||
});
|
||||
}
|
||||
|
||||
return rows.sort((a, b) => b.ts - a.ts).slice(0, 8);
|
||||
};
|
||||
|
||||
const CommandMetricsStrip = ({ state }: { state: TerminalState }) => {
|
||||
const priorityCount =
|
||||
state.filteredSmartMoneyEvents.length + state.filteredAlerts.length + state.filteredFlow.length;
|
||||
const focus = state.activeTickers.length > 0 ? state.activeTickers.join(", ") : "All symbols";
|
||||
const decision =
|
||||
state.selectedInstrument?.kind === "option-contract"
|
||||
? (state.selectedInstrumentLabel ?? "Contract focus")
|
||||
: `${state.chartTicker.toUpperCase()} / ${formatIntervalLabel(state.chartIntervalMs)}`;
|
||||
const risk =
|
||||
state.filteredAlerts[0]?.severity ??
|
||||
(state.filteredInferredDark.length > 0 ? "dark context" : "no active alert");
|
||||
const metrics = [
|
||||
{
|
||||
label: "Regime",
|
||||
value:
|
||||
state.mode === "live" ? statusLabel(state.liveSession.status, false, state.mode) : "Replay",
|
||||
detail: state.lastSeen ? `last ${formatTime(state.lastSeen)}` : "waiting"
|
||||
},
|
||||
{
|
||||
label: "Priority",
|
||||
value: `${formatFlowMetric(priorityCount)} events`,
|
||||
detail: focus
|
||||
},
|
||||
{
|
||||
label: "Decision",
|
||||
value: decision,
|
||||
detail: state.selectedInstrument ? "focused instrument" : "chart context"
|
||||
},
|
||||
{
|
||||
label: "Risk",
|
||||
value: risk,
|
||||
detail: `${state.filteredNews.length} wire / ${state.filteredInferredDark.length} dark`
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="command-metric-strip" aria-label="Session command metrics">
|
||||
{metrics.map((metric) => (
|
||||
<div className="command-metric-cell" key={metric.label}>
|
||||
<span>{metric.label}</span>
|
||||
<strong>{metric.value}</strong>
|
||||
<em>{metric.detail}</em>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
const CommandPriorityBoard = ({ state }: { state: TerminalState }) => {
|
||||
const rows = useMemo(() => buildCommandPriorityRows(state), [state]);
|
||||
|
||||
return (
|
||||
<Pane
|
||||
className="command-priority-pane"
|
||||
title="Priority Board"
|
||||
status={<span className="command-pane-meta">{rows.length} active rows</span>}
|
||||
>
|
||||
{rows.length === 0 ? (
|
||||
<div className="empty">No priority events are available for this scope yet.</div>
|
||||
) : (
|
||||
<div className="command-priority-table" role="table" aria-label="Priority board">
|
||||
<div className="command-priority-row is-head" role="row">
|
||||
{["Time", "Sym", "Packet", "Read", "Score", "Decision", "State"].map((label) => (
|
||||
<span role="columnheader" key={label}>
|
||||
{label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
{rows.map((row) => (
|
||||
<button
|
||||
className={`command-priority-row is-${row.state}`}
|
||||
key={row.key}
|
||||
type="button"
|
||||
onClick={row.onOpen}
|
||||
>
|
||||
<time>{formatTime(row.ts)}</time>
|
||||
<strong>{row.symbol}</strong>
|
||||
<span>{row.packet}</span>
|
||||
<span>{row.read}</span>
|
||||
<span
|
||||
className="command-score-meter"
|
||||
style={{ "--score": `${row.score}%` } as CSSProperties}
|
||||
>
|
||||
<i />
|
||||
<em>{row.score}</em>
|
||||
</span>
|
||||
<span>{row.invalidation}</span>
|
||||
<span className={`command-state command-state-${row.state}`}>{row.state}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Pane>
|
||||
);
|
||||
};
|
||||
|
||||
const CommandDecisionLevels = ({ state }: { state: TerminalState }) => {
|
||||
const topOption = state.filteredOptions[0];
|
||||
const topOptionLabel = topOption
|
||||
? (formatOptionContractLabel(normalizeContractId(topOption.option_contract_id))?.strike ??
|
||||
formatContractLabel(topOption.option_contract_id))
|
||||
: "--";
|
||||
const topAlert = state.filteredAlerts[0];
|
||||
const topDark = state.filteredInferredDark[0];
|
||||
const rows = [
|
||||
["Focus", state.activeTickers.length > 0 ? state.activeTickers.join(", ") : state.chartTicker],
|
||||
["Contract", state.selectedInstrumentLabel ?? topOptionLabel],
|
||||
["Chart", `${state.chartTicker.toUpperCase()} ${formatIntervalLabel(state.chartIntervalMs)}`],
|
||||
[
|
||||
"Evidence",
|
||||
topAlert
|
||||
? `${normalizeAlertSeverity(topAlert)} alert at ${formatTime(topAlert.source_ts)}`
|
||||
: topDark
|
||||
? `${humanizeClassifierId(topDark.type)} ${formatConfidence(topDark.confidence)}`
|
||||
: "waiting"
|
||||
]
|
||||
];
|
||||
|
||||
return (
|
||||
<Pane
|
||||
className="command-levels-pane"
|
||||
title="Decision Levels"
|
||||
status={<span className="command-pane-meta">current scope</span>}
|
||||
>
|
||||
<dl className="command-level-list">
|
||||
{rows.map(([label, value]) => (
|
||||
<div key={label}>
|
||||
<dt>{label}</dt>
|
||||
<dd>{value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</Pane>
|
||||
);
|
||||
};
|
||||
|
||||
const CommandDeckHeader = ({ state }: { state: TerminalState }) => {
|
||||
const focus = state.activeTickers.length > 0 ? state.activeTickers.join(", ") : state.chartTicker;
|
||||
const activeTickerFilter = state.filterInput.trim();
|
||||
|
|
@ -8749,7 +9121,7 @@ const CommandDeckHeader = ({ state }: { state: TerminalState }) => {
|
|||
<div className="compact-command-topline">
|
||||
<div className="compact-command-title">
|
||||
<span>islandflow</span>
|
||||
<strong>Command Deck</strong>
|
||||
<strong>Market Command</strong>
|
||||
</div>
|
||||
<div className="compact-command-controls" aria-label="Active command deck controls">
|
||||
<span className={`command-chip command-chip-${state.liveSession.status}`}>
|
||||
|
|
@ -8796,12 +9168,12 @@ const CommandDeckHeader = ({ state }: { state: TerminalState }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const TickerRail = ({ state }: { state: TerminalState }) => {
|
||||
const CommandSymbolRail = ({ state }: { state: TerminalState }) => {
|
||||
const tickers = useMemo(() => buildCommandDeckTickers(state), [state]);
|
||||
|
||||
return (
|
||||
<div className="command-ticker-rail" aria-label="Live ticker focus rail">
|
||||
<div className="command-ticker-track">
|
||||
<div className="command-symbol-rail" aria-label="Live ticker focus rail">
|
||||
<div className="command-symbol-track">
|
||||
{tickers.map((ticker) => {
|
||||
const direction = ticker.move === null ? "flat" : ticker.move >= 0 ? "up" : "down";
|
||||
const equity = state.filteredEquities.find(
|
||||
|
|
@ -8809,23 +9181,23 @@ const TickerRail = ({ state }: { state: TerminalState }) => {
|
|||
);
|
||||
return (
|
||||
<button
|
||||
className={`command-ticker-card is-${direction}`}
|
||||
className={`command-symbol-row is-${direction}`}
|
||||
key={ticker.symbol}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
equity ? state.focusEquityTicker(equity) : state.setFilterInput(ticker.symbol)
|
||||
}
|
||||
>
|
||||
<span className="command-ticker-symbol">{ticker.symbol}</span>
|
||||
<span className="command-ticker-price">
|
||||
<span className="command-symbol-name">{ticker.symbol}</span>
|
||||
<span className="command-symbol-price">
|
||||
{ticker.price === null ? "--" : `$${formatPrice(ticker.price)}`}
|
||||
</span>
|
||||
<span className="command-ticker-move">
|
||||
<span className="command-symbol-move">
|
||||
{ticker.move === null
|
||||
? "Move n/a"
|
||||
: `${direction === "up" ? "Up" : "Down"} ${formatPct(Math.abs(ticker.move))}`}
|
||||
</span>
|
||||
<span className="command-ticker-meta">
|
||||
<span className="command-symbol-meta">
|
||||
{ticker.options} opt / {ticker.alerts} alerts
|
||||
</span>
|
||||
</button>
|
||||
|
|
@ -9122,6 +9494,141 @@ const ReplayConsole = memo(({ state }: { state: TerminalState }) => {
|
|||
);
|
||||
});
|
||||
|
||||
const OpraIntakeRail = ({ state }: { state: TerminalState }) => {
|
||||
const contractActive = state.selectedInstrument?.kind === "option-contract";
|
||||
const contractLabel = contractActive
|
||||
? (state.selectedInstrumentLabel ?? "Contract focus")
|
||||
: "No contract focus";
|
||||
const filterCount = countActiveFlowFilterGroups(state.flowFilters);
|
||||
|
||||
return (
|
||||
<section className="opra-command-rail" aria-label="OPRA intake controls">
|
||||
<div className="opra-command-cell">
|
||||
<span>Mode</span>
|
||||
<strong>{state.mode === "live" ? "OPRA Live" : "Replay"}</strong>
|
||||
<em>{state.options.lastUpdate ? formatTime(state.options.lastUpdate) : "waiting"}</em>
|
||||
</div>
|
||||
<div className="opra-command-cell">
|
||||
<span>Scope</span>
|
||||
<strong>
|
||||
{state.activeTickers.length > 0 ? state.activeTickers.join(", ") : "All symbols"}
|
||||
</strong>
|
||||
<em>{state.filteredOptions.length} prints visible</em>
|
||||
</div>
|
||||
<div className="opra-command-cell">
|
||||
<span>Contract</span>
|
||||
<strong>{contractLabel}</strong>
|
||||
<em>{contractActive ? "click clear to release" : "select any option row"}</em>
|
||||
</div>
|
||||
<div className="opra-command-cell">
|
||||
<span>Flow Filters</span>
|
||||
<strong>{filterCount > 0 ? `${filterCount} active` : "baseline"}</strong>
|
||||
<em>{state.flowFilters.view === "raw" ? "all prints" : "signal view"}</em>
|
||||
</div>
|
||||
<div className="opra-command-actions">
|
||||
<button
|
||||
className={`terminal-button contract-filter-button${contractActive ? " is-active" : ""}`}
|
||||
type="button"
|
||||
disabled={!contractActive}
|
||||
onClick={() => state.setSelectedInstrument(null)}
|
||||
title={
|
||||
contractActive ? "Clear active contract filter" : "Focus a contract in the OPRA tape"
|
||||
}
|
||||
>
|
||||
<span className="contract-filter-button-label">
|
||||
{contractActive ? "Clear Contract" : "Contract Focus"}
|
||||
</span>
|
||||
</button>
|
||||
<FlowFilterPopover filters={state.flowFilters} onChange={state.setFlowFilters} />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
const NewsControlRails = ({ state }: { state: TerminalState }) => {
|
||||
const sources = useMemo(() => {
|
||||
const counts = new Map<string, number>();
|
||||
for (const story of state.filteredNews) {
|
||||
counts.set(story.source, (counts.get(story.source) ?? 0) + 1);
|
||||
}
|
||||
return Array.from(counts.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 5);
|
||||
}, [state.filteredNews]);
|
||||
const symbols = useMemo(() => {
|
||||
const counts = new Map<string, number>();
|
||||
for (const story of state.filteredNews) {
|
||||
for (const symbol of story.resolved_symbols) {
|
||||
const normalized = symbol.toUpperCase();
|
||||
counts.set(normalized, (counts.get(normalized) ?? 0) + 1);
|
||||
}
|
||||
}
|
||||
return Array.from(counts.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 8);
|
||||
}, [state.filteredNews]);
|
||||
const statusRows = [
|
||||
{
|
||||
label: "Wire",
|
||||
value:
|
||||
state.mode === "live"
|
||||
? statusLabel(state.news.status, state.news.paused, state.mode)
|
||||
: "Live only",
|
||||
detail: state.news.lastUpdate ? formatTime(state.news.lastUpdate) : "waiting"
|
||||
},
|
||||
{
|
||||
label: "Stories",
|
||||
value: formatFlowMetric(state.filteredNews.length),
|
||||
detail: state.activeTickers.length > 0 ? state.activeTickers.join(", ") : "all symbols"
|
||||
},
|
||||
{
|
||||
label: "History",
|
||||
value: state.mode === "live" ? "scroll gate" : "disabled",
|
||||
detail: state.newsScroll.isAtTop ? "at live head" : `${state.newsScroll.missed} queued`
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="wire-control-rails" aria-label="Wire control rails">
|
||||
<div className="wire-status-rail">
|
||||
{statusRows.map((row) => (
|
||||
<div className="wire-rail-row" key={row.label}>
|
||||
<span>{row.label}</span>
|
||||
<strong>{row.value}</strong>
|
||||
<em>{row.detail}</em>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="wire-source-rail" aria-label="News sources">
|
||||
<span className="wire-rail-label">Sources</span>
|
||||
{sources.length === 0 ? (
|
||||
<span className="wire-empty-label">waiting</span>
|
||||
) : (
|
||||
sources.map(([source, count]) => (
|
||||
<span className="wire-source-pill" key={source}>
|
||||
<strong>{source}</strong>
|
||||
<em>{count}</em>
|
||||
</span>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<div className="wire-symbol-rail" aria-label="News symbols">
|
||||
<span className="wire-rail-label">Symbols</span>
|
||||
{symbols.length === 0 ? (
|
||||
<span className="wire-empty-label">unmapped</span>
|
||||
) : (
|
||||
symbols.map(([symbol, count]) => (
|
||||
<button key={symbol} type="button" onClick={() => state.setFilterInput(symbol)}>
|
||||
<strong>{symbol}</strong>
|
||||
<em>{count}</em>
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
function SyntheticControlDock() {
|
||||
const visible = isSyntheticAdminVisible();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
|
@ -9680,16 +10187,22 @@ function TerminalChrome({ children }: { children: ReactNode }) {
|
|||
export function OverviewRoute() {
|
||||
const state = useTerminal();
|
||||
return (
|
||||
<PageFrame title="Home">
|
||||
<div className="command-deck-shell">
|
||||
<PageFrame title="Market Command" eyebrow="Dashboard" variant="dashboard">
|
||||
<div className="market-command-shell">
|
||||
<CommandDeckHeader state={state} />
|
||||
<TickerRail state={state} />
|
||||
<div className="command-deck-grid">
|
||||
<OptionsPane state={state} limit={14} />
|
||||
<ChartPane state={state} title="Price / Flow" />
|
||||
<AlertsPane state={state} limit={8} withStrip className="command-signals-pane" />
|
||||
<CommandMetricsStrip state={state} />
|
||||
<CommandSymbolRail state={state} />
|
||||
<div className="market-command-grid">
|
||||
<CommandPriorityBoard state={state} />
|
||||
<ChartPane state={state} title="Chart Context" />
|
||||
<CommandDecisionLevels state={state} />
|
||||
<OptionsPane
|
||||
state={state}
|
||||
limit={12}
|
||||
title="Recent Contracts"
|
||||
className="command-contracts-pane"
|
||||
/>
|
||||
<FeedHealthPane state={state} />
|
||||
<DarkPane state={state} limit={8} className="command-dark-pane" />
|
||||
<EventContextPane state={state} />
|
||||
<HomeReplayRail state={state} />
|
||||
</div>
|
||||
|
|
@ -9701,8 +10214,9 @@ export function OverviewRoute() {
|
|||
export function NewsRoute() {
|
||||
const state = useTerminal();
|
||||
return (
|
||||
<PageFrame title="News">
|
||||
<div className="page-grid page-grid-news">
|
||||
<PageFrame title="Wire Control" eyebrow="News" variant="news">
|
||||
<div className="wire-control-shell">
|
||||
<NewsControlRails state={state} />
|
||||
<NewsPane state={state} className="news-pane-full" />
|
||||
</div>
|
||||
</PageFrame>
|
||||
|
|
@ -9712,34 +10226,13 @@ export function NewsRoute() {
|
|||
export function OptionsRoute() {
|
||||
const state = useTerminal();
|
||||
return (
|
||||
<PageFrame
|
||||
title="Options"
|
||||
actions={
|
||||
<>
|
||||
<button
|
||||
className={`terminal-button contract-filter-button${state.selectedInstrument?.kind === "option-contract" ? " is-active" : ""}`}
|
||||
type="button"
|
||||
disabled={state.selectedInstrument?.kind !== "option-contract"}
|
||||
onClick={() => state.setSelectedInstrument(null)}
|
||||
title={
|
||||
state.selectedInstrument?.kind === "option-contract"
|
||||
? "Clear active contract filter"
|
||||
: "Contract filter activates when you focus a contract in the Options tape"
|
||||
}
|
||||
>
|
||||
<span className="contract-filter-button-label">
|
||||
{state.selectedInstrument?.kind === "option-contract"
|
||||
? state.selectedInstrumentLabel
|
||||
: "Contract Filter"}
|
||||
</span>
|
||||
</button>
|
||||
<FlowFilterPopover filters={state.flowFilters} onChange={state.setFlowFilters} />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="page-grid page-grid-options">
|
||||
<OptionsPane state={state} />
|
||||
<FlowPane state={state} title="Packets" />
|
||||
<PageFrame title="OPRA Intake" eyebrow="Options" variant="options">
|
||||
<div className="opra-intake-shell">
|
||||
<OpraIntakeRail state={state} />
|
||||
<div className="opra-intake-grid">
|
||||
<OptionsPane state={state} title="OPRA Tape" className="opra-options-pane" />
|
||||
<FlowPane state={state} title="Packet Fit" className="opra-flow-pane" />
|
||||
</div>
|
||||
</div>
|
||||
</PageFrame>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue