diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 7a0fe2d..365ddaa 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -23,8 +23,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-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} -{"_type":"issue","id":"islandflow-hoh","title":"clarify turn-doc exemptions and ambiguity rule","description":"Update AGENTS.md turn documentation rules so minor/trivial checklist takes precedence, ambiguous cases require user check-in, and completion rule applies only when turn docs are required.","status":"closed","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-23T23:02:10Z","created_by":"dirtydishes","updated_at":"2026-05-23T23:02:30Z","closed_at":"2026-05-23T23:02:30Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"islandflow-hoh","title":"clarify turn-doc exemptions and ambiguity rule","description":"Update AGENTS.md turn documentation rules so minor/trivial checklist takes precedence, ambiguous cases require user check-in, and completion rule applies only when turn docs are required.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-23T23:02:10Z","created_by":"dirtydishes","updated_at":"2026-05-23T23:02:10Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-t8b","title":"Update GitHub Pages docs URL target","description":"Adjust the docs Pages publish workflow so the deployed landing behavior explicitly targets dirtydishes.github.io/islandflow/docs and keeps the docs payload path consistent.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T21:18:04Z","created_by":"dirtydishes","updated_at":"2026-05-23T21:18:59Z","started_at":"2026-05-23T21:18:06Z","closed_at":"2026-05-23T21:18:59Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-kgu","title":"Reconcile PR #8 branch with current main","description":"Why this issue exists and what needs to be done: user requested reconciliation for PR #8. Identify the PR #8 branch, merge/rebase with current main, resolve conflicts, validate, and push the updated branch so the PR can merge cleanly.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T20:14:36Z","created_by":"dirtydishes","updated_at":"2026-05-23T20:24:29Z","started_at":"2026-05-23T20:14:39Z","closed_at":"2026-05-23T20:24:29Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-l9h","title":"stop persisting non-signal option prints in clickhouse","description":"Why: non-signal option prints are storage noise and should not be persisted by default.\\n\\nWhat: add OPTIONS_PERSIST_SIGNAL_ONLY env flag (default true), gate option_print inserts in ingest-options, add tests for persistence behavior, update env examples, and document one-off cleanup SQL for existing non-signal rows.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T03:02:32Z","created_by":"dirtydishes","updated_at":"2026-05-23T03:06:34Z","started_at":"2026-05-23T03:02:35Z","closed_at":"2026-05-23T03:06:34Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} @@ -81,7 +80,6 @@ {"_type":"issue","id":"islandflow-zs0","title":"Migrate terminal UI to smart-money profiles","description":"Migrate apps/web terminal rendering to consume SmartMoneyEvent directly: primary profile, probability ladder, reason codes, and suppression/abstention state, while preserving legacy alert/classifier displays during the bridge.","status":"closed","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-04T21:35:23Z","created_by":"dirtydishes","updated_at":"2026-05-05T05:39:58Z","closed_at":"2026-05-05T05:39:58Z","close_reason":"Completed terminal smart-money profile migration","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-igk","title":"Add plan mode","description":"Implement a user-facing plan mode in the application so users can switch into planning before taking action. Scope to be clarified from existing app patterns.","status":"closed","priority":2,"issue_type":"feature","owner":"dishes@dpdrm.com","created_at":"2026-05-04T04:22:37Z","created_by":"dirtydishes","updated_at":"2026-05-04T04:26:18Z","started_at":"2026-05-04T04:22:40Z","closed_at":"2026-05-04T04:26:18Z","close_reason":"Implemented as a global pi extension toggled with Shift+P","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-biq","title":"Finish raw live options delivery and filter/backpressure observability","description":"The smart-money signal path and Tape filters are in place, but the next firehose pass should finish server-side selective raw live delivery for options subscriptions and add explicit filtered-out/backpressure observability for API/web counters. This was discovered while landing islandflow-e4r.\n","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:28:58Z","created_by":"dirtydishes","updated_at":"2026-04-29T03:54:12Z","started_at":"2026-04-29T03:54:12Z","dependencies":[{"issue_id":"islandflow-biq","depends_on_id":"islandflow-e4r","type":"discovered-from","created_at":"2026-04-28T16:28:58Z","created_by":"auto-import","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"islandflow-3by","title":"add interaction coverage for terminal navigation drawer","description":"Add browser- or DOM-level coverage for the shared terminal header drawer so open/close behavior, Escape dismissal, backdrop dismissal, and route-change dismissal are exercised beyond pure route helper tests.","status":"open","priority":3,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-23T23:35:57Z","created_by":"dirtydishes","updated_at":"2026-05-23T23:35:57Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-gm0","title":"Default turn-doc diffs to @pierre/diffs","description":"Why this issue exists and what needs to be done\\n\\nUpdate AGENTS.md turn-documentation guidance to prefer @pierre/diffs output with an explicit fallback path when unavailable, and include the related package manifest/lock updates in the same change set.","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T22:51:57Z","created_by":"dirtydishes","updated_at":"2026-05-23T22:52:23Z","started_at":"2026-05-23T22:52:00Z","closed_at":"2026-05-23T22:52:23Z","close_reason":"completed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-hpf","title":"add anatomy explainer for options print and smart money flow","description":"Create a standalone docs/anatomy.html reference page that explains the end-to-end lifecycle of an options print through enrichment, signal filtering, compute clustering, flow packet creation, smart-money evaluation, classifier hits, alerts, and API/live consumption. The page should be polished, user-readable, and visually strong enough to serve as a reusable reference artifact for both technical and non-technical readers.","notes":"Added docs/anatomy.html as a standalone reference page for the options-print to smart-money pipeline, styled in the repo product register and layered for executive, mixed technical, and operator-level readers. Regenerated docs/index.html so the page is discoverable from the docs surface.","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T02:18:48Z","created_by":"dirtydishes","updated_at":"2026-05-23T02:24:58Z","started_at":"2026-05-23T02:18:53Z","closed_at":"2026-05-23T02:24:58Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-4ca","title":"Publish May 21 standup git summary","description":"Create the daily standup-ready git activity summary for 2026-05-21, save the HTML artifact under docs/general, add the required turn document, and push the result so the automation leaves a durable record.","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-22T13:03:00Z","created_by":"dirtydishes","updated_at":"2026-05-22T13:05:05Z","started_at":"2026-05-22T13:03:03Z","closed_at":"2026-05-22T13:05:05Z","close_reason":"Created the 2026-05-21 standup summary in docs/general, added the required turn document, and prepared the repo for commit/push.","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/apps/desktop/README.md b/apps/desktop/README.md index d8166b8..9781c00 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -24,6 +24,6 @@ This workspace packages a thin Electron shell around the hosted Islandflow app. ## Development Notes -- `ISLANDFLOW_DESKTOP_START_URL` controls which trusted app URL Electron loads. Prefer `/options` for deep links; `/tape` remains supported and redirects in the web app for compatibility. +- `ISLANDFLOW_DESKTOP_START_URL` controls which trusted app URL Electron loads. - `NEXT_PUBLIC_API_URL` remains a web-app setting and should typically be `https://flow.deltaisland.io` when developing the local UI inside Electron. - `assets/` currently contains placeholders only; a real `.icns` icon is deferred. diff --git a/apps/desktop/src/security.test.ts b/apps/desktop/src/security.test.ts index dacabcb..3fe3e23 100644 --- a/apps/desktop/src/security.test.ts +++ b/apps/desktop/src/security.test.ts @@ -8,11 +8,7 @@ import { } from "./security.js"; describe("desktop URL policy", () => { - it("allows the hosted production origin on /options", () => { - expect(isTrustedAppUrl("https://flow.deltaisland.io/options?symbol=SPY")).toBe(true); - }); - - it("keeps /tape trusted as a compatibility path on the same origin", () => { + it("allows the hosted production origin", () => { expect(isTrustedAppUrl("https://flow.deltaisland.io/tape?symbol=SPY")).toBe(true); }); @@ -41,8 +37,5 @@ describe("desktop URL policy", () => { expect(resolveDesktopStartUrl(undefined)).toBe(DESKTOP_PRODUCTION_URL); expect(resolveDesktopStartUrl("https://example.com")).toBe(DESKTOP_PRODUCTION_URL); expect(resolveDesktopStartUrl("http://127.0.0.1:3000")).toBe("http://127.0.0.1:3000"); - expect(resolveDesktopStartUrl("https://flow.deltaisland.io/options")).toBe( - "https://flow.deltaisland.io/options" - ); }); }); diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 8c449c1..cf6746b 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -18,7 +18,7 @@ --red-soft: oklch(0.68 0.16 28 / 0.12); --blue: oklch(0.72 0.13 247); --blue-soft: oklch(0.72 0.13 247 / 0.11); - --drawer-width: min(320px, calc(100vw - 28px)); + --rail-width: 236px; --topbar-height: 64px; } @@ -86,43 +86,22 @@ input { } .terminal-shell { - position: relative; min-height: 100vh; + display: grid; + grid-template-columns: var(--rail-width) minmax(0, 1fr); background: linear-gradient(180deg, oklch(0.14 0.011 250) 0%, oklch(0.11 0.01 250) 100%); } -.terminal-nav-drawer { - position: fixed; - inset: 0 auto 0 0; - z-index: 45; - width: var(--drawer-width); - padding: 20px 18px 18px; +.terminal-rail { + position: sticky; + top: 0; + height: 100vh; + padding: 22px 18px; display: flex; flex-direction: column; gap: 20px; background: linear-gradient(180deg, oklch(0.16 0.012 250 / 0.98), oklch(0.13 0.011 250 / 0.98)); border-right: 1px solid var(--border); - box-shadow: 0 28px 72px rgba(0, 0, 0, 0.48); -} - -.terminal-drawer-backdrop { - position: fixed; - inset: 0; - z-index: 40; - border: 0; - background: rgba(3, 5, 8, 0.62); - cursor: pointer; -} - -.terminal-drawer-head { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 12px; -} - -.terminal-drawer-close { - flex: 0 0 auto; } .terminal-brand { @@ -219,7 +198,6 @@ input { .terminal-frame { min-width: 0; - min-height: 100vh; display: grid; grid-template-rows: minmax(var(--topbar-height), auto) minmax(0, 1fr); } @@ -230,39 +208,11 @@ input { z-index: 20; display: flex; align-items: center; - justify-content: space-between; - gap: 16px; + justify-content: flex-end; + gap: 12px; padding: 10px 20px; background: oklch(0.15 0.012 250 / 0.96); border-bottom: 1px solid var(--border); - backdrop-filter: blur(12px); -} - -.terminal-topbar-leading { - display: flex; - align-items: center; - gap: 12px; - flex: 0 0 auto; -} - -.terminal-menu-trigger { - display: inline-flex; - align-items: center; - gap: 10px; - min-width: 104px; -} - -.terminal-menu-trigger-icon { - display: inline-grid; - gap: 4px; -} - -.terminal-menu-trigger-icon span { - display: block; - width: 14px; - height: 1px; - border-radius: 999px; - background: currentColor; } .status-dot, @@ -513,7 +463,7 @@ input { .terminal-content { min-width: 0; - padding: 24px clamp(16px, 2vw, 28px) 24px; + padding: 24px 24px 24px; } .page-shell { @@ -739,8 +689,8 @@ h3 { grid-template-columns: minmax(0, 2fr) minmax(320px, 1fr); } -.page-grid-options { - grid-template-columns: minmax(0, 1fr); +.page-grid-tape { + grid-template-columns: minmax(0, 1.5fr) minmax(320px, 1fr); } .page-grid-signals { @@ -764,7 +714,7 @@ h3 { .page-grid-home > :nth-child(3), .page-grid-home > :nth-child(4), -.page-grid-options > :nth-child(1), +.page-grid-tape > :nth-child(1), .page-grid-replay > :nth-child(1) { grid-column: 1 / -1; } @@ -1013,11 +963,11 @@ h3 { grid-row: 2; } -.page-grid-options > :first-child { +.page-grid-tape > :first-child { height: clamp(460px, 64vh, 880px); } -.page-grid-options > :not(:first-child) { +.page-grid-tape > :not(:first-child) { height: clamp(400px, 50vh, 680px); } @@ -2015,23 +1965,68 @@ h3 { } @media (max-width: 1180px) { - .terminal-nav-drawer { - width: min(300px, calc(100vw - 24px)); + .terminal-shell { + grid-template-columns: 1fr; + } + + .terminal-rail { + position: sticky; + top: 0; + z-index: 35; + height: auto; + display: grid; + grid-template-columns: minmax(170px, auto) minmax(0, 1fr); + align-items: center; + gap: 14px 18px; + padding: 14px 16px; + border-right: 0; + border-bottom: 1px solid var(--border); + } + + .terminal-brand { + gap: 2px; } .terminal-brand-name { font-size: 1.25rem; } + .terminal-nav { + display: flex; + min-width: 0; + gap: 8px; + overflow-x: auto; + scrollbar-width: thin; + } + + .terminal-nav-link { + flex: 0 0 auto; + white-space: nowrap; + } + + .shell-metrics { + grid-column: 1 / -1; + margin-top: 0; + grid-template-columns: repeat(4, minmax(136px, 1fr)); + gap: 8px; + overflow-x: auto; + padding-bottom: 2px; + scrollbar-width: thin; + } + .shell-metric { min-width: 136px; padding: 10px 12px; } + + .terminal-topbar { + position: static; + } } @media (max-width: 980px) { .page-grid-home, - .page-grid-options, + .page-grid-tape, .page-grid-signals, .page-grid-charts, .page-grid-replay, @@ -2042,7 +2037,7 @@ h3 { .page-grid-home > :nth-child(3), .page-grid-home > :nth-child(4), - .page-grid-options > :nth-child(1), + .page-grid-tape > :nth-child(1), .page-grid-replay > :nth-child(1) { grid-column: auto; grid-row: auto; @@ -2054,8 +2049,8 @@ h3 { .page-grid-home > :nth-child(4), .page-grid-signals > .terminal-pane, .page-grid-replay > :not(:first-child), - .page-grid-options > :first-child, - .page-grid-options > :not(:first-child), + .page-grid-tape > :first-child, + .page-grid-tape > :not(:first-child), .page-grid-charts > :last-child { height: auto; } @@ -2067,12 +2062,14 @@ h3 { .terminal-topbar { align-items: center; - justify-content: space-between; + justify-content: flex-end; padding: 10px 16px; } .terminal-topbar-actions { justify-content: flex-end; + margin-left: auto; + width: auto; } .terminal-topbar-controls { @@ -2089,9 +2086,11 @@ h3 { background-size: 24px 24px, 24px 24px, 100% 100%, auto; } - .terminal-nav-drawer { - width: min(340px, calc(100vw - 12px)); - padding: 16px 12px 12px; + .terminal-rail { + position: static; + grid-template-columns: minmax(0, 1fr); + gap: 12px; + padding: 12px; } .terminal-brand { @@ -2112,6 +2111,20 @@ h3 { padding-bottom: 2px; } + .terminal-nav-link { + padding: 12px; + font-size: 0.72rem; + } + + .shell-metrics { + display: flex; + gap: 8px; + } + + .shell-metric { + flex: 0 0 156px; + } + .terminal-content { padding: 16px 10px 22px; } @@ -2147,10 +2160,6 @@ h3 { padding: 12px 10px; } - .terminal-topbar-leading { - width: 100%; - } - .terminal-button, .mode-button, .filter-clear, @@ -2177,14 +2186,8 @@ h3 { align-items: stretch; } - .terminal-menu-trigger { - width: 100%; - justify-content: center; - } - .terminal-topbar-mode .terminal-button, .terminal-topbar-controls > .terminal-button, - .terminal-topbar-leading > .terminal-button, .page-actions > .terminal-button, .page-actions > .flow-filter-popover { width: 100%; diff --git a/apps/web/app/options/page.tsx b/apps/web/app/options/page.tsx deleted file mode 100644 index abfa3fa..0000000 --- a/apps/web/app/options/page.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { OptionsRoute } from "../terminal"; - -export const dynamic = "force-dynamic"; - -export default function Page() { - return ; -} diff --git a/apps/web/app/routes.test.ts b/apps/web/app/routes.test.ts index e217748..55b29e0 100644 --- a/apps/web/app/routes.test.ts +++ b/apps/web/app/routes.test.ts @@ -28,10 +28,4 @@ describe("legacy page redirects", () => { expect(() => mod.default()).toThrow("NEXT_REDIRECT:/"); expect(redirect).toHaveBeenCalledWith("/"); }); - - it("redirects /tape to /options", async () => { - const mod = await import("./tape/page"); - expect(() => mod.default()).toThrow("NEXT_REDIRECT:/options"); - expect(redirect).toHaveBeenCalledWith("/options"); - }); }); diff --git a/apps/web/app/tape/page.tsx b/apps/web/app/tape/page.tsx index 0c82e4a..a692698 100644 --- a/apps/web/app/tape/page.tsx +++ b/apps/web/app/tape/page.tsx @@ -1,7 +1,7 @@ -import { redirect } from "next/navigation"; +import { TapeRoute } from "../terminal"; export const dynamic = "force-dynamic"; export default function Page() { - redirect("/options"); + return ; } diff --git a/apps/web/app/terminal.test.ts b/apps/web/app/terminal.test.ts index eb666c4..92a9904 100644 --- a/apps/web/app/terminal.test.ts +++ b/apps/web/app/terminal.test.ts @@ -24,7 +24,6 @@ import { getOptionScope, getLiveFeedStatus, getLiveManifest, - getTerminalNavCurrentHref, getRouteFeatures, getTapeVirtualConfig, mergeHeldTapeHistory, @@ -45,7 +44,6 @@ import { smartMoneyProfileLabel, smartMoneyToneForProfile, getAlertFlowPacketRefs, - normalizeTerminalPathname, resolveAlertFlowPacket, statusLabel, toggleFilterValue @@ -167,24 +165,18 @@ describe("alert context hydration helpers", () => { }); describe("live manifest", () => { - it("includes only options channels on /options", () => { + it("includes only tape channels on /tape", () => { const filters = buildDefaultFlowFilters(); - const channels = getLiveManifest("/options", "SPY", 60000, filters).map( + const channels = getLiveManifest("/tape", "SPY", 60000, filters).map( (subscription) => subscription.channel ); - expect(channels).toEqual(["options", "nbbo", "flow"]); + expect(channels).toEqual(["options", "nbbo", "equities", "flow"]); }); - it("keeps /tape as a compatibility alias for /options subscriptions", () => { - expect(getLiveManifest("/tape", "SPY", 60000, buildDefaultFlowFilters())).toEqual( - getLiveManifest("/options", "SPY", 60000, buildDefaultFlowFilters()) - ); - }); - - it("dedupes options subscriptions on /options", () => { + it("dedupes tape options subscription", () => { const tapeOptionsSubscriptions = getLiveManifest( - "/options", + "/tape", "SPY", 60000, buildDefaultFlowFilters() @@ -192,35 +184,35 @@ describe("live manifest", () => { expect(tapeOptionsSubscriptions).toHaveLength(1); }); - it("keeps option filters on /options subscriptions", () => { + it("keeps option filters on /tape options subscriptions", () => { const filters = { ...buildDefaultFlowFilters(), minNotional: 125_000 }; - const tapeOptionsSubscription = getLiveManifest("/options", "SPY", 60000, filters).find( + const tapeOptionsSubscription = getLiveManifest("/tape", "SPY", 60000, filters).find( (subscription) => subscription.channel === "options" ); expect(tapeOptionsSubscription?.filters).toBe(filters); }); - it("applies global flow filters to flow subscriptions on /options", () => { + it("applies global flow filters to flow subscriptions on /tape", () => { const filters = { ...buildDefaultFlowFilters(), minNotional: 50_000 }; - const tapeFlowSubscription = getLiveManifest("/options", "SPY", 60000, filters).find( + const tapeFlowSubscription = getLiveManifest("/tape", "SPY", 60000, filters).find( (subscription) => subscription.channel === "flow" ); expect(tapeFlowSubscription?.filters).toBe(filters); }); - it("includes scoped option subscriptions on /options", () => { + it("includes scoped option and equity subscriptions", () => { const manifest = getLiveManifest( - "/options", + "/tape", "AAPL", 60000, buildDefaultFlowFilters(), @@ -234,11 +226,15 @@ describe("live manifest", () => { (subscription): subscription is Extract<(typeof manifest)[number], { channel: "options" }> => subscription.channel === "options" ); + const equitiesSubscription = manifest.find( + (subscription): subscription is Extract<(typeof manifest)[number], { channel: "equities" }> => + subscription.channel === "equities" + ); expect(optionsSubscription?.underlying_ids).toEqual(["AAPL"]); expect(optionsSubscription?.option_contract_id).toBe("AAPL-2025-01-17-200-C"); expect(optionsSubscription?.snapshot_limit).toBe(100); - expect(manifest.some((subscription) => subscription.channel === "equities")).toBe(false); + expect(equitiesSubscription?.underlying_ids).toEqual(["AAPL"]); }); it("drops option-print filters for contract-focused options subscriptions but keeps flow filters", () => { @@ -248,7 +244,7 @@ describe("live manifest", () => { optionTypes: ["put"] as const }; const manifest = getLiveManifest( - "/options", + "/tape", "AAPL", 60000, filters, @@ -447,21 +443,15 @@ describe("contract-focused option helpers", () => { }); describe("route feature map", () => { - it("maps /options to the options and packets panes", () => { - const features = getRouteFeatures("/options"); + it("maps /tape to tape panes and dependencies", () => { + const features = getRouteFeatures("/tape"); expect(features.showOptionsPane).toBe(true); - expect(features.showEquitiesPane).toBe(false); + expect(features.showEquitiesPane).toBe(true); expect(features.showFlowPane).toBe(true); expect(features.needsClassifierDecor).toBe(true); expect(features.alerts).toBe(false); }); - it("keeps /tape route compatibility while normalizing to /options", () => { - expect(normalizeTerminalPathname("/tape")).toBe("/options"); - expect(getTerminalNavCurrentHref("/tape")).toBe("/options"); - expect(getRouteFeatures("/tape")).toEqual(getRouteFeatures("/options")); - }); - it("maps /signals to signal panes and dependencies", () => { const features = getRouteFeatures("/signals"); expect(features.showAlertsPane).toBe(true); @@ -516,10 +506,10 @@ describe("dark underlying route dependency helper", () => { }); describe("terminal navigation", () => { - it("exposes Home, Options, and News as top-level destinations", () => { + it("exposes Home, Tape, and News as top-level destinations", () => { expect(NAV_ITEMS).toEqual([ { href: "/", label: "Home" }, - { href: "/options", label: "Options" }, + { href: "/tape", label: "Tape" }, { href: "/news", label: "News" } ]); }); diff --git a/apps/web/app/terminal.tsx b/apps/web/app/terminal.tsx index 3444320..3057f58 100644 --- a/apps/web/app/terminal.tsx +++ b/apps/web/app/terminal.tsx @@ -186,34 +186,23 @@ export const shouldIncludeEquitiesForDarkUnderlyingFallback = (): boolean => { return false; }; -const CANONICAL_OPTIONS_PATH = "/options"; -const TAPE_COMPAT_PATH = "/tape"; -const KNOWN_TERMINAL_PATHS = new Set([ - CANONICAL_OPTIONS_PATH, - TAPE_COMPAT_PATH, - "/news", - "/signals", - "/charts", - "/replay" -]); - -export const normalizeTerminalPathname = (pathname: string): string => { - if (pathname === TAPE_COMPAT_PATH) { - return CANONICAL_OPTIONS_PATH; - } - return KNOWN_TERMINAL_PATHS.has(pathname) ? pathname : "/"; -}; - export const getRouteFeatures = (pathname: string): RouteFeatures => { const includeEquitiesFallback = shouldIncludeEquitiesForDarkUnderlyingFallback(); - const normalizedPath = normalizeTerminalPathname(pathname); + const normalizedPath = + pathname === "/tape" || + pathname === "/news" || + pathname === "/signals" || + pathname === "/charts" || + pathname === "/replay" + ? pathname + : "/"; switch (normalizedPath) { - case "/options": + case "/tape": return { options: true, nbbo: true, - equities: false, + equities: true, flow: true, news: false, alerts: false, @@ -224,7 +213,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => { equityCandles: false, equityOverlay: false, showOptionsPane: true, - showEquitiesPane: false, + showEquitiesPane: true, showFlowPane: true, showNewsPane: false, showAlertsPane: false, @@ -381,10 +370,6 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => { } }; -export const getTerminalNavCurrentHref = (pathname: string): string => { - return normalizeTerminalPathname(pathname); -}; - const EMPTY_ALERT_EVENTS: AlertEvent[] = []; const EMPTY_CLASSIFIER_HIT_EVENTS: ClassifierHitEvent[] = []; const EMPTY_SMART_MONEY_EVENTS: SmartMoneyEvent[] = []; @@ -7185,7 +7170,7 @@ const useTerminal = (): TerminalState => { export const NAV_ITEMS = [ { href: "/", label: "Home" }, - { href: "/options", label: "Options" }, + { href: "/tape", label: "Tape" }, { href: "/news", label: "News" } ] as const; @@ -8827,31 +8812,8 @@ function SyntheticControlDock() { export function TerminalAppShell({ children }: { children: ReactNode }) { const state = useTerminalState(); const pathname = usePathname(); - const [drawerOpen, setDrawerOpen] = useState(false); const tickerFieldId = useId(); const tickerHintId = useId(); - const activeNavHref = getTerminalNavCurrentHref(pathname); - - useEffect(() => { - setDrawerOpen(false); - }, [pathname]); - - useEffect(() => { - if (!drawerOpen) { - return; - } - - const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === "Escape") { - setDrawerOpen(false); - } - }; - - document.addEventListener("keydown", handleKeyDown); - return () => { - document.removeEventListener("keydown", handleKeyDown); - }; - }, [drawerOpen]); return ( @@ -8859,26 +8821,31 @@ export function TerminalAppShell({ children }: { children: ReactNode }) { Skip to terminal content +
-
- -
{state.selectedInstrumentLabel && state.selectedInstrument?.kind !== "option-contract" ? ( @@ -8942,53 +8909,6 @@ export function TerminalAppShell({ children }: { children: ReactNode }) {
- {drawerOpen ? ( - <> - -
- - - - - ) : null} - {state.selectedAlert ? ( @@ -9061,11 +8981,11 @@ export function NewsRoute() { ); } -export function OptionsRoute() { +export function TapeRoute() { const state = useTerminal(); return (