diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 9af4502..bb482ea 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -30,6 +30,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-9gb","title":"Rename news route to Newswire","description":"Follow-up to the mock9 production terminal rebuild: rename the /news route title from Wire Control to Newswire and keep the visual verification/docs aligned with the latest user-facing label.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-06-13T14:33:30Z","created_by":"dirtydishes","updated_at":"2026-06-13T14:37:01Z","started_at":"2026-06-13T14:33:42Z","closed_at":"2026-06-13T14:37:01Z","close_reason":"Renamed the /news route to Newswire, updated the design record and turn document, decoded common provider HTML entities in news text, and validated with focused web tests, production build, and Helium fitted/narrow inspection.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-iil","title":"Replace overview with dashboard command page","description":"Turn the mock9 Market Command concept into the production root dashboard, rename the visible route from Home to Dashboard, and keep the layout dense with a chart-first command surface.","acceptance_criteria":"Root page displays Dashboard instead of Home; dashboard includes command metrics, chart area, decision levels, priority board, live context, feed health, dark context, and replay context; web tests and production build pass.","notes":"Implemented from the mock9 direction while preserving the existing / URL and using the existing ChartPane until proper chart implementation lands.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-06-13T07:37:56Z","created_by":"dirtydishes","updated_at":"2026-06-13T07:43:44Z","started_at":"2026-06-13T07:38:02Z","closed_at":"2026-06-13T07:43:44Z","close_reason":"dashboard replacement implemented, validated, and documented","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-7l2","title":"Configure local web and desktop to use hosted Islandflow API","description":"Local web development and the Electron desktop shell are not connecting to the VPS-hosted API reliably after a recent endpoint change. Verify the active Delta Island API hostname, update local/default configuration so bun run dev:web and desktop development target it correctly, and validate the relevant web/desktop paths.","status":"closed","priority":2,"issue_type":"bug","owner":"dishes@dpdrm.com","created_at":"2026-06-13T07:32:28Z","created_by":"dirtydishes","updated_at":"2026-06-13T07:38:19Z","closed_at":"2026-06-13T07:38:19Z","close_reason":"Configured local web and desktop development to use https://api.flow.deltaisland.io as the hosted API origin, updated docs and local ignored env, verified the API host from the VPS, passed focused tests, public API route checks, and web build. Dev-web smoke confirmed the corrected API origin but port 3000 was already occupied.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-4j7","title":"replace activity matrix with alert lineage mock","description":"Rework the confusing Activity Matrix mock into a concrete alert lineage view that shows how a selected alert formed, including evidence chain, confirming/against context, invalidations, and audit state.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-06-12T00:10:18Z","created_by":"dirtydishes","updated_at":"2026-06-12T00:15:45Z","started_at":"2026-06-12T00:10:21Z","closed_at":"2026-06-12T00:15:45Z","close_reason":"Replaced the abstract Activity Matrix mock with an alert lineage view that supports all-symbol scope, selected alert evidence, invalidations, and audit context.","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/DESIGN.md b/DESIGN.md index b427ebe..2ed68d4 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -108,7 +108,7 @@ This system explicitly rejects the anti-references in PRODUCT.md: no meme-stock - Accent color treated as scarce signal. - Monospace-assisted precision for time, numeric, and status data. - Readability preserved during bursty live updates. -- Route-specific signatures: Dashboard is Market Command, Options is OPRA Intake, News is Wire Control. +- Route-specific signatures: Dashboard is Market Command, Options is OPRA Intake, News is Newswire. - Flat terminal sections: border-block dividers and compact headers are the default; rounded cards are not. ## Colors @@ -203,7 +203,7 @@ The system is flat by default. Depth is primarily tonal (background and border d - **Dashboard / Market Command:** command metrics, priority board, decision levels, chart context, source health, recent contracts, replay state, and evidence context in one dense operating board. - **Options / OPRA Intake:** production `OptionsPane` and `FlowPane` remain the source of truth, with TanStack virtual rows, contract focus, scroll gates, and filters tuned for option decision work. -- **News / Wire Control:** virtualized wire rows, source rails, symbol rails, live-only state, older-history scroll gates, and the existing news drawer. +- **News / Newswire:** virtualized wire rows, source rails, symbol rails, live-only state, older-history scroll gates, and the existing news drawer. ### Inputs / Fields diff --git a/apps/web/app/terminal.test.ts b/apps/web/app/terminal.test.ts index b5bf7d0..f098985 100644 --- a/apps/web/app/terminal.test.ts +++ b/apps/web/app/terminal.test.ts @@ -41,6 +41,7 @@ const { composeTapeItems, deriveAlertDirection, countActiveFlowFilterGroups, + decodeNewsText, filterOptionTapeItems, findAnchorRestoreIndex, formatCompactUsd, @@ -562,6 +563,20 @@ describe("fixed tape virtualization config", () => { }); }); +describe("news text formatting", () => { + it("decodes common html entities in provider text", () => { + expect( + decodeNewsText( + "Palantir CEO Alex Karp Is 'Rooting For Elon' & Clients 'Screaming'" + ) + ).toBe("Palantir CEO Alex Karp Is 'Rooting For Elon' & Clients 'Screaming'"); + }); + + it("leaves unknown entities untouched", () => { + expect(decodeNewsText("Keep &market; literal")).toBe("Keep &market; literal"); + }); +}); + describe("dark underlying route dependency helper", () => { it("does not keep extra equities subscriptions when joins+trace fallback are sufficient", () => { expect(shouldIncludeEquitiesForDarkUnderlyingFallback()).toBe(false); diff --git a/apps/web/app/terminal.tsx b/apps/web/app/terminal.tsx index 52e7af7..a61bd29 100644 --- a/apps/web/app/terminal.tsx +++ b/apps/web/app/terminal.tsx @@ -1277,15 +1277,45 @@ export const formatNewsTimestamp = (ts: number, now = Date.now()): string => { }); }; +const NEWS_TEXT_ENTITIES: Record = { + amp: "&", + apos: "'", + gt: ">", + lt: "<", + nbsp: " ", + quot: '"' +}; + +export const decodeNewsText = (value: string): string => + value.replace(/&(#\d+|#x[\da-f]+|[a-z][\da-z]+);/gi, (match, entity: string) => { + if (entity[0] === "#") { + const radix = entity[1]?.toLowerCase() === "x" ? 16 : 10; + const rawCodePoint = radix === 16 ? entity.slice(2) : entity.slice(1); + const codePoint = Number.parseInt(rawCodePoint, radix); + if (!Number.isFinite(codePoint)) { + return match; + } + try { + return String.fromCodePoint(codePoint); + } catch { + return match; + } + } + + return NEWS_TEXT_ENTITIES[entity.toLowerCase()] ?? match; + }); + const sanitizeNewsHtml = ( value: string ): { html: string; fallbackText: string; sanitized: boolean } => { - const fallbackText = value - .replace(//gi, " ") - .replace(//gi, " ") - .replace(/<[^>]+>/g, " ") - .replace(/\s+/g, " ") - .trim(); + const fallbackText = decodeNewsText( + value + .replace(//gi, " ") + .replace(//gi, " ") + .replace(/<[^>]+>/g, " ") + .replace(/\s+/g, " ") + .trim() + ); try { const sanitized = value @@ -5017,13 +5047,15 @@ type NewsDrawerProps = { const NewsDrawer = ({ story, onClose }: NewsDrawerProps) => { const body = sanitizeNewsHtml(story.content_html); + const headline = decodeNewsText(story.headline); + const summary = decodeNewsText(story.summary); return (