Commit Graph

398 Commits

Author SHA1 Message Date
claude@clouddev1 5869eec4f4 docs(ci): ADR-ci-003 — cross-platform release builds (D1 matrix)
Record the multi-platform build strategy as its own decision: cargo-zigbuild
for the four non-macOS targets, the static/standalone posture per platform,
the Windows synchronization stub, the test->build matrix workflow, and the
macOS deferral with its licensing rationale (the public CI image can't carry
the SDK). Shrinks the ci-001 amendment to a pointer; updates the index.

Runtime-verified by the user: Linux x86_64 + Windows aarch64 run correctly.
2026-06-13 19:11:29 +00:00
claude@clouddev1 298475b326 ci: D1 release matrix over the four non-macOS targets
build-ci-image / build (push) Successful in 9m56s
ci / gate (push) Successful in 2m47s
release / test (push) Successful in 2m18s
release / build (aarch64-pc-windows-gnullvm) (push) Successful in 3m31s
release / build (aarch64-unknown-linux-musl) (push) Successful in 3m52s
release / build (x86_64-pc-windows-gnu) (push) Successful in 4m14s
release / build (x86_64-unknown-linux-musl) (push) Successful in 3m25s
release.yaml becomes test (once, host) -> build (matrix) over the four
cargo-zigbuild targets; each matrix job uploads its binary + .sha256 to
the shared release (idempotent create-or-get). Records the expansion in
ADR-ci-001 (2026-06-13 amendment); macOS stays deferred.
2026-06-13 12:14:49 +00:00
claude@clouddev1 04ebd83f08 build: D1 cross-compile via cargo-zigbuild (4 non-macOS targets)
Replace the single-target musl cc with cargo-zigbuild + zig in the flake
devShell — one universal cross cc/linker (incl. rusqlite's bundled SQLite
C) for all four non-macOS D1 targets, added to rust-toolchain.toml:
  x86_64/aarch64-unknown-linux-musl              (static, D2)
  x86_64-pc-windows-gnu, aarch64-pc-windows-gnullvm  (standalone .exe)

Windows links -lsynchronization (std WaitOnAddress), which rust-overlay's
toolchain and zig's mingw don't ship; the symbols are forwarded by
kernel32, so an empty stub libsynchronization.a (ci/winstub/, wired via
.cargo/config.toml for the windows targets only) satisfies the linker.
Verified: all four build; linux static; windows valid PE32+.
2026-06-13 12:14:49 +00:00
claude@clouddev1 18d08642d7 ci: skip the gate for docs-only changes
ci / gate (push) Successful in 2m31s
Add paths-ignore (docs/**, **/*.md) to the gate's push + pull_request
triggers so markdown/docs-only changes don't run a full clippy+test that
can't change the outcome. Mixed code+docs pushes still gate (not all
files are ignored); flake/toolchain changes are deliberately not ignored.
Also refresh a stale ADR-0049 -> ADR-ci-002 comment reference.
2026-06-12 22:42:50 +00:00
claude@clouddev1 da8bfebc36 docs(ci): establish docs/ci/adr namespace (ci-001 pipeline, ci-002 flake)
ci / gate (push) Successful in 2m33s
Records the CI/release pipeline as ADR-ci-001 and relocates the nix-flake
ADR from main's ADR-0049 to ADR-ci-002 (content unchanged, history note
added). Both live in docs/ci/adr/ with a README index — a dated,
ci-segmented namespace disjoint from main's integer ADR sequence, the
same split the website subproject uses to avoid cross-branch number
collisions. Drops the ADR-0049 entry from docs/adr/README.

ci-001 covers the runner model, the baked nix CI image, the clippy+test
gate, the static-musl release on tag, trigger hygiene, auth, and scope.
2026-06-12 22:38:34 +00:00
claude@clouddev1 89b9392c25 ci: release job — test before publish, pin bash, fix diagnostic
release / release (push) Successful in 3m59s
build-ci-image / build (push) Successful in 7m27s
ci / gate (push) Successful in 2m27s
- Run cargo test before the build so a tag never publishes untested code.
- Pin shell: bash on the scripted steps; the runner defaults to dash,
  which rejected `set -o pipefail` and failed run 22's package step.
- Swap `file` (absent in the slim image) for `ls -l`.
2026-06-12 22:11:24 +00:00
claude@clouddev1 bba24120f1 ci: scope gate + image-build to branch pushes (skip tags)
Tag pushes ignore paths: filters, so a release tag spuriously rebuilt
the unchanged CI image and re-ran the gate on a commit the branch push
already gated. Add branches: ['**'] to both push triggers — tag pushes
no longer fire them (release.yaml owns tags). Pushing commits + a tag
together still gates the commits via the branch push.
2026-06-12 22:11:24 +00:00
claude@clouddev1 88145225cc ci: release workflow — static binary to Gitea releases on tag
build-ci-image / build (push) Has been cancelled
ci / gate (push) Successful in 2m32s
release / release (push) Failing after 3m2s
On a v* tag, builds the x86_64-unknown-linux-musl binary in the CI image
and publishes it (+ .sha256) to a Gitea release via the API and the
auto GITEA_TOKEN. x86_64 Linux only for now; rest of the D1 matrix and
D3 packaging layer on later. Correctness comes from the branch gate.
2026-06-12 21:43:23 +00:00
claude@clouddev1 8e3208528e build: static musl release build capability
rust-toolchain.toml gains the x86_64-unknown-linux-musl target; the
flake devShell gains a musl cc (pkgsCross.musl64) + CC/linker env so a
`cargo build --target …-musl` compiles rusqlite's bundled SQLite C and
links fully static (D2: single static binary, no runtime deps). Cargo
release profile strips symbols (13MB -> 10MB). Verified locally: the
musl binary is static-pie, statically linked, stripped, runs standalone.
2026-06-12 21:43:23 +00:00
claude@clouddev1 9d8161218a ci: gate workflow + CI-image build/push, drop probe
build-ci-image / build (push) Successful in 6m18s
ci / gate (push) Successful in 3m19s
- build-ci-image.yaml: builds .gitea/ci-image/Dockerfile via DinD and
  pushes git.lazyeval.net/oli/rdbms-playground-ci:latest (REGISTRY_*
  secrets); triggers on image-input changes + manual dispatch.
