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
@@ -0,0 +1,75 @@
import { useQuery } from "@tanstack/react-query";
import { Link, useParams, useSearch } from "@tanstack/react-router";
import * as stylex from "@stylexjs/stylex";
import InlineError from "@/lib/InlineError";
import { queryDetailQuery } from "@/lib/queries";
import type { QueryDetail } from "@/lib/types";
import { styles as shared } from "@/ui/styles";
import { colors } from "@/ui/tokens.stylex";
import ProvenanceDetail from "./ProvenanceDetail";
import RelatedActions from "./RelatedActions";
import type { ActivitySearch } from "./search";
const styles = stylex.create({
back: {
fontSize: "0.875rem",
lineHeight: "1.25rem",
color: colors.primaryOnSurface,
textDecorationLine: "none",
},
loading: {
marginTop: "1rem",
color: colors.textMuted,
},
});
/**
* The way back to the investigation, not to a bare list. The originating
* Activity search rides in this route's own search, so the reader returns to
* the mode, the filters and the absolute window they left — a plain `/activity`
* would silently widen the range they had chosen.
*/
function BackLink({ origin }: { origin: ActivitySearch }) {
return (
<Link to="/activity" search={origin} {...stylex.props(styles.back, shared.focusRing)}>
Activity
</Link>
);
}
export default function ActivityDetailPage() {
const { id } = useParams({ from: "/shell/activity/queries/$id" });
const origin = useSearch({ from: "/shell/activity/queries/$id" });
const rowId = Number(id);
const { data, error, isPending, refetch } = useQuery(queryDetailQuery(rowId));
if (isPending) {
return (
<p {...stylex.props(styles.loading, shared.pulse)} role="status">
Loading query
</p>
);
}
if (data === undefined) {
return (
<section>
<BackLink origin={origin} />
<InlineError error={error} onRetry={() => void refetch()} />
</section>
);
}
const detail: QueryDetail = data;
const { domain, client, time } = detail.request;
return (
<section>
<BackLink origin={origin} />
<ProvenanceDetail
provenance={detail}
persistedId={detail.id}
relatedActions={<RelatedActions domain={domain} client={client} ts={time} origin={origin} />}
/>
</section>
);
}