From a35a7576220d61e00805d4251266c9f4dc6ceb0b Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Thu, 28 May 2026 05:10:21 -0400 Subject: [PATCH] Redesign home command deck --- .beads/issues.jsonl | 1 + apps/web/app/globals.css | 488 +++++++++++++++- apps/web/app/terminal.tsx | 325 ++++++++++- ...2026-05-28-redesign-home-command-deck.html | 535 ++++++++++++++++++ 4 files changed, 1325 insertions(+), 24 deletions(-) create mode 100644 docs/turns/2026-05-28-redesign-home-command-deck.html diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 7758c70..9b15430 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -24,6 +24,7 @@ {"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"islandflow-ddm","title":"Redesign home as command deck","description":"Implement the mock1-inspired production command deck on / while preserving focused /options and /news workspaces plus existing legacy redirects. Scope includes apps/web terminal layout, production command-deck CSS, validation, turn documentation, and Forgejo publish.","notes":"Scope: redesign / as a mock1-inspired production command deck using live useTerminal state and existing panes; preserve /options, /news, /mock1, and current legacy redirects. Leave unrelated apps/web/next-env.d.ts and piolium/ changes untouched.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-28T08:59:14Z","created_by":"dirtydishes","updated_at":"2026-05-28T09:09:43Z","started_at":"2026-05-28T08:59:29Z","closed_at":"2026-05-28T09:09:43Z","close_reason":"Implemented / as a mock1-inspired production command deck using live terminal state, preserved focused /options and /news routes plus legacy redirects, validated tests/build/screenshots, and documented the turn.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-4xb","title":"Create dashboard structure mock routes","description":"Prototype four alternate islandflow dashboard structures at /mock1 through /mock4 based on the supplied reference so the main dashboard direction can be evaluated live.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-28T08:30:33Z","created_by":"dirtydishes","updated_at":"2026-05-28T08:38:35Z","started_at":"2026-05-28T08:30:39Z","closed_at":"2026-05-28T08:38:35Z","close_reason":"Added four dashboard mock routes, documented the implementation, and validated build/tests plus route responses.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-1gq","title":"Set up Forgejo-native CI baseline","description":"Create a Forgejo-native CI workflow under .forgejo/workflows that runs the existing fast, high-signal validation checks on pull requests, pushes to main, and manual dispatch. Document the runner label expectations, scope of the job, and manual rerun path in repository docs. Keep heavier container/integration work out of the initial PR gate.","status":"closed","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-24T00:31:55Z","created_by":"dirtydishes","updated_at":"2026-05-24T00:36:03Z","closed_at":"2026-05-24T00:36:03Z","close_reason":"Implemented a Forgejo-native CI baseline under .forgejo/workflows, documented runner expectations in the README, and synced the docker workspace snapshot so the fast validate path passes.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-7ez","title":"rename tape to options and replace web rail with drawer shell","description":"Implement the web and desktop route transition from /tape to /options, keep /tape as a compatibility redirect, replace the persistent web rail with a shared sticky header plus overlay drawer, and update validation/docs to match.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T23:30:06Z","created_by":"dirtydishes","updated_at":"2026-05-23T23:38:59Z","started_at":"2026-05-23T23:30:24Z","closed_at":"2026-05-23T23:38:59Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 76add94..a454a20 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -769,6 +769,395 @@ h3 { grid-column: 1 / -1; } +.command-deck-shell { + display: grid; + gap: 12px; +} + +.command-deck-header { + min-width: 0; + display: grid; + grid-template-columns: minmax(220px, 0.8fr) minmax(260px, 1fr) auto; + gap: 14px; + align-items: center; + padding: 13px 14px; + border: 1px solid var(--border); + border-radius: 12px; + background: linear-gradient(180deg, oklch(0.18 0.013 250 / 0.96), oklch(0.145 0.012 250 / 0.96)); +} + +.command-deck-brand { + min-width: 0; + display: flex; + align-items: center; + gap: 11px; +} + +.command-deck-mark { + width: 34px; + height: 34px; + flex: 0 0 auto; + border: 1px solid var(--border-strong); + border-radius: 8px; + background: + linear-gradient(135deg, oklch(0.78 0.12 74 / 0.7), oklch(0.28 0.035 250)), + var(--accent-soft); +} + +.command-deck-kicker, +.command-pane-meta, +.command-health-row, +.command-replay-strip, +.command-ticker-card { + font-family: var(--font-mono), monospace; +} + +.command-deck-kicker { + display: block; + color: var(--text-faint); + font-size: 0.68rem; + letter-spacing: 0.12em; + text-transform: lowercase; +} + +.command-deck-brand h2 { + margin: 1px 0 0; + font-family: var(--font-display), sans-serif; + font-size: 1.2rem; + line-height: 1.05; + letter-spacing: 0; +} + +.command-deck-brief { + min-width: 0; + display: flex; + align-items: center; + gap: 9px; + flex-wrap: wrap; + color: var(--text-dim); + font-size: 0.82rem; +} + +.command-deck-brief strong { + color: var(--text); + font-family: var(--font-mono), monospace; + font-size: 0.86rem; +} + +.command-deck-controls { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 8px; + flex-wrap: wrap; +} + +.command-chip { + min-height: 32px; + display: inline-flex; + align-items: center; + border: 1px solid var(--border); + border-radius: 999px; + padding: 5px 9px; + background: var(--bg-soft); + color: var(--text-dim); + font-family: var(--font-mono), monospace; + font-size: 0.68rem; + text-transform: uppercase; +} + +.command-chip-connected { + color: var(--green); + background: var(--green-soft); +} + +.command-chip-stale, +.command-chip-connecting { + color: var(--accent); + background: var(--accent-soft); +} + +.command-chip-disconnected { + color: var(--red); + background: var(--red-soft); +} + +.command-ticker-rail { + min-width: 0; + overflow: hidden; + border: 1px solid var(--border); + border-radius: 10px; + background: oklch(0.13 0.012 250 / 0.98); +} + +.command-ticker-track { + display: grid; + grid-auto-columns: minmax(176px, 1fr); + grid-auto-flow: column; + gap: 8px; + overflow-x: auto; + padding: 7px; +} + +.command-ticker-card { + min-width: 176px; + min-height: 64px; + display: grid; + grid-template-columns: 1fr auto; + gap: 4px 9px; + align-items: center; + border: 1px solid var(--border); + border-radius: 8px; + padding: 8px 10px; + background: oklch(0.17 0.013 250); + color: inherit; + text-align: left; + cursor: pointer; +} + +.command-ticker-card:hover, +.command-ticker-card:focus-visible { + border-color: var(--border-strong); + outline: none; +} + +.command-ticker-symbol { + color: var(--text); + font-weight: 700; +} + +.command-ticker-price, +.command-ticker-meta { + color: var(--text-dim); + font-size: 0.72rem; +} + +.command-ticker-move { + justify-self: end; + color: var(--text-faint); + font-size: 0.68rem; +} + +.command-ticker-card.is-up .command-ticker-move { + color: var(--green); +} + +.command-ticker-card.is-down .command-ticker-move { + color: var(--red); +} + +.command-ticker-meta { + grid-column: 1 / -1; +} + +.command-deck-grid { + display: grid; + grid-template-columns: minmax(360px, 1.12fr) minmax(420px, 1.38fr) minmax(300px, 0.9fr); + grid-template-areas: + "tape chart signals" + "feed dark context" + "replay replay replay"; + gap: 10px; + align-items: stretch; +} + +.command-deck-grid > .terminal-pane { + border-radius: 10px; +} + +.command-deck-grid > :nth-child(1) { + grid-area: tape; + min-height: 386px; +} + +.command-deck-grid > :nth-child(2) { + grid-area: chart; + min-height: 386px; +} + +.command-deck-grid > :nth-child(3) { + grid-area: signals; + min-height: 386px; +} + +.command-deck-grid > :nth-child(4) { + grid-area: feed; + min-height: 278px; +} + +.command-deck-grid > :nth-child(5) { + grid-area: dark; + min-height: 278px; +} + +.command-deck-grid > :nth-child(6) { + grid-area: context; + min-height: 278px; +} + +.command-deck-grid > :nth-child(7) { + grid-area: replay; + min-height: 116px; +} + +.command-deck-grid .terminal-pane-head { + min-height: 42px; + padding: 10px 12px; +} + +.command-deck-grid .terminal-pane-body { + padding: 10px 12px 12px; +} + +.command-deck-grid .terminal-pane-title { + font-family: var(--font-mono), monospace; + font-size: 0.72rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.command-deck-grid .chart-surface { + height: 300px; +} + +.command-pane-meta { + color: var(--text-faint); + font-size: 0.68rem; + text-transform: uppercase; +} + +.command-health-list, +.command-context-list, +.command-replay-strip { + display: grid; + gap: 8px; +} + +.command-health-row { + min-height: 34px; + display: grid; + grid-template-columns: minmax(96px, 1fr) 92px 76px 92px; + gap: 8px; + align-items: center; + border-bottom: 1px solid oklch(0.72 0.012 250 / 0.09); + color: var(--text-dim); + font-size: 0.72rem; +} + +.command-health-row:last-child { + border-bottom: 0; +} + +.command-health-status { + width: fit-content; + max-width: 100%; + display: inline-flex; + align-items: center; + min-height: 22px; + border: 1px solid var(--border); + border-radius: 999px; + padding: 3px 7px; + font-size: 0.64rem; +} + +.command-health-connected { + color: var(--green); + background: var(--green-soft); +} + +.command-health-stale, +.command-health-connecting { + color: var(--accent); + background: var(--accent-soft); +} + +.command-health-disconnected { + color: var(--red); + background: var(--red-soft); +} + +.command-context-row { + min-width: 0; + min-height: 42px; + display: grid; + grid-template-columns: 62px 52px minmax(0, 1fr); + gap: 4px 8px; + align-items: center; + border: 0; + border-bottom: 1px solid oklch(0.72 0.012 250 / 0.09); + padding: 6px 0; + background: transparent; + color: inherit; + text-align: left; + cursor: pointer; +} + +.command-context-row:last-child { + border-bottom: 0; +} + +.command-context-row time, +.command-context-row span { + color: var(--text-dim); + font-family: var(--font-mono), monospace; + font-size: 0.68rem; +} + +.command-context-row strong { + min-width: 0; + overflow: hidden; + color: var(--text); + font-size: 0.78rem; + text-overflow: ellipsis; + white-space: nowrap; +} + +.command-context-row > span:last-child { + grid-column: 3; + overflow: hidden; + color: var(--text-faint); + text-overflow: ellipsis; + white-space: nowrap; +} + +.command-context-kind { + width: fit-content; + border: 1px solid var(--border); + border-radius: 999px; + padding: 2px 6px; + background: var(--bg-soft); + text-transform: uppercase; +} + +.command-replay-strip { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + +.command-replay-strip div { + min-width: 0; + display: grid; + gap: 3px; + border: 1px solid var(--border); + border-radius: 8px; + padding: 8px 10px; + background: var(--bg-soft); +} + +.command-replay-strip span { + color: var(--text-faint); + font-size: 0.66rem; + text-transform: uppercase; +} + +.command-replay-strip strong { + min-width: 0; + overflow: hidden; + color: var(--text); + font-size: 0.78rem; + text-overflow: ellipsis; + white-space: nowrap; +} + .terminal-pane { min-width: 0; height: 100%; @@ -2027,6 +2416,25 @@ h3 { min-width: 136px; padding: 10px 12px; } + + .command-deck-header { + grid-template-columns: minmax(220px, 0.8fr) minmax(240px, 1fr); + } + + .command-deck-controls { + grid-column: 1 / -1; + justify-content: flex-start; + } + + .command-deck-grid { + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + grid-template-areas: + "signals chart" + "tape chart" + "feed context" + "dark dark" + "replay replay"; + } } @media (max-width: 980px) { @@ -2065,6 +2473,32 @@ h3 { min-height: 0; } + .command-deck-grid { + grid-template-columns: minmax(0, 1fr); + grid-template-areas: + "signals" + "chart" + "tape" + "context" + "replay" + "feed" + "dark"; + } + + .command-deck-grid > .terminal-pane { + min-height: 0; + } + + .command-deck-grid > :nth-child(1), + .command-deck-grid > :nth-child(2), + .command-deck-grid > :nth-child(3), + .command-deck-grid > :nth-child(4), + .command-deck-grid > :nth-child(5), + .command-deck-grid > :nth-child(6), + .command-deck-grid > :nth-child(7) { + min-height: 0; + } + .terminal-topbar { align-items: center; justify-content: space-between; @@ -2129,11 +2563,32 @@ h3 { .terminal-pane-head, .chart-controls, .card-controls, - .terminal-pane-actions { + .terminal-pane-actions, + .command-deck-header { flex-direction: column; align-items: flex-start; } + .command-deck-header { + display: flex; + } + + .command-deck-brief, + .command-deck-controls { + width: 100%; + justify-content: flex-start; + } + + .command-deck-controls .terminal-button, + .command-chip { + width: 100%; + justify-content: center; + } + + .command-ticker-track { + grid-auto-columns: minmax(164px, 78vw); + } + .terminal-pane-title-row { flex-direction: column; align-items: flex-start; @@ -2311,6 +2766,37 @@ h3 { height: 48px; } + .command-deck-grid .chart-surface { + height: 320px; + } + + .command-health-row { + grid-template-columns: minmax(94px, 1fr) 92px; + } + + .command-health-row span:nth-child(3), + .command-health-row span:nth-child(4) { + font-size: 0.68rem; + } + + .command-context-row { + grid-template-columns: 58px minmax(0, 1fr); + } + + .command-context-kind { + grid-column: 2; + grid-row: 1; + } + + .command-context-row strong, + .command-context-row > span:last-child { + grid-column: 2; + } + + .command-replay-strip { + grid-template-columns: minmax(0, 1fr); + } + .time { text-align: left; } diff --git a/apps/web/app/terminal.tsx b/apps/web/app/terminal.tsx index f014379..5375688 100644 --- a/apps/web/app/terminal.tsx +++ b/apps/web/app/terminal.tsx @@ -352,10 +352,10 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => { case "/": default: return { - options: false, + options: true, nbbo: false, equities: true, - flow: false, + flow: true, news: true, alerts: true, smartMoney: true, @@ -364,17 +364,17 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => { equityJoins: true, equityCandles: true, equityOverlay: true, - showOptionsPane: false, + showOptionsPane: true, showEquitiesPane: true, - showFlowPane: false, + showFlowPane: true, showNewsPane: true, showAlertsPane: true, showClassifierPane: false, - showDarkPane: false, + showDarkPane: true, showChartPane: true, showFocusPane: false, showReplayConsole: false, - needsClassifierDecor: false, + needsClassifierDecor: true, needsAlertEvidencePrefetch: true, needsDarkUnderlying: true }; @@ -4215,24 +4215,24 @@ const CandleChart = ({ width, height, layout: { - background: { color: "#fffdf7" }, - textColor: "#4e3e25" + background: { color: "#0d141b" }, + textColor: "#90a0b2" }, grid: { - vertLines: { color: "rgba(82, 64, 36, 0.12)" }, - horzLines: { color: "rgba(82, 64, 36, 0.12)" } + vertLines: { color: "rgba(144, 160, 178, 0.12)" }, + horzLines: { color: "rgba(144, 160, 178, 0.12)" } }, crosshair: { - vertLine: { color: "rgba(47, 109, 79, 0.35)" }, - horzLine: { color: "rgba(47, 109, 79, 0.35)" } + vertLine: { color: "rgba(245, 166, 35, 0.32)" }, + horzLine: { color: "rgba(245, 166, 35, 0.32)" } }, timeScale: { - borderColor: "rgba(111, 91, 57, 0.35)", + borderColor: "rgba(144, 160, 178, 0.24)", timeVisible: true, secondsVisible: intervalMs < 60000 }, rightPriceScale: { - borderColor: "rgba(111, 91, 57, 0.35)" + borderColor: "rgba(144, 160, 178, 0.24)" } }); @@ -4250,11 +4250,11 @@ const CandleChart = ({ overlayCtxRef.current = overlayCanvas.getContext("2d"); const series = chart.addCandlestickSeries({ - upColor: "#2f6d4f", - downColor: "#c46f2a", + upColor: "#25c17a", + downColor: "#ff6b5f", borderVisible: false, - wickUpColor: "#2f6d4f", - wickDownColor: "#c46f2a" + wickUpColor: "#25c17a", + wickDownColor: "#ff6b5f" }); chartRef.current = chart; @@ -8397,6 +8397,278 @@ const ChartPane = memo(({ state, title = "Chart" }: ChartPaneProps) => { ); }); +type CommandDeckTicker = { + symbol: string; + price: number | null; + move: number | null; + options: number; + alerts: number; +}; + +const buildCommandDeckTickers = (state: TerminalState): CommandDeckTicker[] => { + const symbols = new Set(); + for (const symbol of state.activeTickers) { + symbols.add(symbol); + } + for (const print of state.filteredEquities.slice(0, 80)) { + symbols.add(print.underlying_id.toUpperCase()); + } + for (const print of state.filteredOptions.slice(0, 80)) { + const parsed = parseOptionContractId(normalizeContractId(print.option_contract_id)); + const symbol = (print.underlying_id ?? parsed?.root ?? extractUnderlying(print.option_contract_id))?.toUpperCase(); + if (symbol) { + symbols.add(symbol); + } + } + for (const event of state.filteredSmartMoneyEvents.slice(0, 30)) { + symbols.add(event.underlying_id.toUpperCase()); + } + for (const story of state.filteredNews.slice(0, 20)) { + for (const symbol of story.resolved_symbols) { + symbols.add(symbol.toUpperCase()); + } + } + if (symbols.size === 0) { + symbols.add(state.chartTicker.toUpperCase()); + } + + return Array.from(symbols).slice(0, 10).map((symbol) => { + const equityPrints = state.filteredEquities + .filter((print) => print.underlying_id.toUpperCase() === symbol) + .slice(0, 2); + const price = equityPrints[0]?.price ?? null; + const previous = equityPrints[1]?.price ?? null; + const move = price !== null && previous !== null && previous !== 0 ? (price - previous) / previous : null; + const options = state.filteredOptions + .slice(0, 120) + .filter((print) => { + const parsed = parseOptionContractId(normalizeContractId(print.option_contract_id)); + const underlying = (print.underlying_id ?? parsed?.root ?? extractUnderlying(print.option_contract_id))?.toUpperCase(); + return underlying === symbol; + }).length; + const alerts = state.filteredAlerts + .slice(0, 80) + .filter((alert) => alert.trace_id.toUpperCase().includes(symbol)).length; + return { symbol, price, move, options, alerts }; + }); +}; + +const CommandDeckHeader = ({ state }: { state: TerminalState }) => { + const focus = state.activeTickers.length > 0 ? state.activeTickers.join(", ") : state.chartTicker; + const selected = state.selectedInstrumentLabel ?? "No contract lock"; + const connectionLabel = state.mode === "live" ? statusLabel(state.liveSession.status, false, state.mode) : "Replay"; + + return ( +
+
+
+
+ Evidence console + {focus} + {selected} +
+
+ + {state.mode === "live" ? "Live" : "Replay"}: {connectionLabel} + + Last {state.lastSeen ? formatTime(state.lastSeen) : "waiting"} + +
+
+ ); +}; + +const TickerRail = ({ state }: { state: TerminalState }) => { + const tickers = useMemo(() => buildCommandDeckTickers(state), [state]); + + return ( +
+
+ {tickers.map((ticker) => { + const direction = ticker.move === null ? "flat" : ticker.move >= 0 ? "up" : "down"; + const equity = state.filteredEquities.find((print) => print.underlying_id.toUpperCase() === ticker.symbol); + return ( + + ); + })} +
+
+ ); +}; + +const FeedHealthPane = ({ state }: { state: TerminalState }) => { + const rows = [ + { label: "Options", tape: state.options, subscribed: state.routeFeatures.options }, + { label: "Equities", tape: state.equities, subscribed: state.routeFeatures.equities }, + { label: "Flow", tape: state.flow, subscribed: state.routeFeatures.flow }, + { label: "Alerts", tape: state.alerts, subscribed: state.routeFeatures.alerts }, + { label: "News", tape: state.news, subscribed: state.routeFeatures.news }, + { label: "Dark", tape: state.inferredDark, subscribed: state.routeFeatures.inferredDark } + ]; + + return ( + {state.liveSession.manifest.length} subscriptions} + > +
+ {rows.map(({ label, tape, subscribed }) => ( +
+ {label} + + {subscribed ? statusLabel(tape.status, tape.paused, state.mode) : "Idle"} + + {tape.lastUpdate ? formatTime(tape.lastUpdate) : "No update"} + {tape.dropped > 0 ? `${tape.dropped} dropped` : "Queue clear"} +
+ ))} +
+
+ ); +}; + +const EventContextPane = ({ state }: { state: TerminalState }) => { + const events = [ + ...state.filteredAlerts.slice(0, 3).map((alert) => ({ + key: `alert-${alert.trace_id}-${alert.seq}`, + ts: alert.source_ts, + label: "Alert", + title: alert.hits[0] ? humanizeClassifierId(alert.hits[0].classifier_id) : "Classifier alert", + detail: alert.hits[0]?.explanations?.[0] ?? `${alert.hits.length} linked hits`, + action: () => state.setSelectedAlert(alert) + })), + ...state.filteredSmartMoneyEvents.slice(0, 3).map((event) => ({ + key: `smart-${event.event_id}-${event.seq}`, + ts: event.source_ts, + label: "Smart", + title: smartMoneyProfileLabel(event.primary_profile_id), + detail: `${event.underlying_id} ${normalizeDirection(event.primary_direction)} / ${event.packet_ids.length} packets`, + action: () => state.openFromSmartMoneyEvent(event) + })), + ...state.filteredInferredDark.slice(0, 3).map((event) => ({ + key: `dark-${event.trace_id}-${event.seq}`, + ts: event.source_ts, + label: "Dark", + title: humanizeClassifierId(event.type), + detail: `${event.evidence_refs.length} evidence refs / confidence ${formatConfidence(event.confidence)}`, + action: () => state.setSelectedDarkEvent(event) + })), + ...state.filteredNews.slice(0, 2).map((story) => ({ + key: `news-${story.trace_id}-${story.seq}`, + ts: story.published_ts, + label: "News", + title: story.headline, + detail: story.resolved_symbols.length > 0 ? story.resolved_symbols.join(", ") : story.source, + action: () => state.setSelectedNewsStory(story) + })) + ].sort((a, b) => b.ts - a.ts).slice(0, 6); + + return ( + Focus evidence} + > + {events.length === 0 ? ( +
No linked evidence is available for this scope yet.
+ ) : ( +
+ {events.map((event) => ( + + ))} +
+ )} +
+ ); +}; + +const HomeReplayRail = ({ state }: { state: TerminalState }) => { + const replayTime = + state.options.replayTime ?? + state.equities.replayTime ?? + state.flow.replayTime ?? + state.alerts.replayTime ?? + state.inferredDark.replayTime; + const replayComplete = + state.options.replayComplete || + state.equities.replayComplete || + state.flow.replayComplete || + state.alerts.replayComplete || + state.inferredDark.replayComplete; + const activeSource = state.replaySource ? state.replaySource.toUpperCase() : state.mode === "live" ? "LIVE HEAD" : "AUTO"; + + return ( + + } + actions={ + + } + > +
+
+ Source + {activeSource} +
+
+ Cursor + {replayTime ? formatTime(replayTime) : state.lastSeen ? formatTime(state.lastSeen) : "waiting"} +
+
+ Chart + {state.chartTicker} / {formatIntervalLabel(state.chartIntervalMs)} +
+
+ Scope + {state.activeTickers.length > 0 ? state.activeTickers.join(", ") : "All symbols"} +
+
+
+ ); +}; + const FocusPane = memo(({ state }: { state: TerminalState }) => { const hits = state.chartSmartMoneyEvents.slice(-10).reverse(); const dark = state.chartInferredDark.slice(-10).reverse(); @@ -9040,11 +9312,18 @@ export function OverviewRoute() { const state = useTerminal(); return ( -
- - - - +
+ + +
+ + + + + + + +
); diff --git a/docs/turns/2026-05-28-redesign-home-command-deck.html b/docs/turns/2026-05-28-redesign-home-command-deck.html new file mode 100644 index 0000000..a25f128 --- /dev/null +++ b/docs/turns/2026-05-28-redesign-home-command-deck.html @@ -0,0 +1,535 @@ + + + + + + Redesign Home Command Deck + + + +
+
+
Implementation Turn Document
+

Redesign Home Around the Command Deck

+

+ The home route now uses a production command-deck layout inspired by /mock1, backed by + useTerminal() state and existing live panes instead of static mock rows. +

+
+ Created 2026-05-28 05:06 EDT + Beads issue islandflow-ddm + Tests passed + Build passed +
+
+ +
+

Summary

+

+ Reworked / into the main Islandflow command deck with a compact command header, real ticker rail, + options tape, price and flow chart, alerts, feed health, inferred dark activity, event context, and replay or + mode rail. Focused /options and /news routes remain structurally intact. +

+
+ +
+

Changes Made

+
    +
  • Expanded the home route feature map so the command deck subscribes to options, equities, flow, news, alerts, smart-money, inferred-dark, equity-join, candle, and overlay data.
  • +
  • Added home-only components in apps/web/app/terminal.tsx: CommandDeckHeader, TickerRail, FeedHealthPane, EventContextPane, and HomeReplayRail.
  • +
  • Replaced the previous home grid with a mock1-inspired production layout that reuses OptionsPane, ChartPane, AlertsPane, and DarkPane.
  • +
  • Added .command-deck-* CSS classes in apps/web/app/globals.css and left existing .mock-* classes available for reference mock routes.
  • +
  • Changed the chart canvas palette from the previous light canvas to the terminal dark surface so empty and error states no longer flash a bright panel inside the deck.
  • +
+
+ +
+

Context

+

+ /mock1 was the visual reference: dense operational layout, ticker rail, compact pane headers, and + evidence-first sequencing. The implementation keeps that structure but uses production state and pane behavior. + No backend API contracts, runtime dependencies, or @islandflow/types schemas were changed. +

+
+ +
+

Important Implementation Details

+
    +
  • The ticker rail derives symbols from active filters, equity prints, option prints, smart-money events, and news stories, then falls back to the chart ticker.
  • +
  • Home pane empty states remain explicit when infrastructure is absent, for example options still says to start ingest-options and the chart reports fetch or service state.
  • +
  • The mobile command-deck order prioritizes alerts, chart, options, context, replay or status, feed health, then dark activity.
  • +
  • Red and green states are still paired with text labels such as Connected, Disconnected, Up, and Down.
  • +
  • The in-app Browser backend was unavailable, so visual checks used Playwright Chromium screenshots against a local Next dev server on port 3001.
  • +
+
+ +
+

Relevant Diff Snippets

+

+ The snippets below were rendered with @pierre/diffs/ssr using preloadPatchDiff. They focus on the route feature expansion, production command-deck composition, dark chart palette, and responsive command-deck CSS. +

+
apps/web/app/terminal.tsx
-6+6
337 unmodified lines
338
339
340
341
342
343
344
345
346
347
353
354
355
356
357
358
359
360
361
362
363
364
365
366
337 unmodified lines
case "/":
default:
return {
options: false,
nbbo: false,
equities: true,
flow: false,
news: true,
alerts: true,
smartMoney: true,
showOptionsPane: false,
showEquitiesPane: true,
showFlowPane: false,
showNewsPane: true,
showAlertsPane: true,
showClassifierPane: false,
showDarkPane: false,
showChartPane: true,
showFocusPane: false,
showReplayConsole: false,
needsClassifierDecor: false,
needsAlertEvidencePrefetch: true,
needsDarkUnderlying: true
};
337 unmodified lines
338
339
340
341
342
343
344
345
346
347
353
354
355
356
357
358
359
360
361
362
363
364
365
366
337 unmodified lines
case "/":
default:
return {
options: true,
nbbo: false,
equities: true,
flow: true,
news: true,
alerts: true,
smartMoney: true,
showOptionsPane: true,
showEquitiesPane: true,
showFlowPane: true,
showNewsPane: true,
showAlertsPane: true,
showClassifierPane: false,
showDarkPane: true,
showChartPane: true,
showFocusPane: false,
showReplayConsole: false,
needsClassifierDecor: true,
needsAlertEvidencePrefetch: true,
needsDarkUnderlying: true
};
+
apps/web/app/terminal.tsx
-5+12
9358 unmodified lines
9039
9040
9041
9042
9043
9044
9045
9046
9047
9048
9049
9358 unmodified lines
const state = useTerminal();
return (
<PageFrame title="Home">
<div className="page-grid page-grid-home">
<ChartPane state={state} />
<EquitiesPane state={state} />
<NewsPane state={state} limit={6} />
<AlertsPane state={state} withStrip />
</div>
</PageFrame>
);
9358 unmodified lines
9359
9360
9361
9362
9363
9364
9365
9366
9367
9368
9369
9370
9371
9372
9373
9374
9375
9376
9358 unmodified lines
const state = useTerminal();
return (
<PageFrame title="Home">
<div className="command-deck-shell">
<CommandDeckHeader state={state} />
<TickerRail state={state} />
<div className="command-deck-grid">
<OptionsPane state={state} limit={14} />
<ChartPane state={state} title="Price / Flow" />
<AlertsPane state={state} limit={8} withStrip className="command-signals-pane" />
<FeedHealthPane state={state} />
<DarkPane state={state} limit={8} className="command-dark-pane" />
<EventContextPane state={state} />
<HomeReplayRail state={state} />
</div>
</div>
</PageFrame>
);
+
apps/web/app/terminal.tsx
-10+10
4077 unmodified lines
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
19 unmodified lines
4122
4123
4124
4125
4126
4127
4128
4077 unmodified lines
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)" }
},
19 unmodified lines
const series = chart.addCandlestickSeries({
upColor: "#2f6d4f",
downColor: "#c46f2a",
borderVisible: false,
wickUpColor: "#2f6d4f",
wickDownColor: "#c46f2a"
});
4077 unmodified lines
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
19 unmodified lines
4122
4123
4124
4125
4126
4127
4128
4077 unmodified lines
height,
layout: {
background: { color: "#0d141b" },
textColor: "#90a0b2"
},
grid: {
vertLines: { color: "rgba(144, 160, 178, 0.12)" },
horzLines: { color: "rgba(144, 160, 178, 0.12)" }
},
crosshair: {
vertLine: { color: "rgba(245, 166, 35, 0.32)" },
horzLine: { color: "rgba(245, 166, 35, 0.32)" }
},
19 unmodified lines
const series = chart.addCandlestickSeries({
upColor: "#25c17a",
downColor: "#ff6b5f",
borderVisible: false,
wickUpColor: "#25c17a",
wickDownColor: "#ff6b5f"
});
+
apps/web/app/globals.css
+40
768 unmodified lines
769
770
771
772
773
774
1290 unmodified lines
2065
2066
2067
2068
2069
2070
768 unmodified lines
grid-column: 1 / -1;
}
+
.terminal-pane {
min-width: 0;
height: 100%;
1290 unmodified lines
min-height: 0;
}
+
.terminal-topbar {
align-items: center;
justify-content: space-between;
768 unmodified lines
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
1290 unmodified lines
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
768 unmodified lines
grid-column: 1 / -1;
}
+
.command-deck-shell {
display: grid;
gap: 12px;
}
+
.command-deck-header {
min-width: 0;
display: grid;
grid-template-columns: minmax(220px, 0.8fr) minmax(260px, 1fr) auto;
gap: 14px;
align-items: center;
padding: 13px 14px;
border: 1px solid var(--border);
border-radius: 12px;
background: linear-gradient(180deg, oklch(0.18 0.013 250 / 0.96), oklch(0.145 0.012 250 / 0.96));
}
+
.command-deck-grid {
display: grid;
grid-template-columns: minmax(360px, 1.12fr) minmax(420px, 1.38fr) minmax(300px, 0.9fr);
grid-template-areas:
"tape chart signals"
"feed dark context"
"replay replay replay";
gap: 10px;
align-items: stretch;
}
+
.terminal-pane {
min-width: 0;
height: 100%;
1290 unmodified lines
min-height: 0;
}
+
.command-deck-grid {
grid-template-columns: minmax(0, 1fr);
grid-template-areas:
"signals"
"chart"
"tape"
"context"
"replay"
"feed"
"dark";
}
+
.terminal-topbar {
align-items: center;
justify-content: space-between;
+
+ +
+

Expected Impact for End-Users

+

+ Users landing on Islandflow now see the operational cockpit first: live symbol focus, signal context, options + flow, chart state, feed health, inferred dark activity, and replay or mode context are visible without jumping + between focused workspaces. The specialized options and news workflows are still available for deeper work. +

+
+ +
+

Validation

+
    +
  • Passed: bun test, 250 tests, 0 failures.
  • +
  • Passed: bun --cwd=apps/web run build.
  • +
  • Checked: Playwright Chromium screenshots for / desktop and mobile, /options, /news, and /mock1.
  • +
  • Checked: /signals redirects to / and /tape redirects to /options with local HTTP checks; route tests cover /charts and /replay redirects too.
  • +
  • Note: Visual checks were performed without backend market services running, so empty and error states were validated rather than live populated rows.
  • +
+
+ +
+

Issues, Limitations, and Mitigations

+
    +
  • The chart can show a fetch error when the candle API is unavailable; the pane remains framed and visibly explains the state.
  • +
  • The feed health pane reflects frontend tape state and subscription status, not a deep backend diagnostics endpoint.
  • +
  • The existing local port 3000 server returned 500 during verification, so a separate Next dev server was run on port 3001 and stopped afterward.
  • +
  • The in-app Browser plugin listed no available browser instances; Playwright Chromium was installed into the user tool cache for fallback screenshots.
  • +
+
+ +
+

Follow-up Work

+
    +
  • Consider adding a dedicated pure helper test for command-deck ticker derivation if the rail grows more behavior.
  • +
  • Wire feed health to richer backend diagnostics if operators need per-provider latency and throughput in production.
  • +
  • Expose more explicit chart service status if candle fetch failures should distinguish service down, empty data, and network errors.
  • +
+
+
+ +