- ci.yaml: the gate — runs inside that image, clippy -D warnings +
  cargo test, on push/PR. fmt intentionally not gated (ADR-0049).

Removes ci-probe.yaml; it answered the runner questions (jobs run in
containers, host nix unreachable, custom container: works).
2026-06-12 21:08:04 +00:00
claude@clouddev1 dc63ed66f1 ci: nix CI toolchain image (node-slim base + warmed flake)
Job-container image the gate runs in. node:22-bookworm-slim satisfies
the act_runner contract (sleep/bash/node) far more cheaply than the
catthehacker images; single-user nix installed on top (pre-create /nix
+ empty build-users-group so it installs as root in a container) with
the flake's devShell pre-warmed — CI enters a ready 1.95.0 toolchain in
~1.4s. Verified by local build. ~5.5GB (rust toolchain closure); dep/
target caching is a noted follow-up.
2026-06-12 21:08:04 +00:00
claude@clouddev1 c7ac0c9877 ci: add throwaway runner-probe workflow
ci-probe / host (push) Failing after 54s
ci-probe / nix-container (push) Failing after 1m12s
Diagnostic to determine how the ci-public runner executes jobs and
where the nix toolchain is reachable (host vs default container vs a
custom container:), so the real gate is built on facts. Delete once
the gate lands.
2026-06-12 20:35:39 +00:00
claude@clouddev1 9189740028 build(nix): reproducible dev + build env via a flake (ADR-0049)
Root flake with two outputs: devShells.default (pinned 1.95.0
toolchain via rust-toolchain.toml + rust-overlay, plus cargo-sweep)
and packages.default (rustPlatform.buildRustPackage from the committed
Cargo.lock; doCheck=false). flake.lock pins nixpkgs nixos-26.05 /
rust-overlay / flake-utils. .envrc (use flake) for direnv parity.

