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).
190 lines
10 KiB
Markdown
190 lines
10 KiB
Markdown
# 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 <path>/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/<pid>/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] - <release date>`**
|
||
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.
|