# ADR-0057: Non-functional-requirement verification strategy ## Status Accepted (2026-06-22). ## Context The non-functional requirements (`requirements.md` NFR-1..7 — startup, input latency, memory, distinctive design, colour use, cross-platform parity, light/dark legibility) were quality bars that had **never been formally verified**, even though v0.2.0 ships public binaries. With users now pulling updates, we want each NFR either **gated by an automated test** (so a regression fails CI) or **measured-and-documented** with a recorded figure and a reviewer judgement — not left as an aspiration. The decision below was taken with the user: contrast is a hard gate; startup and memory are measured against generous bounds (not the literal target — a tight timing/RSS gate would flake on a shared runner); the qualitative bars are verified by argument and reviewer note. ## Decision ### NFR-5 / NFR-7 — colour contrast & legibility: **gated** Unit tests in `src/theme.rs` (Tier 1, run by the existing gate): - **`all_text_colours_meet_wcag_aa_contrast`** — every text-bearing foreground (`fg`, `muted`, mode labels, `system`/`error`/`warning`, `plan_efficient`, and all `tok_*`) must clear **WCAG-AA 4.5:1** against `bg`, in **both** themes. - **`advanced_mode_border_meets_ui_contrast`** — the advanced-mode border carries a mode-warning signal, so it meets the **3:1** non-text / UI-component threshold. The plain `border` is decorative structural chrome and is **deliberately exempt** (it sits at 2.20:1 dark / 1.82:1 light by design, so panel borders recede behind content — recorded here rather than silently tolerated). - **`syntax_token_colours_are_perceptually_distinct`** — contrast against the background is necessary but not sufficient: two token colours can each clear 4.5:1 yet be hard to tell *apart* on a good screen. Every pair of syntax-token classes that can share a line must clear **CIEDE2000 (ΔE2000) ≥ 15** (post-tuning minimum is ~18). The metric is itself validated against the Sharma et al. reference vectors (`delta_e_2000_matches_reference_vectors`). Computing the real ratios while writing these tests **uncovered two shipped defects** in the v0.2.0 light theme — `tok_string` at 4.42:1 and `tok_flag` at 3.15:1, both below the 4.5:1 the scheme promises — plus two dark-theme token pairs that were perceptually close despite distinct hex (`tok_type`↔`tok_keyword`, `tok_function`↔`tok_identifier`, ΔE2000 ~14). All four were fixed in the same change (commit `65eab71`); the gates now hold the palette to both bars permanently. A dev tool, `scripts/palette-preview.py`, renders the live palette with contrast + ΔE2000 for future palette work. ### NFR-1 / NFR-3 — startup & memory: **measured, generously bounded** The Tier-4 PTY harness (ADR-0008 Amendment 1) measures both: - **`startup_to_first_frame_is_reasonable`** — spawn → first rendered frame. - **`idle_memory_footprint_is_reasonable`** — child `/proc/` VmRSS after the app is idle (Linux only). These run against the **debug** binary, so they assert *generous* bounds (startup < 2000 ms, RSS < 150 MB) for **gross-regression detection**, not the literal NFR targets — a tight 500 ms / 50 MB gate on a debug binary under a loaded shared runner would flake. The **real release figures** (measured 2026-06-22, 5 trials each, this dev machine) are the documented verification: | NFR | Target | Release median | Margin | | --- | --- | --- | --- | | NFR-1 startup → first frame | < 500 ms | **~29 ms** | ~17× under | | NFR-3 idle RSS | < 50 MB | **~10 MB** | ~5× under | (Measurement granularity is the harness poll interval, ~20 ms — figures are "tens of ms", not sub-ms precise.) Per the user decision these are **not** wired as tight CI gates; the generous debug-bound tests are the ongoing regression signal. ### NFR-2 — input responsiveness: **verified by architecture** "Long-running queries execute off the UI thread so the interface stays responsive." This is structural and already true: database work runs on a dedicated **worker thread** (ADR-0010), and terminal input is read on a **separate Tokio task** (`spawn_event_reader` in `src/runtime.rs`), so a running query cannot block keystroke handling or rendering. There is no automated keystroke-latency test — a deterministic sub-16 ms measurement in a PTY harness would be flaky and low-value — so NFR-2 is verified by this architectural argument rather than a gate. ### NFR-4 / NFR-6 — distinctive design & cross-platform parity: **reviewer note** Qualitative bars. **NFR-4** (deliberate, identifiable palette/layout using box-drawing) is satisfied by the bespoke two-theme palette, the three-region framed layout, and the annotated plan/relationship rendering — reviewer judgement, not automatable. **NFR-6** is partly evidenced by the CI matrix (Linux + macOS **execute** the suite; Windows is **build-only**, no runner) and is otherwise a reviewer judgement; one documented divergence: on terminals/multiplexers without true-colour passthrough (e.g. a tmux session missing `Tc`/`RGB`), the 24-bit palette is quantised to the 256-colour cube and near hues can collide — a terminal-capability issue, not a palette one. ## Consequences - Contrast and perceptual distinctness are now **permanently gated** — the palette cannot regress below WCAG-AA or into look-alike token colours without failing the build. - Startup and memory have **recorded figures** and a generous gross-regression test; the literal targets are met with large margins. - The qualitative and architectural NFRs are **documented with evidence** rather than asserted. - A latent behaviour found during this work — fast DDL→insert misparsing against a stale schema cache — is tracked as **issue #39** (deferred; no interactive-user impact). ## Requirements bookkeeping NFR-1, NFR-3, NFR-5, NFR-7 → `[x]` (gated test and/or measured with evidence). NFR-2 → `[x]` (architectural). NFR-4, NFR-6 → `[/]` (reviewer note; not fully automatable).