milestone 29: activity — history, live and policy simulation on one surface
Gates / test (push) Successful in 1m40s
Gates / package (push) Successful in 3m58s
Gates / container (push) Successful in 14s
CI / gates (push) Successful in 12m51s
Gates / frontend (push) Successful in 1m18s
Gates / test-aarch64 (push) Successful in 6m57s

query log, live and lookup merge into /activity. history filters live
in the url, so a pasted link or back/forward reproduces the exact
view; the result column separates servfail and nxdomain from success
in the list. live is follow-by-default with freeze, and a streamed
row opens its in-memory provenance detail — no correlation invented
for rows sqlite has not written. lookup survives as the current
policy simulation under /activity/test. investigation links carry
absolute bounds, and the diagnostics page now honors since/until
instead of ignoring them. the old routes are gone without aliases.
This commit is contained in:
2026-08-22 10:52:56 +02:00
parent 0fd6bbd312
commit fa323c7ed4
42 changed files with 3225 additions and 1426 deletions
@@ -12,18 +12,13 @@ import * as api from "@/lib/api";
import InlineError from "@/lib/InlineError";
import { formatDuration, formatTime } from "@/lib/format";
import { diagnosticPurgeMutation, diagnosticsInfiniteQuery, diagnosticsPurgeResolvedMutation } from "@/lib/queries";
import type {
DiagnosticEvent,
DiagnosticSeverity,
DiagnosticState,
DiagnosticsFilter,
DiagnosticsPage as Page,
} from "@/lib/types";
import type { DiagnosticEvent, DiagnosticSeverity, DiagnosticState, DiagnosticsPage as Page } from "@/lib/types";
import ConfirmDialog from "@/ui/ConfirmDialog";
import Select from "@/ui/Select";
import { styles as shared } from "@/ui/styles";
import { colors } from "@/ui/tokens.stylex";
import SeverityBadge from "./SeverityBadge";
import { diagnosticsFilterOf } from "./filter";
import { DIAGNOSTIC_COMPONENTS, componentLabel, copyFor } from "./eventCopy";
const DARK = "@media (prefers-color-scheme: dark)";
@@ -138,6 +133,19 @@ const styles = stylex.create({
lineHeight: "1rem",
color: colors.textMuted,
},
rangeNotice: {
marginTop: "0.75rem",
borderRadius: "0.25rem",
borderWidth: 1,
borderStyle: "solid",
borderColor: colors.border,
backgroundColor: colors.surfaceHover,
paddingInline: "0.75rem",
paddingBlock: "0.5rem",
fontSize: "0.875rem",
lineHeight: "1.25rem",
color: colors.textSecondary,
},
tableWrap: {
marginTop: "0.75rem",
overflowX: "auto",
@@ -263,6 +271,33 @@ function MoreButton({ section }: { section: Section }) {
);
}
/**
* The window the page is bounded to, whenever it is bounded.
*
* A link from a query detail arrives with an absolute five-minute window, and
* an empty Active section inside it means something very different from an
* empty Active section over the whole history. The page has to say which it is
* showing, and offer the way out of it.
*/
function RangeNotice({ since, until }: { since?: number; until?: number }) {
const navigate = useNavigate({ from: "/diagnostics" });
if (since === undefined && until === undefined) return null;
const from = since === undefined ? "the start of the history" : formatTime(since);
const to = until === undefined ? "now" : formatTime(until);
return (
<p role="status" {...stylex.props(styles.rangeNotice)}>
Showing events that overlap {from} to {to}.{" "}
<button
type="button"
onClick={() => void navigate({ search: (prev) => ({ ...prev, since: undefined, until: undefined }) })}
{...stylex.props(shared.linkButton, shared.focusRing)}
>
Clear the time range
</button>
</p>
);
}
function ActiveCard({ event, now }: { event: DiagnosticEvent; now: number }) {
const copy = copyFor(event.code);
return (
@@ -327,9 +362,7 @@ export default function DiagnosticsPage() {
const navigate = useNavigate({ from: "/diagnostics" });
const state = search.state ?? "all";
const base: DiagnosticsFilter = {};
if (search.severity !== undefined) base.severity = search.severity;
if (search.component !== undefined) base.component = search.component;
const base = diagnosticsFilterOf(search);
const active = useInfiniteQuery(diagnosticsInfiniteQuery({ ...base, state: "active" }, state !== "resolved"));
const history = useInfiniteQuery(diagnosticsInfiniteQuery({ ...base, state: "resolved" }, state !== "active"));
@@ -368,6 +401,8 @@ export default function DiagnosticsPage() {
repeats, and closes when the subject recovers.
</p>
<RangeNotice since={search.since} until={search.until} />
<div {...stylex.props(styles.filterGrid)}>
<Select
variant="compactField"