diff --git a/README.md b/README.md index 375427d..0109566 100644 --- a/README.md +++ b/README.md @@ -17,18 +17,20 @@ attempt to bring that model to a modern terminal, with a formula language that references category and item names, views that can be rearranged with a single keystroke, and a plain-text file format you can diff in git. +See [docs/design-notes.md](docs/design-notes.md) for the original product +vision and non-goals. + ## Quick start ```sh -nix develop -cargo build --release -./target/release/improvise examples/demo.improv +nix build . +./result/bin/improvise examples/demo.improv ``` Or import your own CSV: ```sh -./target/release/improvise import path/to/data.csv +./result/bin/improvise import path/to/data.csv ``` ## Key bindings to try first @@ -47,13 +49,12 @@ Or import your own CSV: ## Installation -### From source with Nix (preferred) +### With Nix (preferred) ```sh -nix develop -cargo build --release -# optionally install to your PATH: -cargo install --path . +nix build . +# or install into your profile: +nix profile install . ``` ### From crates.io @@ -67,22 +68,109 @@ cargo install improvise See the [GitHub releases page](https://github.com/fiddlerwoaroof/improvise/releases) for prebuilt binaries (Linux x86_64, macOS Intel and Apple Silicon). -## What's interesting about the codebase +## The data model -The data model stores every cell at the intersection of named categories and -items — `(Region=East, Measure=Revenue)` — rather than at a grid address like -`B3`. A view is a configuration that assigns each category to a row, column, -page, or hidden axis, and the grid layout is computed as a pure function from -`(Model, View)` to `GridLayout`. Transposing a pivot is just swapping the row -and column assignments; records mode is just assigning the virtual `_Index` and -`_Dim` categories to particular axes. The formula language references dimension -names (`Profit = Revenue - Cost`), so formulas survive any rearrangement -without rewriting. All user actions go through a command/effect pipeline: -commands are pure functions that read an immutable context and return a list of -effects, which are the only things that mutate state. The same command registry -serves both the interactive TUI and the headless `cmd`/`script` CLI -subcommands. Models persist to a markdown-like `.improv` format that is -human-readable, git-diffable, and optionally gzip-compressed. +Every cell lives at the intersection of named categories and items — +`(Region=East, Measure=Revenue)` — not at a grid address like `B3`. A model +can have up to 12 categories, each with an ordered list of items that can be +organized into collapsible groups (e.g. months grouped into quarters). + +Formulas reference dimension names, not cell addresses: + +``` +Profit = Revenue - Cost +Tax = Revenue * 0.08 +Margin = IF(Revenue > 0, Profit / Revenue, 0) +BigDeal = Sum(Revenue WHERE Region = "West") +``` + +The formula language supports `+` `-` `*` `/` `^`, comparisons, `IF`, and +aggregation functions (`Sum`, `Avg`, `Min`, `Max`, `Count`) with optional +`WHERE` clauses. Formulas apply uniformly across all intersections — no +copying, no dragging, no `$A$1` anchoring. + +## Views and axes + +A view assigns each category to one of four axes: **row**, **column**, +**page** (slicer), or **hidden**. The grid layout is a pure function of +`(Model, View)` → `GridLayout` — transposing is just swapping row and column +assignments, and it happens in one keystroke (`t`). Page-axis categories act +as filters: `[` and `]` cycle through items. + +Press `T` to enter tile mode, where each category appears as a tile in the +tile bar. Move between tiles with `h`/`l`, then press `Space` to cycle axes +or `r`/`c`/`p` to set one directly. + +Records mode (`R`) flips to a long-format view by assigning the virtual +`_Index` and `_Dim` categories to row and column axes. Drill-down (`>`) on an +aggregated cell captures a snapshot; edits accumulate in a staging area and +commit atomically on exit. + +## File format + +Models persist to a plain-text `.improv` format that reads like markdown and +diffs cleanly in git: + +``` +# Sales 2025 + +## Category: Region +- North +- South +- East [Coastal] +- West [Coastal] +> Coastal + +## Category: Measure +- Revenue +- Cost +- Profit + +## Formulas +- Profit = Revenue - Cost [Measure] + +## Data +Region=East, Measure=Revenue = 1200 +Region=East, Measure=Cost = 800 + +## View: Default (active) +Region: row +Measure: column +format: ,.2f +``` + +Gzip-compressed `.improv.gz` is also supported. Legacy JSON is auto-detected +for backward compatibility. + +## Import and scripting + +The import wizard analyzes CSV columns and proposes each as a category +(string-valued), measure (numeric), time dimension (date-like, with optional +year/month/quarter extraction), or skip. Multiple CSVs merge automatically +with a synthetic "File" category. + +All model mutations go through a typed command registry, so the same +operations that back the TUI work headless: + +```sh +# single commands +improvise cmd 'add-cat Region' 'add-item Region East' -f model.improv + +# script file (one command per line, # comments) +improvise script setup.txt -f model.improv +``` + +## What's interesting about the architecture + +All user actions flow through a two-phase command/effect pipeline. Commands +are pure functions: they receive an immutable `CmdContext` (model, layout, +cursor position, mode) and return a list of effects. Effects are the only +things that mutate app state, and each one is a small, debuggable struct. +This means commands are testable without a terminal, effects can be logged or +replayed, and the 40+ commands and 50+ effect types are all polymorphic — +dispatched through trait objects and a registry, not a central match block. +The keybinding system gives each of the 14 modes its own keymap, with +Emacs-style prefix keys for multi-stroke sequences. ## Expectations