diff --git a/apps/web/app/_components/trading-desk.tsx b/apps/web/app/_components/trading-desk.tsx new file mode 100644 index 0000000..0472598 --- /dev/null +++ b/apps/web/app/_components/trading-desk.tsx @@ -0,0 +1,4636 @@ +"use client"; + +import Link from "next/link"; +import { + startTransition, + useCallback, + useDeferredValue, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, + type ReactNode +} from "react"; +import type { + AlertEvent, + ClassifierHitEvent, + EquityCandle, + EquityPrint, + EquityPrintJoin, + FlowPacket, + InferredDarkEvent, + OptionNBBO, + OptionPrint +} from "@islandflow/types"; +import { createChart, type IChartApi, type SeriesMarker, type UTCTimestamp } from "lightweight-charts"; + +const MAX_ITEMS = 500; +const NBBO_MAX_AGE_MS = Number(process.env.NEXT_PUBLIC_NBBO_MAX_AGE_MS); +const NBBO_MAX_AGE_MS_SAFE = + Number.isFinite(NBBO_MAX_AGE_MS) && NBBO_MAX_AGE_MS > 0 ? NBBO_MAX_AGE_MS : 1000; +const LOCAL_HOSTS = new Set(["localhost", "127.0.0.1"]); +const CANDLE_INTERVALS = [ + { label: "1m", ms: 60000 }, + { label: "5m", ms: 300000 } +]; + +type CandlestickSeries = ReturnType; + +type EquityOverlayPoint = { + ts: number; + price: number; + size: number; + offExchangeFlag: boolean; +}; + +type ChartCandle = { + time: UTCTimestamp; + open: number; + high: number; + low: number; + close: number; +}; + +const formatIntervalLabel = (intervalMs: number): string => { + const match = CANDLE_INTERVALS.find((interval) => interval.ms === intervalMs); + if (match) { + return match.label; + } + if (intervalMs >= 60000) { + return `${Math.round(intervalMs / 60000)}m`; + } + if (intervalMs >= 1000) { + return `${Math.round(intervalMs / 1000)}s`; + } + return `${intervalMs}ms`; +}; + +const toChartTime = (ts: number): UTCTimestamp => { + return Math.floor(ts / 1000) as UTCTimestamp; +}; + +type ChartTimeLike = number | string | { year: number; month: number; day: number }; + +const chartTimeToMs = (value: ChartTimeLike): number | null => { + if (typeof value === "number") { + return Math.floor(value * 1000); + } + + if (typeof value === "string") { + const parsed = Date.parse(value); + return Number.isFinite(parsed) ? parsed : null; + } + + if (value && typeof value === "object") { + const { year, month, day } = value; + if ( + Number.isFinite(year) && + Number.isFinite(month) && + Number.isFinite(day) && + year >= 1970 && + month >= 1 && + month <= 12 && + day >= 1 && + day <= 31 + ) { + return Date.UTC(year, month - 1, day); + } + } + + return null; +}; + +const toChartCandle = (candle: EquityCandle): ChartCandle => { + return { + time: toChartTime(candle.ts), + open: candle.open, + high: candle.high, + low: candle.low, + close: candle.close + }; +}; + +const clamp = (value: number, min: number, max: number): number => { + if (!Number.isFinite(value)) { + return min; + } + return Math.max(min, Math.min(max, value)); +}; + +const sampleToLimit = (items: T[], limit: number): T[] => { + if (items.length <= limit) { + return items; + } + + const safeLimit = Math.max(1, Math.floor(limit)); + const step = Math.ceil(items.length / safeLimit); + const sampled: T[] = []; + for (let idx = 0; idx < items.length; idx += step) { + sampled.push(items[idx]); + } + + return sampled; +}; + +const readErrorDetail = async (response: Response): Promise => { + const text = await response.text(); + if (!text) { + return ""; + } + try { + const payload = JSON.parse(text) as { + detail?: string; + error?: string; + message?: string; + }; + return payload.detail ?? payload.error ?? payload.message ?? text; + } catch { + return text; + } +}; + +type WsStatus = "connecting" | "connected" | "disconnected"; + +type TapeMode = "live" | "replay"; + +type MessageType = + | "option-print" + | "option-nbbo" + | "equity-print" + | "equity-candle" + | "equity-join" + | "flow-packet" + | "inferred-dark" + | "classifier-hit" + | "alert"; + +type StreamMessage = { + type: MessageType; + payload: T; +}; + +type ReplayCursor = { + ts: number; + seq: number; +}; + +type ReplayResponse = { + data: T[]; + next: ReplayCursor | null; +}; + +const inferTracePrefix = (traceId: string): string => { + const match = traceId.match(/^(.*)-\d+$/); + return match ? match[1] : traceId; +}; + +const extractTracePrefix = (item: T): string | null => { + const traceId = (item as { trace_id?: string }).trace_id; + if (!traceId) { + return null; + } + return inferTracePrefix(traceId); +}; + +const extractReplaySource = (item: T): string | null => { + const prefix = extractTracePrefix(item); + if (!prefix) { + return null; + } + + const normalized = prefix.toLowerCase(); + if (normalized.startsWith("synthetic")) { + return "synthetic"; + } + if (normalized.startsWith("databento")) { + return "databento"; + } + if (normalized.startsWith("alpaca")) { + return "alpaca"; + } + if (normalized.startsWith("ibkr")) { + return "ibkr"; + } + + return prefix; +}; + +type SortableItem = { + ts?: number; + source_ts?: number; + ingest_ts?: number; + seq?: number; + trace_id?: string; + id?: string; +}; + +const extractSortTs = (item: SortableItem): number => + item.ts ?? item.source_ts ?? item.ingest_ts ?? 0; + +const extractSortSeq = (item: SortableItem): number => item.seq ?? 0; + +const buildItemKey = (item: SortableItem): string | null => { + if (item.trace_id) { + return `${item.trace_id}:${item.seq ?? ""}`; + } + + if (item.id) { + return `id:${item.id}`; + } + + return null; +}; + +const mergeNewest = (incoming: T[], existing: T[]): T[] => { + const combined = [...incoming, ...existing]; + if (combined.length === 0) { + return combined; + } + + const seen = new Set(); + const deduped: T[] = []; + + for (const item of combined) { + const key = buildItemKey(item); + if (key) { + if (seen.has(key)) { + continue; + } + seen.add(key); + } + deduped.push(item); + } + + deduped.sort((a, b) => { + const delta = extractSortTs(b) - extractSortTs(a); + if (delta !== 0) { + return delta; + } + return extractSortSeq(b) - extractSortSeq(a); + }); + + return deduped.slice(0, MAX_ITEMS); +}; + +type TapeState = { + status: WsStatus; + items: T[]; + lastUpdate: number | null; + replayTime: number | null; + replayComplete: boolean; + paused: boolean; + dropped: number; + togglePause: () => void; +}; + +const buildWsUrl = (path: string): string => { + const envBase = process.env.NEXT_PUBLIC_API_URL; + + if (envBase) { + const url = new URL(envBase); + url.protocol = url.protocol === "https:" ? "wss:" : "ws:"; + url.pathname = path; + url.search = ""; + url.hash = ""; + return url.toString(); + } + + const { protocol, hostname } = window.location; + const wsProtocol = protocol === "https:" ? "wss" : "ws"; + const isLocal = LOCAL_HOSTS.has(hostname); + const host = isLocal ? `${hostname}:4000` : window.location.host; + + return `${wsProtocol}://${host}${path}`; +}; + +const buildApiUrl = (path: string): string => { + const envBase = process.env.NEXT_PUBLIC_API_URL; + + if (envBase) { + const url = new URL(envBase); + const secure = url.protocol === "https:" || url.protocol === "wss:"; + url.protocol = secure ? "https:" : "http:"; + url.pathname = path; + url.search = ""; + url.hash = ""; + return url.toString(); + } + + const { protocol, hostname } = window.location; + const httpProtocol = protocol === "https:" ? "https" : "http"; + const isLocal = LOCAL_HOSTS.has(hostname); + const host = isLocal ? `${hostname}:4000` : window.location.host; + + return `${httpProtocol}://${host}${path}`; +}; + +const formatPrice = (price: number): string => { + if (!Number.isFinite(price)) { + return "0.00"; + } + return price.toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }); +}; + +const formatSize = (size: number): string => { + return size.toLocaleString(); +}; + +const formatTime = (ts: number): string => { + return new Date(ts).toLocaleTimeString(); +}; + +const formatConfidence = (value: number): string => `${Math.round(value * 100)}%`; + +const formatPct = (value: number): string => `${Math.round(value * 100)}%`; + +const formatUsd = (value: number): string => { + if (!Number.isFinite(value)) { + return "0.00"; + } + return value.toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }); +}; + +const normalizeContractId = (value: string): string => value.trim(); + +const formatContractLabel = (value: string): string => { + const normalized = normalizeContractId(value); + if (!normalized) { + return "Unknown contract"; + } + if (/^\d+$/.test(normalized)) { + return `Instrument ${normalized}`; + } + return normalized; +}; + +const formatDateTime = (ts: number): string => { + const date = new Date(ts); + return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`; +}; + +const humanizeClassifierId = (value: string): string => { + if (!value) { + return "Classifier"; + } + + return value + .split("_") + .map((part) => (part ? part[0].toUpperCase() + part.slice(1) : part)) + .join(" "); +}; + +const normalizeDirection = (value: string): "bullish" | "bearish" | "neutral" => { + const normalized = value.toLowerCase(); + if (normalized === "bullish" || normalized === "bearish" || normalized === "neutral") { + return normalized; + } + return "neutral"; +}; + +const extractUnderlying = (contractId: string): string => { + const match = contractId.match(/^(.+)-\d{4}-\d{2}-\d{2}-/); + if (match?.[1]) { + return match[1].toUpperCase(); + } + return contractId.split("-")[0]?.toUpperCase() ?? contractId.toUpperCase(); +}; + +const extractEquityTraceFromJoin = (joinId: string): string | null => { + const match = joinId.match(/^equityjoin:(.+)$/); + return match?.[1] ?? null; +}; + +const inferDarkUnderlying = ( + event: InferredDarkEvent, + equityPrints: Map, + equityJoins: Map +): string | null => { + for (const ref of event.evidence_refs) { + const join = equityJoins.get(ref); + if (!join) { + continue; + } + const underlying = join.features.underlying_id; + if (typeof underlying === "string" && underlying.length > 0) { + return underlying.toUpperCase(); + } + } + + const match = event.trace_id.match(/^dark:(?:stealth_accumulation|distribution):([^:]+):/); + if (match?.[1]) { + return match[1].toUpperCase(); + } + + for (const ref of event.evidence_refs) { + const traceId = extractEquityTraceFromJoin(ref); + if (!traceId) { + continue; + } + const print = equityPrints.get(traceId); + if (print) { + return print.underlying_id.toUpperCase(); + } + } + + return null; +}; + +const parseNumber = (value: unknown, fallback: number): number => { + if (typeof value === "number" && Number.isFinite(value)) { + return value; + } + + if (typeof value === "string") { + const parsed = Number(value); + if (Number.isFinite(parsed)) { + return parsed; + } + } + + return fallback; +}; + +const parseBoolean = (value: unknown, fallback = false): boolean => { + if (typeof value === "boolean") { + return value; + } + if (typeof value === "number") { + return value !== 0; + } + if (typeof value === "string") { + const normalized = value.trim().toLowerCase(); + if (["true", "1", "yes", "on"].includes(normalized)) { + return true; + } + if (["false", "0", "no", "off"].includes(normalized)) { + return false; + } + } + return fallback; +}; + +const getJoinString = (join: EquityPrintJoin, key: string): string | null => { + const value = join.features[key]; + return typeof value === "string" ? value : null; +}; + +const getJoinNumber = (join: EquityPrintJoin, key: string, fallback = Number.NaN): number => { + return parseNumber(join.features[key], fallback); +}; + +const getJoinBoolean = (join: EquityPrintJoin, key: string): boolean => { + return parseBoolean(join.features[key], false); +}; + +type NbboSide = "AA" | "A" | "B" | "BB"; + +const classifyNbboSide = (price: number, quote: OptionNBBO | null | undefined): NbboSide | null => { + if (!quote || !Number.isFinite(price)) { + return null; + } + + const bid = quote.bid; + const ask = quote.ask; + if (!Number.isFinite(bid) || !Number.isFinite(ask) || ask <= 0) { + return null; + } + + const spread = Math.max(0, ask - bid); + const epsilon = Math.max(0.01, spread * 0.05); + + if (price > ask + epsilon) { + return "AA"; + } + if (price >= ask - epsilon) { + return "A"; + } + if (price < bid - epsilon) { + return "BB"; + } + if (price <= bid + epsilon) { + return "B"; + } + + const mid = (bid + ask) / 2; + return price >= mid ? "A" : "B"; +}; + +type ListScrollState = { + listRef: React.RefObject; + isAtTop: boolean; + isAtTopRef: React.MutableRefObject; + missed: number; + resumeTick: number; + onNewItems: (count: number) => void; + jumpToTop: () => void; +}; + +const useListScroll = (): ListScrollState => { + const listRef = useRef(null); + const [isAtTop, setIsAtTop] = useState(true); + const [missed, setMissed] = useState(0); + const [resumeTick, setResumeTick] = useState(0); + const isAtTopRef = useRef(true); + const prevAtTopRef = useRef(true); + + useEffect(() => { + isAtTopRef.current = isAtTop; + }, [isAtTop]); + + const updateScrollState = useCallback(() => { + const el = listRef.current; + if (!el) { + return; + } + + const atTop = el.scrollTop <= 2; + + if (atTop && !prevAtTopRef.current) { + setResumeTick((prev) => prev + 1); + } + + prevAtTopRef.current = atTop; + isAtTopRef.current = atTop; + setIsAtTop(atTop); + + if (atTop) { + setMissed(0); + } + }, [isAtTopRef]); + + useEffect(() => { + const el = listRef.current; + if (!el) { + return; + } + + const onScroll = () => { + updateScrollState(); + }; + + updateScrollState(); + el.addEventListener("scroll", onScroll); + + return () => { + el.removeEventListener("scroll", onScroll); + }; + }, [updateScrollState]); + + const onNewItems = useCallback((count: number) => { + if (count <= 0) { + return; + } + + if (isAtTopRef.current) { + setMissed(0); + return; + } + + setMissed((prev) => prev + count); + }, []); + + const jumpToTop = useCallback(() => { + const el = listRef.current; + if (!el) { + return; + } + + isAtTopRef.current = true; + el.scrollTop = 0; + updateScrollState(); + }, [isAtTopRef, listRef, updateScrollState]); + + return { + listRef, + isAtTop, + isAtTopRef, + missed, + resumeTick, + onNewItems, + jumpToTop + }; +}; + +const useScrollAnchor = ( + listRef: React.RefObject, + isAtTopRef: React.MutableRefObject +) => { + const pendingRef = useRef<{ height: number } | null>(null); + + const capture = useCallback(() => { + if (isAtTopRef.current) { + pendingRef.current = null; + return; + } + + const el = listRef.current; + if (!el) { + return; + } + + pendingRef.current = { + height: el.scrollHeight + }; + }, [isAtTopRef, listRef]); + + const apply = useCallback(() => { + const pending = pendingRef.current; + if (!pending) { + return; + } + + const el = listRef.current; + if (!el) { + return; + } + + if (isAtTopRef.current) { + pendingRef.current = null; + return; + } + + const delta = el.scrollHeight - pending.height; + if (delta !== 0) { + el.scrollTop = Math.max(0, el.scrollTop + delta); + } + pendingRef.current = null; + }, [isAtTopRef, listRef]); + + return { capture, apply }; +}; + +const statusLabel = (status: WsStatus, paused: boolean, mode: TapeMode): string => { + if (paused) { + return "Paused"; + } + + if (mode === "replay") { + return status === "disconnected" ? "Replay Down" : "Replay"; + } + + switch (status) { + case "connected": + return "Live"; + case "connecting": + return "Connecting"; + case "disconnected": + default: + return "Disconnected"; + } +}; + +type TapeConfig = { + mode: TapeMode; + wsPath: string; + replayPath: string; + latestPath?: string; + expectedType: MessageType; + batchSize?: number; + pollMs?: number; + captureScroll?: () => void; + onNewItems?: (count: number) => void; + getItemTs?: (item: T) => number; + getReplayKey?: (item: T) => string | null; + replaySourceKey?: string | null; + onReplaySourceKey?: (key: string | null) => void; +}; + +const useTape = ( + config: TapeConfig +): TapeState => { + const { mode, wsPath, replayPath, expectedType, latestPath, onNewItems, captureScroll } = config; + const batchSize = config.batchSize ?? 40; + const pollMs = config.pollMs ?? 1000; + const getItemTs = config.getItemTs ?? extractSortTs; + const getReplayKey = config.getReplayKey ?? extractTracePrefix; + const replaySourceKey = config.replaySourceKey ?? null; + const onReplaySourceKey = config.onReplaySourceKey; + const [status, setStatus] = useState("connecting"); + const [items, setItems] = useState([]); + const [lastUpdate, setLastUpdate] = useState(null); + const [replayTime, setReplayTime] = useState(null); + const [replayComplete, setReplayComplete] = useState(false); + const [paused, setPaused] = useState(false); + const [dropped, setDropped] = useState(0); + const reconnectRef = useRef(null); + const socketRef = useRef(null); + const cursorRef = useRef({ ts: 0, seq: 0 }); + const replayEndRef = useRef(null); + const replayCompleteRef = useRef(false); + const replaySourceRef = useRef(null); + const replaySourceNotifiedRef = useRef(null); + const emptyPollsRef = useRef(0); + const pausedRef = useRef(paused); + const pendingRef = useRef([]); + const pendingCountRef = useRef(0); + const flushHandleRef = useRef(null); + + useEffect(() => { + pausedRef.current = paused; + }, [paused]); + + const cancelFlush = useCallback(() => { + if (flushHandleRef.current !== null) { + cancelAnimationFrame(flushHandleRef.current); + flushHandleRef.current = null; + } + }, []); + + const scheduleFlush = useCallback(() => { + if (flushHandleRef.current !== null) { + return; + } + + flushHandleRef.current = requestAnimationFrame(() => { + flushHandleRef.current = null; + const buffered = pendingRef.current; + if (buffered.length === 0) { + return; + } + pendingRef.current = []; + + const pendingCount = pendingCountRef.current; + pendingCountRef.current = 0; + + if (onNewItems && pendingCount > 0) { + onNewItems(pendingCount); + } + + if (captureScroll) { + captureScroll(); + } + + setItems((prev) => mergeNewest(buffered, prev)); + setLastUpdate(Date.now()); + }); + }, [captureScroll, onNewItems]); + + const togglePause = useCallback(() => { + setPaused((prev) => { + const next = !prev; + if (!next) { + setDropped(0); + } + return next; + }); + }, []); + + useEffect(() => { + setItems([]); + setLastUpdate(null); + setReplayTime(null); + setReplayComplete(false); + replayCompleteRef.current = false; + replaySourceRef.current = null; + replaySourceNotifiedRef.current = null; + emptyPollsRef.current = 0; + setDropped(0); + setStatus("connecting"); + cursorRef.current = { ts: 0, seq: 0 }; + pendingRef.current = []; + pendingCountRef.current = 0; + cancelFlush(); + }, [mode, replaySourceKey, cancelFlush]); + + useEffect(() => { + if (mode !== "replay" || !latestPath) { + replayEndRef.current = null; + return; + } + + let active = true; + replayEndRef.current = null; + setReplayComplete(false); + replayCompleteRef.current = false; + + const fetchReplayEnd = async () => { + try { + const url = new URL(buildApiUrl(latestPath)); + url.searchParams.set("limit", "1"); + if (replaySourceKey) { + url.searchParams.set("source", replaySourceKey); + } + const response = await fetch(url.toString()); + if (!response.ok) { + throw new Error(`Replay baseline failed with ${response.status}`); + } + + const payload = (await response.json()) as { data?: T[] }; + const latest = payload.data?.[0]; + if (active && latest) { + replayEndRef.current = getItemTs(latest); + } + } catch (error) { + console.warn("Failed to load replay end cursor", error); + } + }; + + void fetchReplayEnd(); + + return () => { + active = false; + }; + }, [mode, latestPath, getItemTs, replaySourceKey]); + + useEffect(() => { + if (mode !== "live") { + return; + } + + let active = true; + + const connect = () => { + if (!active) { + return; + } + + setStatus("connecting"); + + const socket = new WebSocket(buildWsUrl(wsPath)); + socketRef.current = socket; + + socket.onopen = () => { + if (!active) { + return; + } + setStatus("connected"); + }; + + socket.onmessage = (event) => { + if (!active) { + return; + } + + try { + const message = JSON.parse(event.data) as StreamMessage; + if (!message || message.type !== expectedType) { + return; + } + + if (pausedRef.current) { + setDropped((prev) => prev + 1); + setLastUpdate(Date.now()); + return; + } + + pendingRef.current.push(message.payload); + pendingCountRef.current += 1; + scheduleFlush(); + } catch (error) { + console.warn("Failed to parse websocket payload", error); + } + }; + + socket.onclose = () => { + if (!active) { + return; + } + + setStatus("disconnected"); + reconnectRef.current = window.setTimeout(() => { + connect(); + }, 1000); + }; + + socket.onerror = () => { + if (!active) { + return; + } + + setStatus("disconnected"); + socket.close(); + }; + }; + + connect(); + + return () => { + active = false; + cancelFlush(); + if (reconnectRef.current !== null) { + window.clearTimeout(reconnectRef.current); + } + if (socketRef.current) { + socketRef.current.close(); + } + }; + }, [mode, wsPath, expectedType, scheduleFlush, cancelFlush]); + + useEffect(() => { + if (mode !== "replay") { + return; + } + + let active = true; + + const poll = async () => { + if (!active || pausedRef.current) { + return; + } + + if (replayCompleteRef.current) { + return; + } + + try { + let keepPolling = true; + + while (keepPolling && active && !pausedRef.current) { + const replayEnd = replayEndRef.current; + const cursor = cursorRef.current; + + if (replayEnd !== null && cursor.ts >= replayEnd) { + replayCompleteRef.current = true; + setReplayComplete(true); + setStatus("disconnected"); + return; + } + + const url = new URL(buildApiUrl(replayPath)); + url.searchParams.set("after_ts", cursor.ts.toString()); + url.searchParams.set("after_seq", cursor.seq.toString()); + url.searchParams.set("limit", batchSize.toString()); + const desiredSource = replaySourceKey ?? replaySourceRef.current; + if (desiredSource) { + url.searchParams.set("source", desiredSource); + } + + const response = await fetch(url.toString()); + if (!response.ok) { + throw new Error(`Replay request failed with ${response.status}`); + } + + const payload = (await response.json()) as ReplayResponse; + + let sourcePrefix = replaySourceRef.current; + if (replaySourceKey) { + if (sourcePrefix !== replaySourceKey) { + sourcePrefix = replaySourceKey; + replaySourceRef.current = replaySourceKey; + } + } else if (!sourcePrefix) { + const firstWithTrace = payload.data.find((item) => getReplayKey(item)); + if (firstWithTrace) { + sourcePrefix = getReplayKey(firstWithTrace); + replaySourceRef.current = sourcePrefix ?? null; + } + } + + if (onReplaySourceKey && sourcePrefix && replaySourceNotifiedRef.current !== sourcePrefix) { + replaySourceNotifiedRef.current = sourcePrefix; + onReplaySourceKey(sourcePrefix); + } + + const filtered = sourcePrefix + ? payload.data.filter((item) => getReplayKey(item) === sourcePrefix) + : payload.data; + + const hasForeign = + sourcePrefix && + payload.data.some((item) => { + const prefix = getReplayKey(item); + return prefix !== null && prefix !== sourcePrefix; + }); + + if (filtered.length > 0) { + const nextItems = [...filtered].reverse(); + pendingRef.current.push(...nextItems); + pendingCountRef.current += nextItems.length; + scheduleFlush(); + const last = filtered.at(-1); + if (last) { + const lastTs = getItemTs(last); + setReplayTime(lastTs); + if (replayEnd !== null && lastTs >= replayEnd) { + cursorRef.current = { ts: lastTs, seq: last.seq }; + replayCompleteRef.current = true; + setReplayComplete(true); + setStatus("disconnected"); + return; + } + } + emptyPollsRef.current = 0; + } else if (sourcePrefix) { + emptyPollsRef.current += 1; + } + + if (payload.next) { + cursorRef.current = payload.next; + } + + setStatus("connected"); + keepPolling = filtered.length === batchSize; + + if (keepPolling) { + await new Promise((resolve) => setTimeout(resolve, 0)); + } + + if (!replaySourceKey && hasForeign) { + replayCompleteRef.current = true; + setReplayComplete(true); + setStatus("disconnected"); + return; + } + + if (sourcePrefix && emptyPollsRef.current >= 3) { + replayCompleteRef.current = true; + setReplayComplete(true); + setStatus("disconnected"); + return; + } + } + } catch (error) { + console.warn("Replay poll failed", error); + setStatus("disconnected"); + } + }; + + void poll(); + const interval = window.setInterval(poll, pollMs); + + return () => { + active = false; + window.clearInterval(interval); + cancelFlush(); + }; + }, [ + mode, + replayPath, + batchSize, + pollMs, + scheduleFlush, + cancelFlush, + getItemTs, + getReplayKey, + replaySourceKey, + onReplaySourceKey + ]); + + return { + status, + items, + lastUpdate, + replayTime, + replayComplete, + paused, + dropped, + togglePause + }; +}; + +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); + const [replayComplete] = useState(false); + const [paused, setPaused] = useState(false); + const [dropped, setDropped] = useState(0); + const reconnectRef = useRef(null); + const socketRef = useRef(null); + const pausedRef = useRef(paused); + const pendingRef = useRef([]); + const pendingCountRef = useRef(0); + const flushHandleRef = useRef(null); + const holdRef = useRef([]); + + useEffect(() => { + pausedRef.current = paused; + }, [paused]); + + const cancelFlush = useCallback(() => { + if (flushHandleRef.current !== null) { + cancelAnimationFrame(flushHandleRef.current); + flushHandleRef.current = null; + } + }, []); + + const scheduleFlush = useCallback(() => { + if (flushHandleRef.current !== null) { + return; + } + + flushHandleRef.current = requestAnimationFrame(() => { + flushHandleRef.current = null; + const buffered = pendingRef.current; + if (buffered.length === 0) { + return; + } + pendingRef.current = []; + + const pendingCount = pendingCountRef.current; + pendingCountRef.current = 0; + + if (config.onNewItems && pendingCount > 0) { + config.onNewItems(pendingCount); + } + + const shouldHold = config.shouldHold ? config.shouldHold() : false; + if (!shouldHold && config.captureScroll) { + config.captureScroll(); + } + + if (shouldHold) { + holdRef.current = mergeNewest(buffered, holdRef.current); + setLastUpdate(Date.now()); + return; + } + + const nextBatch = + holdRef.current.length > 0 ? [...holdRef.current, ...buffered] : buffered; + holdRef.current = []; + + setItems((prev) => mergeNewest(nextBatch, prev)); + setLastUpdate(Date.now()); + }); + }, [config.captureScroll, config.onNewItems, config.shouldHold]); + + const togglePause = useCallback(() => { + setPaused((prev) => { + const next = !prev; + if (!next) { + setDropped(0); + } + return next; + }); + }, []); + + useEffect(() => { + if (!config.enabled) { + setStatus("disconnected"); + setItems([]); + setLastUpdate(null); + pendingRef.current = []; + pendingCountRef.current = 0; + holdRef.current = []; + cancelFlush(); + return; + } + + let active = true; + + const connect = () => { + if (!active) { + return; + } + + setStatus("connecting"); + + const socket = new WebSocket(buildWsUrl(config.wsPath)); + socketRef.current = socket; + + socket.onopen = () => { + if (!active) { + return; + } + setStatus("connected"); + }; + + socket.onmessage = (event) => { + if (!active) { + return; + } + + try { + const message = JSON.parse(event.data) as StreamMessage; + if (!message || message.type !== config.expectedType) { + return; + } + + if (pausedRef.current) { + setDropped((prev) => prev + 1); + setLastUpdate(Date.now()); + return; + } + + pendingRef.current.push(message.payload); + pendingCountRef.current += 1; + scheduleFlush(); + } catch (error) { + console.warn("Failed to parse live stream payload", error); + } + }; + + socket.onclose = () => { + if (!active) { + return; + } + + setStatus("disconnected"); + reconnectRef.current = window.setTimeout(() => { + connect(); + }, 1000); + }; + + socket.onerror = () => { + if (!active) { + return; + } + + setStatus("disconnected"); + socket.close(); + }; + }; + + connect(); + + return () => { + active = false; + cancelFlush(); + if (reconnectRef.current !== null) { + window.clearTimeout(reconnectRef.current); + } + if (socketRef.current) { + socketRef.current.close(); + } + }; + }, [config.enabled, config.expectedType, config.wsPath, scheduleFlush, cancelFlush]); + + useEffect(() => { + if (config.resumeSignal === undefined) { + return; + } + if (config.shouldHold && config.shouldHold()) { + return; + } + if (holdRef.current.length === 0) { + return; + } + setItems((prev) => mergeNewest(holdRef.current, prev)); + holdRef.current = []; + setLastUpdate(Date.now()); + }, [config.resumeSignal, config.shouldHold]); + + return { + status, + items, + lastUpdate, + replayTime, + replayComplete, + paused, + dropped, + togglePause + }; +}; + +const useFlowStream = ( + enabled: boolean, + onNewItems?: (count: number) => void, + captureScroll?: () => void, + shouldHold?: () => boolean, + resumeSignal?: number +): TapeState => { + return useLiveStream({ + enabled, + wsPath: "/ws/flow", + expectedType: "flow-packet", + onNewItems, + captureScroll, + shouldHold, + resumeSignal + }); +}; + +type TapeStatusProps = { + status: WsStatus; + lastUpdate: number | null; + replayTime: number | null; + replayComplete: boolean; + paused: boolean; + dropped: number; + mode: TapeMode; + onTogglePause: () => void; +}; + +const TapeStatus = ({ + status, + lastUpdate, + replayTime, + replayComplete, + paused, + dropped, + mode, + onTogglePause +}: TapeStatusProps) => { + const replayClass = mode === "replay" ? "status-replay" : ""; + const pausedClass = paused ? "status-paused" : ""; + const label = replayComplete ? "Replay Complete" : statusLabel(status, paused, mode); + + return ( +
+ + {label} + {lastUpdate ? ( + Updated {formatTime(lastUpdate)} + ) : ( + Waiting for data + )} + {paused && dropped > 0 ? ( + {dropped} new while paused + ) : null} + {mode === "replay" ? ( + + Replay time {replayTime ? formatTime(replayTime) : "—"} + + ) : null} + +
+ ); +}; + +type TapeControlsProps = { + isAtTop: boolean; + missed: number; + onJump: () => void; +}; + +const TapeControls = ({ isAtTop, missed, onJump }: TapeControlsProps) => { + const active = !isAtTop && missed > 0; + return ( +
+ + {active ? `+${missed} new` : ""} +
+ ); +}; + +type CandleChartProps = { + ticker: string; + intervalMs: number; + mode: TapeMode; + replayTime?: number | null; + classifierHits: ClassifierHitEvent[]; + inferredDark: InferredDarkEvent[]; + onClassifierHitClick: (hit: ClassifierHitEvent) => void; + onInferredDarkClick: (event: InferredDarkEvent) => void; +}; + +type MarkerAction = + | { kind: "hit"; hit: ClassifierHitEvent } + | { kind: "dark"; event: InferredDarkEvent }; + +const CandleChart = ({ + ticker, + intervalMs, + mode, + replayTime = null, + classifierHits, + inferredDark, + onClassifierHitClick, + onInferredDarkClick +}: CandleChartProps) => { + const containerRef = useRef(null); + const chartRef = useRef(null); + const seriesRef = useRef(null); + const socketRef = useRef(null); + const reconnectRef = useRef(null); + const overlaySocketRef = useRef(null); + const overlayReconnectRef = useRef(null); + const lastCandleRef = useRef<{ time: UTCTimestamp; seq: number } | null>(null); + + const markerLookupRef = useRef>(new Map()); + const [visibleRangeMs, setVisibleRangeMs] = useState<{ from: number; to: number } | null>(null); + const onHitClickRef = useRef(onClassifierHitClick); + const onDarkClickRef = useRef(onInferredDarkClick); + + const overlayCanvasRef = useRef(null); + const overlayCtxRef = useRef(null); + const overlayDataRef = useRef([]); + const overlayLiveRef = useRef([]); + const overlayLastFetchRef = useRef<{ startTs: number; endTs: number; ticker: string } | null>( + null + ); + const overlayFetchAbortRef = useRef(null); + const overlayTimerRef = useRef(null); + + const [overlayEnabled, setOverlayEnabled] = useState(true); + + const drawOverlay = useCallback( + (points: EquityOverlayPoint[]) => { + const canvas = overlayCanvasRef.current; + const ctx = overlayCtxRef.current; + const chart = chartRef.current; + if (!canvas || !ctx || !chart) { + return; + } + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + if (!overlayEnabled || points.length === 0) { + canvas.style.opacity = "0"; + return; + } + + const timeScale = chart.timeScale(); + if (!seriesRef.current) { + canvas.style.opacity = "0"; + return; + } + + const filtered = points.filter((point) => point.offExchangeFlag); + const sampled = sampleToLimit(filtered, 1400); + + const maxRadius = 10; + const minRadius = 2; + const maxSize = Math.max(1, ...sampled.map((point) => point.size)); + + ctx.globalAlpha = 0.9; + ctx.fillStyle = "rgba(103, 185, 255, 0.4)"; + ctx.strokeStyle = "rgba(103, 185, 255, 0.9)"; + + for (const point of sampled) { + const x = timeScale.timeToCoordinate(toChartTime(point.ts)); + const y = seriesRef.current.priceToCoordinate(point.price); + if (x === null || y === null) { + continue; + } + + const radius = clamp( + minRadius + (Math.sqrt(point.size) / Math.sqrt(maxSize)) * (maxRadius - minRadius), + minRadius, + maxRadius + ); + + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.fill(); + ctx.stroke(); + } + + ctx.globalAlpha = 1; + canvas.style.opacity = "1"; + }, + [overlayEnabled] + ); + + useEffect(() => { + drawOverlay([...overlayDataRef.current, ...overlayLiveRef.current]); + }, [drawOverlay, ticker, intervalMs, mode]); + + useEffect(() => { + onHitClickRef.current = onClassifierHitClick; + }, [onClassifierHitClick]); + + useEffect(() => { + onDarkClickRef.current = onInferredDarkClick; + }, [onInferredDarkClick]); + + const markerBundle = useMemo(() => { + const lookup = new Map(); + const markers: SeriesMarker[] = []; + + if (!visibleRangeMs) { + return { markers, lookup }; + } + + const { from, to } = visibleRangeMs; + const inRangeHits = classifierHits + .filter((hit) => hit.source_ts >= from && hit.source_ts <= to) + .sort((a, b) => { + const delta = a.source_ts - b.source_ts; + if (delta !== 0) { + return delta; + } + return a.seq - b.seq; + }); + const inRangeDark = inferredDark + .filter((event) => event.source_ts >= from && event.source_ts <= to) + .sort((a, b) => { + const delta = a.source_ts - b.source_ts; + if (delta !== 0) { + return delta; + } + return a.seq - b.seq; + }); + + const MAX_HIT_MARKERS = 220; + const MAX_DARK_MARKERS = 120; + const MAX_TOTAL_MARKERS = 320; + + const cappedHits = + inRangeHits.length > MAX_HIT_MARKERS + ? inRangeHits.slice(inRangeHits.length - MAX_HIT_MARKERS) + : inRangeHits; + const cappedDark = + inRangeDark.length > MAX_DARK_MARKERS + ? inRangeDark.slice(inRangeDark.length - MAX_DARK_MARKERS) + : inRangeDark; + + for (const hit of cappedHits) { + const direction = normalizeDirection(hit.direction); + const markerId = `hit:${hit.trace_id}:${hit.seq}`; + lookup.set(markerId, { kind: "hit", hit }); + + markers.push({ + id: markerId, + time: toChartTime(hit.source_ts), + position: direction === "bullish" ? "belowBar" : "aboveBar", + color: + direction === "bullish" + ? "#59d98e" + : direction === "bearish" + ? "#ff8e63" + : "rgba(197, 209, 223, 0.85)", + shape: + direction === "bullish" + ? "arrowUp" + : direction === "bearish" + ? "arrowDown" + : "circle", + text: hit.classifier_id ? hit.classifier_id.slice(0, 3).toUpperCase() : "H" + }); + } + + for (const event of cappedDark) { + const markerId = `dark:${event.trace_id}:${event.seq}`; + lookup.set(markerId, { kind: "dark", event }); + markers.push({ + id: markerId, + time: toChartTime(event.source_ts), + position: "aboveBar", + color: "rgba(103, 185, 255, 0.95)", + shape: "square", + text: "D" + }); + } + + markers.sort((a, b) => { + const delta = Number(a.time) - Number(b.time); + if (delta !== 0) { + return delta; + } + return String(a.id ?? "").localeCompare(String(b.id ?? "")); + }); + + const cappedMarkers = + markers.length > MAX_TOTAL_MARKERS + ? markers.slice(markers.length - MAX_TOTAL_MARKERS) + : markers; + + if (cappedMarkers !== markers) { + const nextLookup = new Map(); + for (const marker of cappedMarkers) { + const id = marker.id; + if (typeof id !== "string") { + continue; + } + const action = lookup.get(id); + if (action) { + nextLookup.set(id, action); + } + } + return { markers: cappedMarkers, lookup: nextLookup }; + } + + return { markers: cappedMarkers, lookup }; + }, [classifierHits, inferredDark, visibleRangeMs]); + + useEffect(() => { + if (!seriesRef.current) { + return; + } + markerLookupRef.current = markerBundle.lookup; + seriesRef.current.setMarkers(markerBundle.markers); + }, [markerBundle]); + + const replayBucket = useMemo(() => { + if (mode !== "replay" || replayTime === null) { + return null; + } + return Math.floor(replayTime / intervalMs); + }, [mode, replayTime, intervalMs]); + const replayEndTs = useMemo(() => { + if (replayBucket === null) { + return null; + } + return (replayBucket + 1) * intervalMs - 1; + }, [replayBucket, intervalMs]); + const [ready, setReady] = useState(false); + const [status, setStatus] = useState(mode === "live" ? "connecting" : "connected"); + const [lastUpdate, setLastUpdate] = useState(null); + const [hasData, setHasData] = useState(false); + const [error, setError] = useState(null); + + useLayoutEffect(() => { + const container = containerRef.current; + if (!container) { + return; + } + + const width = container.clientWidth || 600; + const height = container.clientHeight || 360; + const chart = createChart(container, { + width, + height, + layout: { + background: { color: "#0c1721" }, + textColor: "#d9e7f3" + }, + grid: { + vertLines: { color: "rgba(133, 157, 184, 0.12)" }, + horzLines: { color: "rgba(133, 157, 184, 0.12)" } + }, + crosshair: { + vertLine: { color: "rgba(89, 217, 142, 0.35)" }, + horzLine: { color: "rgba(89, 217, 142, 0.35)" } + }, + timeScale: { + borderColor: "rgba(133, 157, 184, 0.25)", + timeVisible: true, + secondsVisible: intervalMs < 60000 + }, + rightPriceScale: { + borderColor: "rgba(133, 157, 184, 0.25)" + } + }); + + const overlayCanvas = document.createElement("canvas"); + overlayCanvas.width = Math.max(1, Math.floor(width)); + overlayCanvas.height = Math.max(1, Math.floor(height)); + overlayCanvas.style.position = "absolute"; + overlayCanvas.style.inset = "0"; + overlayCanvas.style.pointerEvents = "none"; + overlayCanvas.style.zIndex = "2"; + overlayCanvas.style.opacity = "0"; + container.style.position = "relative"; + container.appendChild(overlayCanvas); + overlayCanvasRef.current = overlayCanvas; + overlayCtxRef.current = overlayCanvas.getContext("2d"); + + const series = chart.addCandlestickSeries({ + upColor: "#59d98e", + downColor: "#ff8e63", + borderVisible: false, + wickUpColor: "#59d98e", + wickDownColor: "#ff8e63" + }); + + chartRef.current = chart; + seriesRef.current = series; + setReady(true); + + const timeScale = chart.timeScale(); + const updateVisibleRange = () => { + const range = timeScale.getVisibleRange(); + if (!range) { + setVisibleRangeMs(null); + return; + } + const from = chartTimeToMs(range.from); + const to = chartTimeToMs(range.to); + if (from === null || to === null) { + setVisibleRangeMs(null); + return; + } + + setVisibleRangeMs({ + from: Math.min(from, to), + to: Math.max(from, to) + }); + }; + + const clickHandler = (param: { hoveredObjectId?: unknown }) => { + const hovered = param.hoveredObjectId; + if (hovered === null || hovered === undefined) { + return; + } + const key = typeof hovered === "string" ? hovered : String(hovered); + const action = markerLookupRef.current.get(key); + if (!action) { + return; + } + if (action.kind === "hit") { + onHitClickRef.current(action.hit); + } else { + onDarkClickRef.current(action.event); + } + }; + + updateVisibleRange(); + timeScale.subscribeVisibleTimeRangeChange(updateVisibleRange); + chart.subscribeClick(clickHandler); + + const resizeObserver = new ResizeObserver((entries) => { + const entry = entries[0]; + if (!entry) { + return; + } + const { width: nextWidth, height: nextHeight } = entry.contentRect; + if (Number.isFinite(nextWidth) && Number.isFinite(nextHeight)) { + const nextW = Math.max(1, Math.floor(nextWidth)); + const nextH = Math.max(1, Math.floor(nextHeight)); + chart.applyOptions({ + width: nextW, + height: nextH + }); + + const canvas = overlayCanvasRef.current; + if (canvas) { + canvas.width = nextW; + canvas.height = nextH; + } + } + }); + + resizeObserver.observe(container); + + return () => { + resizeObserver.disconnect(); + timeScale.unsubscribeVisibleTimeRangeChange(updateVisibleRange); + chart.unsubscribeClick(clickHandler); + chart.remove(); + chartRef.current = null; + seriesRef.current = null; + overlayCtxRef.current = null; + overlayCanvasRef.current?.remove(); + overlayCanvasRef.current = null; + }; + }, []); + + useEffect(() => { + if (!ready || !seriesRef.current) { + return; + } + + if (mode === "replay" && replayBucket === null) { + setError(null); + setHasData(false); + setLastUpdate(null); + lastCandleRef.current = null; + seriesRef.current.setData([]); + overlayDataRef.current = []; + overlayLiveRef.current = []; + overlayLastFetchRef.current = null; + setStatus("connected"); + return; + } + + let active = true; + setError(null); + setHasData(false); + setLastUpdate(null); + lastCandleRef.current = null; + seriesRef.current.setData([]); + overlayDataRef.current = []; + overlayLiveRef.current = []; + overlayLastFetchRef.current = null; + setStatus(mode === "live" ? "connecting" : "connected"); + + const fetchCandles = async () => { + try { + const url = new URL(buildApiUrl("/candles/equities")); + url.searchParams.set("underlying_id", ticker); + url.searchParams.set("interval_ms", intervalMs.toString()); + url.searchParams.set("limit", "300"); + url.searchParams.set("cache", "1"); + if (mode === "replay" && replayEndTs !== null) { + url.searchParams.set("end_ts", replayEndTs.toString()); + } + const response = await fetch(url.toString()); + if (!response.ok) { + const detail = await readErrorDetail(response); + throw new Error( + `Candle fetch failed (${response.status})${detail ? `: ${detail}` : ""}` + ); + } + const payload = (await response.json()) as { data?: EquityCandle[] }; + if (!active || !seriesRef.current) { + return; + } + const sorted = [...(payload.data ?? [])].sort((a, b) => { + if (a.ts !== b.ts) { + return a.ts - b.ts; + } + return a.seq - b.seq; + }); + const chartData = sorted.map(toChartCandle); + seriesRef.current.setData(chartData); + chartRef.current?.timeScale().fitContent(); + drawOverlay([...overlayDataRef.current, ...overlayLiveRef.current]); + + if (sorted.length > 0) { + const last = sorted[sorted.length - 1]; + lastCandleRef.current = { time: toChartTime(last.ts), seq: last.seq }; + setHasData(true); + setLastUpdate(last.ingest_ts ?? last.ts); + } + } catch (error) { + if (!active) { + return; + } + setError(error instanceof Error ? error.message : String(error)); + setStatus("disconnected"); + setHasData(false); + } + }; + + + const ensureOverlayListener = () => { + if (!chartRef.current) { + return; + } + + const handler = () => { + const combined = [...overlayDataRef.current, ...overlayLiveRef.current]; + drawOverlay(combined); + scheduleOverlayFetch(); + }; + + chartRef.current.timeScale().subscribeVisibleTimeRangeChange(handler); + return () => { + chartRef.current?.timeScale().unsubscribeVisibleTimeRangeChange(handler); + }; + }; + + const cancelOverlayFetch = () => { + if (overlayFetchAbortRef.current) { + overlayFetchAbortRef.current.abort(); + overlayFetchAbortRef.current = null; + } + }; + + const fetchOverlayRange = async (startTs: number, endTs: number) => { + cancelOverlayFetch(); + const abort = new AbortController(); + overlayFetchAbortRef.current = abort; + + const url = new URL(buildApiUrl("/prints/equities/range")); + url.searchParams.set("underlying_id", ticker); + url.searchParams.set("start_ts", Math.floor(startTs).toString()); + url.searchParams.set("end_ts", Math.floor(endTs).toString()); + url.searchParams.set("limit", "2500"); + + const response = await fetch(url.toString(), { signal: abort.signal }); + if (!response.ok) { + const detail = await readErrorDetail(response); + throw new Error( + `Equity range fetch failed (${response.status})${detail ? `: ${detail}` : ""}` + ); + } + + const payload = (await response.json()) as { data?: EquityPrint[] }; + const prints = payload.data ?? []; + overlayDataRef.current = prints.map((print) => ({ + ts: print.ts, + price: print.price, + size: print.size, + offExchangeFlag: print.offExchangeFlag + })); + overlayLiveRef.current = []; + overlayLastFetchRef.current = { startTs, endTs, ticker }; + }; + + function scheduleOverlayFetch() { + if (overlayTimerRef.current !== null) { + window.clearTimeout(overlayTimerRef.current); + } + + overlayTimerRef.current = window.setTimeout(() => { + if (!active || !chartRef.current || !seriesRef.current) { + return; + } + + const timeScale = chartRef.current.timeScale(); + const range = timeScale.getVisibleRange(); + if (!range) { + return; + } + + const startTs = chartTimeToMs(range.from); + const endTs = chartTimeToMs(range.to); + if (startTs === null || endTs === null) { + return; + } + const last = overlayLastFetchRef.current; + + const needsFetch = + !last || + last.ticker !== ticker || + startTs < last.startTs || + endTs > last.endTs || + Math.abs(endTs - last.endTs) > intervalMs * 6; + + if (!needsFetch) { + return; + } + + void fetchOverlayRange(startTs, endTs) + .then(() => { + drawOverlay([...overlayDataRef.current, ...overlayLiveRef.current]); + }) + .catch((error) => { + if (!active) { + return; + } + if (error instanceof DOMException && error.name === "AbortError") { + return; + } + console.warn("Overlay fetch failed", error); + }); + }, 180); + } + + const overlayUnsubscribe = ensureOverlayListener(); + scheduleOverlayFetch(); + + void fetchCandles(); + + return () => { + active = false; + cancelOverlayFetch(); + if (overlayTimerRef.current !== null) { + window.clearTimeout(overlayTimerRef.current); + overlayTimerRef.current = null; + } + overlayUnsubscribe?.(); + }; + }, [ready, ticker, intervalMs, mode, replayBucket, replayEndTs]); + + useEffect(() => { + if (!ready || mode !== "live" || !seriesRef.current) { + if (socketRef.current) { + socketRef.current.close(); + } + if (reconnectRef.current !== null) { + window.clearTimeout(reconnectRef.current); + reconnectRef.current = null; + } + + if (overlaySocketRef.current) { + overlaySocketRef.current.close(); + } + if (overlayReconnectRef.current !== null) { + window.clearTimeout(overlayReconnectRef.current); + overlayReconnectRef.current = null; + } + + return; + } + + let active = true; + + const connect = () => { + if (!active) { + return; + } + + setStatus("connecting"); + const socket = new WebSocket(buildWsUrl("/ws/equity-candles")); + socketRef.current = socket; + + socket.onopen = () => { + if (!active) { + return; + } + setStatus("connected"); + }; + + socket.onmessage = (event) => { + if (!active || !seriesRef.current) { + return; + } + + try { + const message = JSON.parse(event.data) as StreamMessage; + if (!message || message.type !== "equity-candle") { + return; + } + + const candle = message.payload; + if (candle.underlying_id !== ticker || candle.interval_ms !== intervalMs) { + return; + } + + const chartCandle = toChartCandle(candle); + const last = lastCandleRef.current; + if (last) { + if (chartCandle.time < last.time) { + return; + } + if (chartCandle.time === last.time && candle.seq <= last.seq) { + return; + } + } + + seriesRef.current.update(chartCandle); + lastCandleRef.current = { time: chartCandle.time, seq: candle.seq }; + setHasData(true); + setLastUpdate(candle.ingest_ts ?? candle.ts); + drawOverlay([...overlayDataRef.current, ...overlayLiveRef.current]); + } catch (error) { + console.warn("Failed to parse candle payload", error); + } + }; + + socket.onclose = () => { + if (!active) { + return; + } + setStatus("disconnected"); + reconnectRef.current = window.setTimeout(connect, 1000); + }; + + socket.onerror = () => { + if (!active) { + return; + } + setStatus("disconnected"); + socket.close(); + }; + }; + + const connectOverlay = () => { + if (!active) { + return; + } + + const socket = new WebSocket(buildWsUrl("/ws/equities")); + overlaySocketRef.current = socket; + + socket.onmessage = (event) => { + if (!active) { + return; + } + + try { + const message = JSON.parse(event.data) as StreamMessage; + if (!message || message.type !== "equity-print") { + return; + } + + const print = message.payload; + if (print.underlying_id !== ticker) { + return; + } + + overlayLiveRef.current.push({ + ts: print.ts, + price: print.price, + size: print.size, + offExchangeFlag: print.offExchangeFlag + }); + + if (overlayLiveRef.current.length > 1500) { + overlayLiveRef.current = overlayLiveRef.current.slice(-1500); + } + + drawOverlay([...overlayDataRef.current, ...overlayLiveRef.current]); + } catch (error) { + console.warn("Failed to parse equity print payload", error); + } + }; + + socket.onclose = () => { + if (!active) { + return; + } + overlayReconnectRef.current = window.setTimeout(connectOverlay, 1500); + }; + + socket.onerror = () => { + if (!active) { + return; + } + socket.close(); + }; + }; + + connect(); + connectOverlay(); + + return () => { + active = false; + if (reconnectRef.current !== null) { + window.clearTimeout(reconnectRef.current); + reconnectRef.current = null; + } + if (socketRef.current) { + socketRef.current.close(); + } + + if (overlayReconnectRef.current !== null) { + window.clearTimeout(overlayReconnectRef.current); + overlayReconnectRef.current = null; + } + if (overlaySocketRef.current) { + overlaySocketRef.current.close(); + } + }; + }, [ready, mode, ticker, intervalMs, drawOverlay]); + + useEffect(() => { + if (!chartRef.current) { + return; + } + chartRef.current.timeScale().applyOptions({ + timeVisible: true, + secondsVisible: intervalMs < 60000 + }); + }, [intervalMs]); + + const statusText = statusLabel(status, false, mode); + const intervalLabel = formatIntervalLabel(intervalMs); + const emptyLabel = + mode === "live" + ? status === "connected" + ? `No candles yet. First ${intervalLabel} candle appears after the window closes.` + : "Chart offline. Start candles service." + : "No candles for this replay window."; + + return ( +
+
+
+ + {statusText} +
+ + {lastUpdate ? `Updated ${formatTime(lastUpdate)}` : "Waiting for data"} + + + Blue circles = off-exchange trades +
+
+ {error ? ( +
Chart error: {error}
+ ) : !hasData ? ( +
{emptyLabel}
+ ) : null} +
+ ); +}; + +type AlertSeverityStripProps = { + alerts: AlertEvent[]; + referenceTimeMs?: number; +}; + +const AlertSeverityStrip = ({ alerts, referenceTimeMs }: AlertSeverityStripProps) => { + const windowMs = 30 * 60 * 1000; + const now = referenceTimeMs ?? Date.now(); + const severityCounts = alerts.reduce( + (acc, alert) => { + if (now - alert.source_ts > windowMs) { + return acc; + } + if (alert.severity === "high") { + acc.high += 1; + } else if (alert.severity === "medium") { + acc.medium += 1; + } else { + acc.low += 1; + } + return acc; + }, + { high: 0, medium: 0, low: 0 } + ); + + const directionCounts = alerts.reduce( + (acc, alert) => { + if (now - alert.source_ts > windowMs) { + return acc; + } + const direction = normalizeDirection(alert.hits[0]?.direction ?? "neutral"); + acc[direction] += 1; + return acc; + }, + { bullish: 0, bearish: 0, neutral: 0 } + ); + + const severityTotal = severityCounts.high + severityCounts.medium + severityCounts.low; + const highPct = severityTotal > 0 ? (severityCounts.high / severityTotal) * 100 : 0; + const mediumPct = severityTotal > 0 ? (severityCounts.medium / severityTotal) * 100 : 0; + const lowPct = severityTotal > 0 ? (severityCounts.low / severityTotal) * 100 : 0; + + const directionTotal = + directionCounts.bullish + directionCounts.bearish + directionCounts.neutral; + const bullishPct = directionTotal > 0 ? (directionCounts.bullish / directionTotal) * 100 : 0; + const bearishPct = directionTotal > 0 ? (directionCounts.bearish / directionTotal) * 100 : 0; + const neutralPct = directionTotal > 0 ? (directionCounts.neutral / directionTotal) * 100 : 0; + + return ( +
+
+
+ Severity (last 30m) + {severityTotal} alerts +
+
+
+ {severityCounts.high > 0 ? `High ${severityCounts.high}` : ""} +
+
+ {severityCounts.medium > 0 ? `Med ${severityCounts.medium}` : ""} +
+
+ {severityCounts.low > 0 ? `Low ${severityCounts.low}` : ""} +
+
+
+
+
+ Direction (last 30m) + {directionTotal} alerts +
+
+
+ {directionCounts.bullish > 0 ? `Bull ${directionCounts.bullish}` : ""} +
+
+ {directionCounts.bearish > 0 ? `Bear ${directionCounts.bearish}` : ""} +
+
+ {directionCounts.neutral > 0 ? `Neut ${directionCounts.neutral}` : ""} +
+
+
+
+ ); +}; + +type EvidenceItem = + | { kind: "flow"; id: string; packet: FlowPacket } + | { kind: "print"; id: string; print: OptionPrint } + | { kind: "unknown"; id: string }; + +type DarkEvidenceItem = + | { kind: "join"; id: string; join: EquityPrintJoin } + | { kind: "unknown"; id: string }; + +type AlertDrawerProps = { + alert: AlertEvent; + flowPacket: FlowPacket | null; + evidence: EvidenceItem[]; + onClose: () => void; +}; + +const AlertDrawer = ({ alert, flowPacket, evidence, onClose }: AlertDrawerProps) => { + const primary = alert.hits[0]; + const direction = primary ? normalizeDirection(primary.direction) : "neutral"; + const evidencePrints = evidence.filter((item) => item.kind === "print"); + const unknownCount = evidence.filter((item) => item.kind === "unknown").length; + + return ( + + ); +}; + +type ClassifierHitDrawerProps = { + hit: ClassifierHitEvent; + flowPacket: FlowPacket | null; + evidence: EvidenceItem[]; + onClose: () => void; +}; + +const ClassifierHitDrawer = ({ hit, flowPacket, evidence, onClose }: ClassifierHitDrawerProps) => { + const direction = normalizeDirection(hit.direction); + const evidencePrints = evidence.filter((item) => item.kind === "print"); + const unknownCount = evidence.filter((item) => item.kind === "unknown").length; + + return ( + + ); +}; + +type DarkDrawerProps = { + event: InferredDarkEvent; + evidence: DarkEvidenceItem[]; + underlying: string | null; + onClose: () => void; +}; + +const DarkDrawer = ({ event, evidence, underlying, onClose }: DarkDrawerProps) => { + const joinEvidence = evidence.filter( + (item): item is { kind: "join"; id: string; join: EquityPrintJoin } => item.kind === "join" + ); + const unknownCount = evidence.filter((item) => item.kind === "unknown").length; + const traceRefs = event.evidence_refs.slice(0, 6); + const extraRefs = Math.max(0, event.evidence_refs.length - traceRefs.length); + + return ( + + ); +}; + +const formatFlowMetric = (value: number, suffix?: string): string => { + if (suffix) { + return `${value}${suffix}`; + } + + return value.toLocaleString(); +}; + +const formatCompactUsd = (value: number): string => { + if (!Number.isFinite(value) || value === 0) { + return "$0"; + } + + return new Intl.NumberFormat(undefined, { + style: "currency", + currency: "USD", + notation: "compact", + maximumFractionDigits: 1 + }).format(value); +}; + +const formatCompactValue = (value: number): string => { + if (!Number.isFinite(value) || value === 0) { + return "0"; + } + + return new Intl.NumberFormat(undefined, { + notation: "compact", + maximumFractionDigits: 1 + }).format(value); +}; + +const classNames = (...values: Array): string => { + return values.filter(Boolean).join(" "); +}; + +const describeBias = (bullish: number, bearish: number, neutral: number): string => { + if (bullish === 0 && bearish === 0 && neutral === 0) { + return "Waiting"; + } + if (bullish === bearish) { + return neutral > 0 ? "Balanced" : "Two-way"; + } + return bullish > bearish ? "Bullish skew" : "Bearish skew"; +}; + +type DashboardView = "overview" | "options-flow" | "signals" | "off-exchange"; + +type TradingDeskProps = { + view: DashboardView; +}; + +type ViewDefinition = { + label: string; + title: string; + description: string; + href: string; + kicker: string; +}; + +const VIEW_DEFINITIONS: Record = { + overview: { + label: "Overview", + title: "Desk Overview", + description: + "Triage the tape fast: active names, alert pressure, flow concentration, and off-exchange context in one pass.", + href: "/", + kicker: "Command view" + }, + "options-flow": { + label: "Options Flow", + title: "Options Flow", + description: + "Stay inside the contracts: raw prints, clustering quality, classifier evidence, and chart context arranged for drill-down.", + href: "/options-flow", + kicker: "Contract view" + }, + signals: { + label: "Signals", + title: "Signals & Evidence", + description: + "Run alert triage with explanations first, then step down into classifier hits and packet evidence without losing context.", + href: "/signals", + kicker: "Alert view" + }, + "off-exchange": { + label: "Off-Exchange", + title: "Off-Exchange & Dark", + description: + "Watch the equity tape, inferred dark events, and quote-join quality side by side to judge whether hidden liquidity is real.", + href: "/off-exchange", + kicker: "Liquidity view" + } +}; + +type TickerPulse = { + ticker: string; + score: number; + alerts: number; + flow: number; + dark: number; + prints: number; + directionScore: number; + lastTs: number; +}; + +type MetricCardProps = { + label: string; + value: string; + foot: string; + tone?: "neutral" | "bullish" | "bearish" | "signal"; +}; + +const MetricCard = ({ label, value, foot, tone = "neutral" }: MetricCardProps) => { + return ( +
+ {label} + {value} + {foot} +
+ ); +}; + +type DeskPanelProps = { + label: string; + title: string; + subtitle: string; + className?: string; + actions?: ReactNode; + children: ReactNode; +}; + +const DeskPanel = ({ label, title, subtitle, className, actions, children }: DeskPanelProps) => { + return ( +
+
+
+ {label} +

{title}

+

{subtitle}

+
+ {actions ?
{actions}
: null} +
+ {children} +
+ ); +}; + +export function TradingDesk({ view }: TradingDeskProps) { + const [mode, setMode] = useState("live"); + const [replaySource, setReplaySource] = useState(null); + const [selectedAlert, setSelectedAlert] = useState(null); + const [selectedDarkEvent, setSelectedDarkEvent] = useState(null); + const [selectedClassifierHit, setSelectedClassifierHit] = useState(null); + const [filterInput, setFilterInput] = useState(""); + const [chartIntervalMs, setChartIntervalMs] = useState(CANDLE_INTERVALS[0].ms); + const settingsReadyRef = useRef(false); + const deferredFilterInput = useDeferredValue(filterInput); + + useEffect(() => { + try { + const savedFilter = window.localStorage.getItem("islandflow:focus-filter"); + const savedMode = window.localStorage.getItem("islandflow:view-mode"); + const savedInterval = window.localStorage.getItem("islandflow:chart-interval"); + + if (savedFilter) { + setFilterInput(savedFilter); + } + if (savedMode === "live" || savedMode === "replay") { + setMode(savedMode); + } + if (savedInterval) { + const parsed = Number(savedInterval); + if (CANDLE_INTERVALS.some((interval) => interval.ms === parsed)) { + setChartIntervalMs(parsed); + } + } + } catch { + // Ignore persisted UI state failures. + } + + settingsReadyRef.current = true; + }, []); + + useEffect(() => { + if (!settingsReadyRef.current) { + return; + } + + try { + window.localStorage.setItem("islandflow:focus-filter", filterInput); + window.localStorage.setItem("islandflow:view-mode", mode); + window.localStorage.setItem("islandflow:chart-interval", String(chartIntervalMs)); + } catch { + // Ignore persisted UI state failures. + } + }, [chartIntervalMs, filterInput, mode]); + + const handleReplaySource = useCallback((value: string | null) => { + setReplaySource(value); + }, []); + + useEffect(() => { + setReplaySource(null); + }, [mode]); + const optionsScroll = useListScroll(); + const equitiesScroll = useListScroll(); + const flowScroll = useListScroll(); + const darkScroll = useListScroll(); + const alertsScroll = useListScroll(); + const classifierScroll = useListScroll(); + + const optionsAnchor = useScrollAnchor(optionsScroll.listRef, optionsScroll.isAtTopRef); + const equitiesAnchor = useScrollAnchor(equitiesScroll.listRef, equitiesScroll.isAtTopRef); + 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 disableReplayGrouping = useCallback(() => null, []); + + const options = useTape({ + mode, + wsPath: "/ws/options", + replayPath: "/replay/options", + latestPath: "/prints/options", + expectedType: "option-print", + batchSize: mode === "replay" ? 120 : undefined, + pollMs: mode === "replay" ? 200 : undefined, + captureScroll: optionsAnchor.capture, + onNewItems: optionsScroll.onNewItems, + getReplayKey: extractReplaySource, + onReplaySourceKey: handleReplaySource + }); + + const equities = useTape({ + mode, + wsPath: "/ws/equities", + replayPath: "/replay/equities", + latestPath: "/prints/equities", + expectedType: "equity-print", + batchSize: mode === "replay" ? 120 : undefined, + pollMs: mode === "replay" ? 200 : undefined, + captureScroll: equitiesAnchor.capture, + onNewItems: equitiesScroll.onNewItems + }); + + const equityJoins = useTape({ + mode, + wsPath: "/ws/equity-joins", + replayPath: "/replay/equity-joins", + latestPath: "/joins/equities", + expectedType: "equity-join", + batchSize: mode === "replay" ? 120 : undefined, + pollMs: mode === "replay" ? 200 : undefined, + getReplayKey: disableReplayGrouping + }); + + const nbbo = useTape({ + mode, + wsPath: "/ws/options-nbbo", + replayPath: "/replay/nbbo", + latestPath: "/nbbo/options", + expectedType: "option-nbbo", + batchSize: mode === "replay" ? 120 : undefined, + pollMs: mode === "replay" ? 200 : undefined, + getReplayKey: extractReplaySource, + replaySourceKey: replaySource + }); + + const inferredDark = useTape({ + mode, + wsPath: "/ws/inferred-dark", + replayPath: "/replay/inferred-dark", + latestPath: "/dark/inferred", + expectedType: "inferred-dark", + batchSize: mode === "replay" ? 120 : undefined, + pollMs: mode === "replay" ? 200 : undefined, + captureScroll: darkAnchor.capture, + onNewItems: darkScroll.onNewItems, + getReplayKey: disableReplayGrouping + }); + + const flow = useTape({ + mode, + wsPath: "/ws/flow", + replayPath: "/replay/flow", + latestPath: "/flow/packets", + expectedType: "flow-packet", + batchSize: mode === "replay" ? 120 : undefined, + pollMs: mode === "replay" ? 200 : undefined, + captureScroll: flowAnchor.capture, + onNewItems: flowScroll.onNewItems, + getReplayKey: disableReplayGrouping + }); + const alerts = useTape({ + mode, + wsPath: "/ws/alerts", + replayPath: "/replay/alerts", + latestPath: "/flow/alerts", + expectedType: "alert", + batchSize: mode === "replay" ? 120 : undefined, + pollMs: mode === "replay" ? 200 : undefined, + captureScroll: alertsAnchor.capture, + onNewItems: alertsScroll.onNewItems, + getReplayKey: disableReplayGrouping + }); + const classifierHits = useTape({ + mode, + wsPath: "/ws/classifier-hits", + replayPath: "/replay/classifier-hits", + latestPath: "/flow/classifier-hits", + expectedType: "classifier-hit", + batchSize: mode === "replay" ? 120 : undefined, + pollMs: mode === "replay" ? 200 : undefined, + captureScroll: classifierAnchor.capture, + onNewItems: classifierScroll.onNewItems, + getReplayKey: disableReplayGrouping + }); + + useLayoutEffect(() => { + optionsAnchor.apply(); + }, [options.items, optionsAnchor.apply]); + + useLayoutEffect(() => { + equitiesAnchor.apply(); + }, [equities.items, equitiesAnchor.apply]); + + useLayoutEffect(() => { + flowAnchor.apply(); + }, [flow.items, flowAnchor.apply]); + + useLayoutEffect(() => { + darkAnchor.apply(); + }, [inferredDark.items, darkAnchor.apply]); + + useLayoutEffect(() => { + alertsAnchor.apply(); + }, [alerts.items, alertsAnchor.apply]); + + useLayoutEffect(() => { + classifierAnchor.apply(); + }, [classifierHits.items, classifierAnchor.apply]); + + const activeTickers = useMemo(() => { + const parts = deferredFilterInput + .split(/[,\s]+/) + .map((value) => value.trim().toUpperCase()) + .filter(Boolean); + return Array.from(new Set(parts)); + }, [deferredFilterInput]); + + const tickerSet = useMemo(() => new Set(activeTickers), [activeTickers]); + + const nbboMap = useMemo(() => { + const map = new Map(); + for (const quote of nbbo.items) { + const contractId = normalizeContractId(quote.option_contract_id); + const existing = map.get(contractId); + if ( + !existing || + quote.ts > existing.ts || + (quote.ts === existing.ts && quote.seq >= existing.seq) + ) { + map.set(contractId, quote); + } + } + return map; + }, [nbbo.items]); + + const optionPrintMap = useMemo(() => { + const map = new Map(); + for (const print of options.items) { + if (print.trace_id) { + map.set(print.trace_id, print); + } + } + return map; + }, [options.items]); + + const equityPrintMap = useMemo(() => { + const map = new Map(); + for (const print of equities.items) { + if (print.trace_id) { + map.set(print.trace_id, print); + } + } + return map; + }, [equities.items]); + + const equityJoinMap = useMemo(() => { + const map = new Map(); + for (const join of equityJoins.items) { + map.set(join.id, join); + } + return map; + }, [equityJoins.items]); + + const flowPacketMap = useMemo(() => { + const map = new Map(); + for (const packet of flow.items) { + map.set(packet.id, packet); + } + return map; + }, [flow.items]); + + const selectedEvidence = useMemo((): EvidenceItem[] => { + if (!selectedAlert) { + return []; + } + + return selectedAlert.evidence_refs.map((id) => { + const packet = flowPacketMap.get(id); + if (packet) { + return { kind: "flow", id, packet }; + } + const print = optionPrintMap.get(id); + if (print) { + return { kind: "print", id, print }; + } + return { kind: "unknown", id }; + }); + }, [selectedAlert, flowPacketMap, optionPrintMap]); + + const selectedFlowPacket = useMemo(() => { + if (!selectedAlert) { + return null; + } + const packetId = selectedAlert.evidence_refs[0]; + return packetId ? flowPacketMap.get(packetId) ?? null : null; + }, [selectedAlert, flowPacketMap]); + + const selectedDarkEvidence = useMemo((): DarkEvidenceItem[] => { + if (!selectedDarkEvent) { + return []; + } + + return selectedDarkEvent.evidence_refs.map((id) => { + const join = equityJoinMap.get(id); + if (join) { + return { kind: "join", id, join }; + } + return { kind: "unknown", id }; + }); + }, [selectedDarkEvent, equityJoinMap]); + + const selectedDarkUnderlying = useMemo(() => { + if (!selectedDarkEvent) { + return null; + } + return inferDarkUnderlying(selectedDarkEvent, equityPrintMap, equityJoinMap); + }, [selectedDarkEvent, equityJoinMap, equityPrintMap]); + + useEffect(() => { + if (mode !== "live") { + setSelectedAlert(null); + } + setSelectedDarkEvent(null); + setSelectedClassifierHit(null); + }, [mode]); + + const extractPacketContract = useCallback((packet: FlowPacket): string => { + const contract = packet.features.option_contract_id; + if (typeof contract === "string") { + return contract; + } + const match = packet.id.match(/^flowpacket:([^:]+):/); + return match?.[1] ?? packet.id; + }, []); + + const extractUnderlyingFromTrace = useCallback((traceId: string): string | null => { + const match = traceId.match(/flowpacket:([^:]+):/); + if (!match?.[1]) { + return null; + } + return extractUnderlying(match[1]); + }, []); + + const extractPacketIdFromClassifierHitTrace = useCallback((traceId: string): string | null => { + const idx = traceId.indexOf("flowpacket:"); + if (idx < 0) { + return null; + } + return traceId.slice(idx); + }, []); + + const selectedClassifierPacketId = useMemo(() => { + if (!selectedClassifierHit) { + return null; + } + return extractPacketIdFromClassifierHitTrace(selectedClassifierHit.trace_id); + }, [extractPacketIdFromClassifierHitTrace, selectedClassifierHit]); + + const selectedClassifierFlowPacket = useMemo(() => { + if (!selectedClassifierPacketId) { + return null; + } + return flowPacketMap.get(selectedClassifierPacketId) ?? null; + }, [flowPacketMap, selectedClassifierPacketId]); + + const selectedClassifierEvidence = useMemo((): EvidenceItem[] => { + if (!selectedClassifierHit) { + return []; + } + + if (!selectedClassifierPacketId) { + return []; + } + + const packet = flowPacketMap.get(selectedClassifierPacketId); + if (!packet) { + return []; + } + + return packet.members.map((id) => { + const print = optionPrintMap.get(id); + if (print) { + return { kind: "print", id, print }; + } + return { kind: "unknown", id }; + }); + }, [flowPacketMap, optionPrintMap, selectedClassifierHit, selectedClassifierPacketId]); + + const inferAlertUnderlying = useCallback( + (alert: AlertEvent): string | null => { + const fromTrace = extractUnderlyingFromTrace(alert.trace_id); + if (fromTrace) { + return fromTrace; + } + + const packetId = alert.evidence_refs[0]; + if (packetId) { + const packet = flowPacketMap.get(packetId); + if (packet) { + return extractUnderlying(extractPacketContract(packet)); + } + } + + for (const ref of alert.evidence_refs) { + const print = optionPrintMap.get(ref); + if (print) { + return extractUnderlying(print.option_contract_id); + } + } + + return null; + }, + [extractPacketContract, extractUnderlyingFromTrace, flowPacketMap, optionPrintMap] + ); + + const matchesTicker = useCallback( + (value: string | null) => { + if (tickerSet.size === 0) { + return true; + } + if (!value) { + return false; + } + return tickerSet.has(value.toUpperCase()); + }, + [tickerSet] + ); + + const filteredOptions = useMemo(() => { + if (tickerSet.size === 0) { + return options.items; + } + return options.items.filter((print) => + matchesTicker(extractUnderlying(normalizeContractId(print.option_contract_id))) + ); + }, [options.items, matchesTicker, tickerSet]); + + const filteredEquities = useMemo(() => { + if (tickerSet.size === 0) { + return equities.items; + } + return equities.items.filter((print) => matchesTicker(print.underlying_id)); + }, [equities.items, matchesTicker, tickerSet]); + + const filteredEquityJoins = useMemo(() => { + if (tickerSet.size === 0) { + return equityJoins.items; + } + return equityJoins.items.filter((join) => matchesTicker(getJoinString(join, "underlying_id"))); + }, [equityJoins.items, matchesTicker, tickerSet]); + + const filteredInferredDark = useMemo(() => { + if (tickerSet.size === 0) { + return inferredDark.items; + } + return inferredDark.items.filter((event) => { + const underlying = inferDarkUnderlying(event, equityPrintMap, equityJoinMap); + return matchesTicker(underlying); + }); + }, [equityJoinMap, equityPrintMap, inferredDark.items, matchesTicker, tickerSet]); + + const filteredFlow = useMemo(() => { + if (tickerSet.size === 0) { + return flow.items; + } + return flow.items.filter((packet) => + matchesTicker(extractUnderlying(extractPacketContract(packet))) + ); + }, [flow.items, extractPacketContract, matchesTicker, tickerSet]); + + const filteredAlerts = useMemo(() => { + if (tickerSet.size === 0) { + return alerts.items; + } + return alerts.items.filter((alert) => matchesTicker(inferAlertUnderlying(alert))); + }, [alerts.items, inferAlertUnderlying, matchesTicker, tickerSet]); + + const filteredClassifierHits = useMemo(() => { + if (tickerSet.size === 0) { + return classifierHits.items; + } + return classifierHits.items.filter((hit) => { + const underlying = extractUnderlyingFromTrace(hit.trace_id); + return matchesTicker(underlying); + }); + }, [classifierHits.items, extractUnderlyingFromTrace, matchesTicker, tickerSet]); + + const tickerPulse = useMemo(() => { + const map = new Map(); + + const touch = (ticker: string | null, delta: Partial & { score: number; lastTs: number }) => { + if (!ticker) { + return; + } + + const symbol = ticker.toUpperCase(); + const current = map.get(symbol) ?? { + ticker: symbol, + score: 0, + alerts: 0, + flow: 0, + dark: 0, + prints: 0, + directionScore: 0, + lastTs: 0 + }; + + current.score += delta.score; + current.alerts += delta.alerts ?? 0; + current.flow += delta.flow ?? 0; + current.dark += delta.dark ?? 0; + current.prints += delta.prints ?? 0; + current.directionScore += delta.directionScore ?? 0; + current.lastTs = Math.max(current.lastTs, delta.lastTs); + map.set(symbol, current); + }; + + for (const alert of alerts.items) { + const direction = normalizeDirection(alert.hits[0]?.direction ?? "neutral"); + touch(inferAlertUnderlying(alert), { + score: alert.severity === "high" ? 7 : alert.severity === "medium" ? 5 : 3, + alerts: 1, + directionScore: direction === "bullish" ? 1 : direction === "bearish" ? -1 : 0, + lastTs: alert.source_ts + }); + } + + for (const packet of flow.items) { + touch(extractUnderlying(extractPacketContract(packet)), { + score: 3, + flow: 1, + lastTs: packet.source_ts + }); + } + + for (const event of inferredDark.items) { + touch(inferDarkUnderlying(event, equityPrintMap, equityJoinMap), { + score: 4, + dark: 1, + lastTs: event.source_ts + }); + } + + for (const print of options.items.slice(0, 240)) { + touch(extractUnderlying(normalizeContractId(print.option_contract_id)), { + score: 1, + prints: 1, + lastTs: print.ts + }); + } + + for (const print of equities.items.slice(0, 240)) { + touch(print.underlying_id, { + score: print.offExchangeFlag ? 2 : 1, + prints: 1, + lastTs: print.ts + }); + } + + return Array.from(map.values()).sort((a, b) => { + if (b.score !== a.score) { + return b.score - a.score; + } + if (b.alerts !== a.alerts) { + return b.alerts - a.alerts; + } + return b.lastTs - a.lastTs; + }); + }, [ + alerts.items, + equities.items, + equityJoinMap, + equityPrintMap, + extractPacketContract, + flow.items, + inferAlertUnderlying, + inferredDark.items, + optionPrintMap, + options.items + ]); + + const chartTicker = useMemo(() => { + return activeTickers[0] ?? tickerPulse[0]?.ticker ?? "SPY"; + }, [activeTickers, tickerPulse]); + + const chartClassifierHits = useMemo(() => { + const desired = chartTicker.toUpperCase(); + return classifierHits.items + .filter((hit) => extractUnderlyingFromTrace(hit.trace_id) === desired) + .sort((a, b) => { + const delta = a.source_ts - b.source_ts; + if (delta !== 0) { + return delta; + } + return a.seq - b.seq; + }); + }, [chartTicker, classifierHits.items, extractUnderlyingFromTrace]); + + const chartInferredDark = useMemo(() => { + const desired = chartTicker.toUpperCase(); + return inferredDark.items + .filter((event) => inferDarkUnderlying(event, equityPrintMap, equityJoinMap) === desired) + .sort((a, b) => { + const delta = a.source_ts - b.source_ts; + if (delta !== 0) { + return delta; + } + return a.seq - b.seq; + }); + }, [chartTicker, inferredDark.items, equityJoinMap, equityPrintMap]); + + const findAlertForClassifierHit = useCallback( + (hit: ClassifierHitEvent): AlertEvent | null => { + const packetId = extractPacketIdFromClassifierHitTrace(hit.trace_id); + if (!packetId) { + return null; + } + + const desiredTrace = `alert:${packetId}`; + return ( + alerts.items.find( + (item) => item.trace_id === desiredTrace || item.evidence_refs[0] === packetId + ) ?? null + ); + }, + [alerts.items, extractPacketIdFromClassifierHitTrace] + ); + + const openFromClassifierHit = useCallback( + (hit: ClassifierHitEvent) => { + const alert = findAlertForClassifierHit(hit); + if (alert) { + setSelectedClassifierHit(null); + setSelectedDarkEvent(null); + setSelectedAlert(alert); + return; + } + + setSelectedAlert(null); + setSelectedDarkEvent(null); + setSelectedClassifierHit(hit); + }, + [findAlertForClassifierHit] + ); + + const handleClassifierMarkerClick = useCallback( + (hit: ClassifierHitEvent) => { + openFromClassifierHit(hit); + }, + [openFromClassifierHit] + ); + + const handleDarkMarkerClick = useCallback((event: InferredDarkEvent) => { + setSelectedAlert(null); + setSelectedClassifierHit(null); + setSelectedDarkEvent(event); + }, []); + + const lastSeen = useMemo(() => { + return [ + options.lastUpdate, + equities.lastUpdate, + inferredDark.lastUpdate, + flow.lastUpdate, + alerts.lastUpdate, + classifierHits.lastUpdate + ] + .filter((value): value is number => value !== null) + .sort((a, b) => b - a)[0] ?? null; + }, [ + options.lastUpdate, + equities.lastUpdate, + inferredDark.lastUpdate, + flow.lastUpdate, + alerts.lastUpdate, + classifierHits.lastUpdate + ]); + + const replayClock = useMemo(() => { + return [ + options.replayTime, + equities.replayTime, + inferredDark.replayTime, + flow.replayTime, + alerts.replayTime, + classifierHits.replayTime + ] + .filter((value): value is number => value !== null) + .sort((a, b) => b - a)[0] ?? null; + }, [ + alerts.replayTime, + classifierHits.replayTime, + equities.replayTime, + flow.replayTime, + inferredDark.replayTime, + options.replayTime + ]); + + const newestSourceTs = useMemo(() => { + return [ + options.items[0]?.source_ts ?? options.items[0]?.ts, + equities.items[0]?.source_ts ?? equities.items[0]?.ts, + inferredDark.items[0]?.source_ts, + flow.items[0]?.source_ts, + alerts.items[0]?.source_ts, + classifierHits.items[0]?.source_ts + ] + .filter((value): value is number => Number.isFinite(value)) + .sort((a, b) => b - a)[0] ?? null; + }, [ + alerts.items, + classifierHits.items, + equities.items, + flow.items, + inferredDark.items, + options.items + ]); + + const referenceTime = mode === "replay" ? replayClock ?? newestSourceTs ?? Date.now() : Date.now(); + + const signalWindowMs = 15 * 60 * 1000; + const tapeWindowMs = 5 * 60 * 1000; + + const recentAlerts = useMemo(() => { + return filteredAlerts.filter((alert) => referenceTime - alert.source_ts <= signalWindowMs); + }, [filteredAlerts, referenceTime]); + + const recentClassifierHits = useMemo(() => { + return filteredClassifierHits.filter((hit) => referenceTime - hit.source_ts <= signalWindowMs); + }, [filteredClassifierHits, referenceTime]); + + const recentFlow = useMemo(() => { + return filteredFlow.filter((packet) => referenceTime - packet.source_ts <= signalWindowMs); + }, [filteredFlow, referenceTime]); + + const recentDark = useMemo(() => { + return filteredInferredDark.filter((event) => referenceTime - event.source_ts <= signalWindowMs); + }, [filteredInferredDark, referenceTime]); + + const recentOptions = useMemo(() => { + return filteredOptions.filter((print) => referenceTime - print.ts <= tapeWindowMs); + }, [filteredOptions, referenceTime]); + + const recentEquities = useMemo(() => { + return filteredEquities.filter((print) => referenceTime - print.ts <= tapeWindowMs); + }, [filteredEquities, referenceTime]); + + const recentJoins = useMemo(() => { + return filteredEquityJoins.filter((join) => referenceTime - join.source_ts <= signalWindowMs); + }, [filteredEquityJoins, referenceTime]); + + const alertDirectionMix = useMemo(() => { + return recentAlerts.reduce( + (acc, alert) => { + const direction = normalizeDirection(alert.hits[0]?.direction ?? "neutral"); + acc[direction] += 1; + if (alert.severity === "high") { + acc.high += 1; + } + return acc; + }, + { bullish: 0, bearish: 0, neutral: 0, high: 0 } + ); + }, [recentAlerts]); + + const recentOptionsNotional = useMemo(() => { + return recentOptions.reduce((sum, print) => sum + print.price * print.size * 100, 0); + }, [recentOptions]); + + const recentOffExchange = useMemo(() => { + return recentEquities.filter((print) => print.offExchangeFlag); + }, [recentEquities]); + + const offExchangeShares = useMemo(() => { + return recentOffExchange.reduce((sum, print) => sum + print.size, 0); + }, [recentOffExchange]); + + const nbboHealth = useMemo(() => { + const inspected = recentOptions.slice(0, 120); + let covered = 0; + let stale = 0; + let missing = 0; + + for (const print of inspected) { + const contractId = normalizeContractId(print.option_contract_id); + const quote = nbboMap.get(contractId); + if (!quote) { + missing += 1; + continue; + } + + const age = Math.abs(print.ts - quote.ts); + if (age > NBBO_MAX_AGE_MS_SAFE) { + stale += 1; + } else { + covered += 1; + } + } + + return { inspected: inspected.length, covered, stale, missing }; + }, [nbboMap, recentOptions]); + + const quoteJoinHealth = useMemo(() => { + return recentJoins.reduce( + (acc, join) => { + acc.total += 1; + if (parseNumber(join.join_quality.quote_stale, 0) > 0) { + acc.stale += 1; + } + if (parseNumber(join.join_quality.quote_missing, 0) > 0) { + acc.missing += 1; + } + const age = parseNumber(join.join_quality.quote_age_ms, Number.NaN); + if (Number.isFinite(age)) { + acc.aged += age; + acc.withAge += 1; + } + return acc; + }, + { total: 0, stale: 0, missing: 0, aged: 0, withAge: 0 } + ); + }, [recentJoins]); + + const headlineAlert = recentAlerts.find((alert) => alert.severity === "high") ?? filteredAlerts[0] ?? null; + const headlineDark = recentDark[0] ?? filteredInferredDark[0] ?? null; + const headlineHit = recentClassifierHits[0] ?? filteredClassifierHits[0] ?? null; + + const focusNames = useMemo(() => { + if (activeTickers.length > 0) { + const selected = activeTickers + .map((ticker) => tickerPulse.find((entry) => entry.ticker === ticker)) + .filter((entry): entry is TickerPulse => Boolean(entry)); + if (selected.length > 0) { + return selected; + } + } + + return tickerPulse.slice(0, 8); + }, [activeTickers, tickerPulse]); + + const dominantBias = describeBias( + alertDirectionMix.bullish, + alertDirectionMix.bearish, + alertDirectionMix.neutral + ); + const dominantBiasTone = + alertDirectionMix.bullish > alertDirectionMix.bearish + ? "bullish" + : alertDirectionMix.bearish > alertDirectionMix.bullish + ? "bearish" + : "neutral"; + + const viewMetrics = useMemo(() => { + const overviewMetrics: MetricCardProps[] = [ + { + label: "High priority", + value: String(alertDirectionMix.high), + foot: "High-severity alerts in the last 15m", + tone: alertDirectionMix.high > 0 ? "signal" : "neutral" + }, + { + label: "Bias", + value: dominantBias, + foot: `${alertDirectionMix.bullish} bull / ${alertDirectionMix.bearish} bear / ${alertDirectionMix.neutral} neutral`, + tone: dominantBiasTone + }, + { + label: "Options notional", + value: formatCompactUsd(recentOptionsNotional), + foot: "Five-minute options premium flowing through the tape", + tone: "neutral" + }, + { + label: "Off-ex blocks", + value: formatCompactValue(recentOffExchange.length), + foot: `${formatCompactValue(offExchangeShares)} shares off-ex in the last 5m`, + tone: "neutral" + } + ]; + + switch (view) { + case "options-flow": + return [ + { + label: "Recent prints", + value: formatCompactValue(recentOptions.length), + foot: "Options prints in the last 5m for the current view" + }, + { + label: "Flow packets", + value: formatCompactValue(recentFlow.length), + foot: "Deterministic flow clusters in the last 15m", + tone: "signal" + }, + { + label: "Premium", + value: formatCompactUsd(recentOptionsNotional), + foot: "Five-minute notional from visible prints" + }, + { + label: "NBBO quality", + value: + nbboHealth.inspected > 0 + ? `${Math.round((nbboHealth.covered / nbboHealth.inspected) * 100)}%` + : "N/A", + foot: `${nbboHealth.stale} stale / ${nbboHealth.missing} missing in sampled prints` + } + ]; + case "signals": + return [ + { + label: "Alerts", + value: formatCompactValue(recentAlerts.length), + foot: "Signals scored in the last 15m", + tone: recentAlerts.length > 0 ? "signal" : "neutral" + }, + { + label: "Bias", + value: dominantBias, + foot: `${alertDirectionMix.bullish} bull / ${alertDirectionMix.bearish} bear`, + tone: dominantBiasTone + }, + { + label: "Classifier hits", + value: formatCompactValue(recentClassifierHits.length), + foot: "Raw rule triggers before alert scoring" + }, + { + label: "Top severity", + value: headlineAlert ? headlineAlert.severity.toUpperCase() : "NONE", + foot: headlineAlert ? humanizeClassifierId(headlineAlert.hits[0]?.classifier_id ?? "alert") : "No alert in focus", + tone: headlineAlert?.severity === "high" ? "signal" : "neutral" + } + ]; + case "off-exchange": + return [ + { + label: "Off-ex prints", + value: formatCompactValue(recentOffExchange.length), + foot: `${formatCompactValue(offExchangeShares)} shares marked off-ex in the last 5m` + }, + { + label: "Dark events", + value: formatCompactValue(recentDark.length), + foot: "Inferred hidden-liquidity events in the last 15m", + tone: recentDark.length > 0 ? "signal" : "neutral" + }, + { + label: "Quote age", + value: + quoteJoinHealth.withAge > 0 + ? `${Math.round(quoteJoinHealth.aged / quoteJoinHealth.withAge)}ms` + : "N/A", + foot: `${quoteJoinHealth.stale} stale / ${quoteJoinHealth.missing} missing joined quotes` + }, + { + label: "Active name", + value: focusNames[0]?.ticker ?? "None", + foot: "Most active symbol in the current liquidity context", + tone: "neutral" + } + ]; + case "overview": + default: + return overviewMetrics; + } + }, [ + alertDirectionMix, + dominantBias, + dominantBiasTone, + focusNames, + headlineAlert, + nbboHealth, + offExchangeShares, + quoteJoinHealth, + recentAlerts, + recentClassifierHits, + recentDark, + recentFlow, + recentOffExchange.length, + recentOptions.length, + recentOptionsNotional, + view + ]); + + const pipelineHealth = [ + { + label: "Options", + status: options.status, + paused: options.paused, + dropped: options.dropped, + count: filteredOptions.length, + lastUpdate: options.lastUpdate + }, + { + label: "Flow", + status: flow.status, + paused: flow.paused, + dropped: flow.dropped, + count: filteredFlow.length, + lastUpdate: flow.lastUpdate + }, + { + label: "Alerts", + status: alerts.status, + paused: alerts.paused, + dropped: alerts.dropped, + count: filteredAlerts.length, + lastUpdate: alerts.lastUpdate + }, + { + label: "Off-Ex", + status: equities.status, + paused: equities.paused, + dropped: equities.dropped, + count: filteredEquities.length, + lastUpdate: equities.lastUpdate + } + ] as const; + + const toggleMode = () => { + setMode((prev) => (prev === "live" ? "replay" : "live")); + }; + + const focusTicker = useCallback((ticker: string) => { + startTransition(() => { + setFilterInput(ticker); + }); + }, []); + + const clearTickerFilter = useCallback(() => { + startTransition(() => { + setFilterInput(""); + }); + }, []); + + const currentView = VIEW_DEFINITIONS[view]; + + const emptyState = useCallback( + (filteredCopy: string, liveCopy: string, replayCopy: string) => { + return ( +
+ {tickerSet.size > 0 ? filteredCopy : mode === "live" ? liveCopy : replayCopy} +
+ ); + }, + [mode, tickerSet.size] + ); + + const renderChartPanel = (className?: string) => { + return ( + + {CANDLE_INTERVALS.map((interval) => ( + + ))} +
+ } + > +
+
+ + {activeTickers.length > 1 + ? `Chart locked to ${chartTicker}, first of ${activeTickers.length} filtered names` + : `Chart locked to ${chartTicker}`} + + + {mode === "replay" && replaySource ? `Replay source ${replaySource}` : "Click markers to open evidence"} + +
+ +
+ + ); + }; + + const renderOptionsPanel = (className?: string, limit?: number) => { + const items = limit ? filteredOptions.slice(0, limit) : filteredOptions; + + return ( + +
+
+ + +
+
+ {items.length === 0 + ? emptyState( + "No option prints match the current focus filter.", + "No option prints yet. Start ingest-options.", + "Replay queue empty. Ensure ClickHouse has data." + ) + : items.map((print) => { + const contractId = normalizeContractId(print.option_contract_id); + const quote = nbboMap.get(contractId); + const nbboAge = quote ? Math.abs(print.ts - quote.ts) : null; + const nbboStale = nbboAge !== null && nbboAge > NBBO_MAX_AGE_MS_SAFE; + const nbboMid = quote ? (quote.bid + quote.ask) / 2 : null; + const nbboSide = classifyNbboSide(print.price, quote); + const notional = print.price * print.size * 100; + + return ( +
+
+
{formatContractLabel(contractId)}
+
+ ${formatPrice(print.price)} + {formatSize(print.size)}x + {print.exchange} + Notional {formatCompactUsd(notional)} + {print.conditions?.length ? {print.conditions.join(", ")} : null} +
+ {quote ? ( +
+ Bid ${formatPrice(quote.bid)} + Ask ${formatPrice(quote.ask)} + Mid ${formatPrice(nbboMid ?? 0)} + {Math.round(nbboAge ?? 0)}ms + {nbboSide ? ( + + {nbboSide} + + + A + Ask + + + AA + Above Ask + + + B + Bid + + + BB + Below Bid + + + + ) : null} + {nbboStale ? Stale : null} +
+ ) : ( +
+ NBBO missing +
+ )} +
+
{formatTime(print.ts)}
+
+ ); + })} +
+
+
+ ); + }; + + const renderEquitiesPanel = (className?: string, limit?: number) => { + const items = limit ? filteredEquities.slice(0, limit) : filteredEquities; + + return ( + +
+
+ + +
+
+ {items.length === 0 + ? emptyState( + "No equity prints match the current focus filter.", + "No equity prints yet. Start ingest-equities.", + "Replay queue empty. Ensure ClickHouse has data." + ) + : items.map((print) => ( +
+
+
{print.underlying_id}
+
+ ${formatPrice(print.price)} + {formatSize(print.size)}x + {print.exchange} + {print.offExchangeFlag ? ( + Off-Ex + ) : ( + Lit + )} +
+
+
{formatTime(print.ts)}
+
+ ))} +
+
+
+ ); + }; + + const renderFlowPanel = (className?: string, limit?: number) => { + const items = limit ? filteredFlow.slice(0, limit) : filteredFlow; + + return ( + +
+
+ + +
+
+ {items.length === 0 + ? emptyState( + "No flow packets match the current focus filter.", + "No flow packets yet. Start compute.", + "Replay queue empty. Ensure ClickHouse has data." + ) + : items.map((packet) => { + const features = packet.features ?? {}; + const contract = String(features.option_contract_id ?? packet.id ?? "unknown"); + const count = parseNumber(features.count, packet.members.length); + const totalSize = parseNumber(features.total_size, 0); + const totalNotional = parseNumber(features.total_notional, Number.NaN); + const notional = + Number.isFinite(totalNotional) ? totalNotional : parseNumber(features.total_premium, 0) * 100; + const startTs = parseNumber(features.start_ts, packet.source_ts); + const endTs = parseNumber(features.end_ts, startTs); + const windowMs = parseNumber(features.window_ms, 0); + const structureType = + 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 : ""; + 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 aggressiveSellRatio = parseNumber(features.nbbo_aggressive_sell_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; + const nbboMissing = parseNumber(packet.join_quality.nbbo_missing, 0) > 0; + + return ( +
+
+
{contract}
+
+ {formatFlowMetric(count)} prints + {formatFlowMetric(totalSize)} size + Notional {formatCompactUsd(notional)} + {windowMs > 0 ? {formatFlowMetric(windowMs, "ms")} : null} + {structureType ? ( + + {structureType.replace(/_/g, " ")} + {structureRights ? ` ${structureRights}` : ""} + {structureLegs > 0 ? ` ${structureLegs}L` : ""} + {structureStrikes > 0 ? ` ${structureStrikes}K` : ""} + + ) : null} + {Number.isFinite(aggressiveCoverage) && aggressiveCoverage > 0 ? ( + + Agg {formatPct(aggressiveBuyRatio)} / {formatPct(aggressiveSellRatio)} + {Number.isFinite(insideRatio) && insideRatio > 0 ? ` · In ${formatPct(insideRatio)}` : ""} + {` · ${formatPct(aggressiveCoverage)} cov`} + + ) : null} + {Number.isFinite(nbboBid) && Number.isFinite(nbboAsk) ? ( + + NBBO ${formatPrice(nbboBid)} x ${formatPrice(nbboAsk)} + + ) : null} + {Number.isFinite(nbboMid) ? Mid ${formatPrice(nbboMid)} : null} + {Number.isFinite(nbboSpread) ? Spread ${formatPrice(nbboSpread)} : null} + {Number.isFinite(nbboAge) ? {Math.round(nbboAge)}ms : null} + {nbboStale ? NBBO stale : null} + {nbboMissing ? NBBO missing : null} +
+
+
+ {formatTime(startTs)} → {formatTime(endTs)} +
+
+ ); + })} +
+
+
+ ); + }; + + const renderAlertsPanel = (className?: string, limit?: number, showStrip = true) => { + const items = limit ? filteredAlerts.slice(0, limit) : filteredAlerts; + + return ( + +
+
+ + +
+ {showStrip ? : null} +
+ {items.length === 0 + ? emptyState( + "No alerts match the current focus filter.", + "No alerts yet. Start compute.", + "Replay queue empty. Ensure ClickHouse has data." + ) + : items.map((alert) => { + const primary = alert.hits[0]; + const direction = primary ? normalizeDirection(primary.direction) : "neutral"; + + return ( + + ); + })} +
+
+
+ ); + }; + + const renderClassifierPanel = (className?: string, limit?: number) => { + const items = limit ? filteredClassifierHits.slice(0, limit) : filteredClassifierHits; + + return ( + +
+
+ + +
+
+ {items.length === 0 + ? emptyState( + "No classifier hits match the current focus filter.", + "No classifier hits yet. Start compute.", + "Replay queue empty. Ensure ClickHouse has data." + ) + : items.map((hit) => { + const direction = normalizeDirection(hit.direction); + return ( + + ); + })} +
+
+
+ ); + }; + + const renderDarkPanel = (className?: string, limit?: number) => { + const items = limit ? filteredInferredDark.slice(0, limit) : filteredInferredDark; + + return ( + +
+
+ + +
+
+ {items.length === 0 + ? emptyState( + "No inferred dark events match the current focus filter.", + "No inferred dark events yet. Start compute.", + "Replay queue empty. Ensure ClickHouse has data." + ) + : items.map((event) => { + const underlying = inferDarkUnderlying(event, equityPrintMap, equityJoinMap); + return ( + + ); + })} +
+
+
+ ); + }; + + const renderPulsePanel = (className?: string) => { + return ( + +
+
+ {viewMetrics.map((metric) => ( + + ))} +
+
+
+
+ Top alert + {headlineAlert ? {headlineAlert.severity} : null} +
+ {headlineAlert ? ( + + ) : ( +
No alert is in the queue yet for this view.
+ )} +
+
+
+ Secondary cue + {headlineDark ? Dark : headlineHit ? Rule hit : null} +
+ {headlineDark ? ( + + ) : headlineHit ? ( + + ) : ( +
No secondary cue is available yet.
+ )} +
+
+
+
+ ); + }; + + const renderFocusPanel = (className?: string) => { + return ( + +
+ {focusNames.length === 0 ? ( +
No active names yet. Start ingest and compute services.
+ ) : ( + focusNames.map((entry) => ( + + )) + )} +
+
+ ); + }; + + const renderHealthPanel = (className?: string) => { + return ( + +
+ {pipelineHealth.map((pipeline) => ( +
+
+ + {pipeline.label} +
+
+ {statusLabel(pipeline.status, pipeline.paused, mode)} + {pipeline.lastUpdate ? `Updated ${formatTime(pipeline.lastUpdate)}` : "Waiting for data"} + {formatCompactValue(pipeline.count)} cached + {pipeline.dropped > 0 ? {pipeline.dropped} dropped while paused : null} +
+
+ ))} +
+
+ ); + }; + + let viewContent: ReactNode; + + switch (view) { + case "options-flow": + viewContent = ( +
+ {renderChartPanel("panel-span-two")} + {renderPulsePanel()} + {renderOptionsPanel("panel-span-two")} + {renderFlowPanel()} + {renderClassifierPanel()} + {renderAlertsPanel(undefined, 8, false)} +
+ ); + break; + case "signals": + viewContent = ( +
+ {renderPulsePanel("panel-span-two")} + {renderHealthPanel()} + {renderAlertsPanel("panel-span-two")} + {renderClassifierPanel()} + {renderChartPanel("panel-span-two")} + {renderFlowPanel(undefined, 10)} +
+ ); + break; + case "off-exchange": + viewContent = ( +
+ {renderChartPanel("panel-span-two")} + {renderPulsePanel()} + {renderDarkPanel("panel-span-two")} + {renderEquitiesPanel()} + {renderHealthPanel()} + {renderAlertsPanel(undefined, 8, false)} +
+ ); + break; + case "overview": + default: + viewContent = ( +
+ {renderChartPanel("panel-span-two")} + {renderPulsePanel()} + {renderAlertsPanel()} + {renderFlowPanel()} + {renderDarkPanel()} + {renderFocusPanel()} + {renderHealthPanel()} + {renderOptionsPanel("panel-span-two", 12)} + {renderClassifierPanel(undefined, 10)} +
+ ); + break; + } + + return ( +
+ + +
+
+
+ {currentView.kicker} +

{currentView.title}

+

{currentView.description}

+
+
+
+ Mode + {mode === "live" ? "Live feed" : "Replay"} + {mode === "replay" && replaySource ? `Source ${replaySource}` : "Shared filters persist across views"} +
+
+ Clock + {formatDateTime(referenceTime)} + {lastSeen ? `UI update ${formatTime(lastSeen)}` : "Waiting for first event"} +
+
+
+ +
+
+ + + +
+
+ + {activeTickers.length > 0 ? `Filtering ${activeTickers.join(", ")}` : "All tickers in view"} + +
+ {focusNames.slice(0, 6).map((entry) => ( + + ))} +
+
+
+ + {viewContent} +
+ + {selectedAlert ? ( + setSelectedAlert(null)} + /> + ) : null} + + {selectedClassifierHit ? ( + setSelectedClassifierHit(null)} + /> + ) : null} + + {selectedDarkEvent ? ( + setSelectedDarkEvent(null)} + /> + ) : null} +
+ ); +} diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 2da9583..c808573 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -1,44 +1,184 @@ :root { - color-scheme: light; - font-family: "IBM Plex Mono", "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + color-scheme: dark; + --bg-0: #071018; + --bg-1: #0b1721; + --bg-2: #10202c; + --panel: rgba(10, 21, 31, 0.86); + --panel-strong: rgba(13, 28, 40, 0.95); + --panel-border: rgba(156, 185, 210, 0.16); + --panel-border-strong: rgba(156, 185, 210, 0.28); + --text-0: #edf4fb; + --text-1: #c5d4e1; + --text-2: #8fa6bb; + --grid: rgba(121, 150, 173, 0.08); + --green: #59d98e; + --green-soft: rgba(89, 217, 142, 0.12); + --red: #ff8e63; + --red-soft: rgba(255, 142, 99, 0.14); + --blue: #67b9ff; + --blue-soft: rgba(103, 185, 255, 0.14); + --amber: #f7c56b; + --amber-soft: rgba(247, 197, 107, 0.14); + --shadow: 0 28px 80px rgba(0, 0, 0, 0.32); + --font-display: "Iowan Old Style", "Book Antiqua", "Palatino Linotype", Georgia, serif; + --font-data: "IBM Plex Mono", "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - background: #efece6; - color: #1d1d1b; - --panel: #fff6e8; - --panel-border: #d9cdb8; - --accent: #2f6d4f; - --accent-soft: rgba(47, 109, 79, 0.18); - --warning: #c46f2a; - --replay: #1f4a7b; - --grid: rgba(82, 64, 36, 0.12); } * { box-sizing: border-box; } +html { + min-height: 100%; + background: var(--bg-0); +} + body { margin: 0; min-height: 100vh; -} - -.dashboard { - min-height: 100vh; - padding: 48px 8vw 72px; - display: grid; - gap: 32px; background: - radial-gradient(circle at top left, #fff7df 0%, #efece6 56%), - repeating-linear-gradient( - 90deg, - transparent, - transparent 44px, - var(--grid) 45px, - var(--grid) 46px - ); + radial-gradient(circle at top left, rgba(103, 185, 255, 0.18), transparent 28%), + radial-gradient(circle at top right, rgba(89, 217, 142, 0.1), transparent 22%), + linear-gradient(180deg, #08111a 0%, #09141d 40%, #071018 100%); + color: var(--text-0); + font-family: var(--font-data); } -.header { +body::before { + content: ""; + position: fixed; + inset: 0; + pointer-events: none; + background: + linear-gradient(90deg, transparent 0, transparent calc(100% - 1px), var(--grid) calc(100% - 1px)), + linear-gradient(180deg, transparent 0, transparent calc(100% - 1px), var(--grid) calc(100% - 1px)); + background-size: 72px 72px; + mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.75), transparent 92%); +} + +button, +input { + font: inherit; +} + +button { + cursor: pointer; +} + +a { + color: inherit; + text-decoration: none; +} + +.terminal-shell { + min-height: 100vh; + display: grid; + grid-template-columns: 280px minmax(0, 1fr); +} + +.terminal-rail { + position: sticky; + top: 0; + align-self: start; + height: 100vh; + padding: 28px 24px; + border-right: 1px solid var(--panel-border); + background: linear-gradient(180deg, rgba(9, 20, 29, 0.98), rgba(7, 16, 24, 0.94)); + display: grid; + grid-template-rows: auto auto 1fr; + gap: 24px; + backdrop-filter: blur(18px); +} + +.terminal-brand { + display: grid; + gap: 10px; +} + +.terminal-mark { + font-family: var(--font-display); + font-size: 2.1rem; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.terminal-tag { + margin: 0; + color: var(--text-2); + line-height: 1.7; + font-size: 0.8rem; +} + +.rail-nav { + display: grid; + gap: 10px; +} + +.rail-link { + display: grid; + gap: 4px; + padding: 14px 16px; + border: 1px solid transparent; + border-radius: 18px; + color: var(--text-1); + background: rgba(255, 255, 255, 0.02); + transition: border-color 0.18s ease, background 0.18s ease, transform 0.18s ease; +} + +.rail-link:hover, +.rail-link:focus-visible { + border-color: var(--panel-border-strong); + background: rgba(255, 255, 255, 0.04); + transform: translateX(2px); +} + +.rail-link-active { + border-color: rgba(103, 185, 255, 0.38); + background: + linear-gradient(135deg, rgba(103, 185, 255, 0.14), transparent 55%), + rgba(255, 255, 255, 0.04); + color: var(--text-0); +} + +.rail-link-label { + font-size: 0.86rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.rail-link-copy { + color: var(--text-2); + font-size: 0.72rem; +} + +.rail-summary { + align-self: end; + display: grid; + gap: 8px; + padding: 16px 18px; + border-radius: 22px; + border: 1px solid var(--panel-border); + background: rgba(255, 255, 255, 0.03); + color: var(--text-1); + font-size: 0.78rem; + line-height: 1.6; +} + +.rail-summary-label { + color: var(--text-2); + text-transform: uppercase; + letter-spacing: 0.18em; + font-size: 0.68rem; +} + +.terminal-main { + padding: 28px 32px 44px; + display: grid; + gap: 24px; +} + +.desk-header { display: flex; align-items: flex-end; justify-content: space-between; @@ -46,798 +186,908 @@ body { flex-wrap: wrap; } -.eyebrow { - text-transform: uppercase; - letter-spacing: 0.4em; - font-size: 0.7rem; - margin: 0 0 12px; - color: #6f5b39; -} - -h1 { - margin: 0 0 12px; - font-size: clamp(2.2rem, 4vw, 3.4rem); - letter-spacing: 0.15em; - text-transform: uppercase; -} - -.subtitle { - margin: 0; - max-width: 460px; - line-height: 1.6; - color: #4e3e25; -} - -.summary { +.desk-header-copy { + max-width: 720px; display: grid; - gap: 12px; - padding: 16px 20px; - border-radius: 16px; - border: 1px solid var(--panel-border); - background: #fffdf7; - min-width: 220px; + gap: 10px; } -.filter-bar { - display: flex; - align-items: center; - justify-content: space-between; - gap: 20px; - padding: 16px 20px; - border-radius: 18px; +.desk-kicker { + margin: 0; + color: var(--amber); + letter-spacing: 0.3em; + text-transform: uppercase; + font-size: 0.7rem; +} + +.desk-header h1 { + margin: 0; + font-family: var(--font-display); + font-size: clamp(2.5rem, 5vw, 4.4rem); + font-weight: 600; + line-height: 0.92; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.desk-header p { + margin: 0; + color: var(--text-1); + max-width: 760px; + line-height: 1.7; +} + +.desk-session { + display: grid; + grid-template-columns: repeat(2, minmax(180px, 1fr)); + gap: 14px; +} + +.session-card { + min-width: 180px; + display: grid; + gap: 8px; + padding: 16px 18px; + border-radius: 22px; border: 1px solid var(--panel-border); - background: rgba(255, 253, 247, 0.9); + background: + linear-gradient(135deg, rgba(103, 185, 255, 0.08), transparent 52%), + var(--panel); + box-shadow: var(--shadow); +} + +.session-card strong { + font-size: 1rem; +} + +.session-card span { + color: var(--text-1); + font-size: 0.75rem; + line-height: 1.6; +} + +.session-label { + color: var(--text-2); + text-transform: uppercase; + letter-spacing: 0.18em; +} + +.command-strip { + display: grid; + gap: 14px; + padding: 18px 20px; + border-radius: 24px; + border: 1px solid var(--panel-border); + background: + linear-gradient(135deg, rgba(89, 217, 142, 0.08), transparent 42%), + var(--panel-strong); + box-shadow: var(--shadow); +} + +.command-primary { + display: flex; + align-items: flex-end; + gap: 12px; + flex-wrap: wrap; +} + +.filter-block { + display: grid; + gap: 8px; + min-width: min(420px, 100%); + flex: 1 1 360px; } .filter-label { - margin: 0 0 6px; + color: var(--text-2); text-transform: uppercase; - letter-spacing: 0.3em; - font-size: 0.7rem; - color: #6f5b39; + letter-spacing: 0.18em; + font-size: 0.68rem; } -.filter-help { +.filter-input { + width: 100%; + border: 1px solid rgba(156, 185, 210, 0.26); + border-radius: 16px; + padding: 14px 16px; + background: rgba(255, 255, 255, 0.04); + color: var(--text-0); +} + +.filter-input::placeholder { + color: var(--text-2); +} + +.filter-input:focus-visible { + outline: 2px solid rgba(103, 185, 255, 0.35); + outline-offset: 2px; +} + +.filter-clear, +.mode-button, +.interval-button, +.overlay-toggle, +.jump-button, +.pause-button, +.drawer-close { + border: 1px solid rgba(156, 185, 210, 0.24); + border-radius: 999px; + padding: 10px 14px; + background: rgba(255, 255, 255, 0.04); + color: var(--text-1); + letter-spacing: 0.12em; + text-transform: uppercase; + font-size: 0.68rem; +} + +.mode-button { + background: rgba(103, 185, 255, 0.12); + color: #d7ecff; + border-color: rgba(103, 185, 255, 0.28); +} + +.filter-clear:disabled, +.jump-button:disabled { + opacity: 0.5; + cursor: default; +} + +.command-secondary { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; +} + +.command-caption { + color: var(--text-2); + font-size: 0.76rem; +} + +.command-focus-list { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.focus-pill { + border: 1px solid rgba(156, 185, 210, 0.2); + border-radius: 999px; + padding: 8px 12px; + background: rgba(255, 255, 255, 0.04); + color: var(--text-1); + font-size: 0.72rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.focus-pill-active, +.interval-button.active, +.overlay-toggle-on { + border-color: rgba(89, 217, 142, 0.34); + background: rgba(89, 217, 142, 0.12); + color: #dcffee; +} + +.view-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 20px; + align-items: start; +} + +.panel-span-two { + grid-column: span 2; +} + +.panel { + min-height: 320px; + display: flex; + flex-direction: column; + gap: 18px; + padding: 22px; + border-radius: 28px; + border: 1px solid var(--panel-border); + background: + linear-gradient(135deg, rgba(103, 185, 255, 0.05), transparent 44%), + linear-gradient(180deg, rgba(255, 255, 255, 0.02), transparent 100%), + var(--panel); + box-shadow: var(--shadow); + animation: rise 420ms ease both; +} + +.view-grid > .panel:nth-child(2) { + animation-delay: 0.05s; +} + +.view-grid > .panel:nth-child(3) { + animation-delay: 0.1s; +} + +.view-grid > .panel:nth-child(4) { + animation-delay: 0.15s; +} + +.panel-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; +} + +.panel-header-copy { + display: grid; + gap: 6px; +} + +.panel-eyebrow { + color: var(--amber); + text-transform: uppercase; + letter-spacing: 0.22em; + font-size: 0.68rem; +} + +.panel-title { margin: 0; - color: #4e3e25; - font-size: 0.9rem; + font-family: var(--font-display); + font-size: 1.7rem; + font-weight: 600; + letter-spacing: 0.04em; + text-transform: uppercase; } -.filter-controls { +.panel-subtitle { + margin: 0; + color: var(--text-2); + line-height: 1.6; + font-size: 0.78rem; + max-width: 620px; +} + +.panel-actions { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} + +.panel-stack { + min-height: 0; + flex: 1 1 auto; + display: flex; + flex-direction: column; + gap: 16px; +} + +.panel-chart { + min-height: 640px; +} + +.panel-tape, +.panel-flow, +.panel-signals { + min-height: 760px; +} + +.panel-pulse { + min-height: 640px; +} + +.panel-focus, +.panel-health { + min-height: 420px; +} + +.metric-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; +} + +.metric-card { + display: grid; + gap: 8px; + padding: 16px; + border-radius: 18px; + border: 1px solid rgba(156, 185, 210, 0.16); + background: rgba(255, 255, 255, 0.035); +} + +.metric-card-bullish { + background: linear-gradient(180deg, rgba(89, 217, 142, 0.12), rgba(255, 255, 255, 0.02)); +} + +.metric-card-bearish { + background: linear-gradient(180deg, rgba(255, 142, 99, 0.14), rgba(255, 255, 255, 0.02)); +} + +.metric-card-signal { + background: linear-gradient(180deg, rgba(247, 197, 107, 0.15), rgba(255, 255, 255, 0.02)); +} + +.metric-label { + color: var(--text-2); + text-transform: uppercase; + letter-spacing: 0.18em; + font-size: 0.65rem; +} + +.metric-value { + font-size: 1.2rem; +} + +.metric-foot { + color: var(--text-1); + line-height: 1.6; + font-size: 0.72rem; +} + +.spotlight-stack { + display: grid; + gap: 12px; +} + +.spotlight-card { + display: grid; + gap: 12px; + padding: 16px; + border-radius: 20px; + border: 1px solid rgba(156, 185, 210, 0.16); + background: rgba(255, 255, 255, 0.03); +} + +.spotlight-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} + +.spotlight-label { + color: var(--text-2); + text-transform: uppercase; + letter-spacing: 0.18em; + font-size: 0.65rem; +} + +.spotlight-button { + width: 100%; + display: grid; + gap: 8px; + border: 0; + padding: 0; + background: transparent; + color: var(--text-0); + text-align: left; +} + +.spotlight-button strong { + font-family: var(--font-display); + font-size: 1.2rem; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.spotlight-button span, +.spotlight-empty { + color: var(--text-1); + line-height: 1.7; + font-size: 0.78rem; +} + +.focus-grid { + display: grid; + gap: 12px; +} + +.focus-chip { + display: grid; + gap: 6px; + width: 100%; + padding: 16px; + border-radius: 18px; + border: 1px solid rgba(156, 185, 210, 0.18); + background: rgba(255, 255, 255, 0.03); + text-align: left; + color: var(--text-0); +} + +.focus-chip-active { + border-color: rgba(103, 185, 255, 0.4); + background: rgba(103, 185, 255, 0.1); +} + +.focus-chip-bullish { + box-shadow: inset 0 0 0 1px rgba(89, 217, 142, 0.24); +} + +.focus-chip-bearish { + box-shadow: inset 0 0 0 1px rgba(255, 142, 99, 0.24); +} + +.focus-symbol { + font-size: 1.1rem; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.focus-meta, +.focus-time { + color: var(--text-1); + font-size: 0.72rem; +} + +.health-grid { + display: grid; + gap: 12px; +} + +.health-card { + display: grid; + gap: 8px; + padding: 15px 16px; + border-radius: 18px; + border: 1px solid rgba(156, 185, 210, 0.16); + background: rgba(255, 255, 255, 0.03); +} + +.health-card-top { display: flex; align-items: center; gap: 10px; } -.filter-input { - border: 1px solid rgba(111, 91, 57, 0.35); - border-radius: 999px; - padding: 8px 14px; - min-width: 220px; - background: #fffdf7; - font-family: inherit; - font-size: 0.9rem; - color: #1d1d1b; -} - -.filter-input:focus-visible { - outline: 2px solid rgba(47, 109, 79, 0.3); - outline-offset: 2px; -} - -.filter-clear { - border: 1px solid rgba(111, 91, 57, 0.35); - border-radius: 999px; - padding: 6px 12px; - background: rgba(111, 91, 57, 0.08); - color: #6f5b39; - font-size: 0.7rem; - letter-spacing: 0.12em; - text-transform: uppercase; - cursor: pointer; -} - -.filter-clear:disabled { - opacity: 0.5; - cursor: default; -} - -.summary-title { - font-size: 0.75rem; - text-transform: uppercase; - letter-spacing: 0.28em; - color: #6f5b39; -} - -.summary-value { - font-size: 1rem; -} - -.mode-button { - border: 1px solid rgba(31, 74, 123, 0.35); - border-radius: 999px; - padding: 8px 14px; - background: rgba(31, 74, 123, 0.12); - color: #1f4a7b; - font-size: 0.75rem; - letter-spacing: 0.12em; - text-transform: uppercase; - cursor: pointer; -} - -.mode-button:focus-visible { - outline: 2px solid rgba(31, 74, 123, 0.4); - outline-offset: 2px; -} - -.cards { +.health-card-meta { display: grid; - gap: 28px; - grid-template-columns: repeat(6, minmax(0, 1fr)); + gap: 4px; + color: var(--text-1); + font-size: 0.72rem; } -.card-options { - grid-column: span 4; -} - -.card-chart { - grid-column: span 6; -} - -.card-equities { - grid-column: span 2; -} - -.card-flow, -.card-alerts, -.card-classifiers, -.card-dark { - grid-column: span 2; -} - -.status { - display: grid; - gap: 8px; - padding: 16px 20px; - border-radius: 16px; - border: 1px solid var(--panel-border); - background: #fffdf7; - min-width: 220px; -} - -.status-compact { - padding: 12px 16px; - min-width: 180px; -} - -.status-paused { - background: #fff3e4; -} - -.status-dot { - width: 12px; - height: 12px; +.health-dot, +.status-dot, +.chart-dot { + width: 10px; + height: 10px; border-radius: 999px; - background: var(--warning); - box-shadow: 0 0 0 4px rgba(196, 111, 42, 0.2); + background: var(--amber); + box-shadow: 0 0 0 5px rgba(247, 197, 107, 0.12); } -.status-connected .status-dot { - background: var(--accent); - box-shadow: 0 0 0 4px var(--accent-soft); +.health-dot-connected, +.status-connected .status-dot, +.chart-status-connected .chart-dot { + background: var(--green); + box-shadow: 0 0 0 5px rgba(89, 217, 142, 0.14); } -.status-replay .status-dot { - background: var(--replay); - box-shadow: 0 0 0 4px rgba(31, 74, 123, 0.18); +.health-dot-connecting, +.chart-status-connecting .chart-dot { + background: var(--blue); + box-shadow: 0 0 0 5px rgba(103, 185, 255, 0.12); + animation: pulse 1.3s ease-in-out infinite; } -.status-connecting .status-dot { - animation: pulse 1.2s ease-in-out infinite; +.health-dot-disconnected, +.chart-status-disconnected .chart-dot { + background: var(--red); + box-shadow: 0 0 0 5px rgba(255, 142, 99, 0.12); } -.status span { - font-size: 0.95rem; -} - -.timestamp { - font-size: 0.8rem; - color: #6f5b39; -} - -.pause-button { - border: 1px solid rgba(47, 109, 79, 0.3); - border-radius: 999px; - padding: 6px 12px; - background: rgba(47, 109, 79, 0.12); - color: #2f6d4f; - font-size: 0.75rem; - letter-spacing: 0.12em; - text-transform: uppercase; - cursor: pointer; -} - -.status-paused .pause-button { - border-color: rgba(196, 111, 42, 0.4); - background: rgba(196, 111, 42, 0.16); - color: #8c4a16; -} - -.pause-button:focus-visible { - outline: 2px solid rgba(47, 109, 79, 0.4); - outline-offset: 2px; -} - -.card-controls { - display: flex; - align-items: flex-end; - justify-content: space-between; - gap: 12px; - width: 100%; - margin-bottom: 20px; - flex: 0 0 auto; -} - -.chart-controls { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - margin-bottom: 18px; - flex-wrap: wrap; -} - -.chart-intervals { - display: flex; - flex-wrap: wrap; - gap: 8px; -} - -.interval-button { - border: 1px solid rgba(111, 91, 57, 0.35); - border-radius: 999px; - padding: 6px 12px; - background: rgba(111, 91, 57, 0.08); - color: #6f5b39; - font-size: 0.75rem; - letter-spacing: 0.12em; - text-transform: uppercase; - cursor: pointer; -} - -.interval-button.active { - border-color: rgba(47, 109, 79, 0.6); - background: rgba(47, 109, 79, 0.1); - color: #2f6d4f; - box-shadow: 0 0 0 2px rgba(47, 109, 79, 0.12); -} - -.interval-button:focus-visible { - outline: 2px solid rgba(47, 109, 79, 0.4); - outline-offset: 2px; -} - -.chart-hint { - font-size: 0.8rem; - color: #6f5b39; -} - -.chart-panel { - display: grid; - gap: 16px; +.health-dot-paused { + background: var(--amber); } +.card-controls, +.chart-controls, .chart-meta { display: flex; - align-items: center; - gap: 16px; + align-items: flex-start; + justify-content: space-between; + gap: 12px; flex-wrap: wrap; - font-size: 0.8rem; - color: #5b4c34; } -.overlay-toggle { - border: 1px solid rgba(31, 74, 123, 0.35); - border-radius: 999px; - padding: 6px 12px; - background: rgba(31, 74, 123, 0.12); - color: #1f4a7b; - font-size: 0.7rem; - letter-spacing: 0.12em; - text-transform: uppercase; - cursor: pointer; -} - -.overlay-toggle.overlay-toggle-on { - border-color: rgba(31, 74, 123, 0.6); - background: rgba(31, 74, 123, 0.2); -} - -.overlay-toggle:focus-visible { - outline: 2px solid rgba(31, 74, 123, 0.4); - outline-offset: 2px; -} - -.overlay-legend { - color: #6f5b39; - font-size: 0.75rem; -} - -@media (max-width: 700px) { - .overlay-legend { - flex: 1 1 100%; - } +.chart-hint, +.chart-meta-time, +.overlay-legend, +.timestamp { + color: var(--text-2); + font-size: 0.72rem; } .chart-status { display: inline-flex; align-items: center; gap: 8px; - font-weight: 600; + color: var(--text-1); } -.chart-dot { - width: 8px; - height: 8px; - border-radius: 999px; - background: rgba(111, 91, 57, 0.4); -} - -.chart-status-connected .chart-dot { - background: rgba(47, 109, 79, 0.8); -} - -.chart-status-connecting .chart-dot { - background: rgba(31, 74, 123, 0.8); - animation: pulse 1.4s ease-in-out infinite; -} - -.chart-status-disconnected .chart-dot { - background: rgba(196, 111, 42, 0.8); -} - -.chart-meta-time { - color: #6f5b39; +.chart-panel { + display: grid; + gap: 14px; } .chart-surface { width: 100%; - height: 360px; - border-radius: 18px; - border: 1px solid rgba(217, 205, 184, 0.6); - background: #fffdf7; + height: 420px; + border-radius: 22px; + border: 1px solid rgba(156, 185, 210, 0.16); + background: #0c1721; overflow: hidden; } .chart-empty { - margin-top: -4px; + margin-top: -2px; +} + +.status { + display: grid; + gap: 6px; + min-width: 190px; + padding: 14px 16px; + border-radius: 18px; + border: 1px solid rgba(156, 185, 210, 0.16); + background: rgba(255, 255, 255, 0.03); +} + +.status-compact { + padding: 12px 14px; +} + +.status-paused { + background: rgba(247, 197, 107, 0.08); +} + +.status-replay .status-dot { + background: var(--blue); + box-shadow: 0 0 0 5px rgba(103, 185, 255, 0.14); +} + +.status-connecting .status-dot { + animation: pulse 1.3s ease-in-out infinite; +} + +.pause-button { + justify-self: start; } .tape-controls { display: flex; flex-direction: column; align-items: flex-end; - gap: 6px; - min-width: 120px; -} - -.jump-button { - border: 1px solid rgba(111, 91, 57, 0.35); - border-radius: 999px; - padding: 6px 12px; - background: rgba(111, 91, 57, 0.08); - color: #6f5b39; - font-size: 0.75rem; - letter-spacing: 0.12em; - text-transform: uppercase; - cursor: pointer; -} - -.jump-button:disabled { - opacity: 0.5; - cursor: default; + gap: 8px; } .jump-button:not(:disabled) { - border-color: rgba(47, 109, 79, 0.6); - background: rgba(47, 109, 79, 0.1); - color: #2f6d4f; - box-shadow: 0 0 0 2px rgba(47, 109, 79, 0.15); -} - -.jump-button:focus-visible { - outline: 2px solid rgba(111, 91, 57, 0.3); - outline-offset: 2px; + border-color: rgba(89, 217, 142, 0.28); + background: rgba(89, 217, 142, 0.12); + color: #dcffee; } .missed-count { - padding: 4px 10px; - border-radius: 999px; - border: 1px solid rgba(31, 74, 123, 0.25); - background: rgba(31, 74, 123, 0.12); - color: #1f4a7b; - font-size: 0.7rem; + color: var(--blue); + font-size: 0.68rem; letter-spacing: 0.12em; text-transform: uppercase; - max-height: 0; opacity: 0; - transform: translateY(-6px); - overflow: hidden; - transition: max-height 0.2s ease, opacity 0.2s ease, transform 0.2s ease; -} - -.tape-controls-active .jump-button { - transform: translateY(-6px); - transition: transform 0.2s ease; + transform: translateY(-4px); + transition: opacity 0.18s ease, transform 0.18s ease; } .tape-controls-active .missed-count { - max-height: 24px; opacity: 1; transform: translateY(0); } -.card { - border: 1px solid var(--panel-border); - border-radius: 24px; - background: var(--panel); - box-shadow: 0 30px 60px rgba(66, 45, 18, 0.14); - padding: 28px; -} - -.card-header { - display: flex; - flex-direction: column; - gap: 6px; - align-items: flex-start; - margin-bottom: 24px; -} - -.card-header h2 { - margin: 0 0 6px; - font-size: 1.4rem; -} - -.card-subtitle { - margin: 0; - color: #5b4c34; -} - .list { - display: grid; - gap: 14px; - height: 480px; + min-height: 340px; + height: 100%; overflow: auto; padding-right: 6px; - overflow-anchor: none; + display: grid; + gap: 12px; scrollbar-gutter: stable; } - .row { display: flex; justify-content: space-between; gap: 16px; - padding: 16px 18px; + padding: 14px 16px; border-radius: 18px; - background: rgba(255, 255, 255, 0.72); - border: 1px solid rgba(217, 205, 184, 0.6); + border: 1px solid rgba(156, 185, 210, 0.12); + background: rgba(255, 255, 255, 0.04); } .row-button { width: 100%; text-align: left; - cursor: pointer; - font: inherit; color: inherit; + border: 0; } -.row-button:hover { - border-color: rgba(47, 109, 79, 0.4); - box-shadow: 0 0 0 2px rgba(47, 109, 79, 0.12); -} - -.row-button:focus-visible { - outline: 2px solid rgba(47, 109, 79, 0.4); +.row-button:hover, +.row-button:focus-visible, +.focus-chip:hover, +.focus-chip:focus-visible, +.focus-pill:hover, +.focus-pill:focus-visible, +.spotlight-button:hover, +.spotlight-button:focus-visible, +.rail-link:focus-visible, +.mode-button:focus-visible, +.filter-clear:focus-visible, +.interval-button:focus-visible, +.overlay-toggle:focus-visible, +.pause-button:focus-visible, +.jump-button:focus-visible, +.drawer-close:focus-visible { + outline: 2px solid rgba(103, 185, 255, 0.32); outline-offset: 2px; } -.contract { - font-weight: 600; - margin-bottom: 6px; +.row-button:hover { + border-color: rgba(103, 185, 255, 0.24); } -.meta { +.contract { + margin-bottom: 6px; + font-weight: 600; +} + +.meta, +.drawer-row-meta { display: flex; flex-wrap: wrap; - gap: 12px; - font-size: 0.85rem; - color: #5b4c34; + gap: 10px; + color: var(--text-1); + font-size: 0.75rem; + line-height: 1.6; } -.pill { +.flow-meta span { + display: inline-flex; + align-items: center; + gap: 6px; +} + +.pill, +.drawer-chip { + display: inline-flex; + align-items: center; padding: 2px 8px; border-radius: 999px; - font-size: 0.7rem; - letter-spacing: 0.08em; + border: 1px solid rgba(156, 185, 210, 0.18); + background: rgba(255, 255, 255, 0.04); + color: var(--text-1); text-transform: uppercase; - border: 1px solid rgba(111, 91, 57, 0.35); - color: #6f5b39; - background: rgba(111, 91, 57, 0.12); + letter-spacing: 0.08em; + font-size: 0.64rem; } -.structure-tag { - border-color: rgba(39, 84, 138, 0.45); - color: #27548a; - background: rgba(39, 84, 138, 0.12); +.structure-tag, +.aggressor-tag, +.flag { + border-color: rgba(103, 185, 255, 0.24); + background: rgba(103, 185, 255, 0.12); + color: #d8eeff; } -.aggressor-tag { - border-color: rgba(93, 70, 144, 0.45); - color: #5d4690; - background: rgba(93, 70, 144, 0.12); +.flag-muted { + border-color: rgba(156, 185, 210, 0.18); + background: rgba(255, 255, 255, 0.04); + color: var(--text-2); } .nbbo-meta { - font-size: 0.72rem; - color: #6f5b39; + font-size: 0.7rem; } .nbbo-side { position: relative; display: inline-flex; align-items: center; - margin-left: 4px; } .nbbo-tag { + display: inline-flex; + align-items: center; padding: 2px 6px; border-radius: 999px; - border: 1px solid rgba(111, 91, 57, 0.35); - font-size: 0.7rem; + border: 1px solid rgba(156, 185, 210, 0.18); + font-size: 0.64rem; letter-spacing: 0.08em; text-transform: uppercase; } -.nbbo-tag-a { - border-color: rgba(47, 109, 79, 0.5); - color: #2f6d4f; - background: rgba(47, 109, 79, 0.16); +.nbbo-tag-a, +.direction-bullish, +.severity-low { + border-color: rgba(89, 217, 142, 0.28); + background: rgba(89, 217, 142, 0.12); + color: #dcffee; } .nbbo-tag-aa { - border-color: rgba(26, 87, 60, 0.6); - color: #1a573c; - background: rgba(26, 87, 60, 0.2); + border-color: rgba(89, 217, 142, 0.42); + background: rgba(89, 217, 142, 0.18); + color: #e9fff3; } -.nbbo-tag-b { - border-color: rgba(140, 74, 22, 0.5); - color: #8c4a16; - background: rgba(196, 111, 42, 0.18); +.nbbo-tag-b, +.direction-bearish, +.severity-high { + border-color: rgba(255, 142, 99, 0.32); + background: rgba(255, 142, 99, 0.14); + color: #ffe7df; } .nbbo-tag-bb { - border-color: rgba(110, 44, 12, 0.6); - color: #6e2c0c; - background: rgba(110, 44, 12, 0.2); + border-color: rgba(255, 142, 99, 0.46); + background: rgba(255, 142, 99, 0.2); + color: #fff0ea; +} + +.direction-neutral, +.severity-medium { + border-color: rgba(103, 185, 255, 0.26); + background: rgba(103, 185, 255, 0.12); + color: #d8eeff; +} + +.nbbo-missing, +.nbbo-stale { + border-color: rgba(247, 197, 107, 0.26); + background: rgba(247, 197, 107, 0.12); + color: #ffeec4; } .nbbo-tooltip { position: absolute; right: 0; - bottom: 100%; - transform: translateY(-6px); + bottom: calc(100% + 8px); + min-width: 160px; display: grid; - gap: 4px; - padding: 8px 10px; - border-radius: 10px; - border: 1px solid rgba(217, 205, 184, 0.8); - background: #fffdf7; - box-shadow: 0 12px 26px rgba(66, 45, 18, 0.18); + gap: 6px; + padding: 10px; + border-radius: 14px; + border: 1px solid rgba(156, 185, 210, 0.18); + background: rgba(9, 20, 29, 0.96); + box-shadow: var(--shadow); opacity: 0; pointer-events: none; + transform: translateY(6px); transition: opacity 0.15s ease, transform 0.15s ease; - z-index: 2; - white-space: nowrap; -} - -.nbbo-tooltip-row { - display: inline-flex; - align-items: center; - gap: 6px; - font-size: 0.68rem; - color: #6f5b39; + z-index: 10; } .nbbo-side:hover .nbbo-tooltip, .nbbo-side:focus-within .nbbo-tooltip { opacity: 1; - transform: translateY(-10px); + transform: translateY(0); } -.nbbo-missing { - border-color: rgba(136, 58, 17, 0.4); - color: #8c3a11; - background: rgba(196, 111, 42, 0.16); +.nbbo-tooltip-row { + display: inline-flex; + align-items: center; + gap: 8px; + color: var(--text-1); + font-size: 0.68rem; } -.nbbo-stale { - border-color: rgba(31, 74, 123, 0.4); - color: #1f4a7b; - background: rgba(31, 74, 123, 0.12); +.note, +.drawer-note, +.drawer-empty, +.empty { + color: var(--text-1); + line-height: 1.7; + font-size: 0.74rem; } -.severity-high { - border-color: rgba(136, 58, 17, 0.6); - color: #8c3a11; - background: rgba(196, 111, 42, 0.2); +.time { + color: var(--text-2); + font-size: 0.72rem; + text-align: right; } -.severity-medium { - border-color: rgba(31, 74, 123, 0.35); - color: #1f4a7b; - background: rgba(31, 74, 123, 0.12); -} - -.severity-low { - border-color: rgba(47, 109, 79, 0.35); - color: #2f6d4f; - background: rgba(47, 109, 79, 0.12); -} - -.direction-bullish { - border-color: rgba(47, 109, 79, 0.35); - color: #2f6d4f; - background: rgba(47, 109, 79, 0.12); -} - -.direction-bearish { - border-color: rgba(136, 58, 17, 0.6); - color: #8c3a11; - background: rgba(196, 111, 42, 0.2); -} - -.direction-neutral { - border-color: rgba(111, 91, 57, 0.35); - color: #6f5b39; - background: rgba(111, 91, 57, 0.12); -} - -.note { - margin-top: 8px; - font-size: 0.78rem; - color: #5b4c34; +.empty { + padding: 20px; + border-radius: 18px; + border: 1px dashed rgba(156, 185, 210, 0.18); + background: rgba(255, 255, 255, 0.03); } .drawer { position: fixed; - top: 88px; - right: 6vw; - width: min(360px, 92vw); - max-height: calc(100vh - 140px); + top: 24px; + right: 24px; + width: min(400px, calc(100vw - 48px)); + max-height: calc(100vh - 48px); overflow: auto; padding: 20px; - border-radius: 20px; - border: 1px solid var(--panel-border); - background: #fffdf7; - box-shadow: 0 32px 60px rgba(66, 45, 18, 0.22); - z-index: 30; + border-radius: 26px; + border: 1px solid var(--panel-border-strong); + background: rgba(9, 20, 29, 0.98); + box-shadow: 0 28px 90px rgba(0, 0, 0, 0.44); + z-index: 40; + backdrop-filter: blur(18px); } .drawer-header { display: flex; align-items: flex-start; justify-content: space-between; - gap: 16px; - margin-bottom: 12px; + gap: 14px; + margin-bottom: 14px; } .drawer-eyebrow { margin: 0 0 6px; + color: var(--amber); text-transform: uppercase; - letter-spacing: 0.3em; - font-size: 0.65rem; - color: #6f5b39; + letter-spacing: 0.2em; + font-size: 0.66rem; } .drawer h3 { margin: 0 0 4px; - font-size: 1.1rem; + font-family: var(--font-display); + font-size: 1.2rem; + text-transform: uppercase; + letter-spacing: 0.05em; } .drawer-subtitle { margin: 0; - color: #6f5b39; - font-size: 0.8rem; -} - -.drawer-close { - border: 1px solid rgba(111, 91, 57, 0.35); - border-radius: 999px; - padding: 6px 12px; - background: rgba(111, 91, 57, 0.08); - color: #6f5b39; - font-size: 0.7rem; - letter-spacing: 0.12em; - text-transform: uppercase; - cursor: pointer; + color: var(--text-2); + font-size: 0.74rem; } .drawer-meta { display: flex; flex-wrap: wrap; gap: 8px; - margin-bottom: 16px; -} - -.drawer-chip { - padding: 2px 8px; - border-radius: 999px; - border: 1px solid rgba(111, 91, 57, 0.35); - background: rgba(111, 91, 57, 0.08); - font-size: 0.7rem; - text-transform: uppercase; - letter-spacing: 0.08em; + margin-bottom: 18px; } .drawer-section { - margin-bottom: 18px; + margin-bottom: 20px; } .drawer-section h4 { margin: 0 0 10px; - font-size: 0.85rem; + color: var(--text-2); text-transform: uppercase; - letter-spacing: 0.22em; - color: #6f5b39; + letter-spacing: 0.18em; + font-size: 0.68rem; } .drawer-list { display: grid; - gap: 12px; + gap: 10px; } .drawer-row { padding: 12px 14px; - border-radius: 14px; - border: 1px solid rgba(217, 205, 184, 0.6); - background: rgba(255, 255, 255, 0.75); + border-radius: 16px; + border: 1px solid rgba(156, 185, 210, 0.16); + background: rgba(255, 255, 255, 0.03); } .drawer-row-title { - font-weight: 600; margin-bottom: 6px; -} - -.drawer-row-meta { - display: flex; - flex-wrap: wrap; - gap: 10px; - font-size: 0.75rem; - color: #5b4c34; -} - -.drawer-note { - margin: 8px 0 0; - font-size: 0.72rem; - color: #5b4c34; -} - -.drawer-empty { - margin: 0; - font-size: 0.78rem; - color: #6f5b39; + font-weight: 600; } .alert-strips { display: grid; gap: 12px; - margin-bottom: 16px; padding: 12px 14px; - border-radius: 14px; - border: 1px solid rgba(217, 205, 184, 0.6); - background: rgba(255, 255, 255, 0.7); + border-radius: 18px; + border: 1px solid rgba(156, 185, 210, 0.16); + background: rgba(255, 255, 255, 0.03); } .alert-strip-section { @@ -847,93 +1097,48 @@ h1 { .alert-strip-header { display: flex; + align-items: center; justify-content: space-between; - font-size: 0.7rem; - color: #6f5b39; + gap: 8px; + color: var(--text-2); text-transform: uppercase; - letter-spacing: 0.22em; + letter-spacing: 0.18em; + font-size: 0.64rem; } .alert-strip-bar { display: flex; - height: 26px; - border-radius: 999px; + height: 24px; overflow: hidden; - border: 1px solid rgba(217, 205, 184, 0.6); - background: rgba(111, 91, 57, 0.08); + border-radius: 999px; + border: 1px solid rgba(156, 185, 210, 0.16); + background: rgba(255, 255, 255, 0.03); } .strip-segment { - display: flex; - align-items: center; - justify-content: center; - font-size: 0.65rem; - color: #fffdf7; - letter-spacing: 0.08em; - text-transform: uppercase; -} - -.alert-strip-bar .severity-high { - background: rgba(196, 111, 42, 0.85); - color: #3b1a09; -} - -.alert-strip-bar .severity-medium { - background: rgba(31, 74, 123, 0.8); -} - -.alert-strip-bar .severity-low { - background: rgba(47, 109, 79, 0.8); -} - -.alert-strip-bar .direction-bullish { - background: rgba(47, 109, 79, 0.85); -} - -.alert-strip-bar .direction-bearish { - background: rgba(196, 111, 42, 0.85); - color: #3b1a09; -} - -.alert-strip-bar .direction-neutral { - background: rgba(111, 91, 57, 0.65); -} - -.flow-meta span { display: inline-flex; align-items: center; - gap: 6px; -} - -.flag { - padding: 2px 8px; - border-radius: 999px; - font-size: 0.7rem; - letter-spacing: 0.1em; + justify-content: center; + color: #061018; + font-size: 0.62rem; + letter-spacing: 0.08em; text-transform: uppercase; - border: 1px solid rgba(47, 109, 79, 0.4); - color: #2f6d4f; - background: rgba(47, 109, 79, 0.12); + white-space: nowrap; } -.flag-muted { - border-color: rgba(111, 91, 57, 0.4); - color: #6f5b39; - background: rgba(111, 91, 57, 0.12); +.alert-strip-bar .severity-high, +.alert-strip-bar .direction-bearish { + background: var(--red); } -.time { - font-size: 0.85rem; - color: #6f5b39; - text-align: right; +.alert-strip-bar .severity-medium, +.alert-strip-bar .direction-neutral { + background: var(--blue); } -.empty { - padding: 24px; - border-radius: 16px; - background: rgba(255, 255, 255, 0.7); - border: 1px dashed rgba(217, 205, 184, 0.8); - color: #5b4c34; +.alert-strip-bar .severity-low, +.alert-strip-bar .direction-bullish { + background: var(--green); } @keyframes pulse { @@ -941,138 +1146,103 @@ h1 { transform: scale(1); } 50% { - transform: scale(1.25); + transform: scale(1.22); } 100% { transform: scale(1); } } -@media (max-width: 720px) { - .dashboard { - padding: 36px 6vw 56px; +@keyframes rise { + from { + opacity: 0; + transform: translateY(14px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@media (max-width: 1340px) { + .terminal-shell { + grid-template-columns: 1fr; } - .filter-bar { - flex-direction: column; + .terminal-rail { + position: static; + height: auto; + grid-template-rows: auto auto auto; + border-right: 0; + border-bottom: 1px solid var(--panel-border); + } + + .rail-summary { + align-self: auto; + } +} + +@media (max-width: 1120px) { + .terminal-main { + padding: 24px 20px 36px; + } + + .view-grid, + .desk-session { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .panel-span-two { + grid-column: span 2; + } +} + +@media (max-width: 780px) { + .terminal-rail, + .terminal-main { + padding-inline: 16px; + } + + .desk-session, + .view-grid, + .metric-grid { + grid-template-columns: minmax(0, 1fr); + } + + .panel-span-two { + grid-column: span 1; + } + + .desk-header { align-items: flex-start; } - .filter-controls { - width: 100%; + .command-primary, + .command-secondary, + .card-controls, + .chart-controls, + .chart-meta, + .panel-header { flex-direction: column; align-items: stretch; } - .filter-input { - width: 100%; - } - .row { flex-direction: column; - align-items: flex-start; } .time { text-align: left; } + .chart-surface { + height: 300px; + } + .drawer { position: static; width: 100%; max-height: none; - } - - .list { - min-height: 360px; - } -} - -@media (max-width: 1100px) { - .cards { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - .card-chart, - .card-options, - .card-equities, - .card-flow, - .card-alerts, - .card-classifiers, - .card-dark { - grid-column: span 2; - } -} - -@media (max-width: 860px) { - .cards { - grid-template-columns: minmax(0, 1fr); - } - - .card-chart, - .card-options, - .card-equities, - .card-flow, - .card-alerts, - .card-classifiers, - .card-dark { - grid-column: span 1; - } -} -.card-flow .row, -.card-alerts .row, -.card-classifiers .row, -.card-dark .row { - padding: 12px 14px; -} - -.card-flow .meta, -.card-alerts .meta, -.card-classifiers .meta, -.card-dark .meta { - font-size: 0.78rem; -} - -.card-flow .note, -.card-alerts .note, -.card-classifiers .note, -.card-dark .note { - font-size: 0.72rem; -} - -.card-flow, -.card-alerts, -.card-classifiers, -.card-dark { - display: flex; - flex-direction: column; - height: 960px; - overflow: hidden; -} - -.card-body { - display: flex; - flex-direction: column; - flex: 1 1 0; - min-height: 0; - gap: 16px; -} - -.card-body .list { - flex: 1 1 0; - min-height: 0; - height: auto; -} - -@media (max-width: 720px) { - .card-flow, - .card-alerts, - .card-classifiers, - .card-dark { - height: 780px; - } - - .chart-surface { - height: 280px; + margin-top: 8px; } } diff --git a/apps/web/app/off-exchange/page.tsx b/apps/web/app/off-exchange/page.tsx new file mode 100644 index 0000000..3193825 --- /dev/null +++ b/apps/web/app/off-exchange/page.tsx @@ -0,0 +1,5 @@ +import { TradingDesk } from "../_components/trading-desk"; + +export default function OffExchangePage() { + return ; +} diff --git a/apps/web/app/options-flow/page.tsx b/apps/web/app/options-flow/page.tsx new file mode 100644 index 0000000..2d00cc6 --- /dev/null +++ b/apps/web/app/options-flow/page.tsx @@ -0,0 +1,5 @@ +import { TradingDesk } from "../_components/trading-desk"; + +export default function OptionsFlowPage() { + return ; +} diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index 579ad2c..83336a5 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -1,3764 +1,5 @@ -"use client"; +import { TradingDesk } from "./_components/trading-desk"; -import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; -import type { - AlertEvent, - ClassifierHitEvent, - EquityCandle, - EquityPrint, - EquityPrintJoin, - FlowPacket, - InferredDarkEvent, - OptionNBBO, - OptionPrint -} from "@islandflow/types"; -import { createChart, type IChartApi, type SeriesMarker, type UTCTimestamp } from "lightweight-charts"; - -const MAX_ITEMS = 500; -const NBBO_MAX_AGE_MS = Number(process.env.NEXT_PUBLIC_NBBO_MAX_AGE_MS); -const NBBO_MAX_AGE_MS_SAFE = - Number.isFinite(NBBO_MAX_AGE_MS) && NBBO_MAX_AGE_MS > 0 ? NBBO_MAX_AGE_MS : 1000; -const LOCAL_HOSTS = new Set(["localhost", "127.0.0.1"]); -const CANDLE_INTERVALS = [ - { label: "1m", ms: 60000 }, - { label: "5m", ms: 300000 } -]; - -type CandlestickSeries = ReturnType; - -type EquityOverlayPoint = { - ts: number; - price: number; - size: number; - offExchangeFlag: boolean; -}; - -type ChartCandle = { - time: UTCTimestamp; - open: number; - high: number; - low: number; - close: number; -}; - -const formatIntervalLabel = (intervalMs: number): string => { - const match = CANDLE_INTERVALS.find((interval) => interval.ms === intervalMs); - if (match) { - return match.label; - } - if (intervalMs >= 60000) { - return `${Math.round(intervalMs / 60000)}m`; - } - if (intervalMs >= 1000) { - return `${Math.round(intervalMs / 1000)}s`; - } - return `${intervalMs}ms`; -}; - -const toChartTime = (ts: number): UTCTimestamp => { - return Math.floor(ts / 1000) as UTCTimestamp; -}; - -type ChartTimeLike = number | string | { year: number; month: number; day: number }; - -const chartTimeToMs = (value: ChartTimeLike): number | null => { - if (typeof value === "number") { - return Math.floor(value * 1000); - } - - if (typeof value === "string") { - const parsed = Date.parse(value); - return Number.isFinite(parsed) ? parsed : null; - } - - if (value && typeof value === "object") { - const { year, month, day } = value; - if ( - Number.isFinite(year) && - Number.isFinite(month) && - Number.isFinite(day) && - year >= 1970 && - month >= 1 && - month <= 12 && - day >= 1 && - day <= 31 - ) { - return Date.UTC(year, month - 1, day); - } - } - - return null; -}; - -const toChartCandle = (candle: EquityCandle): ChartCandle => { - return { - time: toChartTime(candle.ts), - open: candle.open, - high: candle.high, - low: candle.low, - close: candle.close - }; -}; - -const clamp = (value: number, min: number, max: number): number => { - if (!Number.isFinite(value)) { - return min; - } - return Math.max(min, Math.min(max, value)); -}; - -const sampleToLimit = (items: T[], limit: number): T[] => { - if (items.length <= limit) { - return items; - } - - const safeLimit = Math.max(1, Math.floor(limit)); - const step = Math.ceil(items.length / safeLimit); - const sampled: T[] = []; - for (let idx = 0; idx < items.length; idx += step) { - sampled.push(items[idx]); - } - - return sampled; -}; - -const readErrorDetail = async (response: Response): Promise => { - const text = await response.text(); - if (!text) { - return ""; - } - try { - const payload = JSON.parse(text) as { - detail?: string; - error?: string; - message?: string; - }; - return payload.detail ?? payload.error ?? payload.message ?? text; - } catch { - return text; - } -}; - -type WsStatus = "connecting" | "connected" | "disconnected"; - -type TapeMode = "live" | "replay"; - -type MessageType = - | "option-print" - | "option-nbbo" - | "equity-print" - | "equity-candle" - | "equity-join" - | "flow-packet" - | "inferred-dark" - | "classifier-hit" - | "alert"; - -type StreamMessage = { - type: MessageType; - payload: T; -}; - -type ReplayCursor = { - ts: number; - seq: number; -}; - -type ReplayResponse = { - data: T[]; - next: ReplayCursor | null; -}; - -const inferTracePrefix = (traceId: string): string => { - const match = traceId.match(/^(.*)-\d+$/); - return match ? match[1] : traceId; -}; - -const extractTracePrefix = (item: T): string | null => { - const traceId = (item as { trace_id?: string }).trace_id; - if (!traceId) { - return null; - } - return inferTracePrefix(traceId); -}; - -const extractReplaySource = (item: T): string | null => { - const prefix = extractTracePrefix(item); - if (!prefix) { - return null; - } - - const normalized = prefix.toLowerCase(); - if (normalized.startsWith("synthetic")) { - return "synthetic"; - } - if (normalized.startsWith("databento")) { - return "databento"; - } - if (normalized.startsWith("alpaca")) { - return "alpaca"; - } - if (normalized.startsWith("ibkr")) { - return "ibkr"; - } - - return prefix; -}; - -type SortableItem = { - ts?: number; - source_ts?: number; - ingest_ts?: number; - seq?: number; - trace_id?: string; - id?: string; -}; - -const extractSortTs = (item: SortableItem): number => - item.ts ?? item.source_ts ?? item.ingest_ts ?? 0; - -const extractSortSeq = (item: SortableItem): number => item.seq ?? 0; - -const buildItemKey = (item: SortableItem): string | null => { - if (item.trace_id) { - return `${item.trace_id}:${item.seq ?? ""}`; - } - - if (item.id) { - return `id:${item.id}`; - } - - return null; -}; - -const mergeNewest = (incoming: T[], existing: T[]): T[] => { - const combined = [...incoming, ...existing]; - if (combined.length === 0) { - return combined; - } - - const seen = new Set(); - const deduped: T[] = []; - - for (const item of combined) { - const key = buildItemKey(item); - if (key) { - if (seen.has(key)) { - continue; - } - seen.add(key); - } - deduped.push(item); - } - - deduped.sort((a, b) => { - const delta = extractSortTs(b) - extractSortTs(a); - if (delta !== 0) { - return delta; - } - return extractSortSeq(b) - extractSortSeq(a); - }); - - return deduped.slice(0, MAX_ITEMS); -}; - -type TapeState = { - status: WsStatus; - items: T[]; - lastUpdate: number | null; - replayTime: number | null; - replayComplete: boolean; - paused: boolean; - dropped: number; - togglePause: () => void; -}; - -const buildWsUrl = (path: string): string => { - const envBase = process.env.NEXT_PUBLIC_API_URL; - - if (envBase) { - const url = new URL(envBase); - url.protocol = url.protocol === "https:" ? "wss:" : "ws:"; - url.pathname = path; - url.search = ""; - url.hash = ""; - return url.toString(); - } - - const { protocol, hostname } = window.location; - const wsProtocol = protocol === "https:" ? "wss" : "ws"; - const isLocal = LOCAL_HOSTS.has(hostname); - const host = isLocal ? `${hostname}:4000` : window.location.host; - - return `${wsProtocol}://${host}${path}`; -}; - -const buildApiUrl = (path: string): string => { - const envBase = process.env.NEXT_PUBLIC_API_URL; - - if (envBase) { - const url = new URL(envBase); - const secure = url.protocol === "https:" || url.protocol === "wss:"; - url.protocol = secure ? "https:" : "http:"; - url.pathname = path; - url.search = ""; - url.hash = ""; - return url.toString(); - } - - const { protocol, hostname } = window.location; - const httpProtocol = protocol === "https:" ? "https" : "http"; - const isLocal = LOCAL_HOSTS.has(hostname); - const host = isLocal ? `${hostname}:4000` : window.location.host; - - return `${httpProtocol}://${host}${path}`; -}; - -const formatPrice = (price: number): string => { - if (!Number.isFinite(price)) { - return "0.00"; - } - return price.toLocaleString(undefined, { - minimumFractionDigits: 2, - maximumFractionDigits: 2 - }); -}; - -const formatSize = (size: number): string => { - return size.toLocaleString(); -}; - -const formatTime = (ts: number): string => { - return new Date(ts).toLocaleTimeString(); -}; - -const formatConfidence = (value: number): string => `${Math.round(value * 100)}%`; - -const formatPct = (value: number): string => `${Math.round(value * 100)}%`; - -const formatUsd = (value: number): string => { - if (!Number.isFinite(value)) { - return "0.00"; - } - return value.toLocaleString(undefined, { - minimumFractionDigits: 2, - maximumFractionDigits: 2 - }); -}; - -const normalizeContractId = (value: string): string => value.trim(); - -const formatContractLabel = (value: string): string => { - const normalized = normalizeContractId(value); - if (!normalized) { - return "Unknown contract"; - } - if (/^\d+$/.test(normalized)) { - return `Instrument ${normalized}`; - } - return normalized; -}; - -const formatDateTime = (ts: number): string => { - const date = new Date(ts); - return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`; -}; - -const humanizeClassifierId = (value: string): string => { - if (!value) { - return "Classifier"; - } - - return value - .split("_") - .map((part) => (part ? part[0].toUpperCase() + part.slice(1) : part)) - .join(" "); -}; - -const normalizeDirection = (value: string): "bullish" | "bearish" | "neutral" => { - const normalized = value.toLowerCase(); - if (normalized === "bullish" || normalized === "bearish" || normalized === "neutral") { - return normalized; - } - return "neutral"; -}; - -const extractUnderlying = (contractId: string): string => { - const match = contractId.match(/^(.+)-\d{4}-\d{2}-\d{2}-/); - if (match?.[1]) { - return match[1].toUpperCase(); - } - return contractId.split("-")[0]?.toUpperCase() ?? contractId.toUpperCase(); -}; - -const extractEquityTraceFromJoin = (joinId: string): string | null => { - const match = joinId.match(/^equityjoin:(.+)$/); - return match?.[1] ?? null; -}; - -const inferDarkUnderlying = ( - event: InferredDarkEvent, - equityPrints: Map, - equityJoins: Map -): string | null => { - for (const ref of event.evidence_refs) { - const join = equityJoins.get(ref); - if (!join) { - continue; - } - const underlying = join.features.underlying_id; - if (typeof underlying === "string" && underlying.length > 0) { - return underlying.toUpperCase(); - } - } - - const match = event.trace_id.match(/^dark:(?:stealth_accumulation|distribution):([^:]+):/); - if (match?.[1]) { - return match[1].toUpperCase(); - } - - for (const ref of event.evidence_refs) { - const traceId = extractEquityTraceFromJoin(ref); - if (!traceId) { - continue; - } - const print = equityPrints.get(traceId); - if (print) { - return print.underlying_id.toUpperCase(); - } - } - - return null; -}; - -const parseNumber = (value: unknown, fallback: number): number => { - if (typeof value === "number" && Number.isFinite(value)) { - return value; - } - - if (typeof value === "string") { - const parsed = Number(value); - if (Number.isFinite(parsed)) { - return parsed; - } - } - - return fallback; -}; - -const parseBoolean = (value: unknown, fallback = false): boolean => { - if (typeof value === "boolean") { - return value; - } - if (typeof value === "number") { - return value !== 0; - } - if (typeof value === "string") { - const normalized = value.trim().toLowerCase(); - if (["true", "1", "yes", "on"].includes(normalized)) { - return true; - } - if (["false", "0", "no", "off"].includes(normalized)) { - return false; - } - } - return fallback; -}; - -const getJoinString = (join: EquityPrintJoin, key: string): string | null => { - const value = join.features[key]; - return typeof value === "string" ? value : null; -}; - -const getJoinNumber = (join: EquityPrintJoin, key: string, fallback = Number.NaN): number => { - return parseNumber(join.features[key], fallback); -}; - -const getJoinBoolean = (join: EquityPrintJoin, key: string): boolean => { - return parseBoolean(join.features[key], false); -}; - -type NbboSide = "AA" | "A" | "B" | "BB"; - -const classifyNbboSide = (price: number, quote: OptionNBBO | null | undefined): NbboSide | null => { - if (!quote || !Number.isFinite(price)) { - return null; - } - - const bid = quote.bid; - const ask = quote.ask; - if (!Number.isFinite(bid) || !Number.isFinite(ask) || ask <= 0) { - return null; - } - - const spread = Math.max(0, ask - bid); - const epsilon = Math.max(0.01, spread * 0.05); - - if (price > ask + epsilon) { - return "AA"; - } - if (price >= ask - epsilon) { - return "A"; - } - if (price < bid - epsilon) { - return "BB"; - } - if (price <= bid + epsilon) { - return "B"; - } - - const mid = (bid + ask) / 2; - return price >= mid ? "A" : "B"; -}; - -type ListScrollState = { - listRef: React.RefObject; - isAtTop: boolean; - isAtTopRef: React.MutableRefObject; - missed: number; - resumeTick: number; - onNewItems: (count: number) => void; - jumpToTop: () => void; -}; - -const useListScroll = (): ListScrollState => { - const listRef = useRef(null); - const [isAtTop, setIsAtTop] = useState(true); - const [missed, setMissed] = useState(0); - const [resumeTick, setResumeTick] = useState(0); - const isAtTopRef = useRef(true); - const prevAtTopRef = useRef(true); - - useEffect(() => { - isAtTopRef.current = isAtTop; - }, [isAtTop]); - - const updateScrollState = useCallback(() => { - const el = listRef.current; - if (!el) { - return; - } - - const atTop = el.scrollTop <= 2; - - if (atTop && !prevAtTopRef.current) { - setResumeTick((prev) => prev + 1); - } - - prevAtTopRef.current = atTop; - isAtTopRef.current = atTop; - setIsAtTop(atTop); - - if (atTop) { - setMissed(0); - } - }, [isAtTopRef]); - - useEffect(() => { - const el = listRef.current; - if (!el) { - return; - } - - const onScroll = () => { - updateScrollState(); - }; - - updateScrollState(); - el.addEventListener("scroll", onScroll); - - return () => { - el.removeEventListener("scroll", onScroll); - }; - }, [updateScrollState]); - - const onNewItems = useCallback((count: number) => { - if (count <= 0) { - return; - } - - if (isAtTopRef.current) { - setMissed(0); - return; - } - - setMissed((prev) => prev + count); - }, []); - - const jumpToTop = useCallback(() => { - const el = listRef.current; - if (!el) { - return; - } - - isAtTopRef.current = true; - el.scrollTop = 0; - updateScrollState(); - }, [isAtTopRef, listRef, updateScrollState]); - - return { - listRef, - isAtTop, - isAtTopRef, - missed, - resumeTick, - onNewItems, - jumpToTop - }; -}; - -const useScrollAnchor = ( - listRef: React.RefObject, - isAtTopRef: React.MutableRefObject -) => { - const pendingRef = useRef<{ height: number } | null>(null); - - const capture = useCallback(() => { - if (isAtTopRef.current) { - pendingRef.current = null; - return; - } - - const el = listRef.current; - if (!el) { - return; - } - - pendingRef.current = { - height: el.scrollHeight - }; - }, [isAtTopRef, listRef]); - - const apply = useCallback(() => { - const pending = pendingRef.current; - if (!pending) { - return; - } - - const el = listRef.current; - if (!el) { - return; - } - - if (isAtTopRef.current) { - pendingRef.current = null; - return; - } - - const delta = el.scrollHeight - pending.height; - if (delta !== 0) { - el.scrollTop = Math.max(0, el.scrollTop + delta); - } - pendingRef.current = null; - }, [isAtTopRef, listRef]); - - return { capture, apply }; -}; - -const statusLabel = (status: WsStatus, paused: boolean, mode: TapeMode): string => { - if (paused) { - return "Paused"; - } - - if (mode === "replay") { - return status === "disconnected" ? "Replay Down" : "Replay"; - } - - switch (status) { - case "connected": - return "Live"; - case "connecting": - return "Connecting"; - case "disconnected": - default: - return "Disconnected"; - } -}; - -type TapeConfig = { - mode: TapeMode; - wsPath: string; - replayPath: string; - latestPath?: string; - expectedType: MessageType; - batchSize?: number; - pollMs?: number; - captureScroll?: () => void; - onNewItems?: (count: number) => void; - getItemTs?: (item: T) => number; - getReplayKey?: (item: T) => string | null; - replaySourceKey?: string | null; - onReplaySourceKey?: (key: string | null) => void; -}; - -const useTape = ( - config: TapeConfig -): TapeState => { - const { mode, wsPath, replayPath, expectedType, latestPath, onNewItems, captureScroll } = config; - const batchSize = config.batchSize ?? 40; - const pollMs = config.pollMs ?? 1000; - const getItemTs = config.getItemTs ?? extractSortTs; - const getReplayKey = config.getReplayKey ?? extractTracePrefix; - const replaySourceKey = config.replaySourceKey ?? null; - const onReplaySourceKey = config.onReplaySourceKey; - const [status, setStatus] = useState("connecting"); - const [items, setItems] = useState([]); - const [lastUpdate, setLastUpdate] = useState(null); - const [replayTime, setReplayTime] = useState(null); - const [replayComplete, setReplayComplete] = useState(false); - const [paused, setPaused] = useState(false); - const [dropped, setDropped] = useState(0); - const reconnectRef = useRef(null); - const socketRef = useRef(null); - const cursorRef = useRef({ ts: 0, seq: 0 }); - const replayEndRef = useRef(null); - const replayCompleteRef = useRef(false); - const replaySourceRef = useRef(null); - const replaySourceNotifiedRef = useRef(null); - const emptyPollsRef = useRef(0); - const pausedRef = useRef(paused); - const pendingRef = useRef([]); - const pendingCountRef = useRef(0); - const flushHandleRef = useRef(null); - - useEffect(() => { - pausedRef.current = paused; - }, [paused]); - - const cancelFlush = useCallback(() => { - if (flushHandleRef.current !== null) { - cancelAnimationFrame(flushHandleRef.current); - flushHandleRef.current = null; - } - }, []); - - const scheduleFlush = useCallback(() => { - if (flushHandleRef.current !== null) { - return; - } - - flushHandleRef.current = requestAnimationFrame(() => { - flushHandleRef.current = null; - const buffered = pendingRef.current; - if (buffered.length === 0) { - return; - } - pendingRef.current = []; - - const pendingCount = pendingCountRef.current; - pendingCountRef.current = 0; - - if (onNewItems && pendingCount > 0) { - onNewItems(pendingCount); - } - - if (captureScroll) { - captureScroll(); - } - - setItems((prev) => mergeNewest(buffered, prev)); - setLastUpdate(Date.now()); - }); - }, [captureScroll, onNewItems]); - - const togglePause = useCallback(() => { - setPaused((prev) => { - const next = !prev; - if (!next) { - setDropped(0); - } - return next; - }); - }, []); - - useEffect(() => { - setItems([]); - setLastUpdate(null); - setReplayTime(null); - setReplayComplete(false); - replayCompleteRef.current = false; - replaySourceRef.current = null; - replaySourceNotifiedRef.current = null; - emptyPollsRef.current = 0; - setDropped(0); - setStatus("connecting"); - cursorRef.current = { ts: 0, seq: 0 }; - pendingRef.current = []; - pendingCountRef.current = 0; - cancelFlush(); - }, [mode, replaySourceKey, cancelFlush]); - - useEffect(() => { - if (mode !== "replay" || !latestPath) { - replayEndRef.current = null; - return; - } - - let active = true; - replayEndRef.current = null; - setReplayComplete(false); - replayCompleteRef.current = false; - - const fetchReplayEnd = async () => { - try { - const url = new URL(buildApiUrl(latestPath)); - url.searchParams.set("limit", "1"); - if (replaySourceKey) { - url.searchParams.set("source", replaySourceKey); - } - const response = await fetch(url.toString()); - if (!response.ok) { - throw new Error(`Replay baseline failed with ${response.status}`); - } - - const payload = (await response.json()) as { data?: T[] }; - const latest = payload.data?.[0]; - if (active && latest) { - replayEndRef.current = getItemTs(latest); - } - } catch (error) { - console.warn("Failed to load replay end cursor", error); - } - }; - - void fetchReplayEnd(); - - return () => { - active = false; - }; - }, [mode, latestPath, getItemTs, replaySourceKey]); - - useEffect(() => { - if (mode !== "live") { - return; - } - - let active = true; - - const connect = () => { - if (!active) { - return; - } - - setStatus("connecting"); - - const socket = new WebSocket(buildWsUrl(wsPath)); - socketRef.current = socket; - - socket.onopen = () => { - if (!active) { - return; - } - setStatus("connected"); - }; - - socket.onmessage = (event) => { - if (!active) { - return; - } - - try { - const message = JSON.parse(event.data) as StreamMessage; - if (!message || message.type !== expectedType) { - return; - } - - if (pausedRef.current) { - setDropped((prev) => prev + 1); - setLastUpdate(Date.now()); - return; - } - - pendingRef.current.push(message.payload); - pendingCountRef.current += 1; - scheduleFlush(); - } catch (error) { - console.warn("Failed to parse websocket payload", error); - } - }; - - socket.onclose = () => { - if (!active) { - return; - } - - setStatus("disconnected"); - reconnectRef.current = window.setTimeout(() => { - connect(); - }, 1000); - }; - - socket.onerror = () => { - if (!active) { - return; - } - - setStatus("disconnected"); - socket.close(); - }; - }; - - connect(); - - return () => { - active = false; - cancelFlush(); - if (reconnectRef.current !== null) { - window.clearTimeout(reconnectRef.current); - } - if (socketRef.current) { - socketRef.current.close(); - } - }; - }, [mode, wsPath, expectedType, scheduleFlush, cancelFlush]); - - useEffect(() => { - if (mode !== "replay") { - return; - } - - let active = true; - - const poll = async () => { - if (!active || pausedRef.current) { - return; - } - - if (replayCompleteRef.current) { - return; - } - - try { - let keepPolling = true; - - while (keepPolling && active && !pausedRef.current) { - const replayEnd = replayEndRef.current; - const cursor = cursorRef.current; - - if (replayEnd !== null && cursor.ts >= replayEnd) { - replayCompleteRef.current = true; - setReplayComplete(true); - setStatus("disconnected"); - return; - } - - const url = new URL(buildApiUrl(replayPath)); - url.searchParams.set("after_ts", cursor.ts.toString()); - url.searchParams.set("after_seq", cursor.seq.toString()); - url.searchParams.set("limit", batchSize.toString()); - const desiredSource = replaySourceKey ?? replaySourceRef.current; - if (desiredSource) { - url.searchParams.set("source", desiredSource); - } - - const response = await fetch(url.toString()); - if (!response.ok) { - throw new Error(`Replay request failed with ${response.status}`); - } - - const payload = (await response.json()) as ReplayResponse; - - let sourcePrefix = replaySourceRef.current; - if (replaySourceKey) { - if (sourcePrefix !== replaySourceKey) { - sourcePrefix = replaySourceKey; - replaySourceRef.current = replaySourceKey; - } - } else if (!sourcePrefix) { - const firstWithTrace = payload.data.find((item) => getReplayKey(item)); - if (firstWithTrace) { - sourcePrefix = getReplayKey(firstWithTrace); - replaySourceRef.current = sourcePrefix ?? null; - } - } - - if (onReplaySourceKey && sourcePrefix && replaySourceNotifiedRef.current !== sourcePrefix) { - replaySourceNotifiedRef.current = sourcePrefix; - onReplaySourceKey(sourcePrefix); - } - - const filtered = sourcePrefix - ? payload.data.filter((item) => getReplayKey(item) === sourcePrefix) - : payload.data; - - const hasForeign = - sourcePrefix && - payload.data.some((item) => { - const prefix = getReplayKey(item); - return prefix !== null && prefix !== sourcePrefix; - }); - - if (filtered.length > 0) { - const nextItems = [...filtered].reverse(); - pendingRef.current.push(...nextItems); - pendingCountRef.current += nextItems.length; - scheduleFlush(); - const last = filtered.at(-1); - if (last) { - const lastTs = getItemTs(last); - setReplayTime(lastTs); - if (replayEnd !== null && lastTs >= replayEnd) { - cursorRef.current = { ts: lastTs, seq: last.seq }; - replayCompleteRef.current = true; - setReplayComplete(true); - setStatus("disconnected"); - return; - } - } - emptyPollsRef.current = 0; - } else if (sourcePrefix) { - emptyPollsRef.current += 1; - } - - if (payload.next) { - cursorRef.current = payload.next; - } - - setStatus("connected"); - keepPolling = filtered.length === batchSize; - - if (keepPolling) { - await new Promise((resolve) => setTimeout(resolve, 0)); - } - - if (!replaySourceKey && hasForeign) { - replayCompleteRef.current = true; - setReplayComplete(true); - setStatus("disconnected"); - return; - } - - if (sourcePrefix && emptyPollsRef.current >= 3) { - replayCompleteRef.current = true; - setReplayComplete(true); - setStatus("disconnected"); - return; - } - } - } catch (error) { - console.warn("Replay poll failed", error); - setStatus("disconnected"); - } - }; - - void poll(); - const interval = window.setInterval(poll, pollMs); - - return () => { - active = false; - window.clearInterval(interval); - cancelFlush(); - }; - }, [ - mode, - replayPath, - batchSize, - pollMs, - scheduleFlush, - cancelFlush, - getItemTs, - getReplayKey, - replaySourceKey, - onReplaySourceKey - ]); - - return { - status, - items, - lastUpdate, - replayTime, - replayComplete, - paused, - dropped, - togglePause - }; -}; - -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); - const [replayComplete] = useState(false); - const [paused, setPaused] = useState(false); - const [dropped, setDropped] = useState(0); - const reconnectRef = useRef(null); - const socketRef = useRef(null); - const pausedRef = useRef(paused); - const pendingRef = useRef([]); - const pendingCountRef = useRef(0); - const flushHandleRef = useRef(null); - const holdRef = useRef([]); - - useEffect(() => { - pausedRef.current = paused; - }, [paused]); - - const cancelFlush = useCallback(() => { - if (flushHandleRef.current !== null) { - cancelAnimationFrame(flushHandleRef.current); - flushHandleRef.current = null; - } - }, []); - - const scheduleFlush = useCallback(() => { - if (flushHandleRef.current !== null) { - return; - } - - flushHandleRef.current = requestAnimationFrame(() => { - flushHandleRef.current = null; - const buffered = pendingRef.current; - if (buffered.length === 0) { - return; - } - pendingRef.current = []; - - const pendingCount = pendingCountRef.current; - pendingCountRef.current = 0; - - if (config.onNewItems && pendingCount > 0) { - config.onNewItems(pendingCount); - } - - const shouldHold = config.shouldHold ? config.shouldHold() : false; - if (!shouldHold && config.captureScroll) { - config.captureScroll(); - } - - if (shouldHold) { - holdRef.current = mergeNewest(buffered, holdRef.current); - setLastUpdate(Date.now()); - return; - } - - const nextBatch = - holdRef.current.length > 0 ? [...holdRef.current, ...buffered] : buffered; - holdRef.current = []; - - setItems((prev) => mergeNewest(nextBatch, prev)); - setLastUpdate(Date.now()); - }); - }, [config.captureScroll, config.onNewItems, config.shouldHold]); - - const togglePause = useCallback(() => { - setPaused((prev) => { - const next = !prev; - if (!next) { - setDropped(0); - } - return next; - }); - }, []); - - useEffect(() => { - if (!config.enabled) { - setStatus("disconnected"); - setItems([]); - setLastUpdate(null); - pendingRef.current = []; - pendingCountRef.current = 0; - holdRef.current = []; - cancelFlush(); - return; - } - - let active = true; - - const connect = () => { - if (!active) { - return; - } - - setStatus("connecting"); - - const socket = new WebSocket(buildWsUrl(config.wsPath)); - socketRef.current = socket; - - socket.onopen = () => { - if (!active) { - return; - } - setStatus("connected"); - }; - - socket.onmessage = (event) => { - if (!active) { - return; - } - - try { - const message = JSON.parse(event.data) as StreamMessage; - if (!message || message.type !== config.expectedType) { - return; - } - - if (pausedRef.current) { - setDropped((prev) => prev + 1); - setLastUpdate(Date.now()); - return; - } - - pendingRef.current.push(message.payload); - pendingCountRef.current += 1; - scheduleFlush(); - } catch (error) { - console.warn("Failed to parse live stream payload", error); - } - }; - - socket.onclose = () => { - if (!active) { - return; - } - - setStatus("disconnected"); - reconnectRef.current = window.setTimeout(() => { - connect(); - }, 1000); - }; - - socket.onerror = () => { - if (!active) { - return; - } - - setStatus("disconnected"); - socket.close(); - }; - }; - - connect(); - - return () => { - active = false; - cancelFlush(); - if (reconnectRef.current !== null) { - window.clearTimeout(reconnectRef.current); - } - if (socketRef.current) { - socketRef.current.close(); - } - }; - }, [config.enabled, config.expectedType, config.wsPath, scheduleFlush, cancelFlush]); - - useEffect(() => { - if (config.resumeSignal === undefined) { - return; - } - if (config.shouldHold && config.shouldHold()) { - return; - } - if (holdRef.current.length === 0) { - return; - } - setItems((prev) => mergeNewest(holdRef.current, prev)); - holdRef.current = []; - setLastUpdate(Date.now()); - }, [config.resumeSignal, config.shouldHold]); - - return { - status, - items, - lastUpdate, - replayTime, - replayComplete, - paused, - dropped, - togglePause - }; -}; - -const useFlowStream = ( - enabled: boolean, - onNewItems?: (count: number) => void, - captureScroll?: () => void, - shouldHold?: () => boolean, - resumeSignal?: number -): TapeState => { - return useLiveStream({ - enabled, - wsPath: "/ws/flow", - expectedType: "flow-packet", - onNewItems, - captureScroll, - shouldHold, - resumeSignal - }); -}; - -type TapeStatusProps = { - status: WsStatus; - lastUpdate: number | null; - replayTime: number | null; - replayComplete: boolean; - paused: boolean; - dropped: number; - mode: TapeMode; - onTogglePause: () => void; -}; - -const TapeStatus = ({ - status, - lastUpdate, - replayTime, - replayComplete, - paused, - dropped, - mode, - onTogglePause -}: TapeStatusProps) => { - const replayClass = mode === "replay" ? "status-replay" : ""; - const pausedClass = paused ? "status-paused" : ""; - const label = replayComplete ? "Replay Complete" : statusLabel(status, paused, mode); - - return ( -
- - {label} - {lastUpdate ? ( - Updated {formatTime(lastUpdate)} - ) : ( - Waiting for data - )} - {paused && dropped > 0 ? ( - {dropped} new while paused - ) : null} - {mode === "replay" ? ( - - Replay time {replayTime ? formatTime(replayTime) : "—"} - - ) : null} - -
- ); -}; - -type TapeControlsProps = { - isAtTop: boolean; - missed: number; - onJump: () => void; -}; - -const TapeControls = ({ isAtTop, missed, onJump }: TapeControlsProps) => { - const active = !isAtTop && missed > 0; - return ( -
- - {active ? `+${missed} new` : ""} -
- ); -}; - -type CandleChartProps = { - ticker: string; - intervalMs: number; - mode: TapeMode; - replayTime?: number | null; - classifierHits: ClassifierHitEvent[]; - inferredDark: InferredDarkEvent[]; - onClassifierHitClick: (hit: ClassifierHitEvent) => void; - onInferredDarkClick: (event: InferredDarkEvent) => void; -}; - -type MarkerAction = - | { kind: "hit"; hit: ClassifierHitEvent } - | { kind: "dark"; event: InferredDarkEvent }; - -const CandleChart = ({ - ticker, - intervalMs, - mode, - replayTime = null, - classifierHits, - inferredDark, - onClassifierHitClick, - onInferredDarkClick -}: CandleChartProps) => { - const containerRef = useRef(null); - const chartRef = useRef(null); - const seriesRef = useRef(null); - const socketRef = useRef(null); - const reconnectRef = useRef(null); - const overlaySocketRef = useRef(null); - const overlayReconnectRef = useRef(null); - const lastCandleRef = useRef<{ time: UTCTimestamp; seq: number } | null>(null); - - const markerLookupRef = useRef>(new Map()); - const [visibleRangeMs, setVisibleRangeMs] = useState<{ from: number; to: number } | null>(null); - const onHitClickRef = useRef(onClassifierHitClick); - const onDarkClickRef = useRef(onInferredDarkClick); - - const overlayCanvasRef = useRef(null); - const overlayCtxRef = useRef(null); - const overlayDataRef = useRef([]); - const overlayLiveRef = useRef([]); - const overlayLastFetchRef = useRef<{ startTs: number; endTs: number; ticker: string } | null>( - null - ); - const overlayFetchAbortRef = useRef(null); - const overlayTimerRef = useRef(null); - - const [overlayEnabled, setOverlayEnabled] = useState(true); - - const drawOverlay = useCallback( - (points: EquityOverlayPoint[]) => { - const canvas = overlayCanvasRef.current; - const ctx = overlayCtxRef.current; - const chart = chartRef.current; - if (!canvas || !ctx || !chart) { - return; - } - - ctx.clearRect(0, 0, canvas.width, canvas.height); - - if (!overlayEnabled || points.length === 0) { - canvas.style.opacity = "0"; - return; - } - - const timeScale = chart.timeScale(); - if (!seriesRef.current) { - canvas.style.opacity = "0"; - return; - } - - const filtered = points.filter((point) => point.offExchangeFlag); - const sampled = sampleToLimit(filtered, 1400); - - const maxRadius = 10; - const minRadius = 2; - const maxSize = Math.max(1, ...sampled.map((point) => point.size)); - - ctx.globalAlpha = 0.9; - ctx.fillStyle = "rgba(31, 74, 123, 0.55)"; - ctx.strokeStyle = "rgba(31, 74, 123, 0.95)"; - - for (const point of sampled) { - const x = timeScale.timeToCoordinate(toChartTime(point.ts)); - const y = seriesRef.current.priceToCoordinate(point.price); - if (x === null || y === null) { - continue; - } - - const radius = clamp( - minRadius + (Math.sqrt(point.size) / Math.sqrt(maxSize)) * (maxRadius - minRadius), - minRadius, - maxRadius - ); - - ctx.beginPath(); - ctx.arc(x, y, radius, 0, Math.PI * 2); - ctx.fill(); - ctx.stroke(); - } - - ctx.globalAlpha = 1; - canvas.style.opacity = "1"; - }, - [overlayEnabled] - ); - - useEffect(() => { - drawOverlay([...overlayDataRef.current, ...overlayLiveRef.current]); - }, [drawOverlay, ticker, intervalMs, mode]); - - useEffect(() => { - onHitClickRef.current = onClassifierHitClick; - }, [onClassifierHitClick]); - - useEffect(() => { - onDarkClickRef.current = onInferredDarkClick; - }, [onInferredDarkClick]); - - const markerBundle = useMemo(() => { - const lookup = new Map(); - const markers: SeriesMarker[] = []; - - if (!visibleRangeMs) { - return { markers, lookup }; - } - - const { from, to } = visibleRangeMs; - const inRangeHits = classifierHits - .filter((hit) => hit.source_ts >= from && hit.source_ts <= to) - .sort((a, b) => { - const delta = a.source_ts - b.source_ts; - if (delta !== 0) { - return delta; - } - return a.seq - b.seq; - }); - const inRangeDark = inferredDark - .filter((event) => event.source_ts >= from && event.source_ts <= to) - .sort((a, b) => { - const delta = a.source_ts - b.source_ts; - if (delta !== 0) { - return delta; - } - return a.seq - b.seq; - }); - - const MAX_HIT_MARKERS = 220; - const MAX_DARK_MARKERS = 120; - const MAX_TOTAL_MARKERS = 320; - - const cappedHits = - inRangeHits.length > MAX_HIT_MARKERS - ? inRangeHits.slice(inRangeHits.length - MAX_HIT_MARKERS) - : inRangeHits; - const cappedDark = - inRangeDark.length > MAX_DARK_MARKERS - ? inRangeDark.slice(inRangeDark.length - MAX_DARK_MARKERS) - : inRangeDark; - - for (const hit of cappedHits) { - const direction = normalizeDirection(hit.direction); - const markerId = `hit:${hit.trace_id}:${hit.seq}`; - lookup.set(markerId, { kind: "hit", hit }); - - markers.push({ - id: markerId, - time: toChartTime(hit.source_ts), - position: direction === "bullish" ? "belowBar" : "aboveBar", - color: - direction === "bullish" - ? "#2f6d4f" - : direction === "bearish" - ? "#c46f2a" - : "rgba(111, 91, 57, 0.9)", - shape: - direction === "bullish" - ? "arrowUp" - : direction === "bearish" - ? "arrowDown" - : "circle", - text: hit.classifier_id ? hit.classifier_id.slice(0, 3).toUpperCase() : "H" - }); - } - - for (const event of cappedDark) { - const markerId = `dark:${event.trace_id}:${event.seq}`; - lookup.set(markerId, { kind: "dark", event }); - markers.push({ - id: markerId, - time: toChartTime(event.source_ts), - position: "aboveBar", - color: "rgba(31, 74, 123, 0.9)", - shape: "square", - text: "D" - }); - } - - markers.sort((a, b) => { - const delta = Number(a.time) - Number(b.time); - if (delta !== 0) { - return delta; - } - return String(a.id ?? "").localeCompare(String(b.id ?? "")); - }); - - const cappedMarkers = - markers.length > MAX_TOTAL_MARKERS - ? markers.slice(markers.length - MAX_TOTAL_MARKERS) - : markers; - - if (cappedMarkers !== markers) { - const nextLookup = new Map(); - for (const marker of cappedMarkers) { - const id = marker.id; - if (typeof id !== "string") { - continue; - } - const action = lookup.get(id); - if (action) { - nextLookup.set(id, action); - } - } - return { markers: cappedMarkers, lookup: nextLookup }; - } - - return { markers: cappedMarkers, lookup }; - }, [classifierHits, inferredDark, visibleRangeMs]); - - useEffect(() => { - if (!seriesRef.current) { - return; - } - markerLookupRef.current = markerBundle.lookup; - seriesRef.current.setMarkers(markerBundle.markers); - }, [markerBundle]); - - const replayBucket = useMemo(() => { - if (mode !== "replay" || replayTime === null) { - return null; - } - return Math.floor(replayTime / intervalMs); - }, [mode, replayTime, intervalMs]); - const replayEndTs = useMemo(() => { - if (replayBucket === null) { - return null; - } - return (replayBucket + 1) * intervalMs - 1; - }, [replayBucket, intervalMs]); - const [ready, setReady] = useState(false); - const [status, setStatus] = useState(mode === "live" ? "connecting" : "connected"); - const [lastUpdate, setLastUpdate] = useState(null); - const [hasData, setHasData] = useState(false); - const [error, setError] = useState(null); - - useLayoutEffect(() => { - const container = containerRef.current; - if (!container) { - return; - } - - const width = container.clientWidth || 600; - const height = container.clientHeight || 360; - const chart = createChart(container, { - width, - height, - layout: { - background: { color: "#fffdf7" }, - textColor: "#4e3e25" - }, - grid: { - vertLines: { color: "rgba(82, 64, 36, 0.12)" }, - horzLines: { color: "rgba(82, 64, 36, 0.12)" } - }, - crosshair: { - vertLine: { color: "rgba(47, 109, 79, 0.35)" }, - horzLine: { color: "rgba(47, 109, 79, 0.35)" } - }, - timeScale: { - borderColor: "rgba(111, 91, 57, 0.35)", - timeVisible: true, - secondsVisible: intervalMs < 60000 - }, - rightPriceScale: { - borderColor: "rgba(111, 91, 57, 0.35)" - } - }); - - const overlayCanvas = document.createElement("canvas"); - overlayCanvas.width = Math.max(1, Math.floor(width)); - overlayCanvas.height = Math.max(1, Math.floor(height)); - overlayCanvas.style.position = "absolute"; - overlayCanvas.style.inset = "0"; - overlayCanvas.style.pointerEvents = "none"; - overlayCanvas.style.zIndex = "2"; - overlayCanvas.style.opacity = "0"; - container.style.position = "relative"; - container.appendChild(overlayCanvas); - overlayCanvasRef.current = overlayCanvas; - overlayCtxRef.current = overlayCanvas.getContext("2d"); - - const series = chart.addCandlestickSeries({ - upColor: "#2f6d4f", - downColor: "#c46f2a", - borderVisible: false, - wickUpColor: "#2f6d4f", - wickDownColor: "#c46f2a" - }); - - chartRef.current = chart; - seriesRef.current = series; - setReady(true); - - const timeScale = chart.timeScale(); - const updateVisibleRange = () => { - const range = timeScale.getVisibleRange(); - if (!range) { - setVisibleRangeMs(null); - return; - } - const from = chartTimeToMs(range.from); - const to = chartTimeToMs(range.to); - if (from === null || to === null) { - setVisibleRangeMs(null); - return; - } - - setVisibleRangeMs({ - from: Math.min(from, to), - to: Math.max(from, to) - }); - }; - - const clickHandler = (param: { hoveredObjectId?: unknown }) => { - const hovered = param.hoveredObjectId; - if (hovered === null || hovered === undefined) { - return; - } - const key = typeof hovered === "string" ? hovered : String(hovered); - const action = markerLookupRef.current.get(key); - if (!action) { - return; - } - if (action.kind === "hit") { - onHitClickRef.current(action.hit); - } else { - onDarkClickRef.current(action.event); - } - }; - - updateVisibleRange(); - timeScale.subscribeVisibleTimeRangeChange(updateVisibleRange); - chart.subscribeClick(clickHandler); - - const resizeObserver = new ResizeObserver((entries) => { - const entry = entries[0]; - if (!entry) { - return; - } - const { width: nextWidth, height: nextHeight } = entry.contentRect; - if (Number.isFinite(nextWidth) && Number.isFinite(nextHeight)) { - const nextW = Math.max(1, Math.floor(nextWidth)); - const nextH = Math.max(1, Math.floor(nextHeight)); - chart.applyOptions({ - width: nextW, - height: nextH - }); - - const canvas = overlayCanvasRef.current; - if (canvas) { - canvas.width = nextW; - canvas.height = nextH; - } - } - }); - - resizeObserver.observe(container); - - return () => { - resizeObserver.disconnect(); - timeScale.unsubscribeVisibleTimeRangeChange(updateVisibleRange); - chart.unsubscribeClick(clickHandler); - chart.remove(); - chartRef.current = null; - seriesRef.current = null; - overlayCtxRef.current = null; - overlayCanvasRef.current?.remove(); - overlayCanvasRef.current = null; - }; - }, []); - - useEffect(() => { - if (!ready || !seriesRef.current) { - return; - } - - if (mode === "replay" && replayBucket === null) { - setError(null); - setHasData(false); - setLastUpdate(null); - lastCandleRef.current = null; - seriesRef.current.setData([]); - overlayDataRef.current = []; - overlayLiveRef.current = []; - overlayLastFetchRef.current = null; - setStatus("connected"); - return; - } - - let active = true; - setError(null); - setHasData(false); - setLastUpdate(null); - lastCandleRef.current = null; - seriesRef.current.setData([]); - overlayDataRef.current = []; - overlayLiveRef.current = []; - overlayLastFetchRef.current = null; - setStatus(mode === "live" ? "connecting" : "connected"); - - const fetchCandles = async () => { - try { - const url = new URL(buildApiUrl("/candles/equities")); - url.searchParams.set("underlying_id", ticker); - url.searchParams.set("interval_ms", intervalMs.toString()); - url.searchParams.set("limit", "300"); - url.searchParams.set("cache", "1"); - if (mode === "replay" && replayEndTs !== null) { - url.searchParams.set("end_ts", replayEndTs.toString()); - } - const response = await fetch(url.toString()); - if (!response.ok) { - const detail = await readErrorDetail(response); - throw new Error( - `Candle fetch failed (${response.status})${detail ? `: ${detail}` : ""}` - ); - } - const payload = (await response.json()) as { data?: EquityCandle[] }; - if (!active || !seriesRef.current) { - return; - } - const sorted = [...(payload.data ?? [])].sort((a, b) => { - if (a.ts !== b.ts) { - return a.ts - b.ts; - } - return a.seq - b.seq; - }); - const chartData = sorted.map(toChartCandle); - seriesRef.current.setData(chartData); - chartRef.current?.timeScale().fitContent(); - drawOverlay([...overlayDataRef.current, ...overlayLiveRef.current]); - - if (sorted.length > 0) { - const last = sorted[sorted.length - 1]; - lastCandleRef.current = { time: toChartTime(last.ts), seq: last.seq }; - setHasData(true); - setLastUpdate(last.ingest_ts ?? last.ts); - } - } catch (error) { - if (!active) { - return; - } - setError(error instanceof Error ? error.message : String(error)); - setStatus("disconnected"); - setHasData(false); - } - }; - - - const ensureOverlayListener = () => { - if (!chartRef.current) { - return; - } - - const handler = () => { - const combined = [...overlayDataRef.current, ...overlayLiveRef.current]; - drawOverlay(combined); - scheduleOverlayFetch(); - }; - - chartRef.current.timeScale().subscribeVisibleTimeRangeChange(handler); - return () => { - chartRef.current?.timeScale().unsubscribeVisibleTimeRangeChange(handler); - }; - }; - - const cancelOverlayFetch = () => { - if (overlayFetchAbortRef.current) { - overlayFetchAbortRef.current.abort(); - overlayFetchAbortRef.current = null; - } - }; - - const fetchOverlayRange = async (startTs: number, endTs: number) => { - cancelOverlayFetch(); - const abort = new AbortController(); - overlayFetchAbortRef.current = abort; - - const url = new URL(buildApiUrl("/prints/equities/range")); - url.searchParams.set("underlying_id", ticker); - url.searchParams.set("start_ts", Math.floor(startTs).toString()); - url.searchParams.set("end_ts", Math.floor(endTs).toString()); - url.searchParams.set("limit", "2500"); - - const response = await fetch(url.toString(), { signal: abort.signal }); - if (!response.ok) { - const detail = await readErrorDetail(response); - throw new Error( - `Equity range fetch failed (${response.status})${detail ? `: ${detail}` : ""}` - ); - } - - const payload = (await response.json()) as { data?: EquityPrint[] }; - const prints = payload.data ?? []; - overlayDataRef.current = prints.map((print) => ({ - ts: print.ts, - price: print.price, - size: print.size, - offExchangeFlag: print.offExchangeFlag - })); - overlayLiveRef.current = []; - overlayLastFetchRef.current = { startTs, endTs, ticker }; - }; - - function scheduleOverlayFetch() { - if (overlayTimerRef.current !== null) { - window.clearTimeout(overlayTimerRef.current); - } - - overlayTimerRef.current = window.setTimeout(() => { - if (!active || !chartRef.current || !seriesRef.current) { - return; - } - - const timeScale = chartRef.current.timeScale(); - const range = timeScale.getVisibleRange(); - if (!range) { - return; - } - - const startTs = chartTimeToMs(range.from); - const endTs = chartTimeToMs(range.to); - if (startTs === null || endTs === null) { - return; - } - const last = overlayLastFetchRef.current; - - const needsFetch = - !last || - last.ticker !== ticker || - startTs < last.startTs || - endTs > last.endTs || - Math.abs(endTs - last.endTs) > intervalMs * 6; - - if (!needsFetch) { - return; - } - - void fetchOverlayRange(startTs, endTs) - .then(() => { - drawOverlay([...overlayDataRef.current, ...overlayLiveRef.current]); - }) - .catch((error) => { - if (!active) { - return; - } - if (error instanceof DOMException && error.name === "AbortError") { - return; - } - console.warn("Overlay fetch failed", error); - }); - }, 180); - } - - const overlayUnsubscribe = ensureOverlayListener(); - scheduleOverlayFetch(); - - void fetchCandles(); - - return () => { - active = false; - cancelOverlayFetch(); - if (overlayTimerRef.current !== null) { - window.clearTimeout(overlayTimerRef.current); - overlayTimerRef.current = null; - } - overlayUnsubscribe?.(); - }; - }, [ready, ticker, intervalMs, mode, replayBucket, replayEndTs]); - - useEffect(() => { - if (!ready || mode !== "live" || !seriesRef.current) { - if (socketRef.current) { - socketRef.current.close(); - } - if (reconnectRef.current !== null) { - window.clearTimeout(reconnectRef.current); - reconnectRef.current = null; - } - - if (overlaySocketRef.current) { - overlaySocketRef.current.close(); - } - if (overlayReconnectRef.current !== null) { - window.clearTimeout(overlayReconnectRef.current); - overlayReconnectRef.current = null; - } - - return; - } - - let active = true; - - const connect = () => { - if (!active) { - return; - } - - setStatus("connecting"); - const socket = new WebSocket(buildWsUrl("/ws/equity-candles")); - socketRef.current = socket; - - socket.onopen = () => { - if (!active) { - return; - } - setStatus("connected"); - }; - - socket.onmessage = (event) => { - if (!active || !seriesRef.current) { - return; - } - - try { - const message = JSON.parse(event.data) as StreamMessage; - if (!message || message.type !== "equity-candle") { - return; - } - - const candle = message.payload; - if (candle.underlying_id !== ticker || candle.interval_ms !== intervalMs) { - return; - } - - const chartCandle = toChartCandle(candle); - const last = lastCandleRef.current; - if (last) { - if (chartCandle.time < last.time) { - return; - } - if (chartCandle.time === last.time && candle.seq <= last.seq) { - return; - } - } - - seriesRef.current.update(chartCandle); - lastCandleRef.current = { time: chartCandle.time, seq: candle.seq }; - setHasData(true); - setLastUpdate(candle.ingest_ts ?? candle.ts); - drawOverlay([...overlayDataRef.current, ...overlayLiveRef.current]); - } catch (error) { - console.warn("Failed to parse candle payload", error); - } - }; - - socket.onclose = () => { - if (!active) { - return; - } - setStatus("disconnected"); - reconnectRef.current = window.setTimeout(connect, 1000); - }; - - socket.onerror = () => { - if (!active) { - return; - } - setStatus("disconnected"); - socket.close(); - }; - }; - - const connectOverlay = () => { - if (!active) { - return; - } - - const socket = new WebSocket(buildWsUrl("/ws/equities")); - overlaySocketRef.current = socket; - - socket.onmessage = (event) => { - if (!active) { - return; - } - - try { - const message = JSON.parse(event.data) as StreamMessage; - if (!message || message.type !== "equity-print") { - return; - } - - const print = message.payload; - if (print.underlying_id !== ticker) { - return; - } - - overlayLiveRef.current.push({ - ts: print.ts, - price: print.price, - size: print.size, - offExchangeFlag: print.offExchangeFlag - }); - - if (overlayLiveRef.current.length > 1500) { - overlayLiveRef.current = overlayLiveRef.current.slice(-1500); - } - - drawOverlay([...overlayDataRef.current, ...overlayLiveRef.current]); - } catch (error) { - console.warn("Failed to parse equity print payload", error); - } - }; - - socket.onclose = () => { - if (!active) { - return; - } - overlayReconnectRef.current = window.setTimeout(connectOverlay, 1500); - }; - - socket.onerror = () => { - if (!active) { - return; - } - socket.close(); - }; - }; - - connect(); - connectOverlay(); - - return () => { - active = false; - if (reconnectRef.current !== null) { - window.clearTimeout(reconnectRef.current); - reconnectRef.current = null; - } - if (socketRef.current) { - socketRef.current.close(); - } - - if (overlayReconnectRef.current !== null) { - window.clearTimeout(overlayReconnectRef.current); - overlayReconnectRef.current = null; - } - if (overlaySocketRef.current) { - overlaySocketRef.current.close(); - } - }; - }, [ready, mode, ticker, intervalMs, drawOverlay]); - - useEffect(() => { - if (!chartRef.current) { - return; - } - chartRef.current.timeScale().applyOptions({ - timeVisible: true, - secondsVisible: intervalMs < 60000 - }); - }, [intervalMs]); - - const statusText = statusLabel(status, false, mode); - const intervalLabel = formatIntervalLabel(intervalMs); - const emptyLabel = - mode === "live" - ? status === "connected" - ? `No candles yet. First ${intervalLabel} candle appears after the window closes.` - : "Chart offline. Start candles service." - : "No candles for this replay window."; - - return ( -
-
-
- - {statusText} -
- - {lastUpdate ? `Updated ${formatTime(lastUpdate)}` : "Waiting for data"} - - - Blue circles = off-exchange trades -
-
- {error ? ( -
Chart error: {error}
- ) : !hasData ? ( -
{emptyLabel}
- ) : null} -
- ); -}; - -type AlertSeverityStripProps = { - alerts: AlertEvent[]; -}; - -const AlertSeverityStrip = ({ alerts }: AlertSeverityStripProps) => { - const windowMs = 30 * 60 * 1000; - const now = Date.now(); - const severityCounts = alerts.reduce( - (acc, alert) => { - if (now - alert.source_ts > windowMs) { - return acc; - } - if (alert.severity === "high") { - acc.high += 1; - } else if (alert.severity === "medium") { - acc.medium += 1; - } else { - acc.low += 1; - } - return acc; - }, - { high: 0, medium: 0, low: 0 } - ); - - const directionCounts = alerts.reduce( - (acc, alert) => { - if (now - alert.source_ts > windowMs) { - return acc; - } - const direction = normalizeDirection(alert.hits[0]?.direction ?? "neutral"); - acc[direction] += 1; - return acc; - }, - { bullish: 0, bearish: 0, neutral: 0 } - ); - - const severityTotal = severityCounts.high + severityCounts.medium + severityCounts.low; - const highPct = severityTotal > 0 ? (severityCounts.high / severityTotal) * 100 : 0; - const mediumPct = severityTotal > 0 ? (severityCounts.medium / severityTotal) * 100 : 0; - const lowPct = severityTotal > 0 ? (severityCounts.low / severityTotal) * 100 : 0; - - const directionTotal = - directionCounts.bullish + directionCounts.bearish + directionCounts.neutral; - const bullishPct = directionTotal > 0 ? (directionCounts.bullish / directionTotal) * 100 : 0; - const bearishPct = directionTotal > 0 ? (directionCounts.bearish / directionTotal) * 100 : 0; - const neutralPct = directionTotal > 0 ? (directionCounts.neutral / directionTotal) * 100 : 0; - - return ( -
-
-
- Severity (last 30m) - {severityTotal} alerts -
-
-
- {severityCounts.high > 0 ? `High ${severityCounts.high}` : ""} -
-
- {severityCounts.medium > 0 ? `Med ${severityCounts.medium}` : ""} -
-
- {severityCounts.low > 0 ? `Low ${severityCounts.low}` : ""} -
-
-
-
-
- Direction (last 30m) - {directionTotal} alerts -
-
-
- {directionCounts.bullish > 0 ? `Bull ${directionCounts.bullish}` : ""} -
-
- {directionCounts.bearish > 0 ? `Bear ${directionCounts.bearish}` : ""} -
-
- {directionCounts.neutral > 0 ? `Neut ${directionCounts.neutral}` : ""} -
-
-
-
- ); -}; - -type EvidenceItem = - | { kind: "flow"; id: string; packet: FlowPacket } - | { kind: "print"; id: string; print: OptionPrint } - | { kind: "unknown"; id: string }; - -type DarkEvidenceItem = - | { kind: "join"; id: string; join: EquityPrintJoin } - | { kind: "unknown"; id: string }; - -type AlertDrawerProps = { - alert: AlertEvent; - flowPacket: FlowPacket | null; - evidence: EvidenceItem[]; - onClose: () => void; -}; - -const AlertDrawer = ({ alert, flowPacket, evidence, onClose }: AlertDrawerProps) => { - const primary = alert.hits[0]; - const direction = primary ? normalizeDirection(primary.direction) : "neutral"; - const evidencePrints = evidence.filter((item) => item.kind === "print"); - const unknownCount = evidence.filter((item) => item.kind === "unknown").length; - - return ( - - ); -}; - -type ClassifierHitDrawerProps = { - hit: ClassifierHitEvent; - flowPacket: FlowPacket | null; - evidence: EvidenceItem[]; - onClose: () => void; -}; - -const ClassifierHitDrawer = ({ hit, flowPacket, evidence, onClose }: ClassifierHitDrawerProps) => { - const direction = normalizeDirection(hit.direction); - const evidencePrints = evidence.filter((item) => item.kind === "print"); - const unknownCount = evidence.filter((item) => item.kind === "unknown").length; - - return ( - - ); -}; - -type DarkDrawerProps = { - event: InferredDarkEvent; - evidence: DarkEvidenceItem[]; - underlying: string | null; - onClose: () => void; -}; - -const DarkDrawer = ({ event, evidence, underlying, onClose }: DarkDrawerProps) => { - const joinEvidence = evidence.filter( - (item): item is { kind: "join"; id: string; join: EquityPrintJoin } => item.kind === "join" - ); - const unknownCount = evidence.filter((item) => item.kind === "unknown").length; - const traceRefs = event.evidence_refs.slice(0, 6); - const extraRefs = Math.max(0, event.evidence_refs.length - traceRefs.length); - - return ( - - ); -}; - -const formatFlowMetric = (value: number, suffix?: string): string => { - if (suffix) { - return `${value}${suffix}`; - } - - return value.toLocaleString(); -}; - -export default function HomePage() { - const [mode, setMode] = useState("live"); - const [replaySource, setReplaySource] = useState(null); - const [selectedAlert, setSelectedAlert] = useState(null); - const [selectedDarkEvent, setSelectedDarkEvent] = useState(null); - const [selectedClassifierHit, setSelectedClassifierHit] = useState(null); - const [filterInput, setFilterInput] = useState(""); - const [chartIntervalMs, setChartIntervalMs] = useState(CANDLE_INTERVALS[0].ms); - - const handleReplaySource = useCallback((value: string | null) => { - setReplaySource(value); - }, []); - - useEffect(() => { - setReplaySource(null); - }, [mode]); - const optionsScroll = useListScroll(); - const equitiesScroll = useListScroll(); - const flowScroll = useListScroll(); - const darkScroll = useListScroll(); - const alertsScroll = useListScroll(); - const classifierScroll = useListScroll(); - - const optionsAnchor = useScrollAnchor(optionsScroll.listRef, optionsScroll.isAtTopRef); - const equitiesAnchor = useScrollAnchor(equitiesScroll.listRef, equitiesScroll.isAtTopRef); - 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 disableReplayGrouping = useCallback(() => null, []); - - const options = useTape({ - mode, - wsPath: "/ws/options", - replayPath: "/replay/options", - latestPath: "/prints/options", - expectedType: "option-print", - batchSize: mode === "replay" ? 120 : undefined, - pollMs: mode === "replay" ? 200 : undefined, - captureScroll: optionsAnchor.capture, - onNewItems: optionsScroll.onNewItems, - getReplayKey: extractReplaySource, - onReplaySourceKey: handleReplaySource - }); - - const equities = useTape({ - mode, - wsPath: "/ws/equities", - replayPath: "/replay/equities", - latestPath: "/prints/equities", - expectedType: "equity-print", - batchSize: mode === "replay" ? 120 : undefined, - pollMs: mode === "replay" ? 200 : undefined, - captureScroll: equitiesAnchor.capture, - onNewItems: equitiesScroll.onNewItems - }); - - const equityJoins = useTape({ - mode, - wsPath: "/ws/equity-joins", - replayPath: "/replay/equity-joins", - latestPath: "/joins/equities", - expectedType: "equity-join", - batchSize: mode === "replay" ? 120 : undefined, - pollMs: mode === "replay" ? 200 : undefined, - getReplayKey: disableReplayGrouping - }); - - const nbbo = useTape({ - mode, - wsPath: "/ws/options-nbbo", - replayPath: "/replay/nbbo", - latestPath: "/nbbo/options", - expectedType: "option-nbbo", - batchSize: mode === "replay" ? 120 : undefined, - pollMs: mode === "replay" ? 200 : undefined, - getReplayKey: extractReplaySource, - replaySourceKey: replaySource - }); - - const inferredDark = useTape({ - mode, - wsPath: "/ws/inferred-dark", - replayPath: "/replay/inferred-dark", - latestPath: "/dark/inferred", - expectedType: "inferred-dark", - batchSize: mode === "replay" ? 120 : undefined, - pollMs: mode === "replay" ? 200 : undefined, - captureScroll: darkAnchor.capture, - onNewItems: darkScroll.onNewItems, - getReplayKey: disableReplayGrouping - }); - - const flow = useTape({ - mode, - wsPath: "/ws/flow", - replayPath: "/replay/flow", - latestPath: "/flow/packets", - expectedType: "flow-packet", - batchSize: mode === "replay" ? 120 : undefined, - pollMs: mode === "replay" ? 200 : undefined, - captureScroll: flowAnchor.capture, - onNewItems: flowScroll.onNewItems, - getReplayKey: disableReplayGrouping - }); - const alerts = useTape({ - mode, - wsPath: "/ws/alerts", - replayPath: "/replay/alerts", - latestPath: "/flow/alerts", - expectedType: "alert", - batchSize: mode === "replay" ? 120 : undefined, - pollMs: mode === "replay" ? 200 : undefined, - captureScroll: alertsAnchor.capture, - onNewItems: alertsScroll.onNewItems, - getReplayKey: disableReplayGrouping - }); - const classifierHits = useTape({ - mode, - wsPath: "/ws/classifier-hits", - replayPath: "/replay/classifier-hits", - latestPath: "/flow/classifier-hits", - expectedType: "classifier-hit", - batchSize: mode === "replay" ? 120 : undefined, - pollMs: mode === "replay" ? 200 : undefined, - captureScroll: classifierAnchor.capture, - onNewItems: classifierScroll.onNewItems, - getReplayKey: disableReplayGrouping - }); - - useLayoutEffect(() => { - optionsAnchor.apply(); - }, [options.items, optionsAnchor.apply]); - - useLayoutEffect(() => { - equitiesAnchor.apply(); - }, [equities.items, equitiesAnchor.apply]); - - useLayoutEffect(() => { - flowAnchor.apply(); - }, [flow.items, flowAnchor.apply]); - - useLayoutEffect(() => { - darkAnchor.apply(); - }, [inferredDark.items, darkAnchor.apply]); - - useLayoutEffect(() => { - alertsAnchor.apply(); - }, [alerts.items, alertsAnchor.apply]); - - useLayoutEffect(() => { - classifierAnchor.apply(); - }, [classifierHits.items, classifierAnchor.apply]); - - const activeTickers = useMemo(() => { - const parts = filterInput - .split(/[,\s]+/) - .map((value) => value.trim().toUpperCase()) - .filter(Boolean); - return Array.from(new Set(parts)); - }, [filterInput]); - - const tickerSet = useMemo(() => new Set(activeTickers), [activeTickers]); - const chartTicker = useMemo(() => activeTickers[0] ?? "SPY", [activeTickers]); - - const nbboMap = useMemo(() => { - const map = new Map(); - for (const quote of nbbo.items) { - const contractId = normalizeContractId(quote.option_contract_id); - const existing = map.get(contractId); - if ( - !existing || - quote.ts > existing.ts || - (quote.ts === existing.ts && quote.seq >= existing.seq) - ) { - map.set(contractId, quote); - } - } - return map; - }, [nbbo.items]); - - const optionPrintMap = useMemo(() => { - const map = new Map(); - for (const print of options.items) { - if (print.trace_id) { - map.set(print.trace_id, print); - } - } - return map; - }, [options.items]); - - const equityPrintMap = useMemo(() => { - const map = new Map(); - for (const print of equities.items) { - if (print.trace_id) { - map.set(print.trace_id, print); - } - } - return map; - }, [equities.items]); - - const equityJoinMap = useMemo(() => { - const map = new Map(); - for (const join of equityJoins.items) { - map.set(join.id, join); - } - return map; - }, [equityJoins.items]); - - const flowPacketMap = useMemo(() => { - const map = new Map(); - for (const packet of flow.items) { - map.set(packet.id, packet); - } - return map; - }, [flow.items]); - - const selectedEvidence = useMemo((): EvidenceItem[] => { - if (!selectedAlert) { - return []; - } - - return selectedAlert.evidence_refs.map((id) => { - const packet = flowPacketMap.get(id); - if (packet) { - return { kind: "flow", id, packet }; - } - const print = optionPrintMap.get(id); - if (print) { - return { kind: "print", id, print }; - } - return { kind: "unknown", id }; - }); - }, [selectedAlert, flowPacketMap, optionPrintMap]); - - const selectedFlowPacket = useMemo(() => { - if (!selectedAlert) { - return null; - } - const packetId = selectedAlert.evidence_refs[0]; - return packetId ? flowPacketMap.get(packetId) ?? null : null; - }, [selectedAlert, flowPacketMap]); - - const selectedDarkEvidence = useMemo((): DarkEvidenceItem[] => { - if (!selectedDarkEvent) { - return []; - } - - return selectedDarkEvent.evidence_refs.map((id) => { - const join = equityJoinMap.get(id); - if (join) { - return { kind: "join", id, join }; - } - return { kind: "unknown", id }; - }); - }, [selectedDarkEvent, equityJoinMap]); - - const selectedDarkUnderlying = useMemo(() => { - if (!selectedDarkEvent) { - return null; - } - return inferDarkUnderlying(selectedDarkEvent, equityPrintMap, equityJoinMap); - }, [selectedDarkEvent, equityJoinMap, equityPrintMap]); - - useEffect(() => { - if (mode !== "live") { - setSelectedAlert(null); - } - setSelectedDarkEvent(null); - setSelectedClassifierHit(null); - }, [mode]); - - const extractPacketContract = useCallback((packet: FlowPacket): string => { - const contract = packet.features.option_contract_id; - if (typeof contract === "string") { - return contract; - } - const match = packet.id.match(/^flowpacket:([^:]+):/); - return match?.[1] ?? packet.id; - }, []); - - const extractUnderlyingFromTrace = useCallback((traceId: string): string | null => { - const match = traceId.match(/flowpacket:([^:]+):/); - if (!match?.[1]) { - return null; - } - return extractUnderlying(match[1]); - }, []); - - const extractPacketIdFromClassifierHitTrace = useCallback((traceId: string): string | null => { - const idx = traceId.indexOf("flowpacket:"); - if (idx < 0) { - return null; - } - return traceId.slice(idx); - }, []); - - const selectedClassifierPacketId = useMemo(() => { - if (!selectedClassifierHit) { - return null; - } - return extractPacketIdFromClassifierHitTrace(selectedClassifierHit.trace_id); - }, [extractPacketIdFromClassifierHitTrace, selectedClassifierHit]); - - const selectedClassifierFlowPacket = useMemo(() => { - if (!selectedClassifierPacketId) { - return null; - } - return flowPacketMap.get(selectedClassifierPacketId) ?? null; - }, [flowPacketMap, selectedClassifierPacketId]); - - const selectedClassifierEvidence = useMemo((): EvidenceItem[] => { - if (!selectedClassifierHit) { - return []; - } - - if (!selectedClassifierPacketId) { - return []; - } - - const packet = flowPacketMap.get(selectedClassifierPacketId); - if (!packet) { - return []; - } - - return packet.members.map((id) => { - const print = optionPrintMap.get(id); - if (print) { - return { kind: "print", id, print }; - } - return { kind: "unknown", id }; - }); - }, [flowPacketMap, optionPrintMap, selectedClassifierHit, selectedClassifierPacketId]); - - const inferAlertUnderlying = useCallback( - (alert: AlertEvent): string | null => { - const fromTrace = extractUnderlyingFromTrace(alert.trace_id); - if (fromTrace) { - return fromTrace; - } - - const packetId = alert.evidence_refs[0]; - if (packetId) { - const packet = flowPacketMap.get(packetId); - if (packet) { - return extractUnderlying(extractPacketContract(packet)); - } - } - - for (const ref of alert.evidence_refs) { - const print = optionPrintMap.get(ref); - if (print) { - return extractUnderlying(print.option_contract_id); - } - } - - return null; - }, - [extractPacketContract, extractUnderlyingFromTrace, flowPacketMap, optionPrintMap] - ); - - const matchesTicker = useCallback( - (value: string | null) => { - if (tickerSet.size === 0) { - return true; - } - if (!value) { - return false; - } - return tickerSet.has(value.toUpperCase()); - }, - [tickerSet] - ); - - const filteredOptions = useMemo(() => { - if (tickerSet.size === 0) { - return options.items; - } - return options.items.filter((print) => - matchesTicker(extractUnderlying(normalizeContractId(print.option_contract_id))) - ); - }, [options.items, matchesTicker, tickerSet]); - - const filteredEquities = useMemo(() => { - if (tickerSet.size === 0) { - return equities.items; - } - return equities.items.filter((print) => matchesTicker(print.underlying_id)); - }, [equities.items, matchesTicker, tickerSet]); - - const filteredInferredDark = useMemo(() => { - if (tickerSet.size === 0) { - return inferredDark.items; - } - return inferredDark.items.filter((event) => { - const underlying = inferDarkUnderlying(event, equityPrintMap, equityJoinMap); - return matchesTicker(underlying); - }); - }, [equityJoinMap, equityPrintMap, inferredDark.items, matchesTicker, tickerSet]); - - const filteredFlow = useMemo(() => { - if (tickerSet.size === 0) { - return flow.items; - } - return flow.items.filter((packet) => - matchesTicker(extractUnderlying(extractPacketContract(packet))) - ); - }, [flow.items, extractPacketContract, matchesTicker, tickerSet]); - - const filteredAlerts = useMemo(() => { - if (tickerSet.size === 0) { - return alerts.items; - } - return alerts.items.filter((alert) => matchesTicker(inferAlertUnderlying(alert))); - }, [alerts.items, inferAlertUnderlying, matchesTicker, tickerSet]); - - const filteredClassifierHits = useMemo(() => { - if (tickerSet.size === 0) { - return classifierHits.items; - } - return classifierHits.items.filter((hit) => { - const underlying = extractUnderlyingFromTrace(hit.trace_id); - return matchesTicker(underlying); - }); - }, [classifierHits.items, extractUnderlyingFromTrace, matchesTicker, tickerSet]); - - const chartClassifierHits = useMemo(() => { - const desired = chartTicker.toUpperCase(); - return classifierHits.items - .filter((hit) => extractUnderlyingFromTrace(hit.trace_id) === desired) - .sort((a, b) => { - const delta = a.source_ts - b.source_ts; - if (delta !== 0) { - return delta; - } - return a.seq - b.seq; - }); - }, [chartTicker, classifierHits.items, extractUnderlyingFromTrace]); - - const chartInferredDark = useMemo(() => { - const desired = chartTicker.toUpperCase(); - return inferredDark.items - .filter((event) => inferDarkUnderlying(event, equityPrintMap, equityJoinMap) === desired) - .sort((a, b) => { - const delta = a.source_ts - b.source_ts; - if (delta !== 0) { - return delta; - } - return a.seq - b.seq; - }); - }, [chartTicker, inferredDark.items, equityJoinMap, equityPrintMap]); - - const findAlertForClassifierHit = useCallback( - (hit: ClassifierHitEvent): AlertEvent | null => { - const packetId = extractPacketIdFromClassifierHitTrace(hit.trace_id); - if (!packetId) { - return null; - } - - const desiredTrace = `alert:${packetId}`; - return ( - alerts.items.find( - (item) => item.trace_id === desiredTrace || item.evidence_refs[0] === packetId - ) ?? null - ); - }, - [alerts.items, extractPacketIdFromClassifierHitTrace] - ); - - const openFromClassifierHit = useCallback( - (hit: ClassifierHitEvent) => { - const alert = findAlertForClassifierHit(hit); - if (alert) { - setSelectedClassifierHit(null); - setSelectedDarkEvent(null); - setSelectedAlert(alert); - return; - } - - setSelectedAlert(null); - setSelectedDarkEvent(null); - setSelectedClassifierHit(hit); - }, - [findAlertForClassifierHit] - ); - - const handleClassifierMarkerClick = useCallback( - (hit: ClassifierHitEvent) => { - openFromClassifierHit(hit); - }, - [openFromClassifierHit] - ); - - const handleDarkMarkerClick = useCallback((event: InferredDarkEvent) => { - setSelectedAlert(null); - setSelectedClassifierHit(null); - setSelectedDarkEvent(event); - }, []); - - const lastSeen = useMemo(() => { - return [ - options.lastUpdate, - equities.lastUpdate, - inferredDark.lastUpdate, - flow.lastUpdate, - alerts.lastUpdate, - classifierHits.lastUpdate - ] - .filter((value): value is number => value !== null) - .sort((a, b) => b - a)[0] ?? null; - }, [ - options.lastUpdate, - equities.lastUpdate, - inferredDark.lastUpdate, - flow.lastUpdate, - alerts.lastUpdate, - classifierHits.lastUpdate - ]); - - const toggleMode = () => { - setMode((prev) => (prev === "live" ? "replay" : "live")); - }; - - return ( -
-
-
-

Realtime flow workspace

-

Islandflow

-

- Options + equities streaming over WebSocket or replayed from ClickHouse. -

-
-
- Last update - - {lastSeen ? formatTime(lastSeen) : "Waiting for data"} - - -
-
- -
-
-

Ticker filter

-

- {activeTickers.length > 0 ? `Filtering ${activeTickers.join(", ")}` : "All tickers"} -

-
-
- setFilterInput(event.target.value)} - placeholder="SPY, NVDA, AAPL" - /> - -
-
- -
-
-
-
-

Equity Chart

-

- Server-built {formatIntervalLabel(chartIntervalMs)} candles for {chartTicker}. -

-
-
-
-
- {CANDLE_INTERVALS.map((interval) => ( - - ))} -
- {activeTickers.length > 1 ? ( - Charting first of {activeTickers.length} tickers - ) : ( - Charting {chartTicker} - )} -
- -
- -
-
-
-

Options Tape

-

Newest prints first (max {MAX_ITEMS}).

-
-
-
- - -
- -
- {filteredOptions.length === 0 ? ( -
- {tickerSet.size > 0 - ? "No option prints match the current filter." - : mode === "live" - ? "No option prints yet. Start ingest-options." - : "Replay queue empty. Ensure ClickHouse has data."} -
- ) : ( - filteredOptions.map((print) => { - const contractId = normalizeContractId(print.option_contract_id); - const quote = nbboMap.get(contractId); - const nbboAge = quote ? Math.abs(print.ts - quote.ts) : null; - const nbboStale = nbboAge !== null && nbboAge > NBBO_MAX_AGE_MS_SAFE; - const nbboMid = quote ? (quote.bid + quote.ask) / 2 : null; - const nbboSide = classifyNbboSide(print.price, quote); - const notional = print.price * print.size * 100; - - return ( -
-
-
{formatContractLabel(contractId)}
-
- ${formatPrice(print.price)} - {formatSize(print.size)}x - {print.exchange} - Notional ${formatUsd(notional)} - {print.conditions?.length ? ( - {print.conditions.join(", ")} - ) : null} -
- {quote ? ( -
- Bid ${formatPrice(quote.bid)} - Ask ${formatPrice(quote.ask)} - Mid ${formatPrice(nbboMid ?? 0)} - {Math.round(nbboAge ?? 0)}ms - {nbboSide ? ( - - - {nbboSide} - - - - A - Ask - - - AA - Above Ask - - - B - Bid - - - BB - Below Bid - - - - ) : null} - {nbboStale ? Stale : null} -
- ) : ( -
- NBBO missing -
- )} -
-
{formatTime(print.ts)}
-
- ); - }) - )} -
-
- -
-
-
-

Equities Tape

-

Off-exchange flag highlighted.

-
-
-
- - -
- -
- {filteredEquities.length === 0 ? ( -
- {tickerSet.size > 0 - ? "No equity prints match the current filter." - : mode === "live" - ? "No equity prints yet. Start ingest-equities." - : "Replay queue empty. Ensure ClickHouse has data."} -
- ) : ( - filteredEquities.map((print) => ( -
-
-
{print.underlying_id}
-
- ${formatPrice(print.price)} - {formatSize(print.size)}x - {print.exchange} - {print.offExchangeFlag ? ( - Off-Ex - ) : ( - Lit - )} -
-
-
{formatTime(print.ts)}
-
- )) - )} -
-
- -
-
-
-

Flow Packets

-

Deterministic clusters.

-
-
-
- - -
- -
-
- {filteredFlow.length === 0 ? ( -
- {tickerSet.size > 0 - ? "No flow packets match the current filter." - : mode === "live" - ? "No flow packets yet. Start compute." - : "Replay queue empty. Ensure ClickHouse has data."} -
- ) : ( - filteredFlow.map((packet) => { - const features = packet.features ?? {}; - const contract = String(features.option_contract_id ?? packet.id ?? "unknown"); - const count = parseNumber(features.count, packet.members.length); - const totalSize = parseNumber(features.total_size, 0); - const totalNotional = parseNumber(features.total_notional, Number.NaN); - const notional = Number.isFinite(totalNotional) - ? totalNotional - : parseNumber(features.total_premium, 0) * 100; - const startTs = parseNumber(features.start_ts, packet.source_ts); - const endTs = parseNumber(features.end_ts, startTs); - const windowMs = parseNumber(features.window_ms, 0); - const structureType = - 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 : ""; - 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 aggressiveSellRatio = parseNumber( - features.nbbo_aggressive_sell_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; - const nbboMissing = parseNumber(packet.join_quality.nbbo_missing, 0) > 0; - - return ( -
-
-
{contract}
-
- {formatFlowMetric(count)} prints - {formatFlowMetric(totalSize)} size - Notional ${formatUsd(notional)} - {windowMs > 0 ? ( - {formatFlowMetric(windowMs, "ms")} - ) : null} - {structureType ? ( - - {structureType.replace(/_/g, " ")} - {structureRights ? ` ${structureRights}` : ""} - {structureLegs > 0 ? ` ${structureLegs}L` : ""} - {structureStrikes > 0 ? ` ${structureStrikes}K` : ""} - - ) : null} - {Number.isFinite(aggressiveCoverage) && aggressiveCoverage > 0 ? ( - - Agg {formatPct(aggressiveBuyRatio)} / {formatPct(aggressiveSellRatio)} - {Number.isFinite(insideRatio) && insideRatio > 0 - ? ` · In ${formatPct(insideRatio)}` - : ""} - {` · ${formatPct(aggressiveCoverage)} cov`} - - ) : null} - {Number.isFinite(nbboBid) && Number.isFinite(nbboAsk) ? ( - - NBBO ${formatPrice(nbboBid)} x ${formatPrice(nbboAsk)} - - ) : null} - {Number.isFinite(nbboMid) ? ( - Mid ${formatPrice(nbboMid)} - ) : null} - {Number.isFinite(nbboSpread) ? ( - Spread ${formatPrice(nbboSpread)} - ) : null} - {Number.isFinite(nbboAge) ? ( - {Math.round(nbboAge)}ms - ) : null} - {nbboStale ? NBBO stale : null} - {nbboMissing ? ( - NBBO missing - ) : null} -
-
-
- {formatTime(startTs)} → {formatTime(endTs)} -
-
- ); - }) - )} -
-
-
- -
-
-
-

Alerts

-

Rule-based scoring from flow packets.

-
-
-
- - -
- -
- -
- {filteredAlerts.length === 0 ? ( -
- {tickerSet.size > 0 - ? "No alerts match the current filter." - : mode === "live" - ? "No alerts yet. Start compute." - : "Replay queue empty. Ensure ClickHouse has data."} -
- ) : ( - filteredAlerts.map((alert) => { - const primary = alert.hits[0]; - const direction = primary ? normalizeDirection(primary.direction) : "neutral"; - - return ( - - ); - }) - )} -
-
-
- -
-
-
-

Classifier Hits

-

Raw rule hits before alert scoring.

-
-
-
- - -
- -
-
- {filteredClassifierHits.length === 0 ? ( -
- {tickerSet.size > 0 - ? "No classifier hits match the current filter." - : mode === "live" - ? "No classifier hits yet. Start compute." - : "Replay queue empty. Ensure ClickHouse has data."} -
- ) : ( - filteredClassifierHits.map((hit) => { - const direction = normalizeDirection(hit.direction); - return ( - - ); - }) - )} -
-
-
- -
-
-
-

Inferred Dark

-

Off-exchange patterns inferred from equity joins.

-
-
-
- - -
- -
-
- {filteredInferredDark.length === 0 ? ( -
- {tickerSet.size > 0 - ? "No inferred dark events match the current filter." - : mode === "live" - ? "No inferred dark events yet. Start compute." - : "Replay queue empty. Ensure ClickHouse has data."} -
- ) : ( - filteredInferredDark.map((event) => { - const underlying = inferDarkUnderlying(event, equityPrintMap, equityJoinMap); - const evidenceCount = event.evidence_refs.length; - return ( - - ); - }) - )} -
-
-
-
- - {selectedAlert ? ( - setSelectedAlert(null)} - /> - ) : null} - - {selectedClassifierHit ? ( - setSelectedClassifierHit(null)} - /> - ) : null} - - {selectedDarkEvent ? ( - setSelectedDarkEvent(null)} - /> - ) : null} -
- ); +export default function OverviewPage() { + return ; } diff --git a/apps/web/app/signals/page.tsx b/apps/web/app/signals/page.tsx new file mode 100644 index 0000000..480c57d --- /dev/null +++ b/apps/web/app/signals/page.tsx @@ -0,0 +1,5 @@ +import { TradingDesk } from "../_components/trading-desk"; + +export default function SignalsPage() { + return ; +} diff --git a/apps/web/tsconfig.tsbuildinfo b/apps/web/tsconfig.tsbuildinfo new file mode 100644 index 0000000..7149726 --- /dev/null +++ b/apps/web/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"fileNames":["../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.legacy.d.ts","./next-env.d.ts","./app/layout.tsx","./app/_components/trading-desk.tsx","./app/page.tsx","./app/off-exchange/page.tsx","./app/options-flow/page.tsx","./app/signals/page.tsx","./.next/types/app/layout.ts","./.next/types/app/page.ts"],"fileIdsList":[[61],[63],[62]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2e80ee7a49e8ac312cc11b77f1475804bee36b3b2bc896bead8b6e1266befb43","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},"9dd9d642cdb87d4d5b3173217e0c45429b3e47a6f5cf5fb0ead6c644ec5fed01","94801723a3fbefaf6b976d9fc39f4e31674dbfc8b3258c78423722c048a8be5a","c93c2a8ac5c7c164768424295548f60c09930401a27d3d66876eca539d6fb9ca","aa4bf0bb404d7625e85816fdeb95ee7208481adc990fc284c66de08206eba7bd","2b646066d7d5427d4ddab5d6953df8dd8dc464543d62a1dba195972ec34539bf","e5e39e9b70eeae720ff0c18f29a425369239893787e9f761153bc2fb71e5af0b","dcb764620d65c164574ab52821d1516a4e6e8b8921150bb37f38539846af1f6f","a3feb46d8cf723a1cb66b72338d4c1ee6a775cc9803ba2c55151dc79eccf3d8f","b5188d9740bcb85eac8ae23caef5e367bc8872f5317913a3b9e39fd81fa41ab9"],"root":[[60,68]],"options":{"allowJs":true,"esModuleInterop":true,"jsx":1,"module":99,"skipLibCheck":true,"strict":true,"target":9},"referencedMap":[[67,1],[68,2],[64,3],[65,3],[63,3],[66,3]],"semanticDiagnosticsPerFile":[[61,[{"start":55,"length":7,"messageText":"Cannot find module 'react' or its corresponding type declarations.","category":1,"code":2307},{"start":316,"length":16,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":339,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":355,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":367,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026}]],[62,[{"start":32,"length":11,"messageText":"Cannot find module 'next/link' or its corresponding type declarations.","category":1,"code":2307},{"start":197,"length":7,"messageText":"Cannot find module 'react' or its corresponding type declarations.","category":1,"code":2307},{"start":376,"length":19,"messageText":"Cannot find module '@islandflow/types' or its corresponding type declarations.","category":1,"code":2307},{"start":479,"length":20,"messageText":"Cannot find module 'lightweight-charts' or its corresponding type declarations.","category":1,"code":2307},{"start":556,"length":7,"messageText":"Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.","category":1,"code":2580},{"start":6250,"length":7,"messageText":"Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.","category":1,"code":2580},{"start":6831,"length":7,"messageText":"Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.","category":1,"code":2580},{"start":12457,"length":5,"messageText":"Cannot find namespace 'React'.","category":1,"code":2503},{"start":12524,"length":5,"messageText":"Cannot find namespace 'React'.","category":1,"code":2503},{"start":13271,"length":4,"messageText":"Parameter 'prev' implicitly has an 'any' type.","category":1,"code":7006},{"start":13962,"length":4,"messageText":"Parameter 'prev' implicitly has an 'any' type.","category":1,"code":7006},{"start":14389,"length":5,"messageText":"Cannot find namespace 'React'.","category":1,"code":2503},{"start":14436,"length":5,"messageText":"Cannot find namespace 'React'.","category":1,"code":2503},{"start":18547,"length":4,"messageText":"Parameter 'prev' implicitly has an 'any' type.","category":1,"code":7006},{"start":18720,"length":4,"messageText":"Parameter 'prev' implicitly has an 'any' type.","category":1,"code":7006},{"start":21173,"length":4,"messageText":"Parameter 'prev' implicitly has an 'any' type.","category":1,"code":7006},{"start":29174,"length":4,"messageText":"Parameter 'prev' implicitly has an 'any' type.","category":1,"code":7006},{"start":29381,"length":4,"messageText":"Parameter 'prev' implicitly has an 'any' type.","category":1,"code":7006},{"start":30452,"length":4,"messageText":"Parameter 'prev' implicitly has an 'any' type.","category":1,"code":7006},{"start":31743,"length":4,"messageText":"Parameter 'prev' implicitly has an 'any' type.","category":1,"code":7006},{"start":33002,"length":95,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":33104,"length":31,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":33142,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":33155,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":33193,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":33253,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":33281,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":33325,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":33383,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":33437,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":33498,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":33601,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":33631,"length":71,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":33747,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":33761,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":33999,"length":73,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":34079,"length":82,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":34188,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":34204,"length":31,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":34267,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":34279,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":57711,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":57747,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":57784,"length":55,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":57850,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":57891,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":57909,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":57925,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":57940,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":58065,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":58081,"length":186,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":58241,"length":4,"messageText":"Parameter 'prev' implicitly has an 'any' type.","category":1,"code":7006},{"start":58325,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":58343,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":58410,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":58424,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":58437,"length":52,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":58515,"length":35,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":58570,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":58608,"length":35,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":58655,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":58682,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":60421,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":60458,"length":37,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":60504,"length":36,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":60551,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":60576,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":60594,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":60622,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":60638,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":60653,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":60697,"length":78,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":60861,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":60878,"length":82,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":61049,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":61066,"length":76,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":61225,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":61240,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":61253,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":61266,"length":37,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":61312,"length":36,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":61359,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":61385,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":61403,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":61432,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":61448,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":61463,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":61507,"length":85,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":61686,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":61703,"length":85,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":61882,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":61899,"length":85,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":62078,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":62093,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":62106,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":62117,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":62924,"length":26,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":62957,"length":31,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":62997,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63013,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63056,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63071,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63140,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63156,"length":31,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63220,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63233,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63248,"length":65,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63338,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63354,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63368,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63406,"length":52,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63474,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63490,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63551,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63578,"length":48,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63637,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63659,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63673,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63714,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63733,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63786,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63842,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63871,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":63930,"length":3,"messageText":"Parameter 'hit' implicitly has an 'any' type.","category":1,"code":7006},{"start":63935,"length":5,"messageText":"Parameter 'index' implicitly has an 'any' type.","category":1,"code":7006},{"start":63961,"length":84,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64062,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64137,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64160,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64212,"length":72,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64359,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64385,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64436,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64460,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64528,"length":27,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64576,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64621,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64654,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64678,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64692,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64733,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64748,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64788,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64829,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64973,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":64992,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":65040,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":65138,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":65160,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":65237,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":65259,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":65537,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":65557,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":65576,"length":27,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":65905,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":65920,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":65951,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66021,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66043,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66057,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66098,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66117,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66174,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66243,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66272,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66372,"length":42,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66431,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66496,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66519,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66571,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66609,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66635,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66671,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66697,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66724,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66748,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66771,"length":27,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66825,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66844,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66877,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":66935,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":67008,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":67037,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":67048,"length":8,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":67543,"length":26,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":67576,"length":31,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":67616,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":67632,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":67676,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":67691,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":67736,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":67752,"length":31,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":67814,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":67827,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":67842,"length":65,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":67932,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":67948,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":67962,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68000,"length":48,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68059,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68075,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68150,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68164,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68178,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68219,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68234,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68293,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68366,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68395,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68472,"length":4,"messageText":"Parameter 'text' implicitly has an 'any' type.","category":1,"code":7006},{"start":68478,"length":3,"messageText":"Parameter 'idx' implicitly has an 'any' type.","category":1,"code":7006},{"start":68502,"length":73,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68592,"length":27,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68625,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68644,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68677,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68746,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68833,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68862,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68876,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68917,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68932,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":68972,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":69013,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":69157,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":69176,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":69224,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":69354,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":69376,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":69453,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":69475,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":69753,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":69773,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":69792,"length":27,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70121,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70136,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70167,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70237,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70259,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70273,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70314,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70333,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70390,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70464,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70493,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70593,"length":42,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70652,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70717,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70740,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70792,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70830,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70856,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70892,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70918,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70945,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70969,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":70992,"length":27,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":71046,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":71065,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":71098,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":71156,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":71229,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":71258,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":71269,"length":8,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":71884,"length":26,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":71917,"length":31,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":71957,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":71973,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72016,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72031,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72069,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72085,"length":31,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72149,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72162,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72177,"length":65,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72267,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72283,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72297,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72335,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72412,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72442,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72484,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72508,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72575,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72589,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72603,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72644,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72658,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72672,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72711,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72756,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72773,"length":27,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72816,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72829,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72882,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72942,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":72971,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":73029,"length":3,"messageText":"Parameter 'ref' implicitly has an 'any' type.","category":1,"code":7006},{"start":73053,"length":38,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":73108,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":73154,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":73177,"length":27,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":73209,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":73228,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":73261,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":73304,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":73364,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":73383,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":73397,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":73438,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":73456,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":73511,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":73578,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":73607,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":74661,"length":42,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":74722,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":74772,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":74797,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":74877,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":74904,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":74965,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":74990,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":75026,"length":23,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":75060,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":75127,"length":23,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":75156,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":75212,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":75249,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":75329,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":75359,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":75409,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":75454,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":75506,"length":36,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":75555,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":75589,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":75614,"length":27,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":75661,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":75754,"length":27,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":76040,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":76089,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":76139,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":76197,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":76268,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":76297,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":76308,"length":8,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":79291,"length":66,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":79364,"length":31,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":79402,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":79416,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":79456,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":79472,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":79508,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":79520,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":79799,"length":52,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":79858,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":79897,"length":35,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":79943,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":79982,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":80000,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":80035,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":80051,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":80091,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":80104,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":80130,"length":31,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":80170,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":80191,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":80219,"length":10,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":87084,"length":5,"messageText":"Parameter 'value' implicitly has an 'any' type.","category":1,"code":7006},{"start":88832,"length":2,"messageText":"Parameter 'id' implicitly has an 'any' type.","category":1,"code":7006},{"start":89598,"length":2,"messageText":"Parameter 'id' implicitly has an 'any' type.","category":1,"code":7006},{"start":91834,"length":2,"messageText":"Parameter 'id' implicitly has an 'any' type.","category":1,"code":7006},{"start":101905,"length":5,"messageText":"Parameter 'alert' implicitly has an 'any' type.","category":1,"code":7006},{"start":102096,"length":3,"messageText":"Parameter 'hit' implicitly has an 'any' type.","category":1,"code":7006},{"start":102271,"length":6,"messageText":"Parameter 'packet' implicitly has an 'any' type.","category":1,"code":7006},{"start":102450,"length":5,"messageText":"Parameter 'event' implicitly has an 'any' type.","category":1,"code":7006},{"start":102633,"length":5,"messageText":"Parameter 'print' implicitly has an 'any' type.","category":1,"code":7006},{"start":102804,"length":5,"messageText":"Parameter 'print' implicitly has an 'any' type.","category":1,"code":7006},{"start":102976,"length":4,"messageText":"Parameter 'join' implicitly has an 'any' type.","category":1,"code":7006},{"start":103164,"length":3,"messageText":"Parameter 'acc' implicitly has an 'any' type.","category":1,"code":7006},{"start":103169,"length":5,"messageText":"Parameter 'alert' implicitly has an 'any' type.","category":1,"code":7006},{"start":103565,"length":3,"messageText":"Parameter 'sum' implicitly has an 'any' type.","category":1,"code":7006},{"start":103570,"length":5,"messageText":"Parameter 'print' implicitly has an 'any' type.","category":1,"code":7006},{"start":103724,"length":5,"messageText":"Parameter 'print' implicitly has an 'any' type.","category":1,"code":7006},{"start":103864,"length":3,"messageText":"Parameter 'sum' implicitly has an 'any' type.","category":1,"code":7006},{"start":103869,"length":5,"messageText":"Parameter 'print' implicitly has an 'any' type.","category":1,"code":7006},{"start":104645,"length":3,"messageText":"Parameter 'acc' implicitly has an 'any' type.","category":1,"code":7006},{"start":104650,"length":4,"messageText":"Parameter 'join' implicitly has an 'any' type.","category":1,"code":7006},{"start":105232,"length":5,"messageText":"Parameter 'alert' implicitly has an 'any' type.","category":1,"code":7006},{"start":105581,"length":6,"messageText":"Parameter 'ticker' implicitly has an 'any' type.","category":1,"code":7006},{"start":105610,"length":5,"messageText":"Parameter 'entry' implicitly has an 'any' type.","category":1,"code":7006},{"start":105663,"length":5,"messageText":"Parameter 'entry' implicitly has an 'any' type.","category":1,"code":7006},{"start":111519,"length":4,"messageText":"Parameter 'prev' implicitly has an 'any' type.","category":1,"code":7006},{"start":112002,"length":23,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":112122,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":112251,"length":9,"code":2741,"category":1,"messageText":"Property 'children' is missing in type '{ className: string; label: string; title: string; subtitle: string; actions: any; }' but required in type 'DeskPanelProps'.","relatedInformation":[{"start":79661,"length":8,"messageText":"'children' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ className: string; label: string; title: string; subtitle: string; actions: any; }' is not assignable to type 'DeskPanelProps'."}},{"start":112538,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":112636,"length":248,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":112932,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":112968,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":113001,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":113041,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":113086,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":113320,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":113340,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":113501,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":113519,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":113926,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":114134,"length":9,"code":2741,"category":1,"messageText":"Property 'children' is missing in type '{ className: string; label: string; title: string; subtitle: string; }' but required in type 'DeskPanelProps'.","relatedInformation":[{"start":79661,"length":8,"messageText":"'children' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ className: string; label: string; title: string; subtitle: string; }' is not assignable to type 'DeskPanelProps'."}},{"start":114359,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":114399,"length":31,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":114996,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":115013,"length":50,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":115372,"length":5,"messageText":"Parameter 'print' implicitly has an 'any' type.","category":1,"code":7006},{"start":115959,"length":60,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116042,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116072,"length":26,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116131,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116162,"length":22,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116211,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116244,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116278,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116309,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116343,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116365,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116399,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116442,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116504,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116539,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116579,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116647,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116708,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116743,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116779,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116814,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116850,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116888,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116924,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":116958,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117038,"length":71,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117142,"length":64,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117216,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117256,"length":46,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117337,"length":35,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117409,"length":38,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117448,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117492,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117501,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117543,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117585,"length":35,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117657,"length":39,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117698,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117742,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117757,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117799,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117841,"length":35,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117913,"length":38,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117952,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":117996,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118005,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118047,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118089,"length":35,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118161,"length":39,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118202,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118246,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118261,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118303,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118343,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118381,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118468,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118507,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118549,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118612,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118673,"length":36,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118721,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118755,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118811,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118840,"length":22,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118884,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118911,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118969,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":118984,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":119195,"length":9,"code":2741,"category":1,"messageText":"Property 'children' is missing in type '{ className: string; label: string; title: string; subtitle: string; }' but required in type 'DeskPanelProps'.","relatedInformation":[{"start":79661,"length":8,"messageText":"'children' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ className: string; label: string; title: string; subtitle: string; }' is not assignable to type 'DeskPanelProps'."}},{"start":119408,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":119448,"length":31,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":120055,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":120072,"length":51,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":120433,"length":5,"messageText":"Parameter 'print' implicitly has an 'any' type.","category":1,"code":7006},{"start":120463,"length":60,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":120544,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":120572,"length":26,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":120619,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":120648,"length":22,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":120695,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":120728,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":120760,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":120791,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":120823,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":120845,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":120930,"length":23,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":120959,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":121023,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":121060,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":121117,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":121144,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":121171,"length":22,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":121215,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":121240,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":121277,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":121292,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":121491,"length":9,"code":2741,"category":1,"messageText":"Property 'children' is missing in type '{ className: string; label: string; title: string; subtitle: string; }' but required in type 'DeskPanelProps'.","relatedInformation":[{"start":79661,"length":8,"messageText":"'children' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ className: string; label: string; title: string; subtitle: string; }' is not assignable to type 'DeskPanelProps'."}},{"start":121701,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":121741,"length":31,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":122254,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":122271,"length":47,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":122618,"length":6,"messageText":"Parameter 'packet' implicitly has an 'any' type.","category":1,"code":7006},{"start":124899,"length":37,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":124959,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":124989,"length":26,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":125025,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":125056,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":125115,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":125153,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":125187,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":125227,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":125261,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":125304,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":125354,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":125394,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":125483,"length":37,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":125853,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":126018,"length":37,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":126380,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":126536,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":126656,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":126754,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":126787,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":126860,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":126899,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":126969,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":126998,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":127053,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":127097,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":127154,"length":36,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":127202,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":127242,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":127271,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":127300,"length":22,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":127413,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":127440,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":127498,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":127513,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":127736,"length":9,"code":2741,"category":1,"messageText":"Property 'children' is missing in type '{ className: string; label: string; title: string; subtitle: string; }' but required in type 'DeskPanelProps'.","relatedInformation":[{"start":79661,"length":8,"messageText":"'children' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ className: string; label: string; title: string; subtitle: string; }' is not assignable to type 'DeskPanelProps'."}},{"start":127970,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":128010,"length":31,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":128543,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":128670,"length":49,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":129007,"length":5,"messageText":"Parameter 'alert' implicitly has an 'any' type.","category":1,"code":7006},{"start":129213,"length":396,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":129632,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":129662,"length":26,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":129805,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":129836,"length":22,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":129885,"length":52,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":129953,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":129987,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":130024,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":130058,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":130088,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":130133,"length":48,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":130192,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":130232,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":130293,"length":22,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":130340,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":130377,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":130406,"length":22,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":130457,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":130484,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":130545,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":130560,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":130785,"length":9,"code":2741,"category":1,"messageText":"Property 'children' is missing in type '{ className: string; label: string; title: string; subtitle: string; }' but required in type 'DeskPanelProps'.","relatedInformation":[{"start":79661,"length":8,"messageText":"'children' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ className: string; label: string; title: string; subtitle: string; }' is not assignable to type 'DeskPanelProps'."}},{"start":131019,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":131059,"length":31,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":131714,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":131731,"length":53,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":132090,"length":3,"messageText":"Parameter 'hit' implicitly has an 'any' type.","category":1,"code":7006},{"start":132218,"length":236,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":132477,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":132507,"length":26,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":132574,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":132605,"length":22,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":132654,"length":48,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":132713,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":132747,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":132798,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":132830,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":132886,"length":22,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":132929,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":132966,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":132995,"length":22,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":133044,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":133071,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":133132,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":133147,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":133362,"length":9,"code":2741,"category":1,"messageText":"Property 'children' is missing in type '{ className: string; label: string; title: string; subtitle: string; }' but required in type 'DeskPanelProps'.","relatedInformation":[{"start":79661,"length":8,"messageText":"'children' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ className: string; label: string; title: string; subtitle: string; }' is not assignable to type 'DeskPanelProps'."}},{"start":133607,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":133647,"length":31,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":134216,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":134233,"length":47,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":134596,"length":5,"messageText":"Parameter 'event' implicitly has an 'any' type.","category":1,"code":7006},{"start":134751,"length":396,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135170,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135200,"length":26,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135260,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135291,"length":22,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135354,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135372,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135382,"length":23,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135412,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135447,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135500,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135534,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135577,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135609,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135661,"length":22,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135726,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135756,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135785,"length":22,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135836,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135863,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135924,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":135939,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":136051,"length":9,"code":2741,"category":1,"messageText":"Property 'children' is missing in type '{ className: string; label: string; title: string; subtitle: string; }' but required in type 'DeskPanelProps'.","relatedInformation":[{"start":79661,"length":8,"messageText":"'children' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ className: string; label: string; title: string; subtitle: string; }' is not assignable to type 'DeskPanelProps'."}},{"start":136283,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":136323,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":136383,"length":6,"messageText":"Parameter 'metric' implicitly has an 'any' type.","category":1,"code":7006},{"start":136482,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":136499,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":136545,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":136592,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":136643,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":136686,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":136727,"length":60,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":136811,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":136841,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":136897,"length":313,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":137229,"length":8,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":137308,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":137336,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":137414,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":137438,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":137484,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":137560,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":137596,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":137615,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":137662,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":137713,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":137760,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":137800,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":137834,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":137858,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":137896,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":137926,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":137981,"length":312,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":138312,"length":8,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":138361,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":138389,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":138596,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":138620,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":138680,"length":102,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":138801,"length":8,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":138858,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":138886,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":138958,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":138982,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":139028,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":139095,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":139131,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":139148,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":139163,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":139275,"length":9,"code":2741,"category":1,"messageText":"Property 'children' is missing in type '{ className: string; label: string; title: string; subtitle: string; }' but required in type 'DeskPanelProps'.","relatedInformation":[{"start":79661,"length":8,"messageText":"'children' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ className: string; label: string; title: string; subtitle: string; }' is not assignable to type 'DeskPanelProps'."}},{"start":139518,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":139598,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":139686,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":139737,"length":5,"messageText":"Parameter 'entry' implicitly has an 'any' type.","category":1,"code":7006},{"start":139763,"length":441,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":140221,"length":31,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":140266,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":140290,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":140416,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":140440,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":140503,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":140525,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":140571,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":140684,"length":9,"code":2741,"category":1,"messageText":"Property 'children' is missing in type '{ className: string; label: string; title: string; subtitle: string; }' but required in type 'DeskPanelProps'.","relatedInformation":[{"start":79661,"length":8,"messageText":"'children' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ className: string; label: string; title: string; subtitle: string; }' is not assignable to type 'DeskPanelProps'."}},{"start":140919,"length":29,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":141007,"length":50,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":141072,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":141122,"length":118,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":141257,"length":8,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":141281,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":141305,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":141326,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":141377,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":141436,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":141460,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":141555,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":141579,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":141628,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":141676,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":141721,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":141751,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":141770,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":141799,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":141942,"length":45,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":142240,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":142319,"length":45,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":142626,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":142710,"length":49,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":143009,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":143102,"length":46,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":143492,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":143541,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":143581,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":143623,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":143666,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":143708,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":143726,"length":28,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":143835,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":143848,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":143863,"length":55,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144213,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144265,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144287,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144339,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144389,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144404,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144445,"length":37,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144492,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144510,"length":8,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144571,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144591,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144668,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144686,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144789,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144805,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144818,"length":8,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144834,"length":35,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144878,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144921,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":144968,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145018,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145038,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145061,"length":5,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145079,"length":3,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145107,"length":4,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145122,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145139,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145182,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145227,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145263,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145285,"length":8,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145335,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145359,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145467,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145487,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145506,"length":30,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145551,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145588,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145610,"length":8,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145649,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145673,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145755,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145775,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145792,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145807,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145826,"length":35,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145872,"length":33,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145918,"length":32,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":145965,"length":31,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":146009,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":146031,"length":219,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":146142,"length":5,"messageText":"Parameter 'event' implicitly has an 'any' type.","category":1,"code":7006},{"start":146263,"length":8,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":146284,"length":187,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":146510,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":146532,"length":67,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":146674,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":146694,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":146711,"length":35,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":146759,"length":34,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":146913,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":146933,"length":36,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":147013,"length":5,"messageText":"Parameter 'entry' implicitly has an 'any' type.","category":1,"code":7006},{"start":147041,"length":261,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":147352,"length":9,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":147392,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":147409,"length":6,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":147424,"length":10,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":147464,"length":10,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026},{"start":148253,"length":7,"messageText":"JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.","category":1,"code":7026}]],[67,[{"start":166,"length":52,"messageText":"Cannot find module 'next/dist/lib/metadata/types/metadata-interface.js' or its corresponding type declarations.","category":1,"code":2307},{"start":2288,"length":5,"messageText":"Cannot find namespace 'React'.","category":1,"code":2503}]],[68,[{"start":162,"length":52,"messageText":"Cannot find module 'next/dist/lib/metadata/types/metadata-interface.js' or its corresponding type declarations.","category":1,"code":2307},{"start":2276,"length":5,"messageText":"Cannot find namespace 'React'.","category":1,"code":2503}]]],"affectedFilesPendingEmit":[67,68,62,61,64,65,63,66],"version":"5.9.3"} \ No newline at end of file