Commit Graph

327 Commits

Author SHA1 Message Date
6362078032 chore: bd dolt sync conf + issue jsonl 2026-04-16 11:11:30 -07:00
8242ef3dbe bd: update sync.remote 2026-04-16 11:04:54 -07:00
c158a8b99e bd init: initialize beads issue tracking 2026-04-16 10:58:57 -07:00
d99cb5ac8c Merge branch 'main' into worktree-improvise-ewi-formula-crate 2026-04-15 23:43:14 -07:00
4e37e12f9a style: reformat code and cleanup whitespace
Reformat code for improved readability and remove unnecessary whitespace.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-26B-A4B-it-UD-Q5_K_XL.gguf)
2026-04-15 23:42:44 -07:00
a900f147b5 feat(cmd): use new effects to improve command behavior
Update various commands to utilize the new AbortChain and CleanEmptyRecords
effects.

- CommitAndAdvance now pushes a mode change effect when aborting.
- ToggleRecordsMode now cleans up empty records upon exiting.
- EnterAdvance now emits AbortChain when at the bottom-right corner.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-26B-A4B-it-UD-Q5_K_XL.gguf)
2026-04-15 23:42:44 -07:00
489e2805e8 feat(ui): implement AbortChain and CleanEmptyRecords effects
Implement AbortChain and CleanEmptyRecords effects to allow
short-circuiting effect batches and purging cells with empty coordinates.
Update the App struct to support aborting effects during the application of
an effect batch.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-26B-A4B-it-UD-Q5_K_XL.gguf)
2026-04-15 23:42:44 -07:00
f272a9d459 chore: update roadmap 2026-04-15 23:35:08 -07:00
ff74d619a3 docs(io): note improvise-io layout in repo-map
Reflect improvise-8zh: `persistence/` and `import/` now live in the
`improvise-io` sub-crate under `crates/`.

- Sub-crate list expanded to describe improvise-io's scope, its
  dependencies (improvise-core, improvise-formula), and the
  standalone-build guarantee.
- File Inventory reorganized: "Import layer" + persistence entries
  collapsed into a single "I/O crate layers" block with paths rooted at
  `crates/improvise-io/src/`.
- Updated line/test counts to current contents.
- Top-level block trimmed (persistence/format no longer live there) and
  lib.rs annotated as a re-export facade.

No consumer-facing path changes; `crate::persistence::*` and
`crate::import::*` still resolve via the main crate's re-exports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 23:08:55 -07:00
5807464fc7 refactor(io): move persistence and import into improvise-io (improvise-8zh)
Relocate the two I/O module trees into the improvise-io sub-crate
scaffolded in the previous commit:

  git mv src/persistence -> crates/improvise-io/src/persistence
  git mv src/import      -> crates/improvise-io/src/import

The grammar file `improv.pest` moves alongside `persistence/mod.rs`;
the `#[grammar = "persistence/improv.pest"]` attribute resolves relative
to the new crate root and keeps working unchanged.

