Harden Web Terminal UI States
The terminal UI now handles live and replay status, empty panes, clipped market data, and table semantics more reliably for real users, assistive technology, and unusual input values.
Summary
I hardened the main web terminal surface by adding accessible feed announcements, reusable empty-state markup, safer data cells for clipped values, and stronger table semantics on the busiest tape panes.
Changes Made
- Added
buildTapeStatusAnnouncementso live and replay feed states have complete screen-reader labels instead of relying on colored dots or terse visible labels. - Added reusable
DataCellandEmptyStatehelpers for terminal panes. - Updated Options, Equities, and Flow panes with semantic column headers, rowgroups, cells, and useful
titlefallbacks for clipped values. - Improved empty-state layout so long messages wrap cleanly without collapsing the pane.
- Added
unicode-bidi: plaintextto table cells so mixed-direction symbols, ticker text, and unusual copied values are less likely to reorder confusingly. - Added focused tests for the new status-announcement helper.
Context
Islandflow is an evidence console for live market investigation. The UI has to remain useful when feeds are stale, paused, empty, or carrying long contract identifiers and numeric values. Hardening here focused on making the existing dense terminal more robust without changing its visual identity.
Important Implementation Details
TapeStatusnow exposes a polite status region with anaria-labelsuch asLive feed behindorReplay feed paused, time not available, 12 queued rows.- The visible status dot is marked
aria-hidden, keeping color as a visual cue rather than the only status carrier. - Table headers are generated from arrays to keep repeated header markup consistent.
- Clipped values such as option contracts, exact timestamps, full notional values, NBBO quality strings, and venue labels now expose fuller details through
titlewhere useful.
Relevant Diff Snippets
Diff snippets are presented in the format expected by diffs.com-style unified diff rendering. @pierre/diffs is installed in this repo, but it does not expose a CLI binary, so the relevant unified snippets are embedded directly.
diff --git a/apps/web/app/terminal.tsx b/apps/web/app/terminal.tsx
@@
+export const buildTapeStatusAnnouncement = ({
+ status,
+ replayTime,
+ replayComplete,
+ paused,
+ dropped,
+ mode
+}: Pick<TapeStatusProps, "status" | "replayTime" | "replayComplete" | "paused" | "dropped" | "mode">): string => {
+ const label = replayComplete ? "Replay Complete" : statusLabel(status, paused, mode);
+ const feedLabel = mode === "live" && label.toLowerCase().startsWith("feed ")
+ ? label.toLowerCase()
+ : `feed ${label.toLowerCase()}`;
+ const parts = [`${mode === "live" ? "Live" : "Replay"} ${feedLabel}`];
+ ...
+};
+
+const EmptyState = ({ children }: { children: ReactNode }) => (
+ <div className="empty" role="status" aria-live="polite">
+ {children}
+ </div>
+);
@@
- <div className={`status-inline status-${status} ${mode === "replay" ? "status-replay" : ""}`.trim()}>
- <span className="status-dot" />
+ <div
+ className={`status-inline status-${status} ${mode === "replay" ? "status-replay" : ""}`.trim()}
+ role="status"
+ aria-live="polite"
+ aria-label={announcement}
+ >
+ <span className="status-dot" aria-hidden="true" />
diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css
@@
.data-table-cell {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.72rem;
+ unicode-bidi: plaintext;
}
.empty {
+ display: flex;
+ align-items: center;
+ min-height: 76px;
padding: 18px;
border-radius: 12px;
border: 1px dashed var(--border);
background: var(--bg-soft);
color: var(--text-dim);
+ line-height: 1.4;
+ overflow-wrap: anywhere;
}
diff --git a/apps/web/app/terminal.test.ts b/apps/web/app/terminal.test.ts
@@
+describe("tape status hardening", () => {
+ it("announces stale live feeds without relying on the colored dot", () => {
+ expect(
+ buildTapeStatusAnnouncement({
+ status: "stale",
+ replayTime: null,
+ replayComplete: false,
+ paused: false,
+ dropped: 0,
+ mode: "live"
+ })
+ ).toBe("Live feed behind");
+ });
+});
Expected Impact for End-Users
Traders and researchers should get a steadier terminal under imperfect feed conditions. Screen-reader users get explicit live and replay status changes, empty panes announce themselves clearly, and clipped market values are easier to inspect without widening the layout.
Validation
bun test apps/web/app/terminal.test.ts: 76 passing tests.bun --cwd=apps/web run build: production build completed successfully.- Browser verification at
http://localhost:3000/options: confirmed status regions, table semantics, column headers, rowgroup, and cells are present in the rendered page.
Issues, Limitations, and Mitigations
- The Options pane can still be wider than a narrow viewport by design; the table remains inside its horizontal scroll container.
- Alert, classifier, dark-event, and news panes still have some older one-off markup. This task hardened the highest-traffic tape panes first.
- The browser check observed a history-load warning because backend history was unavailable locally. That state rendered cleanly and was not a build blocker.
Follow-up Work
No additional Beads issue was required during this turn. A sensible future pass would extend the same DataCell and EmptyState treatment to Alerts, Smart Money, Dark Events, and the chart evidence lists.