Files
nxdns/specs/ui-visual-redesign.md
T
mokhtar 85b8be50a0
Gates / frontend (push) Successful in 1m57s
Gates / test (push) Successful in 2m34s
Gates / test-aarch64 (push) Successful in 8m9s
Gates / package (push) Successful in 7m14s
Gates / container (push) Successful in 17s
CI / gates (push) Successful in 18m17s
Release / guard (push) Successful in 33s
Gates / test-aarch64 (push) Successful in 7m22s
Gates / container (push) Successful in 11s
Release / gates (push) Successful in 10m35s
Gates / frontend (push) Successful in 2m8s
Gates / test (push) Successful in 2m16s
Gates / package (push) Successful in 44s
Release / publish (push) Successful in 10m4s
admin: overview redesign, device scope, one formatting contract (milestone 39)
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 centred beside the point, a stacked client chart in eight distinct hues plus one Other band that is always a series, and a card row with the cache hit rate, the query types as a single-hue ramp ring, and the upstream breakdown. The count axis grows its margin with the widest grouped tick and draws whole-number ticks only.

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. The route reduces a pasted IPv6 scope to the RFC 5952 spelling the logger stores, mapped addresses included, and drops anything that is not an address. A failed device list says so under the selector with a retry.

All measured quantities go through admin/src/lib/format.ts: grouped counts, two-decimal percentages, one-decimal rates, durations as the two largest nonzero units. Identifiers, configured values and preset labels render as written; the module header states that scope. 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 a one-line description on every panel, the settings form sections on the same card with a floated legend, the sidebar grouped into Monitoring and System with a status block (protection, queries per minute on Overview, uptime), keyboard-focusable table scroll wrappers, 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 reviewed sessions B to D over five rounds (thirty-three findings fixed, thirteen rejected as non-quantities); the owner skipped a sixth round.

Claude-Session: https://claude.ai/code/session_01VTgx3a1zz1R78o4K55kkwR
2026-09-07 23:11:50 +02:00

5.5 KiB
Raw Blame History

UI visual redesign — decision record

Decided 2026-09-03 from the throwaway prototype at scratchpad/proto-overview/index.html (session-local; the screenshots and this record are what survive). This settles the visual language only; the IA, routes and API are ui-redesign.md's and stay as built.

Direction

Pi-hole's structure × NextDNS's visual language, on the light theme. The owner supplied both references directly; four earlier exploration rounds (below) were rejected.

Chosen values

  • Ground and cards. Near-white ground; white cards with hairline borders; radius 4px; card padding ~28px; density 1.00; no panel header bars.
  • Card head. Every card: a 1.4rem weight-650 ink title plus a one-line muted description sentence (0.875rem). The title selector must outrank any heading reset (a .v h2 reset silently ate the title styles in the prototype).
  • Stat tiles. Four tiles: one centered 2.5rem regular-weight numeral over a lowercase muted caption. Semantic color on the numeral itself: ink for totals, red for blocked, muted gray for the percentage, ink for clients. No icons, no borders inside.
  • Toolbar. Two rectangular dropdown selectors (radius token, 44px min-height, hairline border): client scope left, period right. Not pills — the owner rejected the pill shape.
  • Queries chart. Smooth Catmull-Rom area. Total: 2px blue line + 13% blue fill. Blocked: 2px red line + 35% red fill — both series are line-plus-backdrop pairs. Points spread edge to edge: the first bucket sits on the left plot edge, the last on the right; the curve never extends past its own data. Hover: NextDNS-style point hover — a 4px dot on each series (white 2px stroke) plus a thin vertical guide line; no bucket-band highlight. Tooltip sits ~16px beside the point, flipping to the left near the right edge; it never covers the hovered bucket. SVG note: hidden is attribute-only on SVG elements — toggle with setAttribute/removeAttribute, never the .hidden property.
  • Client activity chart. Stacked bars, band-centered, band hover highlight kept. Segments bleed 0.5 viewBox units into the segment below so antialiasing cannot open white seams. Series: the top eight named clients (the /api/overview contract's own count) plus one aggregated flat "Other" band (gray oklch(0.80 0.01 260)) on top. Legend lists the nine names + Other; the tooltip ranks the top four for the bucket, then an Other row, then the total.
  • Client palette (categorical, reserved red at hue 25 excluded): blue oklch(0.60 0.14 258), teal oklch(0.68 0.12 190), violet oklch(0.62 0.15 300), amber oklch(0.75 0.13 80), green oklch(0.65 0.13 150), magenta oklch(0.66 0.14 340), orange oklch(0.70 0.14 55), olive oklch(0.72 0.11 120), sky oklch(0.78 0.09 230). Known-closest pair: amber/orange.
  • Red. oklch(0.65 0.19 25) (#ED5350, 3.54:1 vs white). Compliant for non-text UI and large text (3:1 floor); it must never color small text.
  • Accent. Blue, hue 258, for nav, links and the total-queries series. Green (hue 150) stays the cache/success color. Green and red are fixed semantics, never derived from the accent hue.
  • Ranked lists. Full-bleed rows, hairline dividers, name left with a muted sub-line (list/IP), right-aligned tabular count. Blocked list: a 3px red rule flush on each row's left edge. No hover effect — the rows are not interactive.
  • Progress cards. Cache hit rate as a full-width rounded bar with a colored percentage figure under it (NextDNS's Encrypted DNS pattern).
  • Query types. Donut + legend list with counts and percentages, single-hue lightness/chroma ramp off the accent.
  • Sidebar. Grouped section labels (MONITORING / SYSTEM), the active item as a soft accent-tinted block, and a compact status block at the foot: Active dot + blocking state, queries/min, uptime.
  • Mobile (≤800px). The sidebar becomes a sticky top bar (brand + status pinned, nav scrolls horizontally); everything stacks single-column; the stat tiles merge into one card — numbers side by side, the percentage on a line under.
  • Type. System font stack. No webfont.

Rejected along the way

  • Round 1 (Quiet / Console / Instrument / Signal): dark themes rejected outright; Console's density liked, its darkness not.
  • Round 2 (Ledger / Paper / Grid): a Ledger+Grid mix was "disliked least", never liked.
  • Round 3 (convergence with toggles): the settled config (panels frame, system labels, blue accent, 3px radius) read as competent but had no signature — "not an immediate this-is-it".
  • Round 4 (signatures: IBM Plex type-led, a blocked-share hero Meter, a chart-first Geist): all three rejected; the direct Pi-hole × NextDNS reference replaced them.
  • Chart details rejected in the blend round: pill-shaped selectors; bucket-band hover on the area chart; tooltip centered above the point (covers the data); a solid 0.9-opacity red band (one-color, inconsistent with the blue pair); the curve overhanging the last bucket.
  • Client colors rejected: 15 hues at 24° spacing (rainbow, neighbors indistinguishable); 6 colors + 9 graduated grays (the gray stripes merge into a dull cap). Codex concurred: nine tiny series cannot be made simultaneously legible through color; aggregate the tail.
  • Not adopted from Codex's option list: hue families with lightness steps (15 still exceed discrimination), small multiples (a different card, not a fix), hover isolation (owner declined), top-N-per-bucket recoloring and streamgraphs (both distort trend reading).