Document the regression-hardening work from this session: - ADR-0008 Amendment 1 — Tier 4 realized: the portable-pty + vt100 harness, expectrl dropped, serial/fail-fast design, the four flows, sidebar-region reads, command pacing (issue #39), CI default-target (advances TT5). - ADR-0057 (new) — NFR verification strategy: contrast + ΔE2000 gates (NFR-5/7), startup/RSS measured (NFR-1/3: release ~29 ms / ~10 MB), NFR-2 by architecture, NFR-4/6 reviewer note. Records the two shipped light-theme contrast defects this work caught and fixed. - requirements.md: TT4 [~]->[x], TT5 [/] (only a Windows exec runner left), NFR-1/2/3/5/7 ->[x], NFR-4/6 ->[/], each with evidence. - CHANGELOG.md (new): Keep a Changelog + SemVer, [Unreleased] + [0.2.0]. - README ADR index updated; handoff-76; the working plan. Docs-only (CI gate skips via paths-ignore).
123 lines
6.0 KiB
Markdown
123 lines
6.0 KiB
Markdown
# 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/<pid>` 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).
|