No path edits inside the moved code: the `crate::model::*`,
`crate::view::*`, `crate::workbook::*`, `crate::format::*`, and
`crate::formula::*` imports inside persistence and import all continue
to resolve because improvise-io's lib.rs re-exports those modules from
improvise-core and improvise-formula, mirroring the pattern improvise-core
uses for `formula`. Verified no `crate::ui::*`, `crate::command::*`,
`crate::draw::*` imports exist in the moved code (per improvise-8zh
acceptance criterion #3).

Main-crate `src/lib.rs` now re-exports `import` and `persistence` from
improvise-io, keeping every `crate::persistence::*` and `crate::import::*`
path in the 4 consumer files (ui/app.rs, ui/effect.rs,
ui/import_wizard_ui.rs, main.rs) resolving unchanged — no downstream
edits needed.

`examples/gen-grammar.rs` had `include_str!("../src/persistence/improv.pest")`;
updated the relative path to the new location under
`crates/improvise-io/src/persistence/`.

Verification:
- cargo check --workspace --examples: clean
- cargo test --workspace: 616 passing (219 main + 190 core + 65 formula + 142 io)
- cargo clippy --workspace --tests: clean
- cargo build -p improvise-io: standalone build succeeds, confirming no
  UI/command leakage into the IO crate (improvise-8zh acceptance #2, #3)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 23:08:00 -07:00
bd17aed169 refactor(io): scaffold empty improvise-io sub-crate
Lay groundwork for Phase 3 of the workspace split (improvise-8zh): a new
`crates/improvise-io/` sub-crate that will house the persistence and
import layers once the files move in the next commit.

Only scaffolding here:
- New `crates/improvise-io/Cargo.toml` declares deps on improvise-core,
  improvise-formula, and the external crates persistence+import already
  use: anyhow, chrono, csv, flate2, indexmap, pest, pest_derive, serde,
  serde_json. Dev-deps: pest_meta, proptest, tempfile.
- New `crates/improvise-io/src/lib.rs` re-exports the core modules under
  their conventional names (`format`, `model`, `view`, `workbook`,
  `formula`) so in the next commit the moved code's `crate::model::*`,
  `crate::view::*`, `crate::workbook::*`, `crate::format::*`, and
  `crate::formula::*` paths resolve unchanged.
- Root `Cargo.toml` adds the new crate to workspace members and the main
  crate's `[dependencies]`, ready to receive the move.

No source files change yet; cargo check --workspace still compiles as
before.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 23:05:17 -07:00
79dc54de21 Merge branch 'main' into worktree-improvise-ewi-formula-crate
# Conflicts:
#	TAGS
2026-04-15 22:47:51 -07:00
9efbed403a chore: update tags 2026-04-15 22:46:03 -07:00
03c7c00b25 chore: update tags 2026-04-15 22:45:35 -07:00
08f190a036 chore: update gitignore 2026-04-15 22:45:18 -07:00
d20eb75a0b feat: roadmap from beads 2026-04-15 22:44:47 -07:00
30383f203e refactor(keymap): pass mode arguments in keybindings
Update keybindings for normal, records-normal, editing, and records-editing
modes to pass the appropriate mode names as arguments to the parameterized
commands.

This ensures that the correct mode is entered when using commands like
`edit-or-drill` , `enter-edit-at-cursor` , `commit-cell-edit` , and
`commit-and-advance-right` .

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-26B-A4B-it-UD-Q5_K_XL.gguf)
2026-04-15 22:44:13 -07:00
cece34a1d4 refactor(command): parameterize mode-related commands and effects
Make mode-related commands and effects mode-agnostic by passing the target
mode as an argument instead of inspecting the current application mode.

- `CommitAndAdvance` now accepts `edit_mode` .
- `EditOrDrill` now accepts `edit_mode` .
- `EnterEditAtCursorCmd` now accepts `target_mode` .
- `EnterEditAtCursor` effect now accepts `target_mode` .

Update the command registry to parse mode names from arguments and pass
them to the corresponding commands.

Add tests to verify the new mode-passing behavior.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-26B-A4B-it-UD-Q5_K_XL.gguf)
2026-04-15 22:44:13 -07:00
79f78c496e docs(core): note improvise-core layout in repo-map
Reflect Phase B of improvise-36h: `model/`, `view/`, `workbook.rs`, and
`format.rs` now live in the `improvise-core` sub-crate under `crates/`.

- Sub-crate list expanded to describe improvise-core's scope, its
  dependency on improvise-formula, and the standalone-build guarantee.
- File Inventory reorganized into a single "Core crate layers" block
  covering model/view/workbook/format, with paths rooted at
  `crates/improvise-core/src/`.
- Updated line/test counts to match current contents (Phase A + merge
  with main brought in records mode, IndexMap-backed DataStore, etc).

No architectural change; the main crate's re-exports keep every
`crate::model`/`crate::view`/`crate::workbook`/`crate::format` path
resolving unchanged, so no "How to Find Things" table edits are needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:33:45 -07:00
fc9d9cb52a refactor(core): move model, view, workbook, format into improvise-core
Relocate the four pure-data module trees into the improvise-core
sub-crate scaffolded in the previous commit. Phase A already made
these modules UI/IO-free; this commit is purely mechanical:

  git mv src/format.rs   -> crates/improvise-core/src/format.rs
  git mv src/workbook.rs -> crates/improvise-core/src/workbook.rs
  git mv src/model       -> crates/improvise-core/src/model
  git mv src/view        -> crates/improvise-core/src/view

The moved code contains no path edits: the `crate::formula::*`,
`crate::model::*`, `crate::view::*`, `crate::workbook::*`,
`crate::format::*` imports inside the four trees all continue to
resolve because the new crate mirrors the same module layout and
re-exports improvise_formula under `formula` via its lib.rs.

Main-crate `src/lib.rs` flips from declaring these as owned modules
(`pub mod model;` etc.) to re-exporting them from improvise-core
(`pub use improvise_core::model;` etc.). This keeps every
`crate::model::*`, `crate::view::*`, `crate::workbook::*`,
`crate::format::*` path inside the 26 consumer files in src/ (ui,
command, persistence, import, draw, main) resolving unchanged — no
downstream edits needed.

