The input mode always started in simple; a learner who quit in advanced
had to re-toggle every launch. Store the mode per-project in project.yaml
(project.mode:, optional, default simple) and restore it on every open.
Mode is live UI state, not schema: the worker stamps the current mode
into project.yaml on every write, so a later command rewrites the live
value rather than clobbering it — no db round-trip needed. The mode is
persisted on unload (quit + project switch) so the mode you leave a
project in is always what reopens; the `mode` command also persists
immediately. A switch saves the outgoing mode, then restores the
incoming project's stored mode.
New --mode simple|advanced CLI flag (precedence --mode > stored >
simple; combines with --resume). A teacher can ship a project that
opens in advanced mode and export it to students (the mode travels in
the zip).
ADR-0015 Amendment 1; ADR-0003 note; help banner; requirements L1b.
The undo ring is local working state, handled at all three
project-file seams (R13):
- .gitignore template ignores /.snapshots/
- export excludes .snapshots/ (like playground.db / history.log)
- safely_delete_temp_project allowlists .snapshots/ so a temp that
was modified then undone back to empty stays auto-deletable
- undo::SNAPSHOTS_DIR is now a pub const referenced by all three
- tests: gitignore content, export exclusion, cleanup allowlist
1693 passed / 0 failed / 1 ignored; clippy clean.
`create table … with pk` parsed column types as `name:type`,
while `add column` uses `name(type)`. Unify on the parens
form so column-type syntax is consistent across the DSL:
create table T with pk id(serial), name(text)
Only `COL_SPEC` changes (`:` → `( … )`); `build_create_table`
reads columns by role, so it is unaffected. The `:` that
separates table from column in `add column` / `drop column`
is unchanged. Sweeps the test suite, the typing-surface
matrix (two `after_colon` cells renamed to `after_paren`,
4 snapshots regenerated), the friendly catalog's usage
templates, ADR-0009's example, and requirements.md.
1039 passing / 0 failing / 1 ignored; clippy clean.
Completes the i18n sweep started in the previous commit. All
remaining hand-rolled user-facing English strings inside
thiserror #[error(...)] attributes have been moved into the
catalog. Drops the thiserror dependency entirely.
Twelve error types migrated:
- dsl::action::UnknownAction → parse.custom.unknown_action
- dsl::parser::ParseError → parse.error_wrapper + parse.empty
- dsl::value::ValueError → value.{type_mismatch,format}
- persistence::csv_io::CsvError → persistence.csv.*
- persistence::mod::PersistenceError → persistence.{io,encode}
- persistence::yaml::YamlError → persistence.yaml.*
- persistence::migrations::MigrateError → persistence.migrate.*
- project::lock::LockError → project.lock.*
- project::naming::NamingError → project.naming.*
- project::naming::UserNameError → project.user_name.*
- project::mod::ProjectError → project.{path_not_found,...}
- project::mod::SafeDeleteError → project.safe_delete.*
- archive::ArchiveError → archive.*
- cli::ArgsError → cli.*
- db::DbError → db.error.*
Pattern per type: drop thiserror::Error derive, write manual
Display calling crate::t!(), keep #[from] semantics via
explicit From impls, override Error::source() where applicable
so #[source]-style chaining is preserved.
Why this matters (user rationale): "fine to have fallbacks for
errors that are purely technical, but lift the output to a
place where it can be localized later and where an adjustment
with friendly text is easily possible if any of them become
part of the happy path." All surface strings now live in
en-US.yaml and can be reworded or localized without touching
Rust source.
Tests: 769 passing, 0 failed, 1 ignored. Clippy clean with
-D warnings. Cargo.toml: drop thiserror = "2.0.18".
Implements the `export` and `import` app-level commands per
ADR-0015 §11 + ADR-0007 amendment 1.
- `export [<path>]` writes a zip of project.yaml + data/ to
<data-root>/YYYYMMDD-<projectname>-export-NN.zip by default,
preserving the project's directory name as the single
top-level folder inside the archive.
- `import <zip> [as <target>]` extracts an exported zip into
a new named project and switches to it. Target name is
derived from the zip's top-level folder by default; on
collision the destination auto-suffixes -02, -03, ... up
to -99 instead of refusing (deviates from §2's refuse-on-
collision rule for save/save as; recorded as an amendment
to ADR-0015 §11).
- Excludes playground.db and history.log from the zip.
- Path-traversal protection via zip::enclosed_name + post-
resolution check that the extraction path stays inside
the target directory.
Adds the zip = "5" dep with default-features = false +
features = ["deflate"] to keep the binary-size cost modest.
Test baseline: 370 passing, 0 failing, 0 skipped.