/** * Activity: one surface over the queries nxdns answered, in two modes. * * History reads the persisted log and Live reads the stream, but they are the * same seven columns over the same filters, and the reader moves between them * without losing the question they were asking. The mode lives in the URL with * the filters, so an investigation is one link — including which half of it the * recipient should be looking at. */ import { useCallback } from "react"; import { Link, useNavigate, useSearch } from "@tanstack/react-router"; import * as stylex from "@stylexjs/stylex"; import { Tab, TabList, TabPanel, Tabs } from "react-aria-components"; import { styles as shared } from "@/ui/styles"; import { colors } from "@/ui/tokens.stylex"; import ActivityFilters, { NO_FILTERS, type AppliedFilters } from "./ActivityFilters"; import HistoryActivity from "./HistoryActivity"; import LiveActivity from "./LiveActivity"; import type { ActivityMode } from "./search"; const MODES: ReadonlyArray<{ mode: ActivityMode; label: string }> = [ { mode: "history", label: "History" }, { mode: "live", label: "Live" }, ]; const styles = stylex.create({ header: { display: "flex", flexWrap: "wrap", alignItems: "center", gap: "0.75rem", }, heading: { fontSize: "1.5rem", lineHeight: "2rem", fontWeight: 600, }, switch: { display: "flex", gap: "0.25rem", borderRadius: "0.25rem", borderWidth: 1, borderStyle: "solid", borderColor: colors.border, padding: "0.125rem", }, modeButton: { borderStyle: "none", borderRadius: "0.1875rem", paddingInline: "0.75rem", paddingBlock: "0.25rem", fontSize: "0.875rem", lineHeight: "1.25rem", fontWeight: 500, cursor: "pointer", }, /** A Tab is a `div` with a roving tabindex, so RAC drives the ring, not `:focus-visible`. */ modeFocusVisible: { outlineWidth: 2, outlineStyle: "solid", outlineColor: colors.focus, outlineOffset: 2, }, modeIdle: { backgroundColor: { default: "transparent", ":hover": colors.surfaceHover }, color: { default: colors.textSecondary, ":hover": colors.text }, }, /** The selected mode reads as a filled chip, the same weight the nav uses. */ modeSelected: { backgroundColor: colors.primary, color: colors.primaryText, }, /** The one way into the simulation from here, so it cannot sit behind a row. */ simulationLink: { marginInlineStart: "auto", fontSize: "0.875rem", lineHeight: "1.25rem", color: colors.primaryOnSurface, textDecorationLine: "none", }, liveNote: { marginTop: "0.5rem", fontSize: "0.875rem", lineHeight: "1.25rem", color: colors.textMuted, }, panel: { outlineStyle: "none", }, /** * A panel with nothing tabbable in it — an empty or loading history — is given * a tabindex by RAC so the reader can still reach its content, so it has to be * able to show that it holds the focus. */ panelFocusVisible: { outlineWidth: 2, outlineStyle: "solid", outlineColor: colors.focus, outlineOffset: 2, }, /** Explicit, so RAC's default `react-aria-Tabs` class does not land instead. */ tabsRoot: { display: "block", }, }); function panelClass({ isFocusVisible }: { isFocusVisible: boolean }): string { return stylex.props(styles.panel, isFocusVisible && styles.panelFocusVisible).className ?? ""; } export default function ActivityPage() { const search = useSearch({ from: "/shell/activity" }); const navigate = useNavigate({ from: "/activity" }); // The functional form, not a replacement object: the filters are retained // across a mode switch on purpose, and spelling out a new search here would // drop every one of them on the way to Live and back. function selectMode(mode: ActivityMode) { if (mode === search.mode) return; void navigate({ search: (prev) => ({ ...prev, mode }) }); } // A patch, merged into whatever the URL already says: a segment click must not // spell out the four filters it is not about. `replace` is the debounced text // commits, so the back button steps between investigations, not keystrokes. const apply = useCallback( (patch: Partial, replace = false) => { void navigate({ search: (prev) => ({ ...prev, ...patch }), replace }); }, [navigate], ); const clear = useCallback(() => { void navigate({ search: (prev) => ({ mode: prev.mode, ...NO_FILTERS }) }); }, [navigate]); return (
{/* * The selected tab is the URL's `mode` and nothing else. RAC would hold * the selection itself, but a second copy of it would fight the back * button, so the search parameter stays the only state there is. */} selectMode(key as ActivityMode)} className={() => stylex.props(styles.tabsRoot).className ?? ""} >

Activity

stylex.props(styles.switch).className ?? ""} > {MODES.map((option) => ( stylex.props( styles.modeButton, isSelected ? styles.modeSelected : styles.modeIdle, isFocusVisible && styles.modeFocusVisible, ).className ?? "" } > {option.label} ))} Current policy simulation
{/* * The toolbar belongs to History alone. It is not remounted on a search * change: it resyncs its draft from the URL instead, because a remount * mid-debounce would take the focus out of the input being typed in. */}

The stream carries every query the server answers; the History filters apply to history only.

); }