Verification:
- cargo check --workspace: clean
- cargo test --workspace: 612 passing (357 main + 190 core + 65 formula)
- cargo clippy --workspace --tests: clean
- cargo build -p improvise-core: standalone build succeeds, confirming
  zero UI/IO leakage into the core crate

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:31:42 -07:00
0d75c7bd0b refactor(core): scaffold empty improvise-core sub-crate
Lay the groundwork for Phase B of improvise-36h: a new
`crates/improvise-core/` sub-crate that will house the pure-data core
(model, view, workbook, format) once the files move in the next commit.

Only scaffolding here:
- New `crates/improvise-core/Cargo.toml` mirroring improvise-formula's
  structure; declares deps on improvise-formula, anyhow, indexmap, serde.
- New `crates/improvise-core/src/lib.rs` with the single re-export
  `pub use improvise_formula as formula;` so that in the next commit the
  moved code's `crate::formula::*` paths resolve unchanged.
- Root `Cargo.toml` adds the new crate to the workspace members and the
  main crate's `[dependencies]`, ready to receive the move.

No source files change yet; cargo check --workspace still compiles as
before.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:21:58 -07:00
a22478eb87 Merge branch 'main' into worktree-improvise-ewi-formula-crate
# Conflicts:
#	src/ui/app.rs
#	src/ui/effect.rs
#	src/view/layout.rs
2026-04-15 21:39:00 -07:00
242ddebb49 chore: matches -> method 2026-04-15 21:33:18 -07:00
030865a0ff feat(records): implement records mode for data entry
Implement a new "Records" mode for data entry.
- Add `RecordsNormal` and `RecordsEditing` to `AppMode` and `ModeKey` .
- `DataStore` now uses `IndexMap` and supports `sort_by_key()` to ensure
  deterministic row order.
- `ToggleRecordsMode` command now sorts data and switches to
  `RecordsNormal` .
- `EnterEditMode` command now respects records editing variants.
- `RecordsNormal` mode includes a new `o` keybinding to add a record row.
- `RecordsEditing` mode inherits from `Editing` and adds an `Esc` binding
  to return to `RecordsNormal` .
- Added `SortData` effect to trigger data sorting.
- Updated UI to display "RECORDS" and "RECORDS INSERT" mode names and
  styles.
- Updated keymaps, command registry, and view navigation to support these
  new modes.
- Added comprehensive tests for records mode behavior, including sorting
  and boundary conditions for Tab/Enter.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-26B-A4B-it-UD-Q5_K_XL.gguf)
2026-04-15 21:32:35 -07:00
ded35f705c feat(model): use IndexMap for deterministic insertion order in DataStore
Replace `HashMap` with `IndexMap` in `DataStore::cells` to preserve
insertion order. This allows records mode to display rows in the order they
were added. Update `remove` to use `shift_remove` to maintain order during
deletions.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-26B-A4B-it-UD-Q5_K_XL.gguf)
2026-04-15 21:32:35 -07:00
7c00695398 refactor(navigation): include AppMode in view navigation stack
Introduce `ViewFrame` to store both the view name and the `AppMode` when
pushing to the navigation stack. Update `view_back_stack` and
`view_forward_stack` to use `ViewFrame` instead of `String` . Update
`CmdContext` and `Effect` implementations (SwitchView, ViewBack,
ViewForward) to handle the new `ViewFrame` structure. Add `is_editing()`
helper to `AppMode` .

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-26B-A4B-it-UD-Q5_K_XL.gguf)
2026-04-15 21:32:34 -07:00
23c7c530e3 refactor(parser): simplify tests and generator logic
Refactor formula parser tests to use more concise assert!(matches!(...))
syntax. Simplify the formula generator implementation by removing unused
expression variants and using expect() for mandatory grammar rules. Add a
regression test for hyphenated identifiers in bare names.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-26B-A4B-it-UD-Q5_K_XL.gguf)
2026-04-15 21:32:34 -07:00
1c16dc17e8 fix(workbook): align default virtual-category axes with main (improvise-kos)
Workbook::new was setting _Index=Row and _Dim=Column on the default view,
which forced a fresh workbook into records mode on every startup — records
mode auto-activates when both _Index and _Dim sit on axes. The result felt
like broken keybindings: the grid rendered as records instead of an
aggregated pivot, and every navigation key landed in an unexpected spot.

