diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..56e09b5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,53 @@ +# Changelog + +All notable, user-facing changes to RDBMS Playground are documented here. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0/). + +## [Unreleased] + +### Added +- Install via **Scoop**, **Homebrew**, and **winget** in addition to the + existing channels. +- Tier-4 end-to-end test suite that exercises the real application in a + terminal (cold launch, save/reopen, export/import, undo) — guards against + regressions the unit and render tests can't see. +- Automated colour-accessibility checks: every theme now has its + foreground/background contrast and its syntax-colour distinctness verified + on each build. + +### Fixed +- **Light theme:** string-literal and flag colours in syntax highlighting + were below the WCAG-AA contrast bar; both are now legible. Two dark-theme + token colours that were hard to tell apart have been separated. +- Windows `install.ps1` now works on the in-box Windows PowerShell 5.1 and + updates `PATH` for the current session. + +## [0.2.0] - 2026-06-17 + +### Added +- **Version surfaces:** a `--version` / `-V` command-line flag and an in-app + `version` command, both reporting the build's exact version. +- **Installation options:** publish to crates.io (`cargo install + rdbms-playground`), `cargo binstall` support, a one-line `curl | sh` + installer, and a Windows `install.ps1` — so you no longer have to hand-pick + a release asset. +- **Documentation & landing site** at — the canonical + user guide plus screencast demos. +- A demonstration-mode key alias (Ctrl-G acting as F1) so screencasts can + show the in-app help/hint key. + +### Fixed +- Corrected several errors in the contextual-hint help text. + +## [0.1.0] - 2026-06-15 + +First public release: the cross-platform terminal sandbox for learning +relational-database concepts — tables, keys, relationships, indexes, queries +and query plans — in a guided simple mode or a full advanced (SQL) mode, with +projects, undo, and export/import. + +[Unreleased]: https://git.lazyeval.net/oli/rdbms-playground/compare/v0.2.0...HEAD +[0.2.0]: https://git.lazyeval.net/oli/rdbms-playground/compare/v0.1.0...v0.2.0 +[0.1.0]: https://git.lazyeval.net/oli/rdbms-playground/releases/tag/v0.1.0 diff --git a/docs/adr/0008-testing-approach.md b/docs/adr/0008-testing-approach.md index e37e189..916d8c6 100644 --- a/docs/adr/0008-testing-approach.md +++ b/docs/adr/0008-testing-approach.md @@ -146,4 +146,57 @@ and on a nightly schedule for any extended coverage. - CI cost is real but bounded: Tiers 1–3 are fast; Tier 4 is the only slow tier and is kept narrow. - Adding a feature implies adding tests at the appropriate tier - (or tiers); coverage is not retrofitted later. \ No newline at end of file + (or tiers); coverage is not retrofitted later. + +## Amendment 1 — 2026-06-22: Tier 4 realized (TT4) + +Tier 4 was a specification only (no PTY deps, no tests) until this +amendment. It is now implemented in `tests/e2e_pty.rs`, and the realized +shape refines the original decision in three ways. + +**Tooling — `expectrl` dropped.** The decision named +`portable-pty` + `expectrl` + `vt100`. We kept **`portable-pty`** (0.9, to +spawn the real binary in a PTY at a fixed size) and **`vt100`** (0.16, to +parse the output stream into an inspectable cell grid), both current and +maintained. We **dropped `expectrl`**: it bundles its own PTY abstraction +(which conflicts with `portable-pty`) and matches line-by-line, a poor fit +for a full-screen TUI. A small hand-rolled `wait_for(predicate, timeout)` +that polls the vt100 screen replaces it — fewer dependencies, and assertions +read the actual rendered grid. + +**Harness shape.** Each test spawns the binary +(`env!("CARGO_BIN_EXE_rdbms-playground")`) under a fresh PTY at **100×30** +(the wide three-region layout, ADR-0046), with its **own temp `--data-dir`** +so it never touches real projects or resume state, and `--theme dark` for +deterministic styling. Tests run **serially** (one PTY + child at a time) +via a poison-tolerant global lock, so timing stays predictable on the +low-parallelism self-hosted runner. Waits are **tight and fail-fast** (3 s): +a slow wait is a genuine hang, and a timeout panics with a full screen dump +for debuggability. + +Two non-obvious robustness rules the harness encodes: +- **Read table presence from the Tables *sidebar* region, not the whole + screen** — the Output panel echoes every command, so a table name typed + in a command would pollute a whole-screen match. +- **Pace each command to completion before the next** (real-user / cast + cadence). A command submitted while the previous one's worker rebuild is + still in flight can be misread against a stale schema cache (issue #39, + discovered here — interactive use is unaffected). + +**The four flows** mirror this ADR's Tier-4 scope: (1) cold launch → first +DDL → graceful quit (Ctrl-C); (2) create → quit → reopen via `--resume`, +asserting the *column* (not just the table name) round-trips; (3) export → +import into a fresh project → schema **and** data rebuilt; (4) `undo` after +`DROP TABLE`, through the Y/N confirm modal. + +**CI.** The target runs by default in `cargo test`, so the existing Linux +gate exercises it on every push — this **advances TT5** (Tier 4 now runs in +CI on Linux). PTY-in-container was validated (openpty + child spawn work +under the Docker runtime the Gitea runner uses); the first real CI run is +the final confirmation. **Windows execution remains out of scope** (no +Windows runner), and the original Tier-4 note about a *nightly broader +coverage* schedule is **not** implemented — the focused four flows are the +committed TT4 scope; broader coverage is deferred. + +**Piggybacked NFR measurement.** The same harness measures NFR-1 (startup +to first frame) and NFR-3 (idle RSS, Linux) — see ADR-0057. \ No newline at end of file diff --git a/docs/adr/0057-nfr-verification-strategy.md b/docs/adr/0057-nfr-verification-strategy.md new file mode 100644 index 0000000..714f149 --- /dev/null +++ b/docs/adr/0057-nfr-verification-strategy.md @@ -0,0 +1,122 @@ +# 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). diff --git a/docs/adr/README.md b/docs/adr/README.md index 7d48835..686156b 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -20,7 +20,7 @@ This directory contains the project's ADRs, recorded per - [ADR-0005 — Column type vocabulary](0005-column-type-vocabulary.md) — the ten-type set (`text`/`int`/`real`/`decimal`/`bool`/`date`/`datetime`/`blob`/`serial`/`shortid`), compound PKs, no true UUIDs; `decimal` stored as exact TEXT. **Amendment 1, 2026-06-12** (issue #32): SQLite has no native decimal/BCD type, so arithmetic/aggregation over a TEXT `decimal` is implicitly coerced to an IEEE-754 double and the computed (typeless) result leaked float noise (`298.59999999999997` for `298.60`); floating-point values are now rounded to **15 significant figures for display only** (`format_real_display` in `db.rs`, wired into `format_cell` — the result-set/`show data` cell formatter, the only surface where arithmetic noise surfaces) while every other f64→string path keeps full precision because the distinction is *semantic*: persistence (`csv_io::format_real`) stays byte-exact for round-trip; `render_value` is a *canonical identity key* for the uniqueness dry-runs (`dry_run_unique` ADR-0029 §5, `check_uniqueness_collisions` ADR-0017 §4.3) so rounding it would report collisions the exact-valued engine wouldn't; FK-key matching and EXPLAIN-SQL literals likewise stay exact — so stored `real`/`decimal` round-trips stay byte-exact and raw `decimal` columns render verbatim - [ADR-0006 — Undo snapshots and replay log](0006-undo-snapshots-and-replay-log.md) — **Accepted**. The **replay/journal half** (U3/U4) shipped via ADR-0034; the **undo/snapshot half** (U1/U2) is settled by **Amendment 1 (2026-05-24)** and **implemented 2026-05-24** (plan: `docs/plans/20260524-adr-0006-undo-snapshots.md`; ring in `src/undo.rs`, worker hook in `src/db.rs`). Amendment 1 **supersedes the original "snapshots only before destructive operations" model**: a snapshot is taken before **every** data/schema mutation (DSL + SQL) for familiar single-step (Ctrl-Z) undo — so the confirmation collapses to *naming the one command being undone* (no db-diff). Snapshot is a **hybrid whole-project copy** — database via the online backup API **plus** `project.yaml`/`data/*.csv` as files — reconciling this ADR with ADR-0015's "text is authoritative, db is derived"; undo restores all three directly. Staged before the mutation's transaction, finalised after the db commit (preserves ADR-0015 §6 commit-db-last); rolled-back ops leave no snapshot. **Persisted** ring under `.snapshots/`, **N = 50** (raised from 10), git-ignored + export-excluded + temp-cleanup-aware. `redo` supported, **redo stack discarded on new work**. **Batch ops record one undo step** (`replay` + future batch via a Begin/EndBatch worker primitive); **`import` is outside undo** (it switches projects per ADR-0015 §11, leaving the current project untouched). A **`--no-undo` CLI flag** disables snapshotting (hardware escape hatch). Adds the `backup` feature to `rusqlite` - [ADR-0007 — Sharing and export](0007-sharing-and-export.md) -- [ADR-0008 — Testing approach](0008-testing-approach.md) +- [ADR-0008 — Testing approach](0008-testing-approach.md) — the four-tier strategy (pure-logic units → `TestBackend` render/`insta` snapshots → synthetic event-loop integration → PTY black-box). **Amendment 1 (2026-06-22): Tier 4 realized (TT4).** `tests/e2e_pty.rs` drives the actual built binary in a real pseudo-terminal — `portable-pty` + `vt100`, **`expectrl` dropped** (conflicting PTY layer; replaced by a hand-rolled `wait_for` on the vt100 grid). Fixed 100×30, per-test temp `--data-dir`, serial, tight fail-fast 3 s waits; table presence read from the Tables **sidebar** region (the Output panel echoes commands); commands **paced to completion** (a command sent mid-rebuild can misparse against a stale schema cache, issue #39). The four flows: cold-launch→DDL→quit; create→reopen (`--resume`, column round-trips); export→import→rebuild (schema **+** data); undo-after-DROP through the modal. Runs by default in `cargo test` → the Linux gate exercises it (**advances TT5**); PTY-in-container validated; Windows execution + nightly broader coverage stay out of scope. Also piggybacks NFR-1/NFR-3 measurement (ADR-0057) - [ADR-0009 — DSL command syntax conventions](0009-dsl-command-syntax-conventions.md) - [ADR-0010 — Database access via a dedicated worker thread](0010-database-access-via-worker-thread.md) - [ADR-0011 — Foreign-key column type compatibility](0011-fk-column-type-compatibility.md) @@ -69,3 +69,4 @@ This directory contains the project's ADRs, recorded per - [ADR-0054 — Release versioning policy + version surfaces (`--version` / `version`)](0054-release-versioning-and-version-surfaces.md) — **Accepted + implemented 2026-06-16** (plan: `docs/plans/20260616-public-availability.md`, step 1 on the road to public availability; no prior issue/`requirements.md` item — an untracked gap). Fixes the **tag↔crate-version decoupling**: `Cargo.toml` built `0.1.0` while `release.yaml` named assets from the git tag, so a binary could report a version different from the asset it shipped in. **Decision:** `Cargo.toml` `version` is the **single source of truth** (read via `env!("CARGO_PKG_VERSION")`, no tag-injection); two surfaces report it through one `cli::version_text()` → catalog `cli.version_line` — a **`--version` / `-V`** CLI flag (mirrors `--help`, prints+exits in `main.rs`) and an in-app **`version`** command (REGISTRY node `app::VERSION`, `AppCommand::Version`, emits via `note_system`); and a **release-CI version guard** (`release.yaml` `test` job reads the `[package]` version from `Cargo.toml` and **fails the release** unless the `v*` tag equals `v`; the guard's parse was later switched from `cargo metadata | node` to a `grep` on Cargo.toml after the former broke on the flake devShell's stdout banner). Release ritual: bump `Cargo.toml` → commit → tag → push. New keys `cli.version_line` + `help.app.version` + `parse.usage.version` + `hint.cmd.version.{what,example}` (the new REGISTRY command pulls in the comprehensiveness coverage gate). Rejected: tag-as-source (makes Cargo.toml lie). Deferred: git-hash/build-date enrichment (behind the same `version_text()` seam); UI placement beyond the command. Tested test-first: CLI parse (`--version`/`-V`/default-off), `version_text()` carries `CARGO_PKG_VERSION`, the in-app command parses + emits. Also corrected a stale `release.yaml` header comment ("macOS is deferred" → built by the dispatched `release-macos.yaml`). - [ADR-0055 — `curl | sh` install script (`scripts/install.sh`)](0055-curl-sh-install-script.md) — **Accepted + implemented 2026-06-17** (plan: `docs/plans/20260616-public-availability.md`, step 2; tracked by plan + ADR, no Gitea issue — user decision). A one-line installer (`curl -fsSL /scripts/install.sh | sh`) so beginners don't hand-pick an asset + `chmod +x`. **POSIX `sh`** (shellcheck-clean), detects `uname` OS/arch → target triple (**Linux → the fully-static `*-musl`** build, macOS → `*-apple-darwin`; `amd64`/`arm64` aliased; **Windows rejected** → Scoop/winget/releases page), resolves the version from the **`releases/latest`** API (or `RDBMS_VERSION` to pin), downloads the asset **and its `.sha256` and verifies it** (mismatch aborts), installs to `~/.local/bin` (`RDBMS_INSTALL_DIR` override) with a PATH hint. Testing seams: `RDBMS_OS`/`RDBMS_ARCH` + `--print-target`. macOS note: `curl` downloads aren't Gatekeeper-quarantined so the ad-hoc binary runs as-is (Developer-ID + notarization is the postponed signing task). **Verified end-to-end against the live public `v0.1.0`** (all platform mappings, pinned + latest, checksum incl. tamper-rejection, install + run). Rejected: website-domain hosting (extra moving part; Gitea raw is simplest); deferred: uploading the script as a release asset, and a **shellcheck CI gate** (shellcheck isn't in the flake — touches ADR-ci-002). **Amendment 1 (2026-06-17):** added a Windows **`scripts/install.ps1`** (`irm | iex`; maps host CPU → our `*-windows-gnu`/`-gnullvm` `.exe`, SHA-256-verifies, installs to `%LOCALAPPDATA%\Programs\…` + user PATH) — user chose both a one-liner *and* Scoop/winget; **written but untested from this env** (no PowerShell — validate on Windows). - [ADR-0056 — crates.io publish-readiness + `cargo binstall` metadata (D3)](0056-crates-io-and-cargo-binstall.md) — **Prepared 2026-06-17** (plan step 3a; tracked by plan + ADR). Makes the crate **ready to publish** to crates.io (user decision) and adds `cargo-binstall` metadata; the actual `cargo publish` is a **gated, irreversible maintainer step**. Manifest: drops `publish = false`; adds `homepage` (relplay.org), `keywords`, `categories`, and an `exclude` (`/website`,`/docs`,`/.gitea`,`/.codegraph`) trimming the crate from 585 files/8.3 MiB → **353/913 KiB compressed** (code-only). Authors **`README.md`** (engine-neutral, simple/advanced-mode wording; install via curl|sh/binstall/source/prebuilt) and **`LICENSE-MIT`** (© Lazy Evaluation Ltd — *confirm holder*); the canonical **`LICENSE-APACHE`** is deferred to the maintainer (don't ship retyped legal text) — the SPDX `license` field already satisfies crates.io. **binstall** (syntax verified vs cargo-binstall SUPPORT.md): `pkg-fmt = "bin"` (bare binaries), `pkg-url` spelled `v{ version }` (the placeholder omits the `v`), plus per-target **`overrides`** mapping the common host triples to the assets we ship — `*-linux-gnu` → the static `*-linux-musl` build, `*-pc-windows-msvc` → `*-gnu`/`-gnullvm` `.exe` (macOS matches directly; the docs promise no automatic fallback). **Ordering:** publish at a **new tagged version whose release exists**, after the release — **not `0.1.0`** (diverges from the already-released 0.1.0 binaries that predate `--version`). Verified: `cargo publish --dry-run` packages + verify-builds; `cargo metadata` confirms the binstall block + 4 overrides. **Unverified:** a real `cargo binstall` run (not a dep; nothing on crates.io yet) — validate at first publish. Rejected: cargo-dist (GitHub-centric). Maintainer follow-ups: confirm © holder, add canonical `LICENSE-APACHE`, real binstall validation. **Amendment 1 (2026-06-18):** `0.2.0` **published live** (crates.io; `cargo install` + `cargo binstall` verified — the unverified-overrides caveat is resolved), via a new **manual `workflow_dispatch`** workflow `.gitea/workflows/publish.yaml` (mirrors `release-macos.yaml`; `tag` input; `cargo publish` with a crate-scoped `CARGO_REGISTRY_TOKEN` secret). Publish stays **manual** by decision — irreversible (keeps the token off every tag push), the split release (tag Linux/Windows + dispatched macOS) makes a human the "all assets up" gate, and crates.io has no Gitea-Actions trusted-publishing path. Each registry is its **own idempotent job** (crates.io job no-ops if the version exists) so Scoop/Homebrew/winget can be added as sibling jobs without interfering. **Amendment 2 (2026-06-19):** **Scoop + Homebrew wired** (D3 §3b/§3c) as sibling `publish.yaml` jobs (`scoop-bucket`, `homebrew-tap`) that render manifests from the release `.sha256` sidecars and push to **org-level, multi-package** repos `lazyeval/scoop-bucket` + `lazyeval/homebrew-tap`. Credential: a scoped bot user **`lazyeval-ci`** (Gitea PATs scope by permission-category, not per-repo, so an `oli` token would over-reach to the main repo) on a `lazyeval` org team with Write to the package repos only; its PAT is the `LAZYEVAL_PKG_TOKEN` secret on `oli/rdbms-playground`. Render scripts (`scripts/render-{scoop-manifest,homebrew-formula}.sh`) are **dependency-free bash** (CI image `node:22-slim` has no jq/ruby), tested by `scripts/test-package-renders.sh`. Scoop: `#/`-rename fragment + `checkver`, no `autoupdate`. Homebrew: `on_macos`/`on_linux`×arch bare-binary formula, no Windows. **Unverified:** real `scoop`/`brew install`, the `HEAD:main` branch assumption, macOS Gatekeeper-via-brew (ad-hoc sign). **Remaining D3:** winget. **Amendment 3 (2026-06-19):** **Scoop + Homebrew validated end-to-end** on real Windows + macOS (install + run). Found + fixed a **presigned-URL/HEAD gotcha**: Gitea (≥1.25) 303-redirects release downloads to a **method-bound SigV4 presigned S3 URL**; Homebrew resolves via HEAD, captures the HEAD-signed URL, then GETs it → 403 (GET-only tools unaffected). Fix is a **server-side Caddy rule** (lives in the Gitea edge config, NOT this repo — record it: lost on a rebuild → brew 403s again) answering HEAD on `…/releases/download/…` directly (200, no redirect) so brew's download GET re-runs the redirect fresh. ad-hoc mac signature **runs** via brew; Developer-ID + notarization still parked on the Apple org conversion. **Remaining D3:** winget only. **Amendment 4 (2026-06-20):** **winget wired** (D3 §3d) — the 4th `publish.yaml` sibling job opens a PR to the central, human-gated `microsoft/winget-pkgs` via **komac** (pinned 2.16.0; `LazyEvaluation.RdbmsPlayground`, `portable`, x64+arm64). Async + Microsoft-validated, unlike the bucket/tap. Auth: a **classic `public_repo` PAT** (fine-grained can't open the cross-fork PR, komac #310) on a **dedicated GitHub bot** → `WINGET_GITHUB_TOKEN`, job-scoped, passed via curl config file (not argv). Idempotent via **two guards** (already-merged version + already-open PR) so re-dispatch is safe. Unsigned is fine to submit (portable; only MSIX needs signing) — may earn an AV/SmartScreen manual-review label first time. **One-time `komac new` bootstrap is manual** (interactive); CI `komac update` handles releases after. Completes the D3 package-manager set. +- [ADR-0057 — Non-functional-requirement verification strategy](0057-nfr-verification-strategy.md) — **Accepted 2026-06-22.** How NFR-1..7 are verified now that public binaries ship. **Gated:** NFR-5/NFR-7 colour contrast — `src/theme.rs` unit tests assert every text foreground clears **WCAG-AA 4.5:1** on both themes, the advanced-mode border clears the **3:1** UI bar (plain border decorative-exempt, documented), and every syntax-token pair clears **CIEDE2000 ΔE2000 ≥ 15** (metric reference-validated). Writing these **caught two shipped light-theme defects** (`tok_string` 4.42:1, `tok_flag` 3.15:1) + two near-duplicate dark pairs — all fixed (`65eab71`); dev tool `scripts/palette-preview.py`. **Measured, generously bounded:** NFR-1 startup + NFR-3 idle RSS via the Tier-4 PTY harness (debug-binary gross-regression bounds, not a tight gate per user decision); recorded **release** figures — startup **~29 ms** (< 500 ms), idle RSS **~10 MB** (< 50 MB). **By argument:** NFR-2 off-thread responsiveness (worker thread ADR-0010 + separate input task). **Reviewer note:** NFR-4 distinctive design, NFR-6 cross-platform parity (CI matrix: Linux+macOS execute, Windows build-only; one documented divergence — true-colour quantisation on non-`Tc` terminals). Latent finding **issue #39** (fast DDL→insert vs stale schema cache) deferred. diff --git a/docs/handoff/20260622-handoff-76.md b/docs/handoff/20260622-handoff-76.md new file mode 100644 index 0000000..e67ca51 --- /dev/null +++ b/docs/handoff/20260622-handoff-76.md @@ -0,0 +1,98 @@ +# Session handoff — 2026-06-22 (76) + +Continues from handoff-75 (D3 package managers). This session took up the +**regression-hardening** trio identified as "what's next" now that public +binaries ship: **TT4** (Tier-4 PTY tests), **NFR verification**, and a +**CHANGELOG** — plus a real palette accessibility fix the work uncovered. + +## §1. State + +**Branch `main`.** Three commits this session: +- `65eab71` `fix(theme)` — WCAG-AA palette fix + contrast/ΔE2000 gates + + `scripts/palette-preview.py`. +- `fd63de3` `test(tt4)` — Tier-4 PTY end-to-end suite (`tests/e2e_pty.rs`). +- **(pending)** a docs commit — ADR-0008 Amendment 1, ADR-0057, README index, + `requirements.md`, `CHANGELOG.md`, this handoff, and the plan doc + `docs/plans/20260622-tt4-nfr-changelog.md`. *(Propose + confirm before + committing; not yet committed at time of writing.)* + +**Test baseline: 2519 passed / 0 failed / 1 ignored** (was 2509; +4 theme +gates, +6 e2e_pty). `clippy --all-targets -D warnings` + `fmt --check` clean. +Full suite verified green 3× under parallel load. + +## §2. What shipped + +**Palette accessibility (ADR-0057, commit `65eab71`).** Computing real WCAG +ratios while writing the gate **found two shipped defects** in v0.2.0's light +theme — `tok_string` (4.42:1) and `tok_flag` (3.15:1), below the 4.5:1 the +scheme promises — and two dark token pairs that were perceptually close +despite distinct hex. All four fixed (only `theme.rs` colours changed, 4 +values). New gated tests in `src/theme.rs`: +- `all_text_colours_meet_wcag_aa_contrast` (≥4.5:1, both themes), +- `advanced_mode_border_meets_ui_contrast` (≥3:1; plain border exempt), +- `delta_e_2000_matches_reference_vectors` (CIEDE2000 validated vs Sharma data), +- `syntax_token_colours_are_perceptually_distinct` (ΔE2000 ≥15). +Dev tool **`scripts/palette-preview.py`** renders the live palette (truecolor +swatch + contrast + ΔE2000), supports `theme:key=HEX` overrides for previewing +changes. + +**TT4 — Tier-4 PTY tests (ADR-0008 Amendment 1, commit `fd63de3`).** +`tests/e2e_pty.rs` drives the **real built binary** in a pseudo-terminal: +`portable-pty` 0.9 + `vt100` 0.16 (**`expectrl` dropped** — conflicting PTY +layer; replaced by a hand-rolled `wait_for` on the vt100 grid). 100×30, temp +`--data-dir`, serial, tight 3 s fail-fast waits. The four ADR-0008 flows all +green. Runs by default in `cargo test` → the Linux gate now exercises Tier 4 +(**advances TT5**). PTY-in-container validated (openpty+spawn under Docker). +- **Gotchas baked into the harness:** read table presence from the Tables + **sidebar** region (the Output panel echoes commands, polluting whole-screen + matches); **pace commands to completion** (a command sent mid-rebuild + misparses — issue #39); simple-mode insert needs the `values` keyword and a + single-value insert needs `add column` (a 2nd col in `with pk …` makes a + **compound PK**). + +**NFR verification (ADR-0057).** All seven NFRs formally verified: +- NFR-1/3 **measured** via the PTY harness — release figures **~29 ms** startup + / **~10 MB** idle RSS (well under 500 ms / 50 MB). Harness tests gate generous + *debug*-binary bounds for gross-regression detection (tight gates declined as + flaky — user decision). +- NFR-5/7 **gated** (the contrast/ΔE2000 tests above). +- NFR-2 by architecture (worker thread ADR-0010 + separate input task). +- NFR-4/6 reviewer note (`[/]`). + +**CHANGELOG.md** — Keep a Changelog + SemVer; `[Unreleased]` + `[0.2.0]` +(0.1.0 = pre-history). v0.2.0 tag = `bd5be5e`; the palette fix + package +managers + TT4 are post-tag → Unreleased. + +## §3. Decisions taken with the user + +- Palette: fix the two light contrast bugs **and** the two dark near-duplicates + (4 hex changes, approved after a rendered preview). +- Borders <3:1 → **accept + document** (decorative chrome). +- Work directly on `main`; ADR-0057 numbered now (not draft). +- NFR perf: measured + generously bounded, **not** tightly CI-gated. +- CHANGELOG depth: 0.2.0 + Unreleased only. +- Schema-cache finding → **file a Gitea issue, defer the fix** (see §4). + +## §4. Open / follow-ups + +- **Issue #39** (filed this session): simple-mode **Form-B insert** + (`insert into T values (…)`) submitted faster than the post-DDL **schema-cache + refresh** validates against stale schema and is misparsed as SQL. No + interactive-user impact (cast driver + tests pace); deferred fix. Repro + + diagnosis in the issue. +- **TT5 remaining:** only a **Windows execution runner** now (macOS + Tier-4 + gaps closed this session). First real CI run is the final confirmation that + the PTY tests pass in the Gitea container (validated locally; very low risk). +- Untouched larger items from the "what's next" survey: hint/help issues + **#36/#37/#38**; the big features (V4 session journal, TU1 tutorial). The + CHANGELOG can later feed `--release-notes-url` into the CI winget job + (handoff-75 §6). + +## §5. Process pins + +- Commits user-confirmed, no AI attribution, append-only, **push is the user's + step**. +- `/runda` + DA pass was run on both the plan and the implementation (it caught + the two light-theme contrast bugs and the #39 schema-cache behaviour — both + before finalizing). +- Consider a `cargo sweep` at this milestone (release build added ~ a GB). diff --git a/docs/plans/20260622-tt4-nfr-changelog.md b/docs/plans/20260622-tt4-nfr-changelog.md new file mode 100644 index 0000000..d9c71de --- /dev/null +++ b/docs/plans/20260622-tt4-nfr-changelog.md @@ -0,0 +1,189 @@ +# Plan — TT4 (Tier-4 PTY tests) + NFR verification + CHANGELOG + +Status: **approved 2026-06-22**, implementation pending. Supersedes the +transient harness plan-mode copy. This is the persistent, version-controlled +plan per project convention (`docs/plans/`). + +## Context + +RDBMS Playground now ships public binaries (v0.2.0 on crates.io, plus +Scoop/Homebrew/winget). With real users pulling updates, the priority is +**regression protection before the next release**. Three tracked gaps are taken +up together: + +- **TT4** (`requirements.md`, ADR-0008 Tier 4): the four critical end-to-end + flows are specified but **no PTY harness exists** — no deps, no tests. Tiers + 1–3 never exercise the *real built binary* through a terminal. +- **NFR-1..7**: quality bars (startup, latency, memory, contrast, distinctive + design, parity) that were **never formally verified** despite shipping. +- **CHANGELOG**: none exists; useful now that releases are public (and lets the + CI `winget` job pass `--release-notes-url` later). + +Baseline (Phase 1): `cargo test` = **2509 passed / 0 failed / 1 ignored**; +clippy + fmt clean. This is the number Phase 5 verifies against (new tests add +to it; nothing may regress). + +User decisions (Phase 3): +- **TT4 in CI:** own `e2e_pty` test target, runs by default in `cargo test` + (so the Linux gate exercises it every push). **Tight, fail-fast timeouts** — + the runner is self-hosted, low-parallelism, low expected flakiness (so a + failure surfaces fast rather than hanging on a generous timeout). +- **NFR:** WCAG contrast = hard gated test; startup + idle-memory = measured via + the PTY harness against *generous* bounds + numbers recorded; NFR-2/4/6 = + verify-by-argument + reviewer note. Each requirement → `[x]`/`[/]` with + evidence. +- **CHANGELOG:** Keep a Changelog + SemVer; `[Unreleased]` + a single + consolidated `[0.2.0]` only (0.1.0 treated as pre-history). + +--- + +## Workstream 1 — TT4: Tier-4 PTY harness + 4 flows + +### Tooling (refines ADR-0008's stated `portable-pty`+`expectrl`+`vt100`) +- **`portable-pty`** (wezterm, actively maintained) — spawn the real binary in a + PTY with a fixed window size. +- **`vt100`** — parse the PTY output stream into an inspectable cell grid. +- **Drop `expectrl`** — it bundles its *own* PTY abstraction (conflicts with + portable-pty) and is line-oriented, a poor fit for a full-screen TUI. Replace + it with a small hand-rolled `wait_for(predicate, timeout)` polling the vt100 + screen. This deviation is recorded in the ADR-0008 amendment. +- **Checkpoint at implementation:** verify `vt100` is still maintained/current + (global currency rule). If stale, evaluate `avt` as the screen parser before + committing the dep. `cargo add` picks latest; pin sensibly. + +### Harness (`tests/e2e_pty.rs` — new `[[test]]` target) +A `support` module providing: +- `struct PtyApp` — opens a PTY (e.g. **100×30**, the wide three-region + layout), spawns `env!("CARGO_BIN_EXE_rdbms-playground")` with a **per-test + temp `--data-dir`** (`tempfile::TempDir`), `--theme dark`, `TERM=xterm-256color`; + a reader thread feeds bytes into a `vt100::Parser`. +- `send(bytes)` (raw; `\r` = Enter, `\x03` = Ctrl-C), `send_line(s)` = + `s` + `\r`. +- `wait_for(&str, Duration)` — poll the vt100 screen text until the substring + appears or the (tight, ~**3 s**) timeout fires; on timeout **panic with a full + screen dump** (fail-fast + debuggable). A `screen_text()` helper for assertions. +- `quit()` — `send("\x03")`, wait for child exit, assert success. +- Run **serially** (`serial_test` dep, or a shared `Mutex`/single-threaded + target) — PTYs + temp dirs shouldn't race; also keeps timing stable. +- **Instrument** with `eprintln!`/screen dumps on every wait so a CI failure is + diagnosable from logs (per the project's logging discipline). + +### The four flows (one `#[test]` each, mirroring ADR-0008 §Tier-4) +1. **Cold launch → DDL → quit.** Launch fresh; `wait_for("SIMPLE")` + + `wait_for("(none yet)")`; `send_line("create table Customers with pk + id(serial)")`; `wait_for("Customers")` in the Tables panel; `quit()`. +2. **Save → restart → reopen.** Launch project path P under the temp data-dir; + create a table; `quit()` (autosave already persisted per-command); relaunch + **same P**; `wait_for("Customers")` — schema restored from text/rebuild. +3. **Export → import → rebuild.** Project A: create table + insert a row; + `send_line("export A.zip")` (explicit path under the temp dir); + `wait_for` export-success note; `quit()`. Fresh project B: + `send_line("import /A.zip")`; `wait_for` import note (rebuild auto-runs + on missing `.db`); assert the table + row are present (`show data` / + Tables panel). +4. **Undo after DROP.** Create table; `send_line("drop table Customers")`; + confirm gone; `send_line("undo")`; **`wait_for("Restore that earlier + state?")`** (the real modal); `send("y")`; `wait_for("Customers")` restored. + +### CI +No workflow edit needed for execution: `e2e_pty` is a default target, so the +existing `gate` job's `cargo test --no-fail-fast` runs it on the Linux +container. **Validate locally first** that a PTY opens inside the +`rdbms-playground-ci` image (container `/dev/ptmx`); if not, add `--init`/pts +mount notes to the ADR. Advances **TT5** (Tier-4-in-CI on Linux); Windows +execution runner stays out of scope (no runner). + +--- + +## Workstream 2 — NFR verification + +### Gated: WCAG contrast (NFR-5, NFR-7) — `src/theme.rs` `#[cfg(test)]` +- Add a `relative_luminance(Color) -> f64` + `contrast_ratio(a,b) -> f64` + helper (WCAG 2.x sRGB formula) in the test module; a `match Color::Rgb` channel + extractor (panics on non-RGB — also a guard against a future named-colour + regression). +- For **both** `Theme::light()` and `Theme::dark()`, assert **≥ 4.5:1** for + normal-text foregrounds on `bg`: `fg`, `error`, `warning`, `system`, + `plan_efficient`, `mode_simple`, `mode_advanced`, and the info-carrying syntax + tokens (`tok_keyword/identifier/type/number/string/flag/function/error`). +- **`muted`/`tok_punct`** (deliberately dim secondary text) and **borders** + (`border`/`border_advanced`, non-text UI): assert the WCAG **non-text 3:1** + threshold, with a comment citing the WCAG large-text/non-text allowance. If + any *fails even 3:1*, that's a real finding → surface to the user, don't + silently relax. (Supersedes the existing inequality-only theme tests.) +- This runs in the existing gate — permanent regression protection on the palette. + +### Measured + documented: startup (NFR-1) + idle memory (NFR-3) +Two measurement tests in `e2e_pty.rs`: +- **Startup:** time from spawn to `wait_for("SIMPLE")` (first rendered frame); + assert a **generous** bound (e.g. < 1500 ms locally) and `eprintln!` the actual. +- **Idle memory:** after the app is idle, read the child's `/proc//status` + `VmRSS` (Linux); assert a generous bound and print actual. (Linux-gated via + `#[cfg(target_os = "linux")]`.) +The point is gross-regression detection, not a tight SLA gate (user decision). + +### Verify-by-argument + reviewer note (NFR-2, NFR-4, NFR-6) +- **NFR-2** (input off-thread): already architecturally true — DB runs on the + worker thread (ADR-0010) and input on a separate Tokio task (confirmed in + `runtime.rs`). Record the evidence; optionally a test that a query doesn't + block a subsequent keystroke render. +- **NFR-4** (distinctive design) / **NFR-6** (cross-platform parity): qualitative + / partly-CI. Written reviewer verification (DA-hat) in the NFR verification doc; + parity partly evidenced by the CI matrix (Linux+macOS execute, Windows builds). + +### Requirements bookkeeping +Move NFR-5/7 → `[x]` (gated test), NFR-1/3 → `[x]` (measured + bounded test + +recorded numbers), NFR-2 → `[x]` (architectural evidence), NFR-4/6 → `[/]` with +the reviewer note (honest: not fully automatable). Each with a commit/test ref. + +--- + +## Workstream 3 — CHANGELOG + +- New **`CHANGELOG.md`** at repo root, **Keep a Changelog** + SemVer. +- `[Unreleased]` (empty/seeded) + one consolidated **`[0.2.0] - `** + with Added/Changed/Fixed of the notable user-facing changes (install methods, + packaging, `--version`/`version`, seed improvements, hint feature, readline + keys, cross-mode history) mined from ADRs 0053–0056 + handoffs 73–75. 0.1.0 = + pre-history (a one-line note). +- Optional follow-up (flag, don't auto-do): wire `--release-notes-url` into the + CI `winget`/release path — out of scope unless you want it now. + +--- + +## Docs / ADR updates (project discipline) + +- **Amend ADR-0008** (Tier-4 section): record the realized tooling + (`portable-pty` + `vt100` + hand-rolled `wait_for`, `expectrl` dropped), + serial execution, tight-timeout/fail-fast stance, the default-target CI wiring, + and the four implemented flows. Update `docs/adr/README.md` in the same edit. +- **New ADR-0057 — NFR verification strategy** (next free number; assigned at + merge per ADR-0000): the contrast-gate + thresholds (incl. the 3:1 + muted/border allowance), the measured-not-tightly-gated stance for + startup/memory, and the verify-by-argument treatment of NFR-2/4/6. Add a + short **NFR verification record** (the measured numbers + reviewer notes) — + either in the ADR or a `docs/` companion. Update the README index. +- Update `docs/requirements.md`: TT4 `[~]`→`[x]`, TT5 `[/]` note (Tier-4 now in + Linux CI; Windows-exec still pending), NFR rows as above. +- A handoff note (next sequence number) per project convention. + +--- + +## Verification (Phase 5) + +1. `cargo test` — full suite green; new e2e_pty + contrast tests pass; **2509 + + new, 0 failed, ≤1 ignored**; no regressions vs baseline. +2. `cargo clippy --all-targets -- -D warnings` + `cargo fmt --check` clean. +3. Run `e2e_pty` repeatedly (e.g. 5–10×) locally to confirm non-flaky under the + tight timeouts before relying on the gate. +4. Confirm a PTY opens inside the CI image (or document the fix). +5. DA-hat pass over each workstream + the NFR reviewer judgements, written down. +6. `cargo sweep` at the milestone (CLAUDE.md build hygiene). + +## Risks / checkpoints +- **vt100 currency** — verify before adding; fall back to `avt` if stale. +- **PTY-in-container** — validate early; the whole TT4 CI value depends on it. +- **muted/border contrast** — may fail even 3:1; if so it's a finding to escalate, + not silently relax (could mean a small palette tweak — touches a decided area, + so escalate). +- **Commits** — confirm each message; no AI attribution; append-only; never push. diff --git a/docs/requirements.md b/docs/requirements.md index 82b0b12..a941261 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -918,28 +918,34 @@ since ADR-0027.) for representative views. - [x] **TT3** Tier 3: synthetic event-loop integration tests covering the user-facing flows in this checklist. -- [~] **TT4** Tier 4: PTY-based end-to-end for the four critical +- [x] **TT4** Tier 4: PTY-based end-to-end for the four critical flows named in ADR-0008 (cold launch → DDL → quit; save → reopen; export → import → rebuild; undo after DROP). - *(Verified 2026-06-07: **nothing is wired** — no - `portable-pty` / `expectrl` / `vt100` dependencies, no PTY test - files; ADR-0008 §Tier-4 is a specification only. The Tier-3 - `tests/it/*_e2e.rs` files are synthetic event-loop tests, not - PTY. Correcting a stale `CLAUDE.md` line that read "Tier 4 is - wired only for the listed critical flows" — it was not wired at - all. Genuinely deferred.)* + *(Implemented 2026-06-22 — ADR-0008 **Amendment 1**; + `tests/e2e_pty.rs` (commit `fd63de3`). Drives the actual built + binary in a real pseudo-terminal: `portable-pty` + `vt100` + (**`expectrl` dropped** — conflicting PTY layer, replaced by a + hand-rolled `wait_for` on the vt100 grid). Fixed 100×30, per-test + temp `--data-dir`, serial, tight fail-fast 3 s waits; table + presence read from the Tables sidebar region (Output echoes + commands); commands paced to completion (stale-schema-cache + misparse on faster-than-human input → issue #39). All four flows + green (flow 2 asserts a **column** round-trips, not just the + table name). Was previously a spec-only deferral; the earlier + 2026-06-07 "nothing is wired" note is now resolved.)* - [/] **TT5** CI runs all tiers on Linux, macOS, and Windows on stable Rust. - *(Partial, 2026-06-15. **CI is live** on the self-hosted Gitea - Actions (`docs/ci/adr/`): the gate runs `clippy -D warnings` + - `cargo test` (Tiers 1–3) on the **Linux** runner for every branch - push / PR, and `release-macos` runs the suite natively on the - **macOS** runner. **Windows is build-only** — cross-compiled, not - executed (no Windows runner). **Tier 4** (PTY, TT4) is still - unwired, so "all tiers" is not yet fully met. "Stable Rust" is - satisfied by the flake's pinned `1.95.0` (a stable release, not - nightly). Remaining for full TT5: a Windows execution runner and - Tier-4 PTY in CI.)* + *(Partial, updated 2026-06-22. **CI is live** on the self-hosted + Gitea Actions (`docs/ci/adr/`): the gate runs `clippy -D warnings` + + `cargo test` (now Tiers **1–4** — the `e2e_pty` target runs by + default) on the **Linux** runner for every branch push / PR, and + `release-macos` runs the suite natively on the **macOS** runner. + **Tier 4 in CI on Linux is now met** (TT4); PTY-in-container was + validated (openpty + spawn under Docker), first real CI run is the + final confirmation. **Windows is build-only** — cross-compiled, + not executed (no Windows runner). "Stable Rust" is satisfied by + the flake's pinned `1.95.0`. **Remaining for full TT5: a Windows + execution runner** (the macOS + Tier-4 gaps are now closed).)* ## Cross-cutting @@ -1015,41 +1021,80 @@ target is measurable, it is stated numerically; where it is necessarily qualitative, the criterion is named and the bar is "reviewer judgement against the criterion." -- [ ] **NFR-1 Performance — startup.** Cold launch to first +All seven were formally verified 2026-06-22 (ADR-0057). Approach: +contrast/distinctness **gated** by tests, startup/memory **measured** +against the targets, the rest **verified by argument / reviewer note**. + +- [x] **NFR-1 Performance — startup.** Cold launch to first rendered frame under 500ms on commodity hardware (developer - laptop, mid-range desktop). Measured in CI on the Linux runner - as a regression gate. -- [ ] **NFR-2 Performance — input latency.** Keystroke-to-render + laptop, mid-range desktop). + *(Verified 2026-06-22, ADR-0057 — measured via the Tier-4 PTY + harness; release-binary median **~29 ms** to first frame (5 + trials, this dev machine), ~17× under target. The harness test + `startup_to_first_frame_is_reasonable` gates a generous + debug-binary bound for gross-regression detection; a tight 500 ms + CI gate was declined as flaky on a shared runner — user decision.)* +- [x] **NFR-2 Performance — input latency.** Keystroke-to-render latency under 16ms during normal editing; long-running queries must execute off the UI thread so the interface remains responsive (typing, scrolling, mode switching) while a query is running. -- [ ] **NFR-3 Performance — resource footprint.** Idle memory + *(Verified 2026-06-22, ADR-0057 — by architecture: database work + runs on the dedicated worker thread (ADR-0010) and terminal input + on a separate Tokio task (`spawn_event_reader`, `runtime.rs`), so + a running query cannot block input/render. No automated sub-16 ms + latency test — it would be flaky and low-value.)* +- [x] **NFR-3 Performance — resource footprint.** Idle memory under 50MB on the smallest target platform; no busy-loops; CPU near zero when waiting for input. -- [ ] **NFR-4 Visual quality — distinctive design.** Colour + *(Verified 2026-06-22, ADR-0057 — release-binary idle RSS + **~10 MB** (5 trials, Linux), ~5× under target. The event loop + blocks on `recv` (no busy-loop). The harness test + `idle_memory_footprint_is_reasonable` gates a generous + debug-binary RSS bound on Linux.)* +- [/] **NFR-4 Visual quality — distinctive design.** Colour palette and typography are deliberate and consistent across views; layout uses Unicode box-drawing and symbols where they add clarity; rendering avoids the generic flat-default look that ships with most TUI frameworks. Criterion: a reviewer can identify the app from a screenshot of any view. -- [ ] **NFR-5 Visual quality — colour use.** Colour conveys + *(Reviewer note 2026-06-22, ADR-0057 — satisfied by the bespoke + two-theme palette, the three-region framed layout, and the + annotated plan/relationship rendering; qualitative, not + automatable.)* +- [x] **NFR-5 Visual quality — colour use.** Colour conveys information rather than decoration: mode indication, query result types (numeric vs text vs null), error severity, syntax highlighting categories. Foreground/background combinations meet WCAG-AA contrast (4.5:1 for normal text) even though we have not committed to broader accessibility. -- [ ] **NFR-6 Cross-platform parity.** Behaviour and visual + *(Verified 2026-06-22, ADR-0057 — **gated** in `src/theme.rs`: + every text foreground clears 4.5:1 on both themes; the + advanced-mode border clears 3:1 (plain border decorative-exempt); + token pairs clear ΔE2000 ≥ 15. Writing the gate caught + fixed two + shipped light-theme defects (`tok_string` 4.42:1, `tok_flag` + 3.15:1) and two near-duplicate dark pairs, `65eab71`.)* +- [/] **NFR-6 Cross-platform parity.** Behaviour and visual quality are equivalent across Linux, macOS, and Windows on crossterm-supported terminals. Platform-specific divergence (e.g. font fallbacks) is documented, not silently tolerated. -- [ ] **NFR-7 Light and dark background support.** The colour + *(Reviewer note 2026-06-22, ADR-0057 — partly evidenced by the CI + matrix (Linux + macOS execute the suite incl. Tier 4; Windows is + build-only). Documented divergence: on terminals/multiplexers + without true-colour passthrough the 24-bit palette is quantised to + 256 colours and near hues can collide (a terminal-capability + issue). Full parity awaits a Windows execution runner — see TT5.)* +- [x] **NFR-7 Light and dark background support.** The colour scheme remains legible and visually coherent on both light and dark terminal backgrounds. The mechanism (auto-detect via terminal query, explicit user setting, or both) is an implementation choice, but the outcome is non-negotiable: no dark-on-dark or light-on-light readability failures on either background. + *(Verified 2026-06-22, ADR-0057 — the WCAG-AA contrast gate + (NFR-5) runs over **both** themes, so the legibility outcome is + enforced on light and dark. Selection mechanism is `--theme` + + `COLORFGBG` auto-detect (OSC-11 querying deferred).)* ---