From 3a0c03d7815427ea3c407dd7d81d4e5c6ce95947 Mon Sep 17 00:00:00 2001 From: "claude@clouddev1" Date: Thu, 7 May 2026 09:27:31 +0000 Subject: [PATCH] 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. --- CLAUDE.md | 81 ++++++++++ .../adr/0000-record-architecture-decisions.md | 49 ++++++ docs/adr/0001-language-and-tui-framework.md | 47 ++++++ docs/adr/0002-database-engine.md | 56 +++++++ .../0003-input-modes-and-command-dispatch.md | 103 ++++++++++++ docs/adr/0004-project-file-format.md | 64 ++++++++ docs/adr/0005-column-type-vocabulary.md | 72 +++++++++ .../adr/0006-undo-snapshots-and-replay-log.md | 89 +++++++++++ docs/adr/0007-sharing-and-export.md | 59 +++++++ docs/adr/0008-testing-approach.md | 149 ++++++++++++++++++ docs/adr/README.md | 16 ++ 11 files changed, 785 insertions(+) create mode 100644 CLAUDE.md create mode 100644 docs/adr/0000-record-architecture-decisions.md create mode 100644 docs/adr/0001-language-and-tui-framework.md create mode 100644 docs/adr/0002-database-engine.md create mode 100644 docs/adr/0003-input-modes-and-command-dispatch.md create mode 100644 docs/adr/0004-project-file-format.md create mode 100644 docs/adr/0005-column-type-vocabulary.md create mode 100644 docs/adr/0006-undo-snapshots-and-replay-log.md create mode 100644 docs/adr/0007-sharing-and-export.md create mode 100644 docs/adr/0008-testing-approach.md create mode 100644 docs/adr/README.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..bcae94f --- /dev/null +++ b/CLAUDE.md @@ -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/.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.). diff --git a/docs/adr/0000-record-architecture-decisions.md b/docs/adr/0000-record-architecture-decisions.md new file mode 100644 index 0000000..810a424 --- /dev/null +++ b/docs/adr/0000-record-architecture-decisions.md @@ -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. diff --git a/docs/adr/0001-language-and-tui-framework.md b/docs/adr/0001-language-and-tui-framework.md new file mode 100644 index 0000000..3832f08 --- /dev/null +++ b/docs/adr/0001-language-and-tui-framework.md @@ -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. diff --git a/docs/adr/0002-database-engine.md b/docs/adr/0002-database-engine.md new file mode 100644 index 0000000..ec3a86f --- /dev/null +++ b/docs/adr/0002-database-engine.md @@ -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. diff --git a/docs/adr/0003-input-modes-and-command-dispatch.md b/docs/adr/0003-input-modes-and-command-dispatch.md new file mode 100644 index 0000000..687441a --- /dev/null +++ b/docs/adr/0003-input-modes-and-command-dispatch.md @@ -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. diff --git a/docs/adr/0004-project-file-format.md b/docs/adr/0004-project-file-format.md new file mode 100644 index 0000000..923e804 --- /dev/null +++ b/docs/adr/0004-project-file-format.md @@ -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.yaml # schema, relationships, metadata, version + data/ +
.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/
.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. diff --git a/docs/adr/0005-column-type-vocabulary.md b/docs/adr/0005-column-type-vocabulary.md new file mode 100644 index 0000000..3714ec1 --- /dev/null +++ b/docs/adr/0005-column-type-vocabulary.md @@ -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` | 10–12 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. diff --git a/docs/adr/0006-undo-snapshots-and-replay-log.md b/docs/adr/0006-undo-snapshots-and-replay-log.md new file mode 100644 index 0000000..c2da619 --- /dev/null +++ b/docs/adr/0006-undo-snapshots-and-replay-log.md @@ -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. diff --git a/docs/adr/0007-sharing-and-export.md b/docs/adr/0007-sharing-and-export.md new file mode 100644 index 0000000..b6bd8a8 --- /dev/null +++ b/docs/adr/0007-sharing-and-export.md @@ -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--export-.zip`, where + `YYYYMMDD` is today's local date and `` 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. diff --git a/docs/adr/0008-testing-approach.md b/docs/adr/0008-testing-approach.md new file mode 100644 index 0000000..e37e189 --- /dev/null +++ b/docs/adr/0008-testing-approach.md @@ -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 1–3 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/
.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 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 diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 0000000..c075913 --- /dev/null +++ b/docs/adr/README.md @@ -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)