Initial planning docs: CLAUDE.md and ADRs 0000-0008

Captures up-front design decisions for RDBMS Playground:
stack (Rust + Ratatui + SQLite), input modes, project file
format, type vocabulary, undo snapshots and replay log,
sharing/export, and testing approach. ADR-0000 establishes
the ADR practice itself and mandates index upkeep alongside
any ADR change.
This commit is contained in:
claude@clouddev1
2026-05-07 09:27:31 +00:00
commit 3a0c03d781
11 changed files with 785 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
# RDBMS Playground — project notes for Claude
## What this project is
A cross-platform TUI application that gives learners a sandbox for
exploring relational database concepts: tables, columns, primary
and foreign keys, relationships, indexes, queries, and query
plans. The audience is students from beginners to those ready for
raw SQL, and the design accommodates both ends of that spectrum.
The application is a teaching tool, not a database administration
tool. Decisions about the type system, command surface, and
backend choices are skewed toward pedagogy over breadth.
## Authoritative decisions
All significant design decisions live in `docs/adr/`. Read
`docs/adr/README.md` for the index. **Before proposing changes
that touch a decided area, read the relevant ADR.** Decisions are
not re-litigated casually — if a decision needs to change, write a
new ADR that supersedes the old one.
Current decisions at a glance (each backed by an ADR):
- **Stack:** Rust + Ratatui + Crossterm; `sqlparser-rs` for SQL,
`rusqlite` for the database (ADR-0001).
- **Backend:** SQLite with `STRICT` tables and FK enforcement on
(ADR-0002).
- **Input:** simple mode (DSL only) by default; advanced mode
(SQL + app-level commands) on toggle; `:` one-shot escape from
simple to advanced (ADR-0003). No other sigils.
- **Project format:** `project.yaml` + `data/<table>.csv` +
`history.log`; `playground.db` is a derived artifact (ADR-0004).
- **Types:** `text`, `int`, `real`, `decimal`, `bool`, `date`,
`datetime`, `blob`, `serial`, `shortid`. Compound primary keys
supported. No real UUIDs (ADR-0005).
- **Safety:** auto-snapshot before destructive ops; `:undo`;
append-only `history.log` for replay and scripting (ADR-0006).
- **Sharing:** `export` command produces a zip without the `.db`;
no hosted publishing (ADR-0007).
## Repository layout (planned)
The repository is in early planning. Once code lands, layout
conventions will be added here. For now, `docs/adr/` is the
canonical source of decided ground.
## Working style for this project
- **Documentation discipline.** Significant decisions get an ADR.
In-flight discussion stays in conversation or issues until it
settles.
- **Testing.** Per the user's global standards, tests are
established before changes, bugs are reproduced with failing
tests before being fixed, and "all green, no skips" is the only
acceptable end state. Integration tests exercise full flows
(e.g. project save → reload → rebuild produces the same
observable database), not just units in isolation.
- **No silent feature loss.** Anything in an ADR is decided. If
implementation reveals that a decision is wrong or impractical,
raise it explicitly and update the ADR — do not quietly drift.
- **Pedagogy wins ties.** When a design choice trades clarity for
raw capability, prefer clarity. Real RDBMS power-user features
exist; this app is not the place to teach them.
## Things deliberately deferred
These have been discussed but not yet decided. Do not implement
ahead of an ADR for any of them:
- Tutorial / lesson system architecture (acknowledged as in scope
for design; needs its own ADR before code).
- Query plan rendering specifics (the *what* is decided — annotated
tree view of `EXPLAIN QUERY PLAN`; the *how* is not).
- Friendly error message rewriting layer (decided to exist; design
not done).
- Sample data generation rules, including FK-aware generation for
junction tables.
- Visual ER diagram export.
- DSL grammar specifics (compound key syntax, m:n relationship
command, etc.).
@@ -0,0 +1,49 @@
# ADR-0000: Record architecture decisions
## Status
Accepted
## Context
The RDBMS Playground project will accumulate design decisions as it
grows. We want those decisions to be traceable, reviewable, and easy
to challenge later when context has changed.
## Decision
Record significant architecture and product decisions as Architecture
Decision Records (ADRs) in `docs/adr/`, using the Michael Nygard
format (Status, Context, Decision, Consequences). Files are numbered
sequentially with a four-digit prefix and a kebab-case slug, e.g.
`0001-language-and-tui-framework.md`.
Each ADR captures one decision. Superseding a decision means adding a
new ADR that references the old one and marking the old one as
"Superseded by ADR-NNNN".
ADRs document decisions, not speculation. An idea under discussion
belongs in conversation, an issue, or a design note — not an ADR. An
ADR is written once a decision has actually been made.
## Index discipline
`docs/adr/README.md` contains the canonical index of all ADRs.
Whenever an ADR is added, renamed, or has its status changed (e.g.
"Superseded by ADR-NNNN"), the index MUST be updated in the same
change. An ADR change without a corresponding index update is
incomplete.
The index lists ADRs in numerical order. Each entry shows the
number, title, and — where relevant — status annotations such as
"Superseded by ADR-NNNN" or "Deprecated".
## Consequences
- New significant decisions require an ADR before or alongside the
implementation that depends on them.
- Old decisions remain visible even after they are superseded.
- Reviewers can audit the rationale chain by reading `docs/adr/` in
order.
- The index in `README.md` stays trustworthy because keeping it
current is part of every ADR change, not an afterthought.
@@ -0,0 +1,47 @@
# ADR-0001: Language and TUI framework
## Status
Accepted
## Context
RDBMS Playground is a cross-platform terminal application aimed at
learners. It needs to feel fast, polished, and colourful, install
cleanly on Linux, macOS, and Windows, and ship as a single binary
with no runtime dependencies.
Beyond TUI rendering, the application has substantial SQL-handling
needs: syntax highlighting, parsing user input to distinguish app
DSL commands from SQL, rewriting simplified types into backend
types, and analysing query plans for the teaching features.
Two stacks were realistic candidates:
1. **Rust + Ratatui + Crossterm** — strong cross-platform terminal
support, single static binary, mature ecosystem. Crucially,
`sqlparser-rs` is a high-quality dialect-aware SQL parser
directly applicable to the parsing, highlighting, and query
analysis features.
2. **Go + Bubble Tea (Charm)** — excellent default aesthetics,
single static binary, easy distribution. Lacks an equivalent to
`sqlparser-rs`; SQL parsing would need to be written from
scratch or wrap a less suitable library.
## Decision
Use **Rust with Ratatui and Crossterm** for the TUI, with
`sqlparser-rs` for SQL parsing and `rusqlite` for the database
layer. Distribute as prebuilt binaries via GitHub releases plus
package managers (`cargo binstall`, Homebrew, Scoop, `winget`).
## Consequences
- Single static binary on all three target platforms.
- Strong fit between SQL-heavy features and the Rust SQL ecosystem
(`sqlparser-rs`, `rusqlite`).
- Slightly steeper contributor on-ramp for developers unfamiliar
with Rust compared to Go.
- TUI styling will require explicit work to match the polish that
Bubble Tea / Lipgloss give for free; budget for it in the design
pass.
+56
View File
@@ -0,0 +1,56 @@
# ADR-0002: Database engine
## Status
Accepted
## Context
The application teaches relational database concepts. Requirements
on the engine:
- File-based, with a single binary file that can live in the
project folder.
- Real RDBMS feature set: typed columns, primary keys, foreign keys
with referential integrity, indexes, query planner output we can
expose to learners.
- Industry adoption — students should leave with a skill that maps
to something they will encounter again.
- Embeddable from Rust without arcane build steps.
Candidates considered:
- **SQLite** — file-based, near-universal adoption, supports FK
enforcement, has `EXPLAIN QUERY PLAN`, and since 3.37 supports
`STRICT` tables which give proper type enforcement instead of
SQLite's traditional loose type affinity.
- **DuckDB** — file-based, modern, but analytical/columnar by
design; would teach analytical instincts that mislead in
transactional contexts.
- **Embedded Postgres (pglite, pg_embed)** — closest to a "real"
RDBMS, but adds friction and complicates packaging.
## Decision
Use **SQLite** via the `rusqlite` crate. All tables are created as
`STRICT` tables. Foreign-key enforcement is enabled per-connection
(`PRAGMA foreign_keys = ON`).
Simplified user-facing column types (see ADR-0005) are mapped to
the underlying SQLite STRICT types at parse time.
## Consequences
- Tight, low-friction packaging — `rusqlite` bundles SQLite.
- `EXPLAIN QUERY PLAN` output is directly usable for the query
analysis feature (ADR pending).
- SQLite's `ALTER TABLE` is limited (e.g. type changes, some
drops). Schema evolution will use the rebuild-table technique
internally; this is hidden from users in simple mode and exposed
honestly in advanced mode.
- STRICT tables forbid the historical permissive typing students
might encounter elsewhere — this is intentional and matches the
pedagogical goal.
- Booleans are stored as `INTEGER` (0/1) per SQLite's STRICT type
set; the `bool` user-facing type maps to this and is rendered as
`true`/`false` in result views.
@@ -0,0 +1,103 @@
# ADR-0003: Input modes and command dispatch
## Status
Accepted
## Context
The input field accepts three logically distinct kinds of input:
1. **App DSL data commands** — simplified, space-separated forms
like `create table Customers` or
`add column to table Customers: Name (string)`. These are the
primary teaching surface for beginners.
2. **App-level commands** — operations on the application itself
rather than the data: `save`, `load`, `export`, `quit`,
`undo`, `seed`, etc. These should always be available
regardless of mode.
3. **Raw SQL** — for users ready to type SQL directly, including
DDL and queries.
Beginners should not be exposed to SQL by accident, but advanced
users should not be punished for wanting to drive the app entirely
from the keyboard. Auto-detecting "is this SQL?" is unreliable
because the same first words (`create table …`) appear in both the
DSL and SQL.
## Decision
The input field has two modes, switchable by the user:
- **Simple mode** (default).
- Accepts app DSL data commands and app-level commands.
- Raw SQL is not executed; instead the input is recognised as
SQL and a friendly hint suggests switching to advanced mode or
using the one-shot escape (below).
- The mode is indicated by a label above the input field
(e.g. `Simple`).
- **Advanced mode**.
- Accepts raw SQL by default.
- App-level commands (the canonical list below) are still
recognised without any prefix — forcing users to switch back
to simple mode for these would be user-hostile, and advanced
users are exactly the ones most likely to live in
command-driven workflows.
- Mode is indicated by a label and a distinct input border
colour to make the elevated mode visually obvious.
- **One-shot escape with `:` in simple mode.**
- Prefixing a single line with `:` interprets it as advanced
input for that submission only. The prompt label changes
(e.g. `Advanced:`) while the prefix is present.
- This is the only sigil in the system. App-level commands
never use a sigil.
The set of "always available" app-level commands is enumerated
explicitly; it is not heuristic. The initial canonical list is:
| Command | Effect |
|-----------|---------------------------------------------------------|
| `save` | Persist the project to its current location. |
| `save as` | Persist the project to a chosen name and location. |
| `load` | Open the load dialog (project picker). |
| `new` | Start a new auto-named temporary project. |
| `export` | Produce a shareable zip (ADR-0007). |
| `import` | Open an exported project zip. |
| `seed` | Generate sample data (rules per a future ADR). |
| `replay` | Replay a `history.log` or `.commands` script. |
| `undo` | Restore the most recent snapshot (ADR-0006). |
| `redo` | Re-apply an undone state (ADR-0006). |
| `mode` | Switch between simple and advanced (`mode simple`/`mode advanced`). |
| `help` | Show contextual help. |
| `hint` | Request a hint for the current input (ADR pending). |
| `quit` | Exit the application. |
This list is **definitive** and applies in both modes. Adding,
removing, or renaming a command requires either:
- **Before code exists:** an amendment to this ADR.
- **Once code exists:** the in-code command registry becomes the
source of truth; this ADR is updated to point at the registry,
and any change to the registry's public surface still requires
an ADR note explaining the change.
Commands that operate on the data (e.g. `create table`,
`add column`, `create m:n relationship`) are **not** in this list
— they are DSL data commands and behave per the simple/advanced
mode rules above.
## Consequences
- Parsing must distinguish three categories — app-level command,
DSL data command, or SQL — and the mode determines what happens
if the input does not match the first two.
- Mode state is per-input-session and persists across submissions
until changed.
- Visual distinction (label, border colour) is required for safety:
users must always know whether their next submission will be
treated as SQL.
- The `:` escape lets advanced users in simple mode run a quick
query without flipping modes — useful during teaching when an
instructor wants to demonstrate SQL once.
+64
View File
@@ -0,0 +1,64 @@
# ADR-0004: Project file format
## Status
Accepted
## Context
Projects must be:
- Shareable — students and instructors should be able to send
projects to each other and reconstruct the full database state.
- Diffable — version control should produce meaningful diffs as a
schema or data set evolves.
- Versioned — the format will change as the app evolves, and old
projects must continue to load.
- Efficient enough for moderate amounts of practice data without
forcing users into pathological YAML files of tens of thousands
of rows.
The on-disk SQLite file (`.db`) is convenient but binary and not
suited to sharing or diffing.
## Decision
A project is a directory containing:
```
<project-name>/
project.yaml # schema, relationships, metadata, version
data/
<table>.csv # one CSV file per table, with header row
playground.db # derived; rebuildable from project.yaml + data/
history.log # append-only command/replay log (see ADR-0006)
```
- `project.yaml` carries a top-level `version: 1` field from the
outset, plus all schema, relationship, and project metadata.
- Table data lives in `data/<table>.csv` (UTF-8, header row, RFC
4180 quoting). One file per table keeps diffs scoped and avoids
monolithic YAML.
- `playground.db` is a **derived artifact**. The authoritative
state is `project.yaml` + `data/`. The `.db` file is kept when
present (we never silently drop it) but can be rebuilt from the
text sources at any time.
- Rebuilding when no `.db` exists: silent, automatic.
- Rebuilding when a `.db` exists: requires user confirmation
with a summary diff (e.g. "3 tables, 47 rows will be
recreated; existing `.db` will be replaced").
- A `.gitignore` template is created in each project; by default
the `.db` file is ignored so version control captures only the
authoritative sources.
## Consequences
- Projects round-trip cleanly through git, email, and zip.
- Large practice data sets remain efficient (CSV is appropriate).
- Schema review remains pleasant (YAML is appropriate).
- The app must be able to (re)build a database from the text
sources at any time — this is a first-class code path, not an
edge case.
- The `version` field opens the door to format migrations as the
app evolves; old projects load by running registered migrators
in sequence.
+72
View File
@@ -0,0 +1,72 @@
# ADR-0005: Column type vocabulary
## Status
Accepted
## Context
Real RDBMS engines expose many type variants that exist for
historical, performance, or platform reasons. A learner does not
benefit from picking between `VARCHAR(255)`, `TEXT`, `CHAR(40)`,
and `CLOB`. We control the user-facing surface and can present a
small, semantically clear set of types that maps cleanly to the
chosen backend (SQLite STRICT, ADR-0002).
We also want to teach two distinct lessons about identifiers:
1. The default, easiest path: a simple auto-incrementing integer
primary key. Used in 90% of intro examples.
2. Why integers aren't always the right answer: short random
identifiers that survive merging data sets, sharing, or
migration without collisions.
Real UUIDs (36 characters) are too wide to display comfortably in
TUI columns and exceed what learners actually need to understand
the concept.
## Decision
The user-facing column type vocabulary is:
| User-facing type | SQLite STRICT mapping | Notes |
|------------------|-----------------------|-------------------------------------------|
| `text` | `TEXT` | Strings of any length. |
| `int` | `INTEGER` | Plain integer. |
| `real` | `REAL` | IEEE-754 double. |
| `decimal` | `TEXT` | Stored as decimal string; rendered numeric. |
| `bool` | `INTEGER` | 0/1 internally; `true`/`false` rendered. |
| `date` | `TEXT` | ISO 8601 (`YYYY-MM-DD`). |
| `datetime` | `TEXT` | ISO 8601 (`YYYY-MM-DDTHH:MM:SS[.fff][Z]`).|
| `blob` | `BLOB` | Binary data. |
| `serial` | `INTEGER PK AUTOINC.` | Auto-incrementing integer; PK by default. |
| `shortid` | `TEXT` | 1012 char base58 random; PK by default. |
`shortid` uses base58 (no ambiguous `0`/`O`/`I`/`l`) and is
generated client-side at insert time when the column has no value
supplied.
Decimal is stored as text to preserve precision — applications
that need numeric comparison must use the engine's casts; this is
acceptable for a teaching context and the constraint is documented.
**Compound primary keys are supported.** They are essential for
junction tables in m:n relationships (e.g. `OrderLines` keyed on
`(order_id, product_id)`) and skipping them would teach the wrong
lesson. The simplified DSL provides natural syntax for them
(specifics in a later ADR).
True UUIDs are intentionally **not** in the type set.
## Consequences
- The type system is small enough to teach in five minutes.
- Mapping to SQLite STRICT is mechanical and lossless for the
intended use cases.
- The shortid generator is a small, well-tested utility — bounded
scope, no third-party dependency required.
- Junction tables and other compound-key scenarios are
first-class, reinforcing relational fundamentals.
- Learners who later need a true UUID column will find that the
app does not provide one; this is a deliberate trade-off in
favour of TUI legibility.
@@ -0,0 +1,89 @@
# ADR-0006: Undo snapshots and replay log
## Status
Accepted
## Context
Two related features address the same underlying need — making the
application safe and reproducible for learners:
1. **Accidental destruction.** A student typing `DROP TABLE
Customers` and then realising what they did is a near-certain
event in this audience. Without a recovery path, the experience
is hostile and the learning moment is lost to panic.
2. **Replay and scripting.** A persistent record of every
executed command is useful for tutorials, debugging, sharing
reproducible problem reports, and rebuilding a project from a
blank slate.
Both features are cheap to implement and high-leverage.
## Decision
### Undo snapshots
Before any destructive operation — `DROP`, `DELETE`, `TRUNCATE`,
schema-rebuild migrations, restore, etc. — the application takes a
snapshot of the database using SQLite's online backup API into a
ring buffer of recent snapshots (size to be tuned; initial target
N = 10).
An `undo` command (available in both modes as an app-level
command per ADR-0003 — no sigil) restores the most recent
snapshot. Each undo step is itself snapshotted to keep `redo`
possible.
**Undo requires confirmation.** Snapshots are taken only before
destructive operations, so the "current" state may include
non-destructive work (inserts, updates, schema additions) done
since the last snapshot. Restoring a snapshot therefore *can*
discard data the user has not been explicitly warned about.
Before restoring, the application displays a confirmation prompt
that includes:
- The snapshot's timestamp (local time, with a relative form such
as "12 minutes ago").
- A short summary of the operation that triggered the snapshot
(the command text, e.g. `DROP TABLE Customers`).
- A summary of changes that will be discarded if the undo
proceeds — at minimum, counts of rows added/modified/deleted
per table and any schema changes since the snapshot.
The user must explicitly confirm. A keyboard shortcut for
"confirm" is provided so power users are not slowed down, but
there is no flag to suppress the prompt — undo is rare enough,
and consequential enough, that the prompt is always shown.
### Replay log (`history.log`)
Every successfully executed command — DSL or SQL — is appended to
`history.log` in the project directory, one command per record,
with a timestamp and the resulting status. The format is
deliberately simple and human-readable so it can be hand-edited
and replayed.
The same format serves three purposes:
- A persistent input history surfaced via the TUI history feature.
- A scripting format: `.commands` files (or `history.log` itself)
can be replayed via a `replay` command.
- A reproducible bug-report artifact when a project is shared.
The log is append-only during a session. It is **not** the
authoritative state of the project (that lives in `project.yaml`
+ `data/`, ADR-0004) — it is an audit and replay trail.
## Consequences
- Snapshots add modest overhead per destructive operation. The
cost is bounded; learners care about safety far more than
microseconds.
- The ring buffer size must be tuned later based on realistic
database sizes; an initial value is fine for now.
- The replay log enables a future `replay` / scripting feature
with no additional storage commitment.
- Tutorial authors gain a natural "starter script" format for
exercises.
+59
View File
@@ -0,0 +1,59 @@
# ADR-0007: Sharing and export
## Status
Accepted
## Context
Learners and instructors will want to share projects: an
instructor distributing an exercise, a student attaching a
project to a question, a collaborator sending a snapshot.
The need is real, but committing to a hosted "publish" feature
(account system, server, namespace, moderation) would dwarf the
core app and is not warranted for a teaching tool. We can satisfy
the need entirely with local artifacts and documentation.
## Decision
No hosted publishing feature. Sharing is supported via two
mechanisms:
1. **`export` command.**
- Available as an app-level command in both input modes
(ADR-0003).
- Produces a single zip file containing `project.yaml`,
`data/`, and `history.log`, **excluding** `playground.db`
(the recipient rebuilds it on open per ADR-0004).
- Default output path is the parent directory of the project.
- Default filename:
`YYYYMMDD-<projectname>-export-<sequence>.zip`, where
`YYYYMMDD` is today's local date and `<sequence>` is a
two-digit zero-padded counter that starts at `01` and is
incremented to avoid clobbering an existing file in the
output directory on the same day.
- An explicit path and/or filename may be given to override
the default.
2. **Documented recipes.**
- Each new project includes a `.gitignore` template that
excludes `playground.db`, so committing a project to git
yields a clean, diff-friendly history.
- The user-facing documentation includes recipes for sharing
via git, email, and direct file transfer.
If real-world usage later reveals friction these mechanisms cannot
solve, a publish feature can be revisited as a separate ADR.
## Consequences
- Zero server-side surface area, zero accounts, zero hosting
costs.
- Sharing is a power-user-friendly workflow from day one without
blocking on infrastructure decisions.
- Recipients reconstruct the database deterministically from the
authoritative text sources — sharing is automatically a
validation of the rebuild path.
- If a future hosted feature is ever added, this ADR is
superseded, not extended.
+149
View File
@@ -0,0 +1,149 @@
# ADR-0008: Testing approach
## Status
Accepted
## Context
The project's working standards (`CLAUDE.md`, plus the user's
global testing rules) require:
- Test coverage established before changes.
- Bugs reproduced with failing tests before fixes.
- Integration tests that exercise the full stack a user touches.
- "All green, zero skips" as the only acceptable end state.
A TUI application needs a credible testing story for those rules
to be enforceable. Naïvely, "TUI testing" sounds like an
afterthought of manual screenshots. In practice, the Rust +
Ratatui ecosystem supports automation across every level that
matters, and we want to commit to that explicitly rather than
discover it ad-hoc.
## Decision
Testing is structured in four tiers. Tiers 13 run on every
commit in CI; tier 4 runs on every commit for a focused subset of
critical flows and on a nightly schedule for broader coverage.
### Tier 1 — Pure-logic unit tests
Standard `cargo test` against modules with no terminal
dependency:
- DSL parser and command dispatcher
- Type mapping (user-facing → SQLite STRICT types)
- Project I/O (`project.yaml` + `data/<table>.csv` round-tripping)
- Database rebuild from authoritative text sources
- Snapshot ring buffer logic (ADR-0006)
- Replay log writer/reader
These are the bulk of the test count. Every behavioural unit has
unit tests; modules with non-trivial logic also have property
tests via `proptest` where the input space justifies it (parser
inputs, type coercion, CSV escaping, etc.).
### Tier 2 — Render assertions via Ratatui `TestBackend`
Ratatui's in-tree `TestBackend` renders into a `Buffer` (a 2D
cell grid). Tests build an app state, render a frame, and assert
on the resulting buffer.
- **Cell-level assertions** for narrow tests
("the status bar shows mode label `Simple` in the expected
style").
- **Snapshot tests** via `insta` for whole-frame coverage of
representative views (default screen, query result table,
schema view, undo confirmation prompt). Snapshots are checked
in and reviewed on diff.
Snapshot discipline:
- Snapshots cover stable, intentional UI surfaces; they are not
added reflexively to every component.
- A snapshot diff is treated as a real review item, not a
rubber-stamp. Reviewers must confirm the change is intended.
### Tier 3 — Synthetic event-loop integration tests
The application's update function consumes
`crossterm::event::Event` values. Tests feed sequences of
synthetic events (`KeyEvent`, `MouseEvent`, resize) to the update
function, then render via `TestBackend` and assert on both
state and buffer.
This tier is the equivalent of `react-testing-library` for our
TUI: it exercises the full input → state → render path without a
real terminal, and is where the most valuable behavioural tests
live. Examples:
- Typing a DSL command in simple mode, submitting with
Ctrl-Enter, asserting the table list updates and the schema
view re-renders.
- Triggering `undo`, asserting the confirmation prompt appears
with the expected timestamp and change summary, confirming,
asserting state restoration.
- Switching modes and verifying the prompt label and border
colour change.
### Tier 4 — PTY-based black-box end-to-end
A small number of critical flows are exercised against the
**actual built binary** in a pseudo-terminal:
- Tooling: `portable-pty` for the PTY, `expectrl` for
expect-style scripting, `vt100` to parse the terminal output
stream into an inspectable cell grid.
- These tests catch issues the lower tiers miss: TTY setup,
signal handling, terminal mode transitions, real I/O timing.
Tier 4 is **reserved for the highest-value flows**, not blanket
coverage. The initial scope is:
- Cold launch → first DDL command → graceful quit.
- Project save → process restart → reopen → identical state.
- Project export → import in a fresh project → rebuilt database
matches the source.
- `undo` immediately after a `DROP TABLE`, including the
confirmation prompt.
Tier 4 tests run in CI on every commit (the focused list above)
and on a nightly schedule for any extended coverage.
## Tooling commitments
- `cargo test` — Tier 1, Tier 2, Tier 3.
- `proptest` — property-based testing for parser and conversion
layers.
- Ratatui's `TestBackend` — frame rendering for tests.
- `insta` — snapshot testing of rendered buffers.
- `portable-pty`, `expectrl`, `vt100` — Tier 4 PTY-based tests.
- CI matrix covers Linux, macOS, and Windows on stable Rust.
## Honest limits
- **No cross-terminal-emulator regression coverage.** Tier 4
exercises a PTY but not real terminal emulators (xterm,
Alacritty, Windows Terminal, etc.). Crossterm abstracts these
well in practice; if a real-emulator regression ever surfaces,
we will revisit.
- **No visual aesthetic checks.** Tests assert cell contents and
styles, not "this layout is pretty". Visual polish is reviewed
manually by humans.
- **Snapshot brittleness** is a known failure mode. We mitigate
by being selective about what gets a snapshot and by treating
snapshot diffs as real review items.
## Consequences
- Test discipline from `CLAUDE.md` is enforceable on every
layer: parser bugs caught at Tier 1, UI flow bugs caught at
Tier 3, real-binary regressions caught at Tier 4.
- Module boundaries are designed for testability from the start
(pure logic modules separate from rendering, an explicit update
function consuming events).
- CI cost is real but bounded: Tiers 13 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.
+16
View File
@@ -0,0 +1,16 @@
# Architecture Decision Records
This directory contains the project's ADRs, recorded per
[ADR-0000](0000-record-architecture-decisions.md).
## Index
- [ADR-0000 — Record architecture decisions](0000-record-architecture-decisions.md)
- [ADR-0001 — Language and TUI framework](0001-language-and-tui-framework.md)
- [ADR-0002 — Database engine](0002-database-engine.md)
- [ADR-0003 — Input modes and command dispatch](0003-input-modes-and-command-dispatch.md)
- [ADR-0004 — Project file format](0004-project-file-format.md)
- [ADR-0005 — Column type vocabulary](0005-column-type-vocabulary.md)
- [ADR-0006 — Undo snapshots and replay log](0006-undo-snapshots-and-replay-log.md)
- [ADR-0007 — Sharing and export](0007-sharing-and-export.md)
- [ADR-0008 — Testing approach](0008-testing-approach.md)