rename newswire and clean wire text
Some checks failed
CI / Validate (pull_request) Failing after 18s

This commit is contained in:
dirtydishes 2026-06-13 10:38:08 -04:00
parent 0320533628
commit 38dcf73c44
5 changed files with 79 additions and 31 deletions

View file

@ -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-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-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-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-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-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} {"_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}

View file

@ -108,7 +108,7 @@ This system explicitly rejects the anti-references in PRODUCT.md: no meme-stock
- Accent color treated as scarce signal. - Accent color treated as scarce signal.
- Monospace-assisted precision for time, numeric, and status data. - Monospace-assisted precision for time, numeric, and status data.
- Readability preserved during bursty live updates. - 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. - Flat terminal sections: border-block dividers and compact headers are the default; rounded cards are not.
## Colors ## 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. - **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. - **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 ### Inputs / Fields

View file

@ -41,6 +41,7 @@ const {
composeTapeItems, composeTapeItems,
deriveAlertDirection, deriveAlertDirection,
countActiveFlowFilterGroups, countActiveFlowFilterGroups,
decodeNewsText,
filterOptionTapeItems, filterOptionTapeItems,
findAnchorRestoreIndex, findAnchorRestoreIndex,
formatCompactUsd, 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", () => { describe("dark underlying route dependency helper", () => {
it("does not keep extra equities subscriptions when joins+trace fallback are sufficient", () => { it("does not keep extra equities subscriptions when joins+trace fallback are sufficient", () => {
expect(shouldIncludeEquitiesForDarkUnderlyingFallback()).toBe(false); expect(shouldIncludeEquitiesForDarkUnderlyingFallback()).toBe(false);

View file

@ -1277,15 +1277,45 @@ export const formatNewsTimestamp = (ts: number, now = Date.now()): string => {
}); });
}; };
const NEWS_TEXT_ENTITIES: Record<string, string> = {
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 = ( const sanitizeNewsHtml = (
value: string value: string
): { html: string; fallbackText: string; sanitized: boolean } => { ): { html: string; fallbackText: string; sanitized: boolean } => {
const fallbackText = value const fallbackText = decodeNewsText(
.replace(/<script[\s\S]*?<\/script>/gi, " ") value
.replace(/<style[\s\S]*?<\/style>/gi, " ") .replace(/<script[\s\S]*?<\/script>/gi, " ")
.replace(/<[^>]+>/g, " ") .replace(/<style[\s\S]*?<\/style>/gi, " ")
.replace(/\s+/g, " ") .replace(/<[^>]+>/g, " ")
.trim(); .replace(/\s+/g, " ")
.trim()
);
try { try {
const sanitized = value const sanitized = value
@ -5017,13 +5047,15 @@ type NewsDrawerProps = {
const NewsDrawer = ({ story, onClose }: NewsDrawerProps) => { const NewsDrawer = ({ story, onClose }: NewsDrawerProps) => {
const body = sanitizeNewsHtml(story.content_html); const body = sanitizeNewsHtml(story.content_html);
const headline = decodeNewsText(story.headline);
const summary = decodeNewsText(story.summary);
return ( return (
<aside className="drawer"> <aside className="drawer">
<div className="drawer-header"> <div className="drawer-header">
<div> <div>
<p className="drawer-eyebrow">News wire</p> <p className="drawer-eyebrow">News wire</p>
<h3>{story.headline}</h3> <h3>{headline}</h3>
<p className="drawer-subtitle"> <p className="drawer-subtitle">
{story.source} · Published {formatDateTime(story.published_ts)} {story.source} · Published {formatDateTime(story.published_ts)}
{story.updated_ts !== story.published_ts {story.updated_ts !== story.published_ts
@ -5045,10 +5077,10 @@ const NewsDrawer = ({ story, onClose }: NewsDrawerProps) => {
<span className="drawer-chip">{story.symbol_resolution}</span> <span className="drawer-chip">{story.symbol_resolution}</span>
</div> </div>
{story.summary ? ( {summary ? (
<div className="drawer-section"> <div className="drawer-section">
<h4>Summary</h4> <h4>Summary</h4>
<p className="drawer-note">{story.summary}</p> <p className="drawer-note">{summary}</p>
</div> </div>
) : null} ) : null}
@ -8413,6 +8445,8 @@ const NewsPane = memo(({ state, limit, className }: NewsPaneProps) => {
> >
{virtual.virtualItems.map(({ item: story, key, index, start, size }) => { {virtual.virtualItems.map(({ item: story, key, index, start, size }) => {
const wireStatus = getNewsWireStatus(story); const wireStatus = getNewsWireStatus(story);
const headline = decodeNewsText(story.headline);
const summary = decodeNewsText(story.summary || story.provider);
return ( return (
<button <button
className={`data-table-row data-table-row-button data-table-row-news data-table-virtual-row${index % 2 === 1 ? " is-even" : ""} news-wire-row-${wireStatus}`} className={`data-table-row data-table-row-button data-table-row-news data-table-virtual-row${index % 2 === 1 ? " is-even" : ""} news-wire-row-${wireStatus}`}
@ -8435,10 +8469,8 @@ const NewsPane = memo(({ state, limit, className }: NewsPaneProps) => {
{wireStatus} {wireStatus}
</span> </span>
</span> </span>
<span className="data-table-cell news-headline-cell">{story.headline}</span> <span className="data-table-cell news-headline-cell">{headline}</span>
<span className="data-table-cell news-summary-cell"> <span className="data-table-cell news-summary-cell">{summary}</span>
{story.summary || story.provider}
</span>
</button> </button>
); );
})} })}
@ -8961,7 +8993,7 @@ const buildCommandPriorityRows = (state: TerminalState): CommandPriorityRow[] =>
ts: story.published_ts, ts: story.published_ts,
symbol: story.resolved_symbols[0]?.toUpperCase() ?? "WIRE", symbol: story.resolved_symbols[0]?.toUpperCase() ?? "WIRE",
packet: story.source, packet: story.source,
read: story.headline, read: decodeNewsText(story.headline),
score: story.resolved_symbols.length > 0 ? 55 : 25, score: story.resolved_symbols.length > 0 ? 55 : 25,
invalidation: getNewsWireStatus(story), invalidation: getNewsWireStatus(story),
state: "info", state: "info",
@ -9272,7 +9304,7 @@ const EventContextPane = ({ state }: { state: TerminalState }) => {
key: `news-${story.trace_id}-${story.seq}`, key: `news-${story.trace_id}-${story.seq}`,
ts: story.published_ts, ts: story.published_ts,
label: "News", label: "News",
title: story.headline, title: decodeNewsText(story.headline),
detail: story.resolved_symbols.length > 0 ? story.resolved_symbols.join(", ") : story.source, detail: story.resolved_symbols.length > 0 ? story.resolved_symbols.join(", ") : story.source,
action: () => state.setSelectedNewsStory(story) action: () => state.setSelectedNewsStory(story)
})) }))
@ -10214,7 +10246,7 @@ export function OverviewRoute() {
export function NewsRoute() { export function NewsRoute() {
const state = useTerminal(); const state = useTerminal();
return ( return (
<PageFrame title="Wire Control" eyebrow="News" variant="news"> <PageFrame title="Newswire" eyebrow="News" variant="news">
<div className="wire-control-shell"> <div className="wire-control-shell">
<NewsControlRails state={state} /> <NewsControlRails state={state} />
<NewsPane state={state} className="news-pane-full" /> <NewsPane state={state} className="news-pane-full" />

File diff suppressed because one or more lines are too long