The Overview page takes the decided visual language (specs/ui-visual-redesign.md): four centred totals with their Activity links, a smoothed area chart of total and blocked queries with point hover and a tooltip beside the point, a stacked client chart in eight distinct hues plus one Other band, and a card row with the cache hit rate, the query types as a single-hue ramp ring, and the upstream breakdown.
GET /api/overview takes a client parameter; the scoped read uses idx_query_log_ts and the cache keeps scoped slots. The device selector beside the period selector is URL state, so a scoped view is a link, and the tile links carry the scope into Activity.
All numbers go through admin/src/lib/format.ts: grouped counts, two-decimal percentages, one-decimal rates, durations as the two largest nonzero units. A sweep test refuses toFixed, toLocaleString, Intl.NumberFormat and padStart anywhere else.
Chrome: one 4px radius from the metrics constants, shared Card with a prominent title and description, the sidebar grouped into Monitoring and System with a status block (protection, queries per minute on Overview, uptime), and the accent darkened to 5.43:1 on its wash.
Not built: the spec's ranked-list primitive, which has no consumer and no API rows. Codex could not review sessions B to D, its endpoint returned 404 for every call on 2026-09-03; the owner accepted the smoke-server screenshots.
selected states stop changing font weight, buttons gain a pressed scale and scoped 120ms transitions with a reduced-motion override, every loading and empty state reserves its height, charts measure before first paint, hit targets rise to the 44px enhanced target where layout permits, long domains clamp to two lines on an unpadded inner span, chips and name cells truncate, tabular figures on counts and time columns, a z-index layer scale replaces magic numbers and fixes the dialog-over-confirm tie, history's empty state gains a clear-filters action, page headings balance, font smoothing and color-scheme land on the html reset.
ci's frontend job timed out three page tests at 5s; the runner paid the barrel's ~1500 icon modules on first render. deep imports load only the icons the app uses; local test import time drops by a quarter.
health strip marks, menu ticks, chip crosses, the search magnifier, the checkbox check and the back arrows all come from @phosphor-icons/react now, pinned exactly and entered in the license ledger. changelog and version bump for 0.0.15, including the bundle budget raise to 900000 bytes.
the freetype client field is gone. the picker is a select-only menu of known clients with multi-select, chips for the active set, and a 32-client cap shared with the server. the api accepts a comma-separated client list and filters any-of with bound parameters. the trigger carets come from phosphor icons, newly adopted. bundle budget rises to 900000 bytes for the picker and the icon dependency.
the five-field form with its apply button is gone. one row holds a search-shaped domain field and a client ip field that debounce into the url with enter flushing at once, result segments and a time menu that commit instantly, and a clear that appears only when a filter is active without reflowing the row. presets freeze both absolute bounds at click time so a bookmark describes the same investigation later, custom ranges apply atomically through set range with inline validation, and echo queues keep in-flight commits from clobbering newer typing or newer picks. live mode renders no toolbar, the availability banner became a footer note, previous rows stay visible during refetch, and every control carries a 44px hit target.
the client address follows its name as visible muted text in the query tables, the config lock indicator prints its reason beside the tag except in table rows where a page-level note explains the lock instead, and the locked delete buttons describe themselves through that one visible note. the chart legend tooltip is deleted because a named client is deliberately not addressed in the chart, and the dead series address field went with it. titles that merely repeat visible copyable text stay.
network assignment rows, activity filters, and the settings password pair now mark the offending input with aria-invalid and point it at the error text with aria-describedby. the prefix validator returns the row and field it is about, and any row mutation clears a message that named a position. settings numeric fields carry aria-invalid on an unparseable value.
the streamed-query detail panel is now a rac modal dialog with focus containment and restore. the live/history switch is rac tabs driven by the url, the overview period picker is a rac radio group, and the clients delete flow uses the shared confirm dialog; an authority turn keeps the dialog open and withdraws only the destructive action.
the header row survives only on narrow screens; on desktop its lone occupant, log out, joins pause in the sidebar footer, both full width. pause leaves the query detail page's related actions, where a global control had no business, and its hand-rolled duration dropdown becomes a react-aria menu with real keyboard navigation, dismissal and positioning.
the shared button variants set no cursor, so only components with local one-off styles showed pointer. all nine variants and the six buttons styled outside them now carry pointer, with not-allowed when disabled.
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.