Single source of toolchain for dev and the upcoming CI, so they can't
drift. Verified through the flake: nix build yields a working binary,
clippy clean, 2424 tests pass / 0 fail / 1 intentional ignored doctest.
First step toward requirements.md TT5 + D1/D2/D3.
2026-06-12 20:35:39 +00:00
claude@clouddev1 3d4a0fd45e fix(render): trim IEEE-754 noise from displayed decimal arithmetic (#32)
`decimal` is stored as exact TEXT, but SQLite has no native decimal type,
so arithmetic/aggregation implicitly coerces it to an IEEE-754 double.
The computed result carries no playground type, so `sum(price * qty)`
rendered the double's full noise — `298.59999999999997` for `298.60` — a
confusing, off-topic float lesson for a teaching tool.

Add `format_real_display`: round REAL values to 15 significant figures
(a double's reliable precision) then take the shortest round-tripping
form, collapsing `298.59999999999997` to `298.6`. Wired into `format_cell`
(result-set / `show data` cells) only — the sole surface where the noise
appears, since it arises from arithmetic.

Every other f64->string path keeps full precision for semantic, not
cosmetic, reasons: CSV persistence stays byte-exact for round-trip;
`render_value` is a canonical identity key for the uniqueness dry-runs
(dry_run_unique, check_uniqueness_collisions), where rounding would
report collisions the exact-valued engine wouldn't; FK-key matching and
EXPLAIN-SQL literals likewise stay exact.

ADR-0005 Amendment 1; +7 tests.
2026-06-12 14:42:22 +00:00
claude@clouddev1 7e4bc122be fix(completion): treat a bare in-scope table alias as an alias, not an unknown column (#31)
A bare table alias typed where a column is expected — `… GROUP BY o`,
with `o` aliasing `FROM Orders o` — was a blind spot: completion offered
nothing for `o`, and the hint panel called the in-scope alias an unknown
column (`no such column o on table Orders, ...`).

Completion now offers each FROM source's qualifier (alias-if-present-else
table-name) at a bare sql_expr_ident slot, folded into the column
candidate list; on an exact-qualifier partial the alias source steps
aside so the diagnostic can surface. The bare-reference diagnostic arm
emits a targeted `alias_used_as_column` / `table_used_as_column` hint
("`o` is a table alias — write `o.<column>` ...") after the
projection-alias check, so ORDER-BY alias refs still win and a genuine
unknown column still reports `unknown_column`.

Two guards keep the qualified-form advice correct: SQL only (role
`sql_expr_ident`, so the DSL `expr_column` path keeps `unknown_column`
since the DSL has no `table.column` syntax) and effective-qualifier
match (alias-if-present-else-table, so an aliased source referenced by
its shadowed real name falls through rather than being advised as
`name.<column>`). The diagnostic is a drop-in replacement for
`unknown_column` at the same span/Error severity, so verdict/overlay/hint
paths are unchanged.

ADR-0032 Amendment 3; +10 tests.
2026-06-12 14:03:00 +00:00
claude@clouddev1 82b9f7f9b9 docs: handoff 67 — manual-testing bug fixes + open issues
Captures this session's three committed fixes (flag-completion dash,
table-alias validity, `:` one-shot live feedback), the two
investigated-as-designed findings (comma-FROM, aggregate-without-GROUP
BY), and points the next session at the filed enhancement issues
(#26 seed-count hint, #27 bottom status line, #28 add-column
relationship prose) plus the standing roadmap.
2026-06-12 12:59:38 +00:00
claude@clouddev1 f7155ceafc fix(input): thread the : one-shot escape into live SQL feedback
The `:` one-shot escape (ADR-0003) is stripped at submission, but the
*live* feedback kept the leading `:` in the buffer it handed the
walker — so Tab completion, the validity verdict, and the highlight
overlays all bailed at the `:` and treated the SQL as an unknown
command. Effect: in `:`-mode, Tab completed nothing and a valid query
could flash an error, while the identical line in full `mode advanced`
worked. (The ambient hint already stripped it, which is why the hint
showed the right column name while Tab did nothing.)

Add `App::feedback_view()` — the `:`-stripped SQL view, the cursor
mapped into it, and the stripped byte offset — and route all four live
paths through it:

- completion (Tab): complete against the view, then shift the returned
  `replaced_range` back by the offset so the edit lands in the buffer;
- validity verdict: verdict the SQL, not the sigil;
- highlight/overlays: new `render_input_runs_feedback` highlights and
  diagnoses the view (shifted by the offset) while the `:` renders as
  plain text;
- ambient hint: consolidated onto `feedback_view`, replacing the
  duplicate local `strip_one_shot_prefix`.
2026-06-12 12:43:00 +00:00
claude@clouddev1 4cacb8261c fix(completion): don't flag a table alias used before its FROM clause
In a SELECT, the projection can reference a table alias whose defining
FROM binding sits textually *after* the cursor — e.g.
`select sum(ol.count*p.price) … from … OrderLines ol …`. The candidate
engine already recovers that scope via the §10.6 full-input lookahead
(ADR-0032), but the typing-time validity indicator
(`invalid_ident_at_cursor`) walked only the text before the cursor,
found `ol` in no scope, and flagged it as an unknown column — a red
"ERR" overlay on an otherwise-valid query. (Other aliases escaped only
by coincidentally prefix-matching real columns.)

Give the validity check the same full-input lookahead: at a SQL
expression slot, recover the from-scope from the whole input and bail
when the partial prefix-matches a binding's alias or table name.
2026-06-12 12:19:55 +00:00
claude@clouddev1 c3e010332c fix(completion): flag-aware partial so a dash completes flags, not keywords
The partial-token walk stopped at `-`, so after typing `-` (or `--`)
the partial was empty and the replaced range was a zero-width point
*after* the dash. Two bugs followed at a flag position (e.g.
`add 1:n relationship … -`): the `on` keyword was offered (it
prefix-matched the empty partial), and accepting a candidate inserted
after the dash — `-on`, `---create-fk`, `----all-rows`.

Detect a dash-prefixed token at a word boundary as a flag-in-progress
and fold the whole dash-run into the partial, gated on a flag actually
being expected there (so `where x = -5` stays a signed number, not a
flag). The flag matcher now strips leading dashes and matches the body
uniformly (empty / `-` / `--` → all flags; `--cr` → create-fk).

Keywords like `on` no longer appear after a dash, and accept replaces
the dash(es) so `-` → `--create-fk` and `--all` → `--all-rows`. Two
partial-flag snapshots updated (they had captured the old behaviour).
2026-06-12 10:59:49 +00:00
claude@clouddev1 30b2677bf3 docs: ADR-0048 Phase 2 implemented + handoff 66
- ADR-0048: status → Phase 1 + Phase 2 implemented; D2 amendment
  (quoted dates, no date-literal token) and the override × UNIQUE
  capacity-guard decision; phasing/Status blocks marked done.
- README index: 0048 entry updated (Phase 2 shipped, 2400 tests).
- requirements.md: SD2 → [x] (the override-hooks core + column-fill).
- handoff 66: this session's Phase 2 build + the two /runda passes.
2026-06-12 09:44:36 +00:00
claude@clouddev1 a12facc784 feat(seed): set override clause + column-fill (ADR-0048 Phase 2)
Build the two SD2 surfaces Phase 1 deferred:

- `set` override clause (D2): comma-separated per-column pins —
  `= 'v'` (fixed), `in ('a','b')` (pick-list), `as <generator>`
  (named), `between x and y` (range; numeric and quoted dates).
  Type-aware via the typed `current_column_value` slot; an override
  drops its column from the generic-fill advisory (D13). Folded from
  the flat matched path (build_seed_overrides) and applied to the
  per-column plan (apply_seed_overrides).
- `<table>.<column>` column-fill (D1 form 2): an UPDATE over existing
  rows. Refuses PK/autogen targets, empty-table no-op, FK-samples the
  parent, collision-free for UNIQUE/identifier targets, one undo step;
  `set` may only adjust the filled column.

Supporting work: KNOWN_GENERATORS vocabulary + generator_for_name
(src/seed/vocabulary.rs, D9); a range Generator + range_bounds_reason;
IdentSource::Generators and HighlightClass::Function; completion of the
generator vocabulary after `as` and the set/.col column slots; the
typing-time validity indicator for an unknown generator; help,
parse-error pedagogy rows, and the D13 advisory's Phase-2/3 wording.

A bounded override (fixed value / too-short pick-list) on a
single-column-UNIQUE target is a friendly error rather than a silent
uniqueness cap (post-implementation /runda finding, user-chosen).

Dates in the range form are quoted (no date-literal token exists);
ADR-0048 D2 amended accordingly. Both modes (D5); reproducible (D4).
2026-06-12 09:44:30 +00:00
claude@clouddev1 78c38e8b33 docs: ADR-0048 Phase 1 accepted/implemented + handoff 65
- ADR-0048 status -> Accepted; Phase 1 implemented (commits
  202e25a..fbd219b), with the pre-build and post-implementation /runda
  passes and the 2358-test green state recorded; index entry updated.
- requirements.md: SD1 [x] (whole-row seed + FK/junction, both modes,
  --seed reproducibility with no exceptions), SD2 [/] (core generators /
  determinism done; the set override clause + column-fill are Phase 2),
  A1 14/15 (only hint/H2 remains unregistered).
- Handoff 65: the full seed Phase-1 build, the two /runda passes, where
  the code lives, and Phase-2 / next steps.
2026-06-11 21:49:06 +00:00
claude@clouddev1 fbd219b631 feat(seed): --seed flag, ambient wiring, and /runda hardening (ADR-0048 P1.4 + DA)
P1.4 — user-visible surface:
- Grammar: `seed <table> [count] [--seed <n>]` (the first DSL flag with a
  value); build_seed disambiguates the seed value from the positional count.
- Verified the auto-wired surface: table-name completion, --seed offered as
  a candidate, validity consistent with `show data`, an ADR-0042 near-miss
  row for bare `seed`, and render tests for the seed outcome.

/runda hardening — eight DA findings, all resolved:
- FK sampling now uses ORDER BY so --seed reproducibility no longer relies
  on SQLite's unspecified DISTINCT order (D4).
- shortid columns now generate from seed's seeded RNG (new
  shortid::generate_with_rng) — D4 now holds with no exceptions.
- Added the missing coverage the DA flagged: undo-one-step (D15), replay
  re-runs a seed line (D16), advanced-mode (D5), atomic rollback on a
  constraint failure, seed 0 no-op, complex-CHECK advisory (D17), and
  FK + shortid reproducibility.

2358 pass / 0 fail / 0 skip, clippy all-targets clean.
2026-06-11 21:45:34 +00:00
claude@clouddev1 e6ff63daa2 perf(seed): single-transaction multi-row insert path (ADR-0048 P1.3d)
do_seed inserted row-by-row through do_insert, re-writing the whole
table CSV each time — O(N^2). Extract do_insert's row core into a new
insert_one_row (bind + serial/shortid autofill + FK-enriched execute,
no tx/persist), shared by:
- do_insert: one row in its own transaction (behaviour unchanged).
- do_seed: all rows in ONE transaction, with a single
  finalize_persistence before the single commit — O(N), preserving
  ADR-0015 §6 commit-db-last. A mid-batch failure now rolls the whole
  seed back atomically; the capped preview is read back by rowid.

A near-max 10000-row seed drops from ~tens of seconds to well under
one. do_insert behaviour unchanged (whole suite green: 2346 pass /
0 fail / 0 skip, clippy clean); seed's existing tests exercise the
batch path.
2026-06-11 20:44:34 +00:00
claude@clouddev1 0b3ab3cc13 feat(seed): SeedResult outcome, capped preview, advisory, count cap (ADR-0048 P1.3c)
A dedicated SeedResult replaces the borrowed insert outcome (X5):
- CommandOutcome::Seed + DslSeedSucceeded event + handle_dsl_seed_success
  render: the echo, "N row(s) seeded into T", a capped preview table
  (D18, first 20 rows; full count always reported), and a Hint-styled
  advisory naming enum-ish / un-derivable-CHECK columns filled with
  generic text (D12/D13, Phase-1 wording).
- SeedResult carries requested vs produced, so a junction cap is now
  reported to the user, not only logged.
- Count cap (D6): a seed over 10000 rows is refused with a friendly error.
- Catalog keys ok.rows_seeded / seed.capped / seed.advisory_generic.

4 new tests (advisory flag, IN-check not flagged, preview cap, excess
count). 2346 pass / 0 fail / 0 skip, clippy clean.
2026-06-11 19:11:18 +00:00
claude@clouddev1 9c135010ba feat(seed): uniqueness, junction distinct-combos, IN-CHECK (ADR-0048 P1.3b)
do_seed now enforces value uniqueness and derives enum values:
- Uniqueness groups (D10): the user-fillable PK, compound UNIQUE
  constraints, and single-column UNIQUE / identifier columns stay
  distinct across the batch and against existing rows (retry per row).
  Junction distinct-combos fall out of PK-tuple uniqueness and cap at
  the available parent combinations (logged when capped; the
  user-facing note arrives with the advisory in P1.3c).
- Identifier-int columns get a monotonic sequence past MAX(col) (D10),
  so they never collide.
- IN-CHECK derivation (D17): a simple `col IN ('a','b')` CHECK becomes
  the value source via the new, unit-tested seed::parse_in_check_values,
  so the enum-as-CHECK pattern just works.

8 parser unit tests + 4 integration tests (unique column, identifier
sequencing, junction cap, IN-check enum). 2343 pass / 0 fail / 0 skip,
clippy all-targets clean.

Deferred to P1.3c: dedicated SeedResult + capped preview (D18) + the
enum/CHECK advisory incl. the cap note (D12/D13); P1.3d: multi-row path.
2026-06-11 18:50:05 +00:00
claude@clouddev1 73493fa68b feat(seed): FK sampling, empty-parent error, block guard (ADR-0048 P1.3a)
do_seed fills foreign-key columns by sampling existing parent rows
(D14): sample_parent_key_tuples reads distinct parent keys, and a
compound FK reads all its child columns from one sampled parent row per
child row. An empty parent is refused with a friendly "seed the parent
first" error. The block guard (D1) refuses a NOT NULL blob column (seed
can't generate one); a nullable blob is omitted (-> NULL).

4 integration tests (valid FK references, empty-parent refusal, NOT NULL
blob refusal, nullable-blob omission). 2331 pass / 0 fail / 0 skip,
clippy all-targets clean.

Deferred to P1.3b: identifier/constraint uniqueness incl. junction
distinct-combos (D10), IN-CHECK derivation (D17), dedicated SeedResult +
capped preview (D18) + advisory (D12/D13), and the multi-row path.
2026-06-11 17:22:04 +00:00
claude@clouddev1 f1e9484af3 feat(seed): command plumbing + walking skeleton (ADR-0048 P1.2)
End-to-end `seed <table> [count]` path, both modes:
- Command::Seed AST + grammar node (show-data table slot + optional
  positional count) + REGISTRY registration + build_seed.
- Runtime dispatch -> Database::seed -> Request::Seed worker arm ->
  do_seed.
- do_seed (Phase-1 skeleton): generates whole rows for non-FK,
  non-autogen columns via the seed library and inserts them one at a
  time through do_insert (reusing validation / autogen autofill /
  FK-error / persistence). One undo step (snapshot_then wraps it) and
  one history.log line (only the first row carries the source);
  default count 20.
- help (`help seed`) + parse-usage catalog entries.
- Reuses CommandOutcome::Insert for the auto-show; a dedicated
  SeedResult (capped preview + advisory) replaces it in P1.3.

5 Tier-3 integration tests (parse, populate+persist, default-20,
reproducible --seed, one history line). 2327 pass / 0 fail / 0 skip,
clippy all-targets clean.

Deferred to P1.3: FK sampling, identifier/constraint uniqueness, CHECK
derivation, block guard, capped preview, advisory, multi-row path.
Deferred to P1.4: completion/highlight/hint/validity wiring + --seed flag.
2026-06-11 16:57:43 +00:00
claude@clouddev1 202e25a94f feat(seed): fake-data generation library + fake dependency (ADR-0048 P1.1)
The pure generation half of `seed` — no command wiring yet:
- src/seed/: ColumnSpec + Generator model and a seeded StdRng; the
  type-gated name-heuristic catalogue (D7) with documented
  false-positive guards; table-context name disambiguation (D11);
  identifier (D10) and enum-ish (D12) detection; per-type + bounded-date
  generators (D8); the hand-rolled product generator (D9); and PickFrom
  for IN-CHECK / enum lists.
- Adds the `fake` crate (v5, default features). Verified: single rand
  0.10.1 (no duplication), determinism via one seeded StdRng driving
  both fake and the hand-rolled generators, security-clean across
  osv/grype/trivy.
- ADR-0048 D3 updated to record the dependency verification.

32 Tier-1 tests (exact-value via fixed --seed); 1673 lib tests pass,
clippy all-targets clean.
2026-06-11 15:35:17 +00:00
claude@clouddev1 0af7f56821 docs: ADR-0048 — seed fake-data generation command (SD1/SD2, A1)
Dedicated `seed` command: realistic, name-aware fake data via the
`fake` crate + a type-gated heuristic catalogue, table-context name
disambiguation, a hand-rolled product generator, bounded dates, an
identifier-uniqueness rule, a quoted `set` override clause (value /
list / generator / range), `--seed` reproducibility, FK sampling
(empty-parent error, junction distinct combinations), CHECK
derive-or-friendly-fail, undo as one batch step, replay as a
data-write, capped auto-show, and an enum/CHECK advisory.

Design settled across an extended user fork dialogue, then hardened
by a /runda DA pass that found six blockers (undo, replay, set
quoting, CHECK handling, advisory phasing, auto-show flood) — all
folded in, genuine forks re-escalated and user-resolved.

Takes up SD2 ([~]->[ ]); SD1 implementation in progress.
2026-06-11 13:48:19 +00:00
claude@clouddev1 4d0ae776cc docs: withdraw multi-tab output from scope (S3/V2 → satisfied)
The trailing "multiple tabs" clause of S3 and V2 is dropped from
tracked scope by user decision. The output pane is settling on the
single scrollable V4 journal model rather than switchable result
tabs, so both items are now fully satisfied (the table view and
single-element visualisation were already built). V4 noted as the
sole tracked direction for evolving the output pane. A future
return to tabbed output would be a fresh requirement.

No ADR touched: multi-tab output never had a deciding ADR.
2026-06-11 12:26:33 +00:00
claude@clouddev1 5d9ef6b21f docs: finalize handoff 64 — issues closed, tree clean
Bring handoff-64 current: it was written just before #22/#24 were closed
and the docs commit landed. §1 now reflects HEAD f0afec3 with both
issues closed on Gitea; §6 removes the completed finalization steps and
points the next session at the open requirements backlog (§7).
2026-06-11 12:08:12 +00:00
claude@clouddev1 f0afec3812 docs: session handoff 64 + ADR-0047 implemented (#22/#24)
Flip ADR-0047 Status -> implemented (commits f879d54..2d0f4b2, phased
A->B->C + flat-rectangle restyle); update the README index entry to
match (implemented, flat black-on-yellow rectangles, final 2290-green
tally, website-cast follow-up noted). Add session handoff 64 covering
#24 (vi load-picker nav) and #22 (ADR-0047 demo overlay layer).
2026-06-11 09:59:51 +00:00
claude@clouddev1 2d0f4b2958 feat(ui): flat filled rectangles for demo overlays (#22, ADR-0047 D4)
Render the keystroke badge and step caption as a solid yellow rectangle
with no border glyphs and a one-cell text margin, instead of a
rounded-border box — deliberately unlike the app's bordered panels so
the demo overlays read as a distinct, eye-catching callout. Shared
fill_overlay_rect helper (borderless Block fill + inset Paragraph).
Snapshots regenerated; ADR-0047 D4 wording updated.
2026-06-11 08:40:07 +00:00
claude@clouddev1 241f60c503 feat(ui): demo-mode step-caption stealth buffer (#22, ADR-0047 D3/D4)
Ctrl+] (decodes to Char('5')+CONTROL) toggles an invisible capture
buffer: typed characters accumulate without touching the input/output,
Backspace edits, every other key is inert, and a second Ctrl+] commits
the text to a caption box (empty commit dismisses). Handled at the top
of handle_key — before the badge and modal gates — so captions can be
authored over the load picker (the #24 cast); an ordinary keystroke
clears a visible caption. The caption renders as a floating
black-on-yellow box at the output panel's bottom-right, wrapped to <=3
lines (then ellipsised), with the keystroke badge stacked directly
above it when both are present.

Tier 1: capture/commit, invisible accumulation, backspace, inert keys
(incl. no badge), empty-commit dismiss, next-key clear, over-modal,
demo-off inert. Tier 2: caption / stacked / wrapped snapshots. Phase C
of ADR-0047 — feature complete.
2026-06-11 08:32:16 +00:00
claude@clouddev1 2584e76b22 feat(ui): demo-mode keystroke badges (#22, ADR-0047 D2/D4/D5)
In --demo mode, an otherwise-invisible key (Tab, Enter, arrows,
Ctrl-O, …) raises a transient [LABEL] badge — a floating
black-on-yellow box inset at the output panel's bottom-right. Set in
App::update before the modal gate (so it shows over the load picker,
the #24 cast); pure demo_badge_label maps the key set. The runtime
expires it on a ~1.5s timer via a new nearest_deadline helper that
extends the existing time-boxed-recv arm condition without disturbing
the ADR-0027 indicator debounce. New App.last_output_area lets the
top-level draw anchor the overlay; overlay colours centralised in
theme.rs.

Tier 1 (label fn, badge set/seq, over-modal), Tier 2 (dark/light
snapshots, black-on-yellow style, too-small clamp), runtime unit
(nearest_deadline). Phase B of ADR-0047; captions land in C.
2026-06-11 07:02:23 +00:00
claude@clouddev1 f879d54721 feat(cli): --demo demonstration mode flag + app plumbing (#22, ADR-0047 D1)
Add `--demo` (and the RDBMS_PLAYGROUND_DEMO env fallback) to enter
demonstration mode, threaded onto App.demo_mode through run_loop —
mirrors the --no-undo plumbing. Off by default, zero footprint when
off. The --help line advertises only the visible keystroke badges;
the Ctrl+] caption trigger is kept low-profile (ADR-0047 D6 updated).

Phase A of ADR-0047; behaviour (badges/captions) lands in B and C.
2026-06-10 22:22:12 +00:00
claude@clouddev1 e9eb1b177e docs: ADR-0047 — demonstration overlay layer for casts/teaching (#22)
Accepted decision record for the in-app demo overlay: a --demo mode
that shows automatic keystroke badges ([TAB], [ENTER], …) and a
stealth Ctrl+]-delimited step-caption buffer, both as floating
black-on-yellow boxes at the output panel's bottom-right. All forks
user-confirmed; a /runda pass contributed 10 tightening findings.
Indexed in docs/adr/README.md.
2026-06-10 22:16:44 +00:00
claude@clouddev1 638b4c9664 feat(app): vi-style j/k/g/G navigation in the load picker (#24)
Add j (down), k (up), g (first) and G (last) to the load picker's
list sub-mode, alongside the existing arrow keys. Typeable keys keep
the picker drivable by autocast in the website's documentation casts,
which cannot emit arrow keys. Footer hint left unchanged.
2026-06-10 21:36:18 +00:00
claude@clouddev1 18303784a0 docs: session handoff 63 + ADR-0046 marked implemented (#20/#21/#23)
ADR-0046 status -> Accepted + implemented (8 commits 9f5f76b..22bec61);
README index updated; the two draft-divergent decisions recorded inline
(App.relationships not SchemaCache; nav overlay partial-clear + gutter).
Handoff 63 covers the full UI build across Phases A/B/C; issues
#20/#21/#23 closed on Gitea.
2026-06-10 21:30:00 +00:00
claude@clouddev1 22bec61d11 feat(ui): scroll the focused sidebar panel + refine the nav overlay (#21, ADR-0046 DC3 + DC2)
DC3 — navigation-mode scroll: the focused Tables / Relationships panel
scrolls (Up/Down by a line, PageUp/PageDown by its visible-row count).
Per-panel offsets are clamped to content at render time, and the
renderer reports each panel's visible rows for paging — mirroring the
output panel's scroll. render_items_panel / render_relationships_panel
take &mut App, count their rows, and store+clamp the offset before
building the borrowing lines.

DC2 refinement: the expand-on-focus overlay now clears only the sidebar
strip plus a one-column gutter, leaving the base output/input/hint
visible (unchanged) to the right rather than blanking the whole area —
truer to "underneath keeps its layout", with the gutter keeping the
cut-off edge clean (chosen after eyeballing both variants). ADR DC2 and
the overlay snapshot updated to match.

Tests: line/page scroll move only the focused panel and clamp; the
render clamps a past-the-end offset so the last row stays visible.
2026-06-10 21:27:13 +00:00
claude@clouddev1 c9da6ff785 feat(ui): Ctrl-O navigation mode — peek + expand the schema sidebar (#21, ADR-0046 DC1/DC2/DC4)
Ctrl-O enters a navigation mode orthogonal to the input mode, cycling
focus Input -> Tables -> Relationships -> Input (Esc exits). While a
sidebar panel is focused the sidebar is revealed (a peek, even when
width-hidden) and drawn as an expanded 45-column overlay over a cleared
main area, so the schema is browsable without the cramped 26-column
unfocused width. The focused panel gets an accent border.

Routing lives in the main key handler after the modal gate, so Ctrl-O
and nav keys are inert while a modal is open; in nav mode every
non-navigation key (printable/Enter/Tab/Backspace/...) is inert because
the input is occluded. Scroll keys (Up/Down, PageUp/PageDown) are
reserved for DC3 (next).

New App state: NavFocus { Input, SidebarTables, SidebarRelationships }.
Tests: the focus cycle, Esc exit, input-keys-inert, overlay reveal +
expansion, the accent-border style, and an overlay snapshot.
2026-06-10 18:56:39 +00:00
claude@clouddev1 94825d0f36 feat(ui): relationships sidebar panel + schema data (#21, ADR-0046 DB2/DB4)
The left column now stacks a Tables panel over a Relationships panel.
Each relationship renders as three narrow lines — its name, then the
endpoints broken at the arrow (Customers.id -> / indented
Orders.customer_id) — ellipsized past the inner width. The panel is
content-sized within [5 rows ("(none)" when empty), half the column];
the Tables panel keeps the rest (>=3 rows). Phase C adds focus+scroll
for content beyond the cap (clipped for now).

Data path: a new worker Request::ReadAllRelationships +
Database::read_all_relationships returns full RelationshipSchema
records; the runtime posts them via a RelationshipsRefreshed event
alongside the schema-cache refresh, and the App holds them in a new
`relationships` field.

ADR deviation (recorded in ADR-0046 DB2 + index): DB2 specified this
data on SchemaCache; it lives on the App instead — SchemaCache is
walker/completion-facing and needs only relationship names (untouched),
while the full records are UI-only, so App is the cleaner home and it
avoids editing ~23 SchemaCache literals. No behavioural difference.

Tests: panel-height bounds, the three-line render, the empty "(none)"
case, a snapshot, read_all_relationships end-to-end (real DB via the
m:n junction), and the event->field handler.
2026-06-10 18:44:27 +00:00
claude@clouddev1 386627a262 feat(ui): width-derived sidebar visibility — hide at <=90 cols (#21, ADR-0046 DB1)
The schema sidebar (the left Tables column) is now shown only when the
terminal is wider than 90 columns; at or below that it is hidden and
the output/input panels span the full width. This reclaims horizontal
space on narrow terminals — notably the 90-column screencasts, where
the sidebar added little and cost the output panel its width.

Visibility is a pure function of terminal width (sidebar_visible);
the Ctrl-O peek-reveal lands in Phase C. render() splits the layout
conditionally — full-width right column when the sidebar is hidden.

Snapshots/tests that rendered at 80 wide now reflect the hidden
sidebar; those whose intent IS the sidebar (populated_with_table, the
items-panel and drop-table integration checks) render at 110 so the
Tables list is actually exercised — one masked-intent integration
check (matched "Customers" in the output, not the panel) is corrected
the same way. New tests cover the width gate and the show/hide
boundary.
2026-06-10 18:28:57 +00:00
claude@clouddev1 41bae99ab3 feat(ui): two-row input display on tall terminals (#23, ADR-0046 DA4)
On a comfortable terminal (height >= 40) the input panel shows two
rows: the single logical command soft-wraps across them — the first
row stops 6 columns short for the ADR-0027 validity indicator, the
second uses the full width — so a medium command is fully visible
without horizontal scrolling. A line longer than both rows still
scrolls (DA3-style, one column each side reserved for < / > markers)
to keep the cursor visible.

hint_rows generalises to panel_heights(area) -> (input_rows, hint_rows):
compact (<40) stays input 1 / hint 2; comfortable becomes input 2,
degrading hint-then-input on tiny terminals to protect the output
Min(5). render_input_panel splits into render_input_one_row (the
existing DA3 path, unchanged) and render_input_two_rows, with a new
expand_runs_to_cells helper placing styled cells across the rows.

Tests: panel_heights geometry, two-row wrap, overflow-scroll, the
indicator-stays-on-the-first-row case, and a two-row layout snapshot.
Compact one-row snapshots are byte-identical (that path is untouched).
2026-06-10 18:19:15 +00:00
claude@clouddev1 e0b9470feb feat(ui): horizontal-scroll long input so the cursor stays visible (#23, ADR-0046 DA3)
A command longer than the input field used to clip silently at the
right edge, hiding the cursor and the command tail. Now the single
logical input line scrolls horizontally to keep the cursor in view,
with muted `<` / `>` markers at the reserved edge columns signalling
hidden content on either side.

The offset is a pure function of (line length, cursor column, field
width, previous offset) — input_scroll_offset — so the view only moves
when the cursor would leave the window, and one column is held on each
side for the markers so a marker never hides the cursor. The stored
App::input_scroll_offset resets when the buffer is replaced wholesale
(submit, history recall). The ADR-0027 6-column indicator reserve is
preserved.

Tests: pure-offset cases, tail-visible + head-visible render checks,
and the reset-on-submit/history check. One layout snapshot now shows a
long command's tail instead of its clipped head.
2026-06-10 18:08:45 +00:00
claude@clouddev1 9f5f76b05d fix(ui): geometry-fixed hint-panel height kills the typing jump (#20, ADR-0046 DA1/DA2)
The hint panel's height was recomputed every frame from the wrapped
hint content (1–3 rows), so it resized as the user typed and shoved
the input/output panels — the flicker visible in the screencasts.

Make the height a pure function of terminal geometry (new hint_rows),
fixed between resizes: 2 content rows on compact (<40-row) terminals,
3 only on comfortable terminals narrow enough (<54 inner cols) to wrap
the longest catalog hint past two lines, degrading toward 1 on tiny
terminals to protect the output Min(5). resolve_hint_lines clamps to
that fixed budget (long hints ellipsize; short ones leave rows blank).

This reverses issue #12's shrink-to-content "reclaim"; its two tests
are replaced by an anti-jump invariant plus geometry-helper and
third-row tests. Two layout snapshots regenerated.
2026-06-10 17:08:25 +00:00
claude@clouddev1 93266b99c9 docs: ADR-0046 UI sidebar nav-mode + responsive input/hint (#20/#21/#23)
Accepted; implementation pending, phased A→B→C. Treats the three
coupled UI issues as one decision (shared width/height budget):

- #20 hint jumpiness: hint height becomes a function of terminal
  geometry, fixed between resizes, so it no longer shoves the
  input/output panels.
- #21 left column: kept but width-optional (hidden by default ≤90),
  with a new relationships sibling panel and a Ctrl-O navigation/focus
  mode (peek-reveal, expand-on-focus overlay, scroll).
- #23 long input: single-logical-line horizontal scroll plus a 2-row
  display when tall, preserving the ADR-0027 indicator reserve.

A pre-build /runda DA pass drove key corrections: Ctrl-B→Ctrl-O (Ctrl-B
is the tmux prefix), an additive SchemaCache.relationship_details field
(retyping would break completion), full nav-mode key disposition +
modal gate, and Tier-2 snapshot coverage. Reconciles requirements
S1 (evolved), S2 (overridden — separate relationships panel), and
S4 (corrected — the stale "keyboard-toggleable" hint claim is struck;
no toggle added).

Updates docs/adr/README.md index and docs/requirements.md S1/S2/S4.
2026-06-10 16:57:46 +00:00
claude@clouddev1 f88018b4be docs: session handoff 62 — C4 m:n convenience command + issue #19 2026-06-10 14:28:50 +00:00
claude@clouddev1 8bd43ccadf feat: create m:n relationship convenience command (C4, ADR-0045)
`create m:n relationship from <T1> to <T2> [as <name>]` generates a
junction table with one FK column per parent PK column ({table}_{pkcol},
typed via fk_target_type), a compound PK over them, and two CASCADE 1:n
relationships -- all in one do_create_table call = one undo step.
Auto-named {T1}_{T2} (optional `as`), both modes, compound-parent PKs
supported (ADR-0043). Self-referential m:n / PK-less parent / internal
junction name / name collision all refused.

Wired across every surface: grammar (separate CREATE_M2N node), worker
executor, runtime dispatch, completion ("m:n" composite), hints,
highlighting, help + usage catalog + disambiguator, and the advanced-mode
DSL->SQL teaching echo (render_create_m2n, round-trips as valid SQL).

Generalized/fixed framework assumptions the build + two /runda passes
surfaced (all behaviour-preserving for existing commands):
- simple-mode dispatch committed simple.first() unconditionally -> tries
  candidates, so `create table` no longer shadows `create m:n`.
- the completion continuation-merge was advanced-only -> runs in simple
  mode too when an entry word has >1 DSL form (gated simple_count>1).
- do_create_table now rejects internal `__rdbms_*` names (closes a
  pre-existing hole on the DSL create-table path too, not just m:n).
- usage disambiguator now recognizes the `m:n` opener.

Tests: 14 integration (tests/it/m2n.rs), 7 typing-surface matrix, echo /
highlight / usage / internal-name units. Closes C4.
2237 pass / 0 fail / 1 ignored. Clippy clean.
2026-06-10 14:26:33 +00:00