Main fixed this on the Model layer in 709f2df + 6d4b19a (improvise-kos);
the Workbook refactor accidentally reintroduced the pre-fix behavior.
Restore the post-kos defaults: all virtual categories start on Axis::None
(via on_category_added's "_"-prefix rule), then _Measure is bumped to
Axis::Page so aggregated pivot views show one measure at a time. _Index
and _Dim only move onto axes when the user explicitly enters records mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:22:59 -07:00
f1229a60e4 Merge branch 'main' into worktree-improvise-ewi-formula-crate
# Conflicts:
#	src/model/types.rs
#	src/view/layout.rs
2026-04-15 21:11:55 -07:00
3fbf56ec8b refactor: break Model↔View cycle, introduce Workbook wrapper
Model is now pure data (categories, cells, formulas, measure_agg) with
no references to view/. The Workbook struct owns the Model together
with views and the active view name, and is responsible for cross-slice
operations (add/remove category → notify views, view management).

- New: src/workbook.rs with Workbook wrapper and cross-slice helpers
  (add_category, add_label_category, remove_category, create_view,
  switch_view, delete_view, normalize_view_state).
- Model: strip view state and view-touching methods. recompute_formulas
  remains on Model as a primitive; the view-derived none_cats list is
  gathered at each call site (App::rebuild_layout, persistence::load)
  so the view dependency is explicit, not hidden behind a wrapper.
- View: add View::none_cats() helper.
- CmdContext: add workbook and view fields so commands can reach both
  slices without threading Model + View through every call.
- App: rename `model` field to `workbook`.
- Persistence (save/load/format_md/parse_md/export_csv): take/return
  Workbook so the on-disk format carries model + views together.
- Widgets (GridWidget, TileBar, CategoryContent, ViewContent): take
  explicit &Model + &View instead of routing through Model.

Tests updated throughout to reflect the new shape. View-management
tests that previously lived on Model continue to cover the same
behaviour via a build_workbook() helper in model/types.rs. All 573
tests pass; clippy is clean.

This is Phase A of improvise-36h. Phase B will mechanically extract
crates/improvise-core/ containing model/, view/, format.rs, workbook.rs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:08:11 -07:00
6d4b19a940 fix(model): default _Measure to Page axis, skip empty page categories (improvise-kos)
Virtual categories _Index and _Dim now default to None on new models
instead of being forced onto Row/Column. _Measure defaults to Page
(the natural home for measure filtering). Fixed page_coords builder
to skip Page categories with no items/selection, preventing empty-string
contamination of cell keys.

Made-with: Cursor
2026-04-15 04:37:06 -07:00
709f2df11f fix(model): initialize virtual categories with Axis::None
Ensure that virtual categories (_Index, _Dim, _Measure) are registered in
the default view with Axis::None. This prevents potential panics when
calling axis_of on these categories and allows users to move them to a
specific axis manually.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-31B-it-UD-Q4_K_XL.gguf)
2026-04-15 04:32:14 -07:00
5fbc73269f refactor(formula): trust grammar invariants in parser
Refactor the formula parser to assume grammar invariants, replacing
Result-based error handling in tree walkers with infallible functions and
.expect(GRAMMAR_INVARIANT) calls.

This simplification is based on the guarantee that the Pest grammar already
validates the input structure. To verify these invariants and ensure
correctness, extensive new unit tests and property-based tests using
proptest have been added.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-31B-it-UD-Q4_K_XL.gguf)
2026-04-15 04:32:14 -07:00
dba8a5269e refactor(records): use CategoryKind for column filtering, stabilize _Measure position
- Filter records-mode columns by CategoryKind instead of string names
- Simplify cell fetching: matching_cells already handles empty filters
- Sort _Measure to always appear right before Value column

Made-with: Cursor
2026-04-15 04:16:09 -07:00
3f69f88709 refactor!(formula): migrate parser to use pest
Replace the manual tokenizer and recursive descent parser with a PEG
grammar using the pest library.

This migration involves introducing a formal grammar in formula.pest and
updating the parser implementation to utilize the generated Pest parser
with a tree-walking approach to construct the AST.

The change introduces a stricter requirement for identifiers: multi-word
identifiers must now be enclosed in pipe quotes (e.g., |Total Revenue|) and
are no longer accepted as bare words.

Tests have been updated to reflect the new parsing logic, remove
tokenizer-specific tests, and verify the new pipe-quoting and escape
semantics.

BREAKING CHANGE: Multi-word identifiers now require pipe-quoting (e.g. |Total Revenue|) and
are no longer accepted as bare words.
Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-31B-it-UD-Q4_K_XL.gguf)
2026-04-15 04:04:57 -07:00
38f83b2417 fix(records): include _Measure as visible column in records mode (improvise-rbv)
The records mode column filter excluded all categories starting with '_',
which hid _Measure. Changed to explicitly exclude only _Index and _Dim,
making _Measure visible as a data column. Updated the blank-model editing
test to reflect the new column order (_Measure first, Value last).

Made-with: Cursor
2026-04-15 03:56:18 -07:00
ee5fc89e43 chore(merge): branch 'worktree-improvise-ewi-formula-crate'
Fixes: improvise-ewi
2026-04-15 03:02:14 -07:00
efab0cc32e test(ui): clean up formatting in app.rs tests
Clean up formatting of a test assertion in app.rs to improve readability.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-31B-it-UD-Q4_K_XL.gguf)
2026-04-15 03:01:30 -07:00
d8375ceaa7 refactor(command): simplify commit_cell_value by extracting helper functions
Simplify the commit_cell_value function by extracting its core logic into
specialized helper functions: commit_regular_cell_value, stage_drill_edit,
and commit_plain_records_edit. This improves readability and reduces
nesting.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-31B-it-UD-Q4_K_XL.gguf)
2026-04-15 03:01:30 -07:00
6f291ccd04 refactor(ui): extract record coordinates error message to constant
Extract the hardcoded error message "Record coordinates cannot be empty"
into a public constant in effect.rs to avoid duplication and improve
maintainability.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-31B-it-UD-Q4_K_XL.gguf)
2026-04-15 03:01:30 -07:00
9710fb534e feat(cmd): allow direct commit of synthetic records in records mode
Allow synthetic record edits to be applied directly to the model when not
in drill mode, whereas they remain staged in drill state when a drill
snapshot is active. This enables editing of records in plain records view.

Additionally, add validation to prevent creating records with empty
coordinates in both direct commits and when applying staged drill edits.
Includes regression tests for persistence in blank models, drill state
staging, and empty coordinate prevention.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-31B-it-UD-Q4_K_XL.gguf)
2026-04-14 02:32:03 -07:00
f02d905aac refactor(formula): extract formula parser into separate crate
Extract the formula AST and parser into a dedicated `improvise-formula`
crate and convert the project into a Cargo workspace.

The root crate now re-exports `improvise-formula` as `crate::formula` to
maintain backward compatibility for internal callers. The repository map is
updated to reflect the new crate structure.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-31B-it-UD-Q4_K_XL.gguf)
2026-04-14 01:49:35 -07:00
c79498b04b chore: update gitignore 2026-04-14 01:43:32 -07:00
60a8559a7f docs(design): add principle about decomposing instead of early returning
Add a new design principle encouraging decomposition of functions instead
of relying on early returns, as early returns often signal mixed
responsibilities.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-31B-it-UD-Q4_K_XL.gguf)
2026-04-14 01:26:30 -07:00
1cea06e14b fix(records): allow adding rows in empty records view
Add helper methods to CmdContext to clarify layout state and synthetic
record presence. Update AddRecordRow to check for records mode generally
rather than requiring a synthetic record at the current cursor, which
allows adding the first row to an empty records view.

Includes a regression test for the empty records view scenario.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-31B-it-UD-Q4_K_XL.gguf)
2026-04-14 01:26:29 -07:00
8b7b45587b refactor(ui): use iterator in TileBar loop
Update the loop in TileBar to use an iterator-based approach with
enumerate, take, and skip instead of indexing.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-31B-it-UD-Q4_K_XL.gguf)
2026-04-14 01:03:25 -07:00
d551d53eb4 refactor(test): simplify assertions and calls in various tests
Clean up various test cases by simplifying Option checks, removing
redundant clones, using contains instead of any for DateComponent checks,
and removing unnecessary references in string formatting.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-31B-it-UD-Q4_K_XL.gguf)
2026-04-14 01:03:25 -07:00
f3996da2ec refactor(cmd): simplify commit and navigation logic
Simplify the return value of CommitFormula::execute to use a vec! macro and
move the page_cat_data helper function in navigation.rs for better
organization.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-31B-it-UD-Q4_K_XL.gguf)
2026-04-14 01:03:25 -07:00
648e50860e refactor(grammar): use is_multiple_of and let-chains
Replace modulo operations with is_multiple_of calls and simplify nested if
statements using let-chains in the grammar generator.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-31B-it-UD-Q4_K_XL.gguf)
2026-04-14 01:03:24 -07:00
bdbfe08476 chore: update {AGENTS,CLAUDE}.md 2026-04-14 00:52:51 -07:00