cc113c732f
- repo-stats reads the gotcha list from context/gotchas.md (the digest in repo-map.md no longer measures the doc-growth signal) and reports it as `docs.gotchas`; the test fixture and its derived expectations follow. - AGENTS.md and the wave agent-rules protect gotchas.md and file-inventory.md like the repo map they were split from. - doc-site.md's publication table lists the two new pages. - file-inventory.md's Context docs rows describe the three files correctly. - the code-review skill points at gotchas.md for the long form. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
431 lines
33 KiB
Markdown
431 lines
33 KiB
Markdown
# File Inventory (the long form)
|
||
|
||
The per-file listing that `context/repo-map.md` summarises. Moved here unchanged.
|
||
|
||
Line counts are a **dated snapshot**, not a maintained fact: measured 2026-09-24 at `c1772ca9`, and **70 of 105 had drifted past 10%** in the three days since the 2026-09-21 refresh — eleven had doubled or more (`client.rs` 255 → 943, `lockstep.rs` 1126 → 4064). Treat them as "is this file big", never as a figure to cite; re-derive with `wc -l`. The `ws-server/` and `wasm-client/` entries use a shorthand prefix, so a refresh must map it to `crates/improvise-<name>/{src,}/` by hand or they drift silently. Test counts are informational — run `cargo test --workspace` for live numbers. Files under 100 lines and render-only widgets omitted.
|
||
|
||
### `improvise-core` (`crates/improvise-core/src/`)
|
||
```
|
||
model/types.rs 3560 / 112t Model, formula eval (fixed-point), CRUD
|
||
model/cell.rs 1358 / 47t CellKey (sorted), CellValue, DataStore (interned + index),
|
||
live_records (insertion order — gotcha 18)
|
||
model/record.rs 226 / 8t RecordHash / CellId surrogates: the opaque identities a
|
||
thin client names a cell by (ADR 002)
|
||
model/category.rs 310 / 9t Category, Item, Group, CategoryKind
|
||
model/symbol.rs 89 / 3t Symbol interning
|
||
view/layout.rs 2604 / 57t GridLayout, drill, records mode, records_columns; a private
|
||
per-axis position index (`row_pos`/`col_pos`, rebuilt by `reindex`)
|
||
so a data position resolves in O(1) (improvise-felo.13)
|
||
view/types.rs 817 / 43t View config (axes, pages, hidden, collapsed, format)
|
||
model/id.rs 290 slugify + IdAllocator: how a generated id is made (ADR 007
|
||
clause 4, core.md §20). Nothing outside the file calls it yet
|
||
view/axis.rs 190 / 5t Axis enum + label/next methods (no colour: that is a Face)
|
||
workbook.rs 413 / 22t Workbook: Model + cross-view ops, item facade
|
||
format.rs 519 / 40t format_f64, parse_number_format (display only)
|
||
```
|
||
|
||
### `improvise-formula` (`crates/improvise-formula/src/`)
|
||
```
|
||
parser.rs 1394 / 79t pest grammar + tokenizer → Formula AST
|
||
quote.rs 160 / 7t quote_name / is_bare_name / pipe_quote / unescape_pipe_body:
|
||
the one home, both halves (improvise-i2m5). improvise-gp
|
||
re-exports it; improvise-io calls it from
|
||
persistence/{format,parse}.rs
|
||
formula.pest 108 PEG grammar for formula text
|
||
ast.rs 130 Expr, BinOp, AggFunc, Formula, Filter
|
||
```
|
||
|
||
### `improvise-io` (`crates/improvise-io/src/`)
|
||
```
|
||
storage.rs 334 / 13t Storage trait (read/write/list/delete), PhysicalFs, MemoryFs,
|
||
`impl Storage for Rc<T>` (improvise-hmws), and
|
||
contract::storage_contract — public, not cfg(test), because
|
||
a browser must run that sequence (harness/)
|
||
opfs.rs 289 / 0t OpfsFs: Storage over the browser's Origin Private File
|
||
System. wasm32 only. Run by harness/, not by cargo test
|
||
persistence/improv.pest 179 PEG grammar — single source of truth
|
||
persistence/mod.rs 441 / 11t document lifecycle: save/load/gzip/legacy-JSON over Storage;
|
||
facade re-exporting format_md/parse_md/export_csv*
|
||
persistence/format.rs 401 / 11t format_md: Workbook → .improv text
|
||
persistence/parse.rs 1684 / 46t parse_md: pest tree-walk → Workbook (+ grammar generator)
|
||
persistence/export.rs 243 / 5t export_csv / render_csv. Builds its layout with
|
||
with_frozen_records, NOT new — `new` skips prune_empty
|
||
(improvise-iot6)
|
||
persistence/roundtrip_tests.rs
|
||
1236 / 62t format_md ↔ parse_md characterization (tests only)
|
||
import/wizard.rs 1967 / 62t ImportPipeline + ImportWizard. `Plan::place` is the one
|
||
placement pass, shared by `collisions()` and the build so
|
||
the two cannot disagree; `DuplicatePolicy` / `Collision`
|
||
(improvise-cgqt)
|
||
import/analyzer.rs 434 / 17t Field kind detection (Category/Measure/Time/Skip);
|
||
analyze_records is first-seen order, not HashSet (gotcha 18)
|
||
import/csv_parser.rs 590 / 26t CSV parsing, multi-file merge
|
||
```
|
||
|
||
### `improvise-protocol` (`crates/improvise-protocol/src/`)
|
||
```
|
||
lib.rs 619 / 2t ViewState, ViewCursor, ViewFrame, AppMode (17), Panel,
|
||
MinibufferConfig, MinibufColor, HELP_PAGE_COUNT
|
||
reduce.rs 749 / 10t Command + ModelCommand (the wire format), HelpPage, reduce_view
|
||
render.rs 2157 / 46t CellDisplay, WidgetCache trait + one cache per widget
|
||
(GridCache/CategoryTreeCache/FormulaCache/ViewListCache/
|
||
TileBarCache), AxisFrame/FrameEntry/FrameGroup and
|
||
header_level_widths (the grid frame, gotcha 44),
|
||
GridCache::seed (the bootstrap compaction), Projection,
|
||
bootstrap-only Snapshot
|
||
keymap.rs 975 / 0t KeyPattern, Binding, Keymap, ModeKey (17), TextEntry,
|
||
PanelKeymap, Keymaps::default_keymaps/prefix_keymap — the
|
||
shared table both front ends look up. Tests live with
|
||
dispatch in improvise-command/src/command/keymap.rs
|
||
viewmodel.rs 170 / 5t flatten_cat_tree — derivations between a cache and a
|
||
renderer, called by both front ends (improvise-edp)
|
||
face.rs 682 Face (a role: Axis, WizardField, Mode*, MinibufferOf…), Theme,
|
||
AnsiSlot — appearance as data a front end applies (ADR 004,
|
||
protocol.md §10); draw.rs::face_style is the TUI's one translation
|
||
key.rs 96 / 3t KeyCode, KeyModifiers, KeyEvent (moved from command/)
|
||
frame.rs 156 / 3t ClientFrame/ServerFrame — the ONE envelope pair, both
|
||
deployments (improvise-xks6). Nothing else defines one.
|
||
ClientFrame::Save is a request, not a Command (gotcha 45)
|
||
basis.rs 257 / 6t Basis (the three tiers), Rejection, BasisCache — ADR 001
|
||
layer 1. Basis has no Default and no public constructor
|
||
help.rs 1241 / 14t HelpPage content and Hint { keys: Vec<(KeyCode, KeyModifiers)>,
|
||
command, caption }. `compact` drops a chord by its MODIFIERS
|
||
(anything beyond shift), never by parsing a label it just
|
||
formatted (improvise-thpe) — the guard is the signature
|
||
resolve.rs 4883 / 88t the browser's key resolvers: a BrowserKey to the Commands it
|
||
composes, mirroring each Cmd line for line INCLUDING where
|
||
siblings disagree (gotcha 47). The largest file in the crate
|
||
client.rs 943 / 15t ClientState: ViewState + one cache per widget, and the fold;
|
||
in_flight, `opened` (OpenedCell), notify, settle_status
|
||
(gotcha 47). EditorSession is in lib.rs beside ViewState
|
||
```
|
||
|
||
### `gp-tree` / `improvise-gp` (`crates/gp-tree/`, `crates/improvise-gp/`)
|
||
```
|
||
gp-tree/src/lib.rs 575 / 8t Tree, Spec, eval, simplify, render, breed, Population,
|
||
SplitMix64 (vendored seeded Rng; seed() keeps the seed so
|
||
a fit can be shown and recorded, pinned to the published
|
||
reference vectors)
|
||
gp-tree/src/data.rs 1000 / 21t raw string rows -> f32 feature vectors: per-column type
|
||
inference, one-hot, mean-imputation + col.missing flag,
|
||
ColumnSpec/encode_row, Dataset (incl. warnings),
|
||
Model = trees + plan + seed + FitConfig
|
||
gp-tree/src/fit.rs 961 / 32t the search: FitConfig (Default = the CLI's numbers), fit()
|
||
-> simplified champion, cv_regress/cv_classify,
|
||
permutation_importance, folds/standardize/mse.
|
||
Returns data; never prints
|
||
gp-tree/examples/fit_csv.rs 583 CLI over the above: flags, demo dataset, all printing
|
||
gp-tree/tests/scoring_known_answers.rs
|
||
250 / 6t f32 bit patterns + a next_u64 witness: the evidence any
|
||
change to the scoring paths needs (gotcha 37c)
|
||
improvise-gp/src/lib.rs 978 / 25t formula_text / fitted_formula: a Tree as IF(...) formula
|
||
text, validated back through parse_formula. A threshold is
|
||
the SHORTEST decimal that reads back as the same f64 (not
|
||
the exact expansion), so a row on a split boundary routes
|
||
the same in f64 as in f32 eval; non-finite values panic
|
||
improvise-gp/src/dataset.rs 506 / 15t a View's records -> the feature matrix a fit consumes —
|
||
the improvise-side counterpart of preprocess. Records mode
|
||
is already a feature matrix (gotcha 1)
|
||
improvise-gp/src/fit.rs 1778 / 35t one fit end to end: a View's records in, an improvise
|
||
Formula and the evidence for believing it out. Not in a
|
||
command, because none of it is terminal-specific
|
||
```
|
||
|
||
### Command layer (`crates/improvise-command/src/command/`)
|
||
```
|
||
cmd/core.rs 475 / 2t Cmd trait, CmdContext, CmdRegistry, parse helpers
|
||
cmd/registry.rs 903 / 13t default_registry() — all registrations
|
||
cmd/navigation.rs 507 / 12t Move, EnterAdvance, Page*
|
||
cmd/cell.rs 724 / 20t ClearCellCommand, YankCell, PasteCell, TransposeAxes, SaveCmd
|
||
cmd/commit.rs 1304 / 27t CommitAndAdvance, CommitBuffer(CommitKind), CommitExport,
|
||
CommitSave
|
||
cmd/effect_cmds.rs 519 / 6t effect_cmd! macro, 25+ simple wrappers, `unless_unknown` (gotcha 51)
|
||
cmd/fit.rs 293 / 8t fit-measure: evolve a tree over the active view and store it
|
||
as an ordinary `## Formulas` line — nothing GP-specific is
|
||
left in the document afterwards
|
||
cmd/grid.rs 988 / 22t ToggleGroupAtCursor, ViewNavigate, DrillIntoCell, TogglePruneEmpty
|
||
cmd/mode.rs 469 / 13t EnterMode, Quit, EditOrDrill, EnterTileSelect
|
||
cmd/panel.rs 936 / 24t Panel toggle/cycle/cursor, formula/category/view panels
|
||
cmd/search.rs 202 / 4t SearchNavigate, SearchOrCategoryAdd, ExitSearchMode
|
||
cmd/text_buffer.rs 399 / 14t AppendChar, PopChar, CommandModeBackspace, ExecuteCommand
|
||
cmd/tile.rs 170 / 6t MoveTileCursor, TileAxisOp
|
||
cmd/wizard.rs 244 / 6t HandleWizardKey — per-step import-wizard key tables
|
||
keymap.rs 1353 / 44t KeymapSet, Keymap::dispatch/dispatch_transient, effects_of,
|
||
SetTransientPrefix — what a thin client cannot have. The
|
||
table itself is improvise-protocol/src/keymap.rs
|
||
parse.rs 350 / 23t Script/command-line parser (prefix syntax)
|
||
```
|
||
|
||
### Semantic UI state (`crates/improvise-command/src/ui/`)
|
||
```
|
||
ui/effect.rs 4020 / 99t Effect trait. 63 effects: 53 `impl Effect for` blocks, 9
|
||
simple_effect! expansions, and SetTransientPrefix in
|
||
command/keymap.rs. The 10 outside the brace-match are all
|
||
ViewOnly; 28 of the 53 are Model(_) (gotcha 38, h3y7)
|
||
ui/app.rs 5558 / 134t App, ModelState, ViewState + dispatch methods, Panel, handle_key, dispatch_key, document_generation, autosave; apply_effects declares `dirty` for WholeWorkbook and observes it for CellData/LayoutGeometry (gotcha 45)
|
||
ui/project.rs 6383 / 118t Dirty, Viewport, grid_of/category_rows/formula_texts/
|
||
view_summaries/tile_entries, grid_updates (the diff),
|
||
ClientSession + Sessions (connect/apply/rebase/project_all)
|
||
ui/reduce.rs 1217 / 24t Checked / CheckedBatch / apply_command / model_effects /
|
||
reduce_key — one door from a Command or a keypress to the
|
||
authoritative App. `reduce_full` is GONE (improvise-h3y7)
|
||
ui/host.rs 3410 / 67t Host: one App + one Sessions; handle, commands, project_all,
|
||
rebase. Lives here, not in ws-server (gotcha 45)
|
||
ui/cat_tree.rs 174 / 8t Category tree flattener for panel
|
||
ui/viewport.rs 259 compute_col_widths / compute_row_header_width / visible_cols
|
||
(pure geometry; the width rule itself is the protocol's);
|
||
grid_width, SIDE_PANEL_WIDTH, MIN_GRID_WIDTH (improvise-gtn7.1);
|
||
GridSummary / grid_summary / grid_window (improvise-felo.13)
|
||
```
|
||
|
||
### TUI rendering, draw, main (`src/ui/`, `src/draw.rs`, `src/main.rs`)
|
||
```
|
||
ui/grid.rs 905 / 14t GridWidget (ratatui): no &Model, no &View — the frame comes
|
||
off GridCache (gotcha 44). Its module doc is the best-written
|
||
statement of that rule in the repo
|
||
ui/help.rs 277 5-page help overlay (render only)
|
||
ui/import_wizard_ui.rs 829 / 9t Import wizard rendering. `Panel` is the one writer, and
|
||
every write is clipped to the panel's rows and width
|
||
ui/text.rs 169 truncate and wrap, both measured in terminal cells, never bytes
|
||
draw.rs 1122 / 20t TUI event loop, frame composition; `content_tests` draws a
|
||
whole frame to check the selection beside a side panel
|
||
main.rs 428 CLI entry (clap): open, import, cmd, script
|
||
# other ui/*.rs are small panel renderers — skip unless changing layout/style
|
||
tests/grid_frame_agreement.rs
|
||
479 / 6t the two front ends draw the same grid frame (improvise-anr9)
|
||
tests/records_wide_smoke.rs 67 / 1t
|
||
tests/shipped_documents_load.rs 333 / 8t every TRACKED .improv/.improv.gz loads, and every record
|
||
it projects is writable. "Shipped" is read off `git ls-files`,
|
||
not a .gitignore reading (improvise-c0aj), and the walk's git
|
||
is scrubbed of every hostile GIT_* setting the caller may carry
|
||
tests/web_demo_model.rs 83 / 2t every visible cell in web/demo.improv is a Record
|
||
```
|
||
|
||
### Browser host and client (`crates/improvise-ws-server/src/`, `crates/improvise-wasm-client/src/`, `web/`)
|
||
```
|
||
ws-server/registry.rs 616 / 14t Registry of Hosts keyed by DocumentId; ClientId {document,
|
||
session} — a SessionId is unique only within its document.
|
||
A document is never evicted while the server runs (ADR 001)
|
||
ws-server/host.rs 4 / 0t a re-export shim only. The session loop is
|
||
improvise-command/src/ui/host.rs (gotcha 45)
|
||
ws-server/origin.rs 132 / 6t OriginPolicy: which origins may open a socket. Loopback by
|
||
default, --allow-origin widens, absent allowed, two refused
|
||
(improvise-kc7n). The only decision transport::admitted makes
|
||
ws-server/transport.rs 330 / 4t tokio + tungstenite plumbing, plus the Origin check at the
|
||
handshake — a refused upgrade, not an ignored socket
|
||
ws-server/main.rs 190 / 5t CLI entry: serve a file on a port; --bind, --allow-origin
|
||
ws-server/tests/socket.rs 389 / 6t the one test that uses a real socket: handshake, JSON
|
||
framing, SessionId routing, which document a URL asked for
|
||
wasm-client/lib.rs 455 / 8t Client: the wasm-bindgen surface (init, on_message, on_frame,
|
||
viewport/set_viewport, render_model, set_cell_at, typed_cell)
|
||
wasm-client/keys.rs 187 / 5t BrowserKey ONLY — a KeyboardEvent flattened to an improvise
|
||
key. It does NOT resolve: `resolve` and KeyOutcome live in
|
||
improvise-protocol/src/resolve.rs, shared by both front ends
|
||
wasm-client/render.rs 1249 / 21t RenderModel: editable / selected / cat tree, decided here
|
||
wasm-client/socket.rs 547 / 13t the fold (fold_frame/handle_frame), the two compositions
|
||
and typed() text→CellValue. Defines no enums
|
||
# state.rs is GONE — ClientState moved to improvise-protocol/src/client.rs
|
||
wasm-client/tests/browser_round_trip.rs 2136 / 34t improvise-1ey's scope against a real Host
|
||
wasm-client/tests/lockstep.rs 4064 / 78t host and client agree on a keypress
|
||
web/app.js 816 string calls only, no protocol knowledge, no framework.
|
||
Run by harness/tests/shell.spec.js (Chromium; the standalone half on Firefox too) — see
|
||
gotcha 48 for what that does and does not reach
|
||
web/worker.js 86 three string calls, no protocol knowledge. RUN by
|
||
shell.spec.js's no-?ws path, with bindings.rs (gotcha 45)
|
||
web/shell.js 399 everything app.js DECIDES that is not painting: no DOM,
|
||
no browser API, one `node --test` away. Owns socketUrl,
|
||
gridPlan, frameCostOf, documentTitle — so a chunk of the
|
||
browser's render decisions is NOT in the file gotcha 42
|
||
and gotcha 46 name. Reasoning: context/code/clients.md
|
||
web/serial.js 19 the one rule worker.js exists to keep: one frame at a
|
||
time, in arrival order (gotcha 48's async-listener race),
|
||
extracted so it is testable without worker globals
|
||
web/index.html, style.css the page; legibility only
|
||
web/build.sh 38 wasm-pack build → web/pkg (gitignored). Needs
|
||
wasm-opt = ["-O", "--enable-bulk-memory"] or the bundled
|
||
wasm-opt rejects LLVM's memory.copy
|
||
web/assemble.sh 112 assembles the deployable site (copies web/*.improv)
|
||
web/demo.improv 65 the document the site ships and seeds from. Every cell a
|
||
Record — asserted (gotcha 46)
|
||
```
|
||
|
||
### Browser worker host (`crates/improvise-worker-server/`)
|
||
```
|
||
improvise-worker-server/src/worker.rs 765 / 17t Worker: one port, one client; handshake, Addressed
|
||
filter, persist trigger (model_state.dirty). Every decision, every test
|
||
improvise-worker-server/src/vfs.rs 348 / 8t documents/open: seeding is a Storage::write
|
||
improvise-worker-server/src/bindings.rs 185 / 0t wasm32 shim: OpfsFs awaits + postMessage. No Rust test
|
||
reaches it, but harness/tests/shell.spec.js DOES: the
|
||
no-?ws path runs start()/OpfsFs::open under Chromium
|
||
and Firefox.
|
||
Called NEVER RUN here for months: see gotcha 45
|
||
improvise-worker-server/src/lib.rs 51 / 1t the module doc that states exactly what is and is not
|
||
covered — the one correct copy of that fact
|
||
improvise-worker-server/tests/post_message.rs 407 / 8t main-thread traffic, strings only
|
||
```
|
||
|
||
### Browser harness (`harness/`) — not a workspace member
|
||
|
||
```
|
||
harness/playwright.config.js 50 projects: chromium (every spec), firefox (opfs, plus
|
||
shell.spec's standalone half: grepInvert @mocked-peer),
|
||
webkit (opfs only, which skips on a runtime probe: no
|
||
OPFS). CI runs `--project=chromium --project=firefox` only:
|
||
nixpkgs' WebKit aborts inside the runner VMs (ci.yml)
|
||
harness/tests/shell.spec.js 345 the first execution of web/app.js. A change to web/*.js
|
||
belongs here (gotcha 48). Playwright serves harness/serve/,
|
||
which harness/build.sh ASSEMBLES: edit web/*.js, rebuild,
|
||
or the specs run the old page
|
||
harness/tests/opfs.spec.js 103 drives the OPFS contract page and reads back its result
|
||
harness/opfs-contract/src/lib.rs 470 runs improvise_io::storage::contract::storage_contract
|
||
against a real OpfsFs inside a real Web Worker
|
||
harness/www/opfs/{index.html,worker.js} the page and worker that host it
|
||
harness/build.sh 50 assembles harness/serve/ (web/assemble.sh, the fixtures,
|
||
and the contract crate's wasm-pack build) — gitignored
|
||
# gated by the `browser` job in .github/workflows/ci.yml, which can fail the build
|
||
```
|
||
|
||
### Examples
|
||
```
|
||
examples/gen-grammar.rs Grammar-walking random .improv generator (pest_meta)
|
||
examples/pretty-print.rs Parse stdin, print formatted .improv
|
||
crates/improvise-core/examples/category_width_bench.rs
|
||
The same records at 12 and at 24 regular categories
|
||
(improvise-felo.1; context/code/core.md)
|
||
```
|
||
|
||
### Context docs
|
||
```
|
||
.claude/skills/code-review/ Review skill: checks a change against the doctrine in these docs + kb.pl
|
||
scripts/check-comment-runs.py The two-line comment cap AND --check-evidence. The flag is
|
||
ADDITIVE since improvise-ijql — every invocation that can
|
||
exit 0 answers the cap — guarded by a subprocess self-test,
|
||
the only level a dispatch bug is visible at. Reasoning:
|
||
context/code/tooling.md §2
|
||
scripts/check-ci-graph.py pages-deploy must `needs` every job in ci.yml, and every
|
||
suite `checkers.sh --list` knows must run in some CI job
|
||
(improvise-dak5). It REFUSES what it cannot read rather than
|
||
parsing YAML: the values it accepts are an allowlist
|
||
(tooling.md). --self-test; run in the `test` job. README
|
||
states the rule and lists no jobs (improvise-x7cq)
|
||
scripts/checkers.sh The one entry point for every *-test.sh suite: scrubs the
|
||
env to an allowlist, runs under the bash that runs it (no
|
||
version floor since #297). `ci_suites` is the table of which
|
||
suites each CI job runs (`--ci <job>`); ci.yml names none
|
||
scripts/pr-watch Waits on a PR's checks (the pr-watch skill). Exit 5: the PR
|
||
is already MERGED or CLOSED, so there is nothing to wait for
|
||
scripts/assemble-stamp.sh The cache-bust stamp gate web/assemble.sh calls, extracted
|
||
so its refusals can be exercised without a web/pkg
|
||
scripts/assemble-stamp-test.sh Its check_the_checker, run first in the `build` CI job
|
||
context/code/tooling.md The repository's own checkers: why an evidence heading's
|
||
names come from cargo rather than from the source, and
|
||
why a leaf name is not an identity
|
||
context/test-evidence/tooling.md Evidence for the gates in scripts/ and for the tests that
|
||
hold CI's shape
|
||
scripts/nix-seed-flake-inputs.sh Seeds locked flake inputs over git so `nix develop` works in cloud sessions
|
||
prolog-sessions/kb.pl Executable knowledge base: invariants, doctrine, decisions (CI-checked)
|
||
context/adr/007-stable-identities.md Stable identities: ids unique across the document are the
|
||
keys and names are display-only; gensym suffixes apply to
|
||
generated ids only, and hand-written ids are canonical.
|
||
Accepted 2026-09-25. (There is no ADR 005 or 006 file:
|
||
improvise-l2b8 and improvise-ilvl claim those numbers.)
|
||
context/adr/004-faces-and-themes.md Faces: appearance as UI-agnostic data a front end
|
||
applies, emacs-style. Accepted 2026-09-22.
|
||
context/adr/003-live-backed-models.md A model may be backed by a live query source —
|
||
improvise as a DB client. Accepted, NOT a priority.
|
||
context/adr/002-naming-a-cell.md How a client names a cell: an opaque surrogate on
|
||
CellDisplay, whose absence means the cell is an
|
||
aggregate. Records why _Index cannot serve.
|
||
context/adr/001-concurrent-edits.md Concurrent edits between clients: record-level atomicity,
|
||
optimistic basis check per batch, advisory reservations.
|
||
Records why a causal clock was rejected.
|
||
context/cloud-agents.md Cloud-session environment: tracker sync, bd pitfalls, what is blocked
|
||
context/design-principles.md Architectural principles & testing doctrine
|
||
context/plan.md Show HN launch plan
|
||
context/repo-map.md The map: crates, how-to-find-things, the gotcha digest, a file map
|
||
context/gotchas.md The long form of every numbered gotcha (the digest is in repo-map.md)
|
||
context/file-inventory.md This file: the per-file listing the repo map summarises
|
||
docs/design-notes.md Product vision & non-goals
|
||
```
|
||
|
||
---
|
||
|
||
---
|
||
|
||
# Wasm, CLI, testing and dependencies (long form)
|
||
|
||
The three repo-map sections of these names, as they stood before the map was condensed.
|
||
|
||
## Wasm
|
||
|
||
The browser bundle is `improvise-core`, `improvise-formula`, `improvise-io`,
|
||
`improvise-protocol`, `improvise-command`, `improvise-wasm-client` and
|
||
`improvise-worker-server`. All seven
|
||
`cargo check --target wasm32-unknown-unknown` clean with no feature flags
|
||
(improvise-ywd), gated by the `wasm` job in `.github/workflows/ci.yml`. That
|
||
job gates **eight** crates, not seven: `improvise-gp` was added (improvise-z5eg)
|
||
explicitly *not* as a bundle member but as a candidate, because staying
|
||
wasm-clean is free now and expensive to retrofit — so the bundle is seven and
|
||
the gate is eight, and neither number is the other one's typo.
|
||
**A feature that adds a dependency to a bundle crate must be checked with `cargo tree --edges normal`** — the default output includes **dev** edges, so a plain `cargo tree -p improvise-protocol --target wasm32-unknown-unknown` reports a dev-only `serde_json` as *present* and reads exactly like a leak. Check per-crate too, because the `wasm` CI job invokes each crate separately and feature unification cannot reach across that (improvise-26dm added a `test-support` feature on `improvise-protocol` on those terms; it is off in every shipped build).
|
||
|
||
`improvise-ws-server` is the host and is native only — tokio, tungstenite and
|
||
`Box<dyn Storage>` — so `improvise-wasm-client` cannot depend on it in anger. It
|
||
no longer needs to: both ends `pub use` `improvise_protocol::frame` and neither
|
||
defines a pair (gotcha 42). The **dev**-dependency remains, for
|
||
`tests/browser_round_trip.rs`, which hands the client's own serialized frame to
|
||
a real `Host::handle`. The root `improvise` crate is
|
||
the terminal binary and does **not** build for wasm — it depends on
|
||
crossterm; that is correct, not a gap.
|
||
|
||
`cargo check` passing is not the same as running. Two known runtime hazards
|
||
it cannot see: `Instant::now()` panics on wasm (hence `web-time` in
|
||
`improvise-command`), and any `std::fs` call compiles but fails in a browser
|
||
— improvise-fuj closed the effects that bypassed `Storage`; autosave and the headless final write still do (improvise-31oe.11).
|
||
|
||
## CLI
|
||
|
||
```
|
||
improvise [model.improv] # open TUI (default)
|
||
improvise import data.csv [--no-wizard] [-o out] # import CSV/JSON
|
||
improvise cmd 'add-cat Region' -f model.improv # headless command(s)
|
||
improvise script setup.txt -f model.improv # run script file
|
||
```
|
||
|
||
Import flags: `--category`, `--measure`, `--time`, `--skip`, `--extract`, `--axis`, `--formula`, `--name`, `--split-date <field>` (a date column imports as ONE hierarchical category by default; this restores the flat split, and `--extract` still picks the components — improvise-8jk6.3), `--strict` (with `--no-wizard`, any skipped record is a refusal and nothing is written, where the default writes and warns with a count — improvise-8jk6.6), and `--on-duplicate refuse|sum|separate` (default `refuse`; applies with `--no-wizard`, where the wizard would otherwise ask — improvise-cgqt). A headless import also refuses a `--split-date` that names a non-date field and an `--axis` that names a category the import did not build (exit 1).
|
||
|
||
---
|
||
|
||
## Testing — the short version
|
||
|
||
Full guidance lives in `context/design-principles.md` §7. Quick reminders:
|
||
|
||
- **Two numbers, not one** — the ambiguity between them is what let the old
|
||
"suite runs in <2s" drift: **no single test binary over 2 seconds**, and the
|
||
workspace wall clock is the sum. Measured at `63a5c13`: **1464 tests over 35
|
||
binaries** (1 ignored), longest single binary **0.6–1.0s across two runs on one machine** —
|
||
the spread being its own argument for reading your own run. Excludes compilation,
|
||
and excludes `harness/` (Playwright, minutes, its own CI job). *Re-measure* rather
|
||
than re-assert — `cargo test --workspace` prints a `finished in` per binary, and
|
||
**the portable signal is the ratio, not the seconds**. **Not the ratio to the
|
||
median, though: it is degenerate.** Most binaries print `0.00s` at two decimals,
|
||
so the median is zero and every ratio to it is undefined (improvise-i5z6, 39
|
||
binaries). Compare the slowest binary to the next few slowest instead: an outlier
|
||
is one that sits well clear of them *in every run*. That is how i5z6 was read —
|
||
`improvise-formula`'s lib longest of 39 in all three runs, at 0.91–1.95s against a
|
||
runner-up's 0.50–0.92s. Both outliers so far had one cause: a generator re-parsing
|
||
its pest grammar per case. `improvise-io` (improvise-8shu, 1.42s → 0.21s) and
|
||
`improvise-formula` (improvise-i5z6, 0.84–0.98s → 0.06–0.07s) each read it once through a
|
||
`OnceLock` now. A new grammar-walking strategy should do the same from the start.
|
||
The full argument is `context/design-principles.md` §7.
|
||
- Commands: build a `CmdContext`, call `execute`, assert on returned effects. No terminal needed.
|
||
- Property tests (`proptest`, default 256 cases) cover invariants: CellKey sort, axis consistency, save/load roundtrip, `parse(format(parse(generate())))` stability.
|
||
- **Bug-fix workflow**: write a failing test *before* the fix (regression guard). Record the bug in `context/test-evidence/`, **keyed by test name** — *not* in the test's doc comment, which the two-line comment cap forbids and `scripts/check-comment-runs.py` enforces. A heading may be module-qualified and must be when its leaf is not unique.
|
||
- Coverage target ~80% line/branch on logic code; skip ratatui render paths.
|
||
|
||
---
|
||
|
||
## Key dependencies
|
||
|
||
ratatui 0.30, crossterm 0.29, clap 4.6 (derive), serde/serde_json, indexmap 2, anyhow, chrono 0.4, pest + pest_derive, flate2 (gzip), csv, enum_dispatch. Dev: proptest, tempfile, pest_meta.
|