+20
-12
@@ -1,7 +1,8 @@
|
||||
{"_type":"issue","id":"improvise-cq1q.12","title":"ADR 007 step 2b: displays in the grid frame, page bar, records labels, item rows and CSV export","description":"The second slice of improvise-cq1q.3 (ADR 007 step 2). The first slice put `display` on Category, Item and View, and made the category-panel headers, the tile bar and the view list paint it in both front ends. This slice covers the rest of the grid and the panels:\n- item rows in the category panel (CategoryRow.items needs an item display beside each key);\n- the grid frame's label tuples and group headers;\n- the page bar (PageAxis cat/selected/items);\n- records-mode column headers and coordinate cells;\n- CSV export headers (persistence/export.rs render_csv).\n\nConstraint: the frame tuples are also cell coordinates (`cell_key`, `innermost_group`, `run_start`), so the display must be added at projection time (for example, a display tuple beside the key tuple in AxisFrame), never swapped in place. The page bar's items are cycled by the browser (`SetPageSelection`), so keys stay beside displays there too. See context/code/core.md §21.\n\n## Acceptance Criteria\n- Tests first, each seen to fail. With a category, an item and a group each displayed differently from its key:\n - the TUI grid, the browser render model and CSV export draw the displays;\n - `cell_key` and every command a client composes still name keys.\n- Rival: a frame whose tuple carries the display instead of the key. Cell addressing must fail.\n","status":"open","priority":1,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-25T19:10:38Z","created_by":"agent-adr007","updated_at":"2026-09-25T19:10:38Z","dependencies":[{"issue_id":"improvise-cq1q.12","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T19:10:39Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.12","depends_on_id":"improvise-cq1q.3","type":"blocks","created_at":"2026-09-25T19:10:48Z","created_by":"agent-adr007","metadata":"{}"}],"comments":[{"id":"2c593fb5-4faa-5695-9e21-3e00aa3ee75b","issue_id":"improvise-cq1q.12","author":"Edward Langley","text":"From the adversarial review of PR #328 at 3116d524 (Claude agent, in the owner's session):\n\n1. **One more surface that still paints category keys.** The view panel's axis summary, `viewmodel::view_axis_summary` (for example `R:Region,Product`), is drawn by `src/ui/view_panel.rs` and by `wasm-client/render.rs` (`RenderViewRow.axes`). `ViewSummary.axes` holds `(category key, Axis)` only, so it needs a display beside each key, just as the frame does. It is not listed here or in cq1q.13.\n2. **A test gap to close in this slice.** A fault in which `view_summaries` gives every view the ACTIVE view's display passes the whole workspace. Add a second view whose display differs from both the active view's display and its own key.\n","created_at":"2026-09-25T19:38:50Z"}],"dependency_count":1,"dependent_count":1,"comment_count":1}
|
||||
{"_type":"issue","id":"improvise-cq1q.5","title":"ADR 007 step 4: .improv ids — new version literal, id grammar, loader refusals, writer, round-trip properties","description":"ADR 007 clauses 5, 9 and 10: persistence, and migration step 4.\n- A second version literal. The grammar's `file` rule gets one arm per literal, and the version line alone picks the regime.\n- The new arm reads declarations with an id (header_decl, item_decl, grouped_item) and references as id_ref. id_token is spelled with ranges (improvise-rxz9). axis_line is tried before format_line (improvise-le4p).\n- The old arm is read exactly as today: whole-line headers, references resolved against displays, ids assigned by slugify, and undeclared items declared at load (answer 24).\n- The loader refuses a new-version file with a duplicate id, a missing id, an id outside the alphabet or a reserved `_` id. The error names the line, and for a duplicate it names both lines.\n- The writer always writes the new literal, and pipe-quotes any display that would otherwise split (`## Category: |Region(code)|(regioncode)`, improvise-80rm).\n- The round-trip properties from clause 5, plus a formula-line generator strategy (improvise-rxz9), so the formula properties are not vacuous.\n\nThe formula-line syntax waits for improvise-m56i.\n\n## Acceptance Criteria\n- Tests first, each failing before the change:\n - an old file with `## Category: Region(code)` loads with display `Region(code)` and id `regioncode`, and its data stays drawn;\n - a new-version file keeps every id verbatim, including `east-3`;\n - duplicate, missing and capitalised ids are each refused with the line named;\n - a category displayed `Format` on a row axis round-trips;\n - format(parse(format(parse(x)))) == format(parse(x)) holds.\n- Evidence that the formula strategy emits `=` lines the walker keeps: count them.\n","status":"open","priority":1,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-25T04:49:41Z","created_by":"agent-adr007","updated_at":"2026-09-25T04:49:41Z","dependencies":[{"issue_id":"improvise-cq1q.5","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T04:49:41Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.5","depends_on_id":"improvise-cq1q.1","type":"blocks","created_at":"2026-09-25T04:50:54Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.5","depends_on_id":"improvise-cq1q.4","type":"blocks","created_at":"2026-09-25T04:50:53Z","created_by":"agent-adr007","metadata":"{}"}],"dependency_count":2,"dependent_count":1,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-cq1q.4","title":"ADR 007 step 3: re-key storage by id (Model, Workbook views, CellKey, DataStore, View config, RecordHash)","description":"ADR 007 clauses 2, 3 and 8, and migration step 3: the wide re-key. Every creation site takes its key from the workbook's IdAllocator (step 1): add_category, add_item, add_item_in_group, create_view and add_formula's target item. The display is the typed name.\n- Model::categories and Workbook::views are keyed by id.\n- CellKey is a sorted list of (CategoryId, ItemId), sorted by category id, so gotcha 5's canonical form holds under the new sort key. DataStore interns ids.\n- View config is keyed by id: axis maps, page selections, and hidden and collapsed sets (collapsed stays (CategoryId, group name), per improvise-p003).\n- RecordHash is computed over id text (clause 8). Update ADR 002's prose in the same PR, since the ADR assigns that update to the implementation PR.\n- Display-\u003eid resolution moves to the input edges: minibuffer commits, the command-line/script parser (coordinate with improvise-31oe.2 and improvise-31oe.3), and formula reference resolution.\n\nThis step is blocked by the user question in improvise-m56i on how typed formula names resolve (item 6) and on a non-_Measure formula target's identity (item 2).\n\n## Acceptance Criteria\n- Tests first: after add-category \"Time Period\", the key is `time_period` and the display is \"Time Period\".\n- Adding `East` to Region and to Direction gives `east` and `east-1` (answer 22).\n- A display rename (a test hook until fz77) moves no CellKey, no RecordHash and no layout_version. This is the property the design exists for, so assert that the labels changed and the surrogates did not. The rival, re-keying on rename, must fail it.\n- The wasm gate passes for every bundle crate.\n","notes":"Scope added for improvise-cq1q.11 (agent-adr007, 2026-09-25). This step now owns the OLD-format .improv edge, because it is the only format until cq1q.5: the old arm's loader assigns ids by slugify and resolves references against displays (answer 24 included), and the writer writes DISPLAYS in the old format. cq1q.5 keeps the new version literal, the id grammar, the refusals and the writer switch. Wire operands are already keys, via cq1q.7, which now lands first. Added acceptance criteria: (1) a document with categories displayed Region and Café, and item East, survives save and load at this step with every display intact. The rival, a writer that writes keys, gives region/caf/east and fails. (2) The browser round-trip suite (tests/browser_round_trip.rs, tests/lockstep.rs) stays green with no display-\u003eid resolver in ui/reduce.rs. The main branch is shippable after this step.","status":"open","priority":1,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-25T04:49:27Z","created_by":"agent-adr007","updated_at":"2026-09-25T06:42:12Z","dependencies":[{"issue_id":"improvise-cq1q.4","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T04:49:27Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.4","depends_on_id":"improvise-cq1q.2","type":"blocks","created_at":"2026-09-25T06:41:57Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.4","depends_on_id":"improvise-cq1q.3","type":"blocks","created_at":"2026-09-25T04:50:50Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.4","depends_on_id":"improvise-cq1q.7","type":"blocks","created_at":"2026-09-25T06:41:47Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.4","depends_on_id":"improvise-m56i","type":"blocks","created_at":"2026-09-25T04:50:52Z","created_by":"agent-adr007","metadata":"{}"}],"dependency_count":4,"dependent_count":4,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-cq1q.3","title":"ADR 007 step 2: a display field on Category, Item and View, read by every renderer (keys unchanged)","description":"ADR 007 clause 2, first half. Category, Item and View each gain `display: String`. Every place that shows a name to a person reads `display`: the grid frame labels, the category, formula and view panels, the tile bar, the page bar, CSV export headers, the minibuffer prompts and the status messages. The key is still the name, and `display == name` everywhere at the end of this step, so behaviour does not change. This splits \"what people see\" from \"what keys the map\" before any key moves, so the re-key step (the next bead) changes only what goes into the key. It follows the user's answer 1: \"reserve `name` ... for the id\".\n\nCoordinate with improvise-felo.6, which is working in category.rs and view/layout.rs. Land after it, or merge.\n\n## Acceptance Criteria\n- A test-only document whose display differs from its key (constructed through a test hook, since no production path can build one yet) shows the display in the grid frame, the category panel, the view panel, the tile bar and the CSV export header. The key never appears.\n- Rival: revert one renderer to read the key, and the matching test fails.\n- The whole suite is otherwise unchanged.\n","status":"open","priority":1,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-25T04:49:14Z","created_by":"agent-adr007","updated_at":"2026-09-25T04:49:14Z","dependencies":[{"issue_id":"improvise-cq1q.3","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T04:49:14Z","created_by":"agent-adr007","metadata":"{}"}],"dependency_count":0,"dependent_count":2,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-cq1q.2","title":"ADR 007 step 1: slugify and the gensym id allocator (pure, improvise-core)","description":"ADR 007 clause 4: slugify, and the gensym-style id allocator. A pure module in improvise-core with no callers yet. It is migration step 2's first piece and depends on nothing.\n\n- `slugify(display)` does:\n 1. Unicode full case folding (answer 26). The `unicase` crate provides it; it is no_std, has no dependencies and is wasm-clean.\n 2. Each run of spaces becomes one `_`.\n 3. Every character outside a-z, 0-9, `-` and `_` is stripped.\n 4. Leading `_` is removed.\n 5. An empty result falls through to the allocator.\n- `IdAllocator`: one increment-only counter per document, starting at 1. A generated id stays bare unless it collides. A collision takes `-\u003cn\u003e` from the counter, advancing again until the candidate is free (answers 27, 28 and 29). An empty slug becomes `\u003ckind\u003e-\u003cn\u003e`. The allocator keeps every id it has ever seen, so a removed id is never reissued within a session.\n- `claim(id)` is how a hand-written id enters the allocator. It refuses a duplicate (answer 29), an id outside the alphabet, and a leading `_` other than the virtual ids `_index`, `_dim` and `_measure` (answer 23). A claimed id is never renamed, because hand-written ids are canonical.\n\n## Acceptance Criteria\n- Tests first, each shown failing: the ADR's examples (`East`-\u003e`east`, `Time Period`-\u003e`time_period`, `Region(code)`-\u003e`regioncode`, `Café`-\u003e`caf`, `Straße`-\u003e`strasse`, `_Draft`-\u003e`draft`, `Ü`-\u003e`category-\u003cn\u003e`); a collision across kinds; a claimed `east-1` that forces `east-2`; retirement; determinism across two independently built allocators.\n- Each test's rival injected and seen to fail: `to_lowercase` instead of folding (`Straße`), a lowest-free-number suffix, and replacing each single space instead of the run.\n- The wasm check passes for improvise-core, and harness/opfs-contract's lock is updated for the new dependency.\n","status":"in_progress","priority":1,"issue_type":"task","assignee":"agent-adr007","owner":"el-github@elangley.org","created_at":"2026-09-25T04:49:02Z","created_by":"agent-adr007","updated_at":"2026-09-25T04:51:42Z","started_at":"2026-09-25T04:51:42Z","lease_expires_at":"2026-09-25T04:56:42Z","heartbeat_at":"2026-09-25T04:51:42Z","dependencies":[{"issue_id":"improvise-cq1q.2","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T04:49:02Z","created_by":"agent-adr007","metadata":"{}"}],"comments":[{"id":"a07b9c7f-8972-53a8-8b97-aed300617104","issue_id":"improvise-cq1q.2","author":"agent-adr007","text":"PR #318 opened, branch adr007/slugify, head a2ddb06e. It adds model::id (slugify, IdAllocator, claim) and the unicase dependency. All 11 tests were written first, and each of the six rivals was caught (context/test-evidence/core.md). Every gate is green locally after merging gh/main c91cc055.","created_at":"2026-09-25T05:15:38Z"},{"id":"13895058-8d4e-538d-9939-3ed93a325d69","issue_id":"improvise-cq1q.2","author":"agent-adr007","text":"Pushed 689480bf: Cargo.nix regenerated with crate2nix for unicase. The first CI run's crate2nix job would have failed without it. nix build .#improvise passes locally.","created_at":"2026-09-25T05:37:45Z"}],"dependency_count":0,"dependent_count":1,"comment_count":2}
|
||||
{"_type":"issue","id":"improvise-cq1q.4","title":"ADR 007 step 3: re-key storage by id (Model, Workbook views, CellKey, DataStore, View config, RecordHash)","description":"ADR 007 clauses 2, 3 and 8, and migration step 3: the wide re-key. Every creation site takes its key from the workbook's IdAllocator (step 1): add_category, add_item, add_item_in_group, create_view and add_formula's target item. The display is the typed name.\n- Model::categories and Workbook::views are keyed by id.\n- CellKey is a sorted list of (CategoryId, ItemId), sorted by category id, so gotcha 5's canonical form holds under the new sort key. DataStore interns ids.\n- View config is keyed by id: axis maps, page selections, and hidden and collapsed sets (collapsed stays (CategoryId, group name), per improvise-p003).\n- RecordHash is computed over id text (clause 8). Update ADR 002's prose in the same PR, since the ADR assigns that update to the implementation PR.\n- Display-\u003eid resolution moves to the input edges: minibuffer commits, the command-line/script parser (coordinate with improvise-31oe.2 and improvise-31oe.3), and formula reference resolution.\n\nThis step is blocked by the user question in improvise-m56i on how typed formula names resolve (item 6) and on a non-_Measure formula target's identity (item 2).\n\n## Acceptance Criteria\n- Tests first: after add-category \"Time Period\", the key is `time_period` and the display is \"Time Period\".\n- Adding `East` to Region and to Direction gives `east` and `east-1` (answer 22).\n- A display rename (a test hook until fz77) moves no CellKey, no RecordHash and no layout_version. This is the property the design exists for, so assert that the labels changed and the surrogates did not. The rival, re-keying on rename, must fail it.\n- The wasm gate passes for every bundle crate.\n","notes":"Scope added for improvise-cq1q.11 (agent-adr007, 2026-09-25). This step now owns the OLD-format .improv edge, because it is the only format until cq1q.5: the old arm's loader assigns ids by slugify and resolves references against displays (answer 24 included), and the writer writes DISPLAYS in the old format. cq1q.5 keeps the new version literal, the id grammar, the refusals and the writer switch. Wire operands are already keys, via cq1q.7, which now lands first. Added acceptance criteria: (1) a document with categories displayed Region and Café, and item East, survives save and load at this step with every display intact. The rival, a writer that writes keys, gives region/caf/east and fails. (2) The browser round-trip suite (tests/browser_round_trip.rs, tests/lockstep.rs) stays green with no display-\u003eid resolver in ui/reduce.rs. The main branch is shippable after this step.\nUpdated for the user's decisions of 2026-09-25 (ADR 007 answers 30 and 31; PR #326). A formula target outside _Measure keeps a real item with its own ItemId, allocated like any item, and the formula gets a separate FormulaId (answer 30). Typed names are resolved at the CLIENT, displays first and then ids, and an ambiguous display is refused with the candidates' ids listed (answer 31). The host-side edges in this step, the minibuffer commits and the script parser, follow the same order. Formula references are resolved by the client/editor edge, never by the host from display text (answer 32). The dependency on improvise-m56i is satisfied: it is answered.","status":"open","priority":1,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-25T04:49:27Z","created_by":"agent-adr007","updated_at":"2026-09-25T18:59:37Z","dependencies":[{"issue_id":"improvise-cq1q.4","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T04:49:27Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.4","depends_on_id":"improvise-cq1q.12","type":"blocks","created_at":"2026-09-25T19:10:50Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.4","depends_on_id":"improvise-cq1q.13","type":"blocks","created_at":"2026-09-25T19:10:50Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.4","depends_on_id":"improvise-cq1q.2","type":"blocks","created_at":"2026-09-25T06:41:57Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.4","depends_on_id":"improvise-cq1q.3","type":"blocks","created_at":"2026-09-25T04:50:50Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.4","depends_on_id":"improvise-cq1q.7","type":"blocks","created_at":"2026-09-25T06:41:47Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.4","depends_on_id":"improvise-m56i","type":"blocks","created_at":"2026-09-25T04:50:52Z","created_by":"agent-adr007","metadata":"{}"}],"dependency_count":6,"dependent_count":4,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-cq1q.3","title":"ADR 007 step 2: a display field on Category, Item and View, read by every renderer (keys unchanged)","description":"ADR 007 clause 2, first half. Category, Item and View each gain `display: String`. Every place that shows a name to a person reads `display`: the grid frame labels, the category, formula and view panels, the tile bar, the page bar, CSV export headers, the minibuffer prompts and the status messages. The key is still the name, and `display == name` everywhere at the end of this step, so behaviour does not change. This splits \"what people see\" from \"what keys the map\" before any key moves, so the re-key step (the next bead) changes only what goes into the key. It follows the user's answer 1: \"reserve `name` ... for the id\".\n\nCoordinate with improvise-felo.6, which is working in category.rs and view/layout.rs. Land after it, or merge.\n\n## Acceptance Criteria\n- A test-only document whose display differs from its key (constructed through a test hook, since no production path can build one yet) shows the display in the grid frame, the category panel, the view panel, the tile bar and the CSV export header. The key never appears.\n- Rival: revert one renderer to read the key, and the matching test fails.\n- The whole suite is otherwise unchanged.\n","notes":"Split for review size (agent-adr007, 2026-09-25). This bead is the FIRST slice: display on Category/Item/View; CategoryRow/TileEntry/ViewSummary carry display beside the key; the category-panel headers, the tile bar and the view list paint it in the TUI and the browser. The rest is improvise-cq1q.12 (grid frame, page bar, records labels, item rows, CSV export) and improvise-cq1q.13 (status messages, view title). cq1q.4 now depends on both.","status":"in_progress","priority":1,"issue_type":"task","assignee":"agent-adr007","owner":"el-github@elangley.org","created_at":"2026-09-25T04:49:14Z","created_by":"agent-adr007","updated_at":"2026-09-25T19:10:51Z","started_at":"2026-09-25T18:59:52Z","lease_expires_at":"2026-09-25T19:04:52Z","heartbeat_at":"2026-09-25T18:59:52Z","dependencies":[{"issue_id":"improvise-cq1q.3","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T04:49:14Z","created_by":"agent-adr007","metadata":"{}"}],"dependency_count":0,"dependent_count":4,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-cq1q.2","title":"ADR 007 step 1: slugify and the gensym id allocator (pure, improvise-core)","description":"ADR 007 clause 4: slugify, and the gensym-style id allocator. A pure module in improvise-core with no callers yet. It is migration step 2's first piece and depends on nothing.\n\n- `slugify(display)` does:\n 1. Unicode full case folding (answer 26). The `unicase` crate provides it; it is no_std, has no dependencies and is wasm-clean.\n 2. Each run of spaces becomes one `_`.\n 3. Every character outside a-z, 0-9, `-` and `_` is stripped.\n 4. Leading `_` is removed.\n 5. An empty result falls through to the allocator.\n- `IdAllocator`: one increment-only counter per document, starting at 1. A generated id stays bare unless it collides. A collision takes `-\u003cn\u003e` from the counter, advancing again until the candidate is free (answers 27, 28 and 29). An empty slug becomes `\u003ckind\u003e-\u003cn\u003e`. The allocator keeps every id it has ever seen, so a removed id is never reissued within a session.\n- `claim(id)` is how a hand-written id enters the allocator. It refuses a duplicate (answer 29), an id outside the alphabet, and a leading `_` other than the virtual ids `_index`, `_dim` and `_measure` (answer 23). A claimed id is never renamed, because hand-written ids are canonical.\n\n## Acceptance Criteria\n- Tests first, each shown failing: the ADR's examples (`East`-\u003e`east`, `Time Period`-\u003e`time_period`, `Region(code)`-\u003e`regioncode`, `Café`-\u003e`caf`, `Straße`-\u003e`strasse`, `_Draft`-\u003e`draft`, `Ü`-\u003e`category-\u003cn\u003e`); a collision across kinds; a claimed `east-1` that forces `east-2`; retirement; determinism across two independently built allocators.\n- Each test's rival injected and seen to fail: `to_lowercase` instead of folding (`Straße`), a lowest-free-number suffix, and replacing each single space instead of the run.\n- The wasm check passes for improvise-core, and harness/opfs-contract's lock is updated for the new dependency.\n","status":"closed","priority":1,"issue_type":"task","assignee":"agent-adr007","owner":"el-github@elangley.org","created_at":"2026-09-25T04:49:02Z","created_by":"agent-adr007","updated_at":"2026-09-25T17:51:51Z","started_at":"2026-09-25T04:51:42Z","closed_at":"2026-09-25T17:51:51Z","close_reason":"PR #318 merged (6da6a040, head 7ddc50ac); reviewed PASS (issuecomment-5836573405); cq1q.11 closed by the re-sequenced bead graph","dependencies":[{"issue_id":"improvise-cq1q.2","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T04:49:02Z","created_by":"agent-adr007","metadata":"{}"}],"comments":[{"id":"a07b9c7f-8972-53a8-8b97-aed300617104","issue_id":"improvise-cq1q.2","author":"agent-adr007","text":"PR #318 opened, branch adr007/slugify, head a2ddb06e. It adds model::id (slugify, IdAllocator, claim) and the unicase dependency. All 11 tests were written first, and each of the six rivals was caught (context/test-evidence/core.md). Every gate is green locally after merging gh/main c91cc055.","created_at":"2026-09-25T05:15:38Z"},{"id":"13895058-8d4e-538d-9939-3ed93a325d69","issue_id":"improvise-cq1q.2","author":"agent-adr007","text":"Pushed 689480bf: Cargo.nix regenerated with crate2nix for unicase. The first CI run's crate2nix job would have failed without it. nix build .#improvise passes locally.","created_at":"2026-09-25T05:37:45Z"}],"dependency_count":0,"dependent_count":1,"comment_count":2}
|
||||
{"_type":"issue","id":"improvise-31oe.1.1","title":"Complete command names on the : command line","description":"First child of the auto-complete epic; the user's top priority. On the ':' command line, completing the command name offers every registered command (names read off the registry's prototypes, gotcha 15 -- no string list), filtered by the typed prefix. Decide and test: the key (Tab, with what on an exact match), single-match insertion, multi-match display (cycle vs list), and that the TUI and the browser use the same engine (improvise-y11g brings the ':' line to the browser). Tests first: every registered command is completable; a prefix narrows; an unknown prefix offers nothing; the candidate set equals the registry's name set (so a new command cannot be missing).","status":"open","priority":1,"issue_type":"feature","owner":"el-github@elangley.org","created_at":"2026-09-25T03:41:48Z","created_by":"Edward Langley","updated_at":"2026-09-25T03:41:48Z","labels":["command","protocol","ux"],"dependencies":[{"issue_id":"improvise-31oe.1.1","depends_on_id":"improvise-31oe.1","type":"parent-child","created_at":"2026-09-25T03:41:48Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-8jk6.2","title":"Import drops whole records that lack a category value; keep them with the coordinate absent","description":"USER 2026-09-25, verbatim: 'this limit in import should be fixed, it dates to when this wasn't sparse.' Screenshot: 'Skipped 6 of 342 records missing category values: Settlement Date (6)'. build_model_with_warnings (crates/improvise-io/src/import/wizard.rs ~262-284) skips any record with no value for an accepted Category field; improvise-5j9 only made the loss visible. Records are sparse, so such a record should be KEPT with that coordinate absent from its CellKey. Same mechanism as the sparse folder merge (improvise-lpzu), where a whole file lacks a category column -- so it belongs with that work. Decide and test how an absent coordinate behaves downstream: a view with that category on Row/Column (is the record invisible there, shown under a blank/(none) row, or counted only in totals?), on Page (which selection shows it), on None (aggregates include it), records mode (blank cell in that column), .improv round-trip (a Data line simply omits the category), drill, and ADR 007/identity. The display question for Row/Column is the user's to confirm; state the recommendation in the PR.","status":"closed","priority":1,"issue_type":"bug","assignee":"agent-lpzu","owner":"el-github@elangley.org","created_at":"2026-09-25T03:37:31Z","created_by":"Edward Langley","updated_at":"2026-09-25T05:02:21Z","started_at":"2026-09-25T03:37:52Z","closed_at":"2026-09-25T05:02:21Z","close_reason":"Landed in PR #314; reviewed PASS at c43ee724","labels":["import"],"dependencies":[{"issue_id":"improvise-8jk6.2","depends_on_id":"improvise-8jk6","type":"parent-child","created_at":"2026-09-25T03:37:31Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-felo.1","title":"Raise MAX_CATEGORIES from 12 to 24","description":"USER DECISION 2026-09-25, verbatim: '12 categories is feeling small, we should increase to 24.' Prompted by an Etrade transactions import that hit exactly 12 categories, which left no room for the duplicate-disambiguating category of improvise-cgqt (PR #304). Supersedes context/plan.md:260 ('Do not raise or restructure the MAX_CATEGORIES = 12 limit'). The limit lives in crates/improvise-core/src/model/types.rs and is MIRRORED by a private copy in crates/improvise-io/src/import/wizard.rs — make the wizard read the model's (one home), rather than changing two numbers. Update every doc that states 12 (README, design-principles, context/code/*, plan.md) and every test that fills to the cap by literal count; tests should derive the cap, not spell it. Before changing it, find and state why 12 was chosen (any cost that scales with category count: CellKey size, the secondary index, layout cross products, formula fold) and measure one representative operation at 24 categories.","status":"closed","priority":1,"issue_type":"feature","assignee":"wave-felo1","owner":"el-github@elangley.org","created_at":"2026-09-25T02:06:57Z","created_by":"Edward Langley","updated_at":"2026-09-25T03:10:53Z","started_at":"2026-09-25T02:07:37Z","closed_at":"2026-09-25T03:10:53Z","close_reason":"Landed in PR #307; reviewed PASS at 74e460be","labels":["core","import"],"dependencies":[{"issue_id":"improvise-felo.1","depends_on_id":"improvise-felo","type":"parent-child","created_at":"2026-09-25T02:06:57Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
@@ -101,7 +102,9 @@
|
||||
{"_type":"issue","id":"improvise-km8","title":"1.2 Fix Cargo.toml metadata","description":"Add missing package fields: description, repository, homepage, documentation, readme, keywords, categories, license. Description: 'Terminal pivot-table modeling in the spirit of Lotus Improv'. Keywords: tui, pivot, spreadsheet, data, improv. Categories: command-line-utilities, visualization.","status":"closed","priority":1,"issue_type":"task","assignee":"Edward Langley","owner":"el-github@elangley.org","created_at":"2026-04-09T04:06:48Z","created_by":"Edward Langley","updated_at":"2026-07-30T23:06:31Z","closed_at":"2026-04-09T07:20:17Z","close_reason":"Metadata already present: description, license, repository, homepage, readme, keywords, categories all populated.","dependency_count":0,"dependent_count":2,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-902","title":"1.1 Audit and remove or update context/SPEC.md","description":"Compare context/SPEC.md against actual code. If spec contradicts code significantly, delete it. Optionally move salvageable conceptual content to docs/design-notes.md with a staleness disclaimer. Do not try to bring spec into sync.","status":"closed","priority":1,"issue_type":"task","assignee":"Edward Langley","owner":"el-github@elangley.org","created_at":"2026-04-09T04:06:46Z","created_by":"Edward Langley","updated_at":"2026-07-30T23:06:31Z","closed_at":"2026-04-09T07:10:05Z","close_reason":"Audited SPEC.md against code. Found minor inaccuracies (storage internals, wizard step count, search mode representation) but no major architectural contradictions. Deleted SPEC.md since it was largely redundant with repo-map.md and design-principles.md. Salvaged product vision and non-goals into docs/design-notes.md with staleness disclaimer.","dependency_count":0,"dependent_count":1,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-tik","title":"Phase 1: Repository hygiene","description":"Raise the quality floor of what a stranger sees when they land on the repo. Subtasks cover spec audit, Cargo.toml metadata, publish-readiness, CSV audit, and example files.","status":"closed","priority":1,"issue_type":"feature","owner":"el-github@elangley.org","created_at":"2026-04-09T04:05:45Z","created_by":"Edward Langley","updated_at":"2026-04-09T07:22:34Z","closed_at":"2026-04-09T07:22:34Z","close_reason":"All 5 subtasks complete: SPEC.md audited, Cargo.toml metadata fixed, publish dry-run clean, CSV audit done, example files created.","dependencies":[{"issue_id":"improvise-tik","depends_on_id":"improvise-2fr","type":"blocks","created_at":"2026-04-08T23:37:31Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-tik","depends_on_id":"improvise-902","type":"blocks","created_at":"2026-04-08T23:37:30Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-tik","depends_on_id":"improvise-bv1","type":"blocks","created_at":"2026-04-08T23:37:32Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-tik","depends_on_id":"improvise-km8","type":"blocks","created_at":"2026-04-08T23:37:30Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-tik","depends_on_id":"improvise-n1h","type":"blocks","created_at":"2026-04-08T23:37:32Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":5,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-cq1q.11","title":"ADR 007 plan: cq1q.4 (re-key) omits two input edges it reaches before their own steps — the old-format .improv reader/writer (before .5) and wire operands/projection names (before .7/.8)","description":"Found by the adversarial review of PR #318 (head 689480bf), while checking the cq1q plan's ordering.\n\nimprovise-cq1q.4 (re-key storage by id) lists these input edges where display→id resolution moves: minibuffer commits, the command-line/script parser, and formula reference resolution. Two more edges reach the re-key before their own steps land, and the ticket does not name either.\n\n1. **The .improv reader and writer, before cq1q.5.** After .4, the keys are slugs (`region`, `east`) and the displays are the typed names. Until .5 lands, the only file format is the old one. So:\n - the old-format loader must already assign ids by slugify and resolve references against displays (clause 5's old arm, currently .5's scope);\n - the writer must write displays, not keys.\n\n If the writer writes keys, every save between .4 and .5 lowercases and strips the user's names (`Region` → `region`, `Café` → `caf`), and nothing can restore them.\n\n2. **Wire operands and projection names, before cq1q.7/.8.** After .3, the browser's panels show displays taken from the projections, and the browser composes `SetAxis { category, .. }`, `HideItem` and the rest from the same strings. After .4 the host keys by id, so between .4 and .8 every such command names a key that does not exist. That covers almost every real name, because slugify folds case. The fix is one of:\n - `ui/reduce.rs::model_effects` resolves display→id as an interim input edge;\n - the projections keep carrying the key alongside the display.\n\n ViewState's name-keyed cursors, expanded_cats and view history (cq1q.7) meet the same seam.\n\nADR 007 clause 11 orders the migration the same way (re-key, then persistence, then projections, then vocabulary), so this is a gap in the step boundaries rather than in the ADR's decisions. What is needed: .4's acceptance criteria say the suite stays green, and they name these two interim edges. Or .4 is split so that each step leaves main shippable. A test for edge 1: a document with display `Region` survives save/load between .4 and .5 with its display intact.\n\nA smaller point: cq1q.3 depends on cq1q.2, but adding a display field needs neither slugify nor the allocator. The dependency serializes work for no reason.\n","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-25T06:19:38Z","created_by":"Edward Langley","updated_at":"2026-09-25T06:19:38Z","dependencies":[{"issue_id":"improvise-cq1q.11","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T06:19:38Z","created_by":"Edward Langley","metadata":"{}"}],"comments":[{"id":"ca207052-0f62-5f31-b8d9-3d4a3a30b68f","issue_id":"improvise-cq1q.11","author":"agent-adr007","text":"agent-adr007: resolved by reordering, with no new beads. (1) cq1q.7 (projections and ViewState carry an id field) now depends only on cq1q.3 and lands before the re-key, using the display-first trick: id == key == name until cq1q.4 changes what the key is. So the wire never names a key that does not exist, and no interim resolver in model_effects is needed. (2) cq1q.4 now owns the old-format reader and writer edge: the loader slugifies, the writer writes displays, and a save/load test keeps the displays Region and Café intact. (3) The cq1q.3 -\u003e cq1q.2 dependency is removed. cq1q.4 now depends on .2, .3 and .7, and cq1q.8 depends on .4 and .7. Details are appended to the notes of cq1q.4 and cq1q.7.","created_at":"2026-09-25T06:42:12Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1}
|
||||
{"_type":"issue","id":"improvise-cq1q.13","title":"ADR 007 step 2c: displays in status messages and the view title","description":"The third slice of improvise-cq1q.3 (ADR 007 step 2): the remaining places that show a key to a person.\n- Status and minibuffer messages that interpolate a category, item or view name: about 10 sites in improvise-command (ui/effect.rs, cmd/panel.rs, cmd/commit.rs, cmd/core.rs), plus `non_item_refusal` in improvise-protocol and the anyhow errors in workbook.rs that reach the status line.\n- The view title: `grid.rs` \" View: {}\" in the TUI, and `render.rs` `view_name` in the browser. Both read the key from `ViewState::active_view`; they need the active view's display from a projection (the ViewListCache already carries it since the first slice).\n\n## Acceptance Criteria\n- Tests first: with a view and a category displayed differently from their keys, the title and a representative status message (for example, the axis-change status) show the display.\n- Rival: interpolating the key. The test fails.\n","status":"open","priority":2,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-25T19:10:41Z","created_by":"agent-adr007","updated_at":"2026-09-25T19:10:41Z","dependencies":[{"issue_id":"improvise-cq1q.13","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T19:10:41Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.13","depends_on_id":"improvise-cq1q.3","type":"blocks","created_at":"2026-09-25T19:10:49Z","created_by":"agent-adr007","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-nn4s.10","title":"ADR 004 spells reverse selection as background=accent, which REVERSED draws inverted; the accent must be the foreground","description":"Found in the post-merge review of PR #319 (merged head 3583359a). This needs the user,\nbecause it is a correction to accepted ADR 004 text, not to code.\n\nADR 004 clause 3 and the vocabulary section say the twelve `fg(Black).bg(accent)`\nselection sites become faces that inherit `selection` (which carries `reverse`) and\n\"override `background` from its own surface's accent\". That spelling is inverted.\nREVERSED (SGR 7) swaps the cell's foreground and background. So `bg = accent` plus\n`reverse` draws accent-coloured text on the terminal's default-foreground colour. It\ndoes not draw default-background ink on the accent. Clause 5's CSS rule does the same\nswap (`.reversed { color: var(--face-bg) }`), so the browser inverts identically.\n\nTo reproduce \"ink on accent\" with reverse, the derived face must state the accent as\nits FOREGROUND, leave background unstated, and set reverse. That is also what makes the\nlight-terminal fix in clause 3 work: the ink becomes the terminal's own background.\n\nThis blocks improvise-nn4s.6. If the ADR is migrated as written, every selection\nhighlight renders inverted. It is also not a no-op on a TestBackend buffer. Clause 8\nrequires that change to be named as an exception, with who decided it. So nn4s.6 needs\nboth the corrected spelling and the user's ruling on the exception.\n","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-25T17:09:00Z","created_by":"Edward Langley","updated_at":"2026-09-25T17:09:00Z","labels":["adr","adversarial-review","needs-user","pr-319"],"dependencies":[{"issue_id":"improvise-nn4s.10","depends_on_id":"improvise-nn4s","type":"parent-child","created_at":"2026-09-25T17:09:00Z","created_by":"Edward Langley","metadata":"{}"}],"comments":[{"id":"04cf9807-5d5e-5926-a40c-56dfa32f62a7","issue_id":"improvise-nn4s.10","author":"Edward Langley","text":"USER RULING 2026-09-25, verbatim: 'ADR 004 is allowed to specify visual changes that make the UI more consistent'. So clause 8's exceptions are not limited to category-add: the selection faces using reverse (with the accent as FOREGROUND, per this ticket's correction) are an allowed visual change. Unblocks nn4s.6 once the ADR text is corrected.","created_at":"2026-09-25T17:15:08Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1}
|
||||
{"_type":"issue","id":"improvise-cq1q.11","title":"ADR 007 plan: cq1q.4 (re-key) omits two input edges it reaches before their own steps — the old-format .improv reader/writer (before .5) and wire operands/projection names (before .7/.8)","description":"Found by the adversarial review of PR #318 (head 689480bf), while checking the cq1q plan's ordering.\n\nimprovise-cq1q.4 (re-key storage by id) lists these input edges where display→id resolution moves: minibuffer commits, the command-line/script parser, and formula reference resolution. Two more edges reach the re-key before their own steps land, and the ticket does not name either.\n\n1. **The .improv reader and writer, before cq1q.5.** After .4, the keys are slugs (`region`, `east`) and the displays are the typed names. Until .5 lands, the only file format is the old one. So:\n - the old-format loader must already assign ids by slugify and resolve references against displays (clause 5's old arm, currently .5's scope);\n - the writer must write displays, not keys.\n\n If the writer writes keys, every save between .4 and .5 lowercases and strips the user's names (`Region` → `region`, `Café` → `caf`), and nothing can restore them.\n\n2. **Wire operands and projection names, before cq1q.7/.8.** After .3, the browser's panels show displays taken from the projections, and the browser composes `SetAxis { category, .. }`, `HideItem` and the rest from the same strings. After .4 the host keys by id, so between .4 and .8 every such command names a key that does not exist. That covers almost every real name, because slugify folds case. The fix is one of:\n - `ui/reduce.rs::model_effects` resolves display→id as an interim input edge;\n - the projections keep carrying the key alongside the display.\n\n ViewState's name-keyed cursors, expanded_cats and view history (cq1q.7) meet the same seam.\n\nADR 007 clause 11 orders the migration the same way (re-key, then persistence, then projections, then vocabulary), so this is a gap in the step boundaries rather than in the ADR's decisions. What is needed: .4's acceptance criteria say the suite stays green, and they name these two interim edges. Or .4 is split so that each step leaves main shippable. A test for edge 1: a document with display `Region` survives save/load between .4 and .5 with its display intact.\n\nA smaller point: cq1q.3 depends on cq1q.2, but adding a display field needs neither slugify nor the allocator. The dependency serializes work for no reason.\n","status":"closed","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-25T06:19:38Z","created_by":"Edward Langley","updated_at":"2026-09-25T17:51:52Z","closed_at":"2026-09-25T17:51:52Z","close_reason":"PR #318 merged (6da6a040, head 7ddc50ac); reviewed PASS (issuecomment-5836573405); cq1q.11 closed by the re-sequenced bead graph","dependencies":[{"issue_id":"improvise-cq1q.11","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T06:19:38Z","created_by":"Edward Langley","metadata":"{}"}],"comments":[{"id":"ca207052-0f62-5f31-b8d9-3d4a3a30b68f","issue_id":"improvise-cq1q.11","author":"agent-adr007","text":"agent-adr007: resolved by reordering, with no new beads. (1) cq1q.7 (projections and ViewState carry an id field) now depends only on cq1q.3 and lands before the re-key, using the display-first trick: id == key == name until cq1q.4 changes what the key is. So the wire never names a key that does not exist, and no interim resolver in model_effects is needed. (2) cq1q.4 now owns the old-format reader and writer edge: the loader slugifies, the writer writes displays, and a save/load test keeps the displays Region and Café intact. (3) The cq1q.3 -\u003e cq1q.2 dependency is removed. cq1q.4 now depends on .2, .3 and .7, and cq1q.8 depends on .4 and .7. Details are appended to the notes of cq1q.4 and cq1q.7.","created_at":"2026-09-25T06:42:12Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1}
|
||||
{"_type":"issue","id":"improvise-8jk6.16","title":"A --measure value that does not read as a number is dropped silently and the import exits 0","description":"Found in the adversarial review of PR #321 at efaa5050. A value in a column the user forced to Measure that does not read as a number is dropped with no warning, and the import exits 0. This is the same \"saved less than was asked\" family that context/code/cli.md section 13 closes for --category, --formula and kept-nothing, but it is not covered there.\n\nRepro, identical on main at c91cc055 and on the PR head:\n\n printf 'k,v\\na,abc\\nb,7\\nc,\"1,000\"\\n' \u003e nn.csv\n improvise import nn.csv --no-wizard --measure v -o nn.improv; echo \"exit=$?\"\n\nWhat happens: only `_Measure=v, k=b = 7` is written, \"Saved to\" is printed, and the exit status is 0. The `abc` and `1,000` values are gone with no report. Row `a` also loses its whole record, which is the improvise-8jk6.14 loss, but here the value was present and could not be read. That is a different reason.\n\nSince 8jk6.4, `csv_parser::number_in` is the one gate that decides whether a measure's CSV text is a number (via `Scalars::number` in `Plan::place`). So there is one place to count what it refused. Decide: warn with a count and the column (the 5j9 and 8jk6.6 partial-skip shape), or refuse headless. A thousands separator such as \"1,000\" is worth deciding about explicitly, since `number_in` does not read it.\n","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-25T06:14:58Z","created_by":"Edward Langley","updated_at":"2026-09-25T06:14:58Z","labels":["headless","import"],"dependencies":[{"issue_id":"improvise-8jk6.16","depends_on_id":"improvise-8jk6","type":"parent-child","created_at":"2026-09-25T06:14:58Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-10o9","title":"Formula WHERE/IS naming a GROUP matches none of its members, so a date hierarchy cannot be crosstabbed by formula","description":"The crosstab half of improvise-8jk6.3 does not exist yet. The user: \"I think dates should usually be one category and we can use formulas to crosstab\". But a formula `WHERE` or `IS` that names a GROUP matches none of that group's members. Both compare the record's coordinate to the name exactly, and no record carries the group's name.\n\nMeasured by the adversarial review of PR #322 at 6b910889. The file was a headless import of a two-year CSV with `--extract Date:Year --extract Date:Quarter --extract Date:Month`, so `Date` has items `2024-01`… in groups `2024-Q1`… under years `2024`/`2025`:\n\n- `Y24 = SUM(Revenue WHERE Date = |2024|)` gives -0.00 on every row. The expected value is 1111.\n- `IsY = (Date IS |2024|) * Revenue` gives 0 on every row, including the 2024 months.\n- Naming an item works: `SUM(Revenue WHERE Date = |2024-01|)` = 11, and `(Date IS |2024-01|) * Revenue` = 11 on the 2024-01 row.\n\nThe PR author measured the same and reported it for filing (PR #322 body, and `context/code/formula-and-io.md` \"Crosstabs by formula are not available yet\").\n\nShape: resolve a name that is a group of the filtered category to its covered items, as the fold already does via `Category::items_in_group` (felo.6). Decide the item-vs-group name ambiguity first: improvise-vwc7 says an item may share a name with a group of its category. Tests first: a 3-level Year\u003eQuarter\u003eMonth fixture, with a WHERE and an IS on each level.\n","status":"open","priority":2,"issue_type":"feature","owner":"el-github@elangley.org","created_at":"2026-09-25T06:09:18Z","created_by":"Edward Langley","updated_at":"2026-09-25T06:09:18Z","labels":["core","formula"],"dependencies":[{"issue_id":"improvise-10o9","depends_on_id":"improvise-8jk6.3","type":"discovered-from","created_at":"2026-09-25T06:09:18Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-1xfb.9","title":"checkers.sh's env allowlist drops the Nix linker variables, so no suite can link Rust on macOS","description":"Found in the PR #313 review (issuecomment-5827613256): inside the .#ci shell on macOS, 'env -i HOME PATH TMPDIR cargo build --bin improvise' fails with 'ld: library not found for -liconv' (a trivial crate still links). scripts/checkers.sh scrubs the environment down to HOME/PATH/TMPDIR plus three NIX_* variables and drops the ones the Nix cc/linker wrapper needs (NIX_LDFLAGS, NIX_CFLAGS_COMPILE and friends). So any suite that builds Rust fails on macOS under checkers.sh; repo-stats-test.sh worked around it by requiring a prebuilt improvise. context/code/tooling.md (~line 911) overstates the allowlist. Fix: keep the wrapper's variables (or re-enter the dev shell inside the scrub); test by building a crate with a system-library dependency under checkers.sh's env on macOS. CI (Linux) is unaffected as far as known.","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-25T06:00:52Z","created_by":"Edward Langley","updated_at":"2026-09-25T06:00:52Z","labels":["ci","tooling"],"dependencies":[{"issue_id":"improvise-1xfb.9","depends_on_id":"improvise-1xfb","type":"parent-child","created_at":"2026-09-25T06:00:52Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
@@ -113,13 +116,13 @@
|
||||
{"_type":"issue","id":"improvise-8jk6.14","title":"Import silently stores nothing for a record with no measure value; a measureless CSV in a folder vanishes","description":"Found in the delta review of PR #314 at c43ee724. A record whose accepted measures are all absent writes no cell, and no warning says so. The categories and items it named are created, but the record is gone. The same thing held for a single-file import before #314, but folder import makes it easy to hit. Reproduced: improvise import \u003cdir\u003e --no-wizard, where dir holds age.csv (entity,age-months / src/a.rs,2) and tags.csv (entity,tag / e1,red). The output has items e1 and red and a records view for tags, but ## Data holds only the age line, and the tags view draws zero rows. The CLI prints only \"Saved to\". The test a_file_with_no_measure_gets_a_records_view_not_another_files_measure asserts row_count 0 (\"a record with no measure has no cell\"), which pins the loss rather than the intent. Decide: warn and count (as 5j9 did for skips), or store the record, e.g. as a Text cell or a presence marker. Records are sparse, and the user wants sparse records kept.","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-25T04:58:03Z","created_by":"Edward Langley","updated_at":"2026-09-25T04:58:03Z","labels":["import"],"dependencies":[{"issue_id":"improvise-8jk6.14","depends_on_id":"improvise-8jk6","type":"parent-child","created_at":"2026-09-25T04:58:03Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-n28d.1","title":"Drill shows only informative columns: drop categories constant across the drilled records","description":"USER 2026-09-25: 'I think 1+2 make sense, reseting is fine'. When drilling a cell, the _Drill view's records columns include the categories the drill fixed (they are on Axis::Filter at one item, gotcha 27) -- constant across every drilled record, so they carry no information. Default drill columns: categories that vary among the drilled records, plus the measures the drilled cell shows (not every measure in the model). Drill columns RESET on each drill (the user: 'reseting is fine'); _Drill stays virtual and unpersisted. Builds on improvise-31oe.5 (records columns follow the view). Tests first: a drill fixes Region and Month, so neither is a column; a category that varies stays; a hidden measure is absent; re-drilling resets. Mind pending-edit addressing by (row, RecordCol) (gotcha 1): column indices change with the column set.","status":"open","priority":2,"issue_type":"feature","owner":"el-github@elangley.org","created_at":"2026-09-25T04:57:25Z","created_by":"Edward Langley","updated_at":"2026-09-25T04:57:25Z","labels":["drill","records","ux"],"dependencies":[{"issue_id":"improvise-n28d.1","depends_on_id":"improvise-31oe.5","type":"blocks","created_at":"2026-09-25T04:57:32Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-n28d.1","depends_on_id":"improvise-n28d","type":"parent-child","created_at":"2026-09-25T04:57:25Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-cq1q.10","title":"ADR 007 step 9: import generates ids by slugify and never parses them from headers","description":"ADR 007 answer 8 and clause 5, \"Import\". CSV and JSON dimension names are displays and are never parsed for ids. Every id comes from the workbook's IdAllocator, so two headers that slug alike get a gensym suffix. The saved file is the new version. Most of this falls out of step 3, when creation sites allocate ids. This bead is the import-specific evidence, plus whatever the wizard's preview or naming steps show.\n\nImport is other agents' ground (improvise-8jk6.3/.4/.5/.6 and improvise-lpzu), so coordinate and merge.\n\n## Acceptance Criteria\n- Tests first, through `improvise import --no-wizard`:\n - headers `Region(code)`, `Size(cm)` and `Time Period`/`Time_Period` import as displays with ids `regioncode`, `sizecm`, `time_period` and `time_period-\u003cn\u003e`;\n - the saved file pipe-quotes `|Region(code)|(regioncode)`;\n - the saved file reloads with every cell still drawn (improvise-80rm).\n- Rival: parse a trailing `(token)` as an id, and the first test fails.\n","status":"open","priority":2,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-25T04:50:38Z","created_by":"agent-adr007","updated_at":"2026-09-25T04:50:38Z","dependencies":[{"issue_id":"improvise-cq1q.10","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T04:50:38Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.10","depends_on_id":"improvise-cq1q.5","type":"blocks","created_at":"2026-09-25T04:51:03Z","created_by":"agent-adr007","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-cq1q.9","title":"ADR 007 step 8: clause 12 — a formula edit captures its display-to-id mapping when the editor opens","description":"ADR 007 clause 12: a formula edit maps its display names to ids when the editor opens, following the OpenedCell pattern. It is blocked by the user question in improvise-hexb item 2 and improvise-m56i item 6: which side holds the mapping, what ReplaceFormula.raw carries on the wire, and how an ambiguous typed name resolves. The TUI and the browser must reach the same answer.\n\n## Acceptance Criteria\n- The required regression test, as corrected by improvise-hexb item 1:\n 1. Open the editor on `Profit = Revenue - Cost`.\n 2. From another client, rename `Revenue` to `Sales`, and rename a different item to `Revenue`.\n 3. Commit `Revenue - Cost * 1`.\n 4. The stored formula references the original item's id, and the commit is not refused.\n- Two rivals, each seen to fail: resolving at commit time (binds to the new Revenue), and refusing on rename.\n- A newly typed name that does not resolve is refused, and the refusal names it.\n- A captured id that is gone at commit gets Rejection::Gone.\n","status":"open","priority":2,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-25T04:50:26Z","created_by":"agent-adr007","updated_at":"2026-09-25T04:50:26Z","dependencies":[{"issue_id":"improvise-cq1q.9","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T04:50:26Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.9","depends_on_id":"improvise-cq1q.6","type":"blocks","created_at":"2026-09-25T04:51:00Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.9","depends_on_id":"improvise-cq1q.8","type":"blocks","created_at":"2026-09-25T04:51:01Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.9","depends_on_id":"improvise-hexb","type":"blocks","created_at":"2026-09-25T04:51:02Z","created_by":"agent-adr007","metadata":"{}"}],"dependency_count":3,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-cq1q.9","title":"ADR 007 step 8: clause 12 — a formula edit captures its display-to-id mapping when the editor opens","description":"ADR 007 clause 12: a formula edit maps its display names to ids when the editor opens, following the OpenedCell pattern. It is blocked by the user question in improvise-hexb item 2 and improvise-m56i item 6: which side holds the mapping, what ReplaceFormula.raw carries on the wire, and how an ambiguous typed name resolves. The TUI and the browser must reach the same answer.\n\n## Acceptance Criteria\n- The required regression test, as corrected by improvise-hexb item 1:\n 1. Open the editor on `Profit = Revenue - Cost`.\n 2. From another client, rename `Revenue` to `Sales`, and rename a different item to `Revenue`.\n 3. Commit `Revenue - Cost * 1`.\n 4. The stored formula references the original item's id, and the commit is not refused.\n- Two rivals, each seen to fail: resolving at commit time (binds to the new Revenue), and refusing on rename.\n- A newly typed name that does not resolve is refused, and the refusal names it.\n- A captured id that is gone at commit gets Rejection::Gone.\n","notes":"Updated for the user's decision of 2026-09-25 (answer 32; PR #326): the CLIENT holds the capture, as it holds OpenedCell, and the resolver lives in improvise-protocol so the TUI and the browser share it. ReplaceFormula.raw and AddFormula.raw carry only id-form text, and the host never resolves a display name. It only parses ids, and one that is gone is Rejection::Gone. Typed names new to the edit resolve against the client's id-bearing projection, displays first and then ids (answer 31); an ambiguous display is refused with the candidates' ids listed. The regression test edits a non-name token (Revenue - Cost -\u003e Revenue - Cost * 1), per hexb item 1. The dependency on improvise-hexb is satisfied: it is answered.","status":"open","priority":2,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-25T04:50:26Z","created_by":"agent-adr007","updated_at":"2026-09-25T18:59:40Z","dependencies":[{"issue_id":"improvise-cq1q.9","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T04:50:26Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.9","depends_on_id":"improvise-cq1q.6","type":"blocks","created_at":"2026-09-25T04:51:00Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.9","depends_on_id":"improvise-cq1q.8","type":"blocks","created_at":"2026-09-25T04:51:01Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.9","depends_on_id":"improvise-hexb","type":"blocks","created_at":"2026-09-25T04:51:02Z","created_by":"agent-adr007","metadata":"{}"}],"dependency_count":3,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-cq1q.8","title":"ADR 007 step 7: wire operands by id, create by display, Rejection::Gone, ids_named and broken_chain's id arm","description":"ADR 007 clauses 6 and 7: the wire vocabulary by id; migration step 6.\n- Every ModelCommand operand that names an existing thing is an id. A create op takes a display and its parent's id: AddItem { category: CategoryId, display }, AddItemInGroup, AddFormula { target_category, raw }.\n- CreateView { display, switch: true } replaces the [CreateView, SwitchView] compound.\n- RenameRecordCoordinate { record, category: CategoryId, item: ItemRef::{Existing(ItemId), New(display)} }, and the category in CellId::Coordinate becomes a CategoryId.\n- The older variants move to ids one whole variant at a time: SetAxis, SetPageSelection, HideItem, SetCell/ClearCell { view }, and the rest.\n- Rejection::Gone(Named) comes with an exhaustive ids_named feeding Checked::against, and an id arm in broken_chain (improvise-gkyo). Rejection loses Copy. Remove CheckedBatch's created-record exemption, which no longer has a producer (improvise-p003 item 5).\n- The host assigns every id.\n\nThis may be split per variant group if it is too wide for one PR.\n\n## Acceptance Criteria\n- Tests first:\n - [RemoveFormula(f), ReplaceFormula(f)] is refused whole;\n - a command naming a removed id gets Rejection::Gone with the id's text;\n - an AddItem racing a remove-and-re-add of its category lands nowhere (Gone), not on a namesake. The rival, a parent addressed by display, fails it;\n - create-and-switch-view is one command and switches to the id the host assigned.\n- vocabulary_is_complete and vocabulary_is_composed still hold.\n","status":"open","priority":2,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-25T04:50:15Z","created_by":"agent-adr007","updated_at":"2026-09-25T04:50:15Z","dependencies":[{"issue_id":"improvise-cq1q.8","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T04:50:15Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.8","depends_on_id":"improvise-cq1q.4","type":"blocks","created_at":"2026-09-25T06:41:56Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.8","depends_on_id":"improvise-cq1q.7","type":"blocks","created_at":"2026-09-25T04:50:59Z","created_by":"agent-adr007","metadata":"{}"}],"dependency_count":2,"dependent_count":2,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-cq1q.7","title":"ADR 007 step 6: projections carry id and display; ViewState keyed by id","description":"ADR 007 clause 6, projections and ViewState; migration step 5.\n- The projections carry id and display together:\n - CategoryRow { id, display, items: Vec\u003c(ItemHandle, String)\u003e, axis }, with ItemHandle::{Item(ItemId), FormulaTarget(FormulaId)} (improvise-i0n7);\n - FormulaCache rows { id, stored, display };\n - ViewSummary { id, display, .. }.\n- AxisFrame labels stay display strings.\n- ViewState keys by id: cursors, expanded_cats, view_back_stack/view_forward_stack, and active_view (improvise-p003).\n\n## Acceptance Criteria\n- Tests first: after a display rename of a category, that client's expanded_cats still holds the category, and its cursors and back history still name the view after a view rename. The rival, name keys, fails both.\n- The panel's formula row shows `Profit (profit) = Revenue - Cost` and shows ids where a display is ambiguous (answer 19). Use a fixture with two items displayed `Revenue` in different categories.\n- view_state_survives_a_json_round_trip still holds.\n","notes":"Reordered for improvise-cq1q.11 (agent-adr007, 2026-09-25). This step now lands BEFORE the re-key (cq1q.4) and depends only on cq1q.3. The projections gain an id field beside the display (CategoryRow, FormulaCache, ViewSummary). Clients compose every wire operand from that id field, and ViewState keys its cursors, expanded_cats, view history and active_view by it. Until cq1q.4 lands, id == the key == the name, so nothing changes behaviour. After cq1q.4 the key becomes the slug, and the browser's commands still name real keys. That closes the gap without an interim display-\u003eid resolver in model_effects. Added acceptance criterion: a test-only document whose key differs from its display (the cq1q.3 hook) composes SetAxis/HideItem from the panel with the KEY, and the host applies it. The rival, composing from the display, fails.","status":"open","priority":2,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-25T04:50:03Z","created_by":"agent-adr007","updated_at":"2026-09-25T06:42:11Z","dependencies":[{"issue_id":"improvise-cq1q.7","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T04:50:03Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.7","depends_on_id":"improvise-cq1q.3","type":"blocks","created_at":"2026-09-25T06:41:46Z","created_by":"agent-adr007","metadata":"{}"}],"dependency_count":1,"dependent_count":2,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-cq1q.6","title":"ADR 007 step 5: formula identity — FormulaId becomes one id, formula text over ids, lift kb1d","description":"ADR 007 clauses 1 and 5, and the km71 section: formula identity.\n- FormulaId's private fields become one id. Signatures do not change, which is why km71 made it opaque.\n- A formula's target display lives on the Formula. Where it is stored depends on the user's answer to improvise-m56i item 2.\n- Formula text is stored over ids (Formula::raw is no longer verbatim) and quoted through quote::quote_name (for example `|2024|` and `|sum|`).\n- WHERE/IS filter values are item ids in the stored text (the ADR's reading of answer 3; improvise-m56i item 5).\n- A target change becomes a display rename, so kb1d's refusal outside _Measure is lifted.\n\nBlocked by the user's answers to improvise-m56i items 2 and 6.\n\n## Acceptance Criteria\n- Tests first: renaming an item that a formula references does not change the formula's stored text or its FormulaId. The rival, a FormulaId still derived from (target, target_category), fails it.\n- Retargeting a formula outside _Measure succeeds. The test is the kb1d refusal inverted.\n- An id that begins with a digit or is a keyword round-trips through formula text.\n","status":"open","priority":2,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-25T04:49:52Z","created_by":"agent-adr007","updated_at":"2026-09-25T04:49:52Z","dependencies":[{"issue_id":"improvise-cq1q.6","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T04:49:52Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.6","depends_on_id":"improvise-cq1q.4","type":"blocks","created_at":"2026-09-25T04:50:55Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.6","depends_on_id":"improvise-m56i","type":"blocks","created_at":"2026-09-25T04:50:56Z","created_by":"agent-adr007","metadata":"{}"}],"dependency_count":2,"dependent_count":1,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-cq1q.1","title":"ADR 007: text fixes from the review tickets (no decision changes)","description":"Text-only fixes to context/adr/007-stable-identities.md. They clarify the ADR and change no decision the user made. This is an ADR PR, so it needs the user's explicit approval before it merges (AGENTS.md, \"ADR pull requests\").\n\nFold in:\n- improvise-le4p. In the new-version arm of view_entry, try axis_line (id_ref ~ \": \" ~ axis keyword) before format_line. The id `format` then reads as an axis line, and no number format is spelled as an axis keyword. id_ref needs no quoting arm. Required test: a category displayed `Format` on a row axis round-trips.\n- improvise-p003:\n 1. The virtual views get the reserved ids `_records` and `_drill`, following answer 23 (\"no non-virtual id can start with _\").\n 2. _Index/_Dim synthetic coordinates are record positions, not items. They are outside the id namespace and are never persisted. This is the ADR's own reading; flag it as such.\n 3. ViewState::active_view and Workbook::active_view hold a ViewId.\n 4. The collapsed set is (CategoryId, group name) while groups have no id (answer 9), and `collapsed:` writes the category id then the group name.\n 5. Under clause 6, CheckedBatch's created-record exemption has nothing left that produces it. It is removed in the wire step.\n- improvise-i0n7, what remains: on an ItemHandle::FormulaTarget row, remove means RemoveFormula, and rename means a display rename of the formula target.\n- improvise-hexb items 1, 3 and 4:\n - The regression test edits a non-name token (`Revenue - Cost` -\u003e `Revenue - Cost * 1`) instead of committing unedited text.\n - Clause 11 lists clause 12's capture step.\n - Fix the drift in \"the only reader is one test\" and \"on the idempotent path\" (this is also improvise-8vs2 item 4).\n - The kb.pl fact decision('improvise-sboz', a_formula_edit_maps_display_names_to_ids_when_the_editor_opens, ...) restates the vacuous test. It must be replaced in the same change, and only the orchestrator writes kb.pl (scripts/kb replace).\n- improvise-8tk5, improvise-80rm, improvise-gkyo and improvise-rxz9 are already answered by revision 8 (answers 13 and 15; clauses 5, 6 and 7). Add one sentence on merges: two branches that add different things under one slug produce a duplicate id, and the merged file's load is refused (answers 11 and 29).\n\nOut of scope: improvise-m56i items 2 and 6, and improvise-hexb item 2. Those are user questions.\n\nAcceptance: `./scripts/bd-audit-ids` passes, no clause's decision changes, and the user approves on the PR.\n","status":"open","priority":2,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-25T04:48:44Z","created_by":"agent-adr007","updated_at":"2026-09-25T04:48:44Z","dependencies":[{"issue_id":"improvise-cq1q.1","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T04:48:44Z","created_by":"agent-adr007","metadata":"{}"}],"comments":[{"id":"bb182e07-65db-5fa1-84f0-1473d61d7fa8","issue_id":"improvise-cq1q.1","author":"Edward Langley","text":"From the adversarial review of PR #318 (head 689480bf), which is Claude as an agent in the owner's session. Here are two readings that clause 4 leaves implicit and PR #318 had to choose. Consider flagging them in the ADR as its own readings, as p003 item 2 does.\n\n1. **\"Space\" means U+0020 only.** A tab, a no-break space (U+00A0) and an ideographic space (U+3000) are stripped, not turned into `_`. So `Time\\tPeriod` → `timeperiod`, which collides with `TimePeriod` rather than `Time Period`. The reasoning is in context/code/core.md §19. A tab-separated import header is the realistic case.\n2. **A slug made only of `-` is a legal bare id.** Displays `-` and `---` slug to `-` and `---`, and they are not treated as empty. The id_token alphabet admits them, and an `.improv` line such as `-: row`, or an item line beginning `- -`, is legal but unreadable. Step 5's \"if nothing is left\" treats only the empty string as empty.\n","created_at":"2026-09-25T06:19:59Z"}],"dependency_count":0,"dependent_count":1,"comment_count":1}
|
||||
{"_type":"issue","id":"improvise-nn4s.2","title":"Faces step 2: draw.rs face_style; axis_fg, minibuf_fg, field_kind_fg and mode_style become callers and disappear","description":"ADR 004 clause 8 step 2. One face_style(Face)-\u003eStyle in the TUI. AxisColor and Axis::color leave improvise-core (clause 6: face_of(Axis) in protocol). mode_style decomposes into the five mode faces plus the text-entry chain. Carries clause 8's one declared exception: minibuffer.category-add becomes green (user, Q1).","status":"in_progress","priority":2,"issue_type":"task","assignee":"agent-faces","owner":"el-github@elangley.org","created_at":"2026-09-25T04:48:26Z","created_by":"agent-faces","updated_at":"2026-09-25T04:48:48Z","started_at":"2026-09-25T04:48:48Z","lease_expires_at":"2026-09-25T04:53:48Z","heartbeat_at":"2026-09-25T04:48:48Z","dependencies":[{"issue_id":"improvise-nn4s.2","depends_on_id":"improvise-nn4s","type":"parent-child","created_at":"2026-09-25T04:48:26Z","created_by":"agent-faces","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-nn4s.1","title":"Faces step 1: Face, Theme, resolve and the default theme in improvise-protocol","description":"ADR 004 clause 8 step 1. Face vocabulary as semantic data in improvise-protocol (clause 6), derived from domain enums where one exists (clause 2); Colour = Default|Named|Indexed|Rgb (clause 4); six portable attributes (clause 5); inheritance resolved once by Theme::resolve(Face) (clause 3); serde JSON shape with face names as keys (clause 9). The default theme contains no Rgb. Faces land only where a migrated call site needs them; later steps add the rest.","status":"in_progress","priority":2,"issue_type":"task","assignee":"agent-faces","owner":"el-github@elangley.org","created_at":"2026-09-25T04:48:21Z","created_by":"agent-faces","updated_at":"2026-09-25T04:48:47Z","started_at":"2026-09-25T04:48:47Z","lease_expires_at":"2026-09-25T04:53:47Z","heartbeat_at":"2026-09-25T04:48:47Z","dependencies":[{"issue_id":"improvise-nn4s.1","depends_on_id":"improvise-nn4s","type":"parent-child","created_at":"2026-09-25T04:48:21Z","created_by":"agent-faces","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-cq1q.6","title":"ADR 007 step 5: formula identity — FormulaId becomes one id, formula text over ids, lift kb1d","description":"ADR 007 clauses 1 and 5, and the km71 section: formula identity.\n- FormulaId's private fields become one id. Signatures do not change, which is why km71 made it opaque.\n- A formula's target display lives on the Formula. Where it is stored depends on the user's answer to improvise-m56i item 2.\n- Formula text is stored over ids (Formula::raw is no longer verbatim) and quoted through quote::quote_name (for example `|2024|` and `|sum|`).\n- WHERE/IS filter values are item ids in the stored text (the ADR's reading of answer 3; improvise-m56i item 5).\n- A target change becomes a display rename, so kb1d's refusal outside _Measure is lifted.\n\nBlocked by the user's answers to improvise-m56i items 2 and 6.\n\n## Acceptance Criteria\n- Tests first: renaming an item that a formula references does not change the formula's stored text or its FormulaId. The rival, a FormulaId still derived from (target, target_category), fails it.\n- Retargeting a formula outside _Measure succeeds. The test is the kb1d refusal inverted.\n- An id that begins with a digit or is a keyword round-trips through formula text.\n","notes":"Updated for the user's decisions of 2026-09-25 (answer 30; PR #326). This step implements: (1) FormulaId becomes one id, allocated separately from the target. (2) For a target outside _Measure, add_formula keeps creating the real item (its own ItemId), and the formula line names that target by the ITEM's id. For a _Measure target, Formula carries the target's display, and a formula cell's CellKey carries the FormulaId text under _measure. (3) Formula text is stored over ids, and a WHERE/IS filter value is an item id. The exact formula-line spelling waits for Q23 on PR #326; option (a) is recommended there. Added acceptance criteria: a formula targeting CustomCat item Tax has item id tax and a distinct formula id (tax-\u003cn\u003e). Renaming the item's display changes neither id, and the formula line still names tax.","status":"open","priority":2,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-25T04:49:52Z","created_by":"agent-adr007","updated_at":"2026-09-25T18:59:39Z","dependencies":[{"issue_id":"improvise-cq1q.6","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T04:49:52Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.6","depends_on_id":"improvise-cq1q.4","type":"blocks","created_at":"2026-09-25T04:50:55Z","created_by":"agent-adr007","metadata":"{}"},{"issue_id":"improvise-cq1q.6","depends_on_id":"improvise-m56i","type":"blocks","created_at":"2026-09-25T04:50:56Z","created_by":"agent-adr007","metadata":"{}"}],"dependency_count":2,"dependent_count":1,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-cq1q.1","title":"ADR 007: text fixes from the review tickets (no decision changes)","description":"Text-only fixes to context/adr/007-stable-identities.md. They clarify the ADR and change no decision the user made. This is an ADR PR, so it needs the user's explicit approval before it merges (AGENTS.md, \"ADR pull requests\").\n\nFold in:\n- improvise-le4p. In the new-version arm of view_entry, try axis_line (id_ref ~ \": \" ~ axis keyword) before format_line. The id `format` then reads as an axis line, and no number format is spelled as an axis keyword. id_ref needs no quoting arm. Required test: a category displayed `Format` on a row axis round-trips.\n- improvise-p003:\n 1. The virtual views get the reserved ids `_records` and `_drill`, following answer 23 (\"no non-virtual id can start with _\").\n 2. _Index/_Dim synthetic coordinates are record positions, not items. They are outside the id namespace and are never persisted. This is the ADR's own reading; flag it as such.\n 3. ViewState::active_view and Workbook::active_view hold a ViewId.\n 4. The collapsed set is (CategoryId, group name) while groups have no id (answer 9), and `collapsed:` writes the category id then the group name.\n 5. Under clause 6, CheckedBatch's created-record exemption has nothing left that produces it. It is removed in the wire step.\n- improvise-i0n7, what remains: on an ItemHandle::FormulaTarget row, remove means RemoveFormula, and rename means a display rename of the formula target.\n- improvise-hexb items 1, 3 and 4:\n - The regression test edits a non-name token (`Revenue - Cost` -\u003e `Revenue - Cost * 1`) instead of committing unedited text.\n - Clause 11 lists clause 12's capture step.\n - Fix the drift in \"the only reader is one test\" and \"on the idempotent path\" (this is also improvise-8vs2 item 4).\n - The kb.pl fact decision('improvise-sboz', a_formula_edit_maps_display_names_to_ids_when_the_editor_opens, ...) restates the vacuous test. It must be replaced in the same change, and only the orchestrator writes kb.pl (scripts/kb replace).\n- improvise-8tk5, improvise-80rm, improvise-gkyo and improvise-rxz9 are already answered by revision 8 (answers 13 and 15; clauses 5, 6 and 7). Add one sentence on merges: two branches that add different things under one slug produce a duplicate id, and the merged file's load is refused (answers 11 and 29).\n\nOut of scope: improvise-m56i items 2 and 6, and improvise-hexb item 2. Those are user questions.\n\nAcceptance: `./scripts/bd-audit-ids` passes, no clause's decision changes, and the user approves on the PR.\n","status":"in_progress","priority":2,"issue_type":"task","assignee":"agent-adr007","owner":"el-github@elangley.org","created_at":"2026-09-25T04:48:44Z","created_by":"agent-adr007","updated_at":"2026-09-25T17:52:26Z","started_at":"2026-09-25T17:52:26Z","lease_expires_at":"2026-09-25T17:57:26Z","heartbeat_at":"2026-09-25T17:52:26Z","dependencies":[{"issue_id":"improvise-cq1q.1","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T04:48:44Z","created_by":"agent-adr007","metadata":"{}"}],"comments":[{"id":"bb182e07-65db-5fa1-84f0-1473d61d7fa8","issue_id":"improvise-cq1q.1","author":"Edward Langley","text":"From the adversarial review of PR #318 (head 689480bf), which is Claude as an agent in the owner's session. Here are two readings that clause 4 leaves implicit and PR #318 had to choose. Consider flagging them in the ADR as its own readings, as p003 item 2 does.\n\n1. **\"Space\" means U+0020 only.** A tab, a no-break space (U+00A0) and an ideographic space (U+3000) are stripped, not turned into `_`. So `Time\\tPeriod` → `timeperiod`, which collides with `TimePeriod` rather than `Time Period`. The reasoning is in context/code/core.md §19. A tab-separated import header is the realistic case.\n2. **A slug made only of `-` is a legal bare id.** Displays `-` and `---` slug to `-` and `---`, and they are not treated as empty. The id_token alphabet admits them, and an `.improv` line such as `-: row`, or an item line beginning `- -`, is legal but unreadable. Step 5's \"if nothing is left\" treats only the empty string as empty.\n","created_at":"2026-09-25T06:19:59Z"}],"dependency_count":0,"dependent_count":1,"comment_count":1}
|
||||
{"_type":"issue","id":"improvise-nn4s.2","title":"Faces step 2: draw.rs face_style; axis_fg, minibuf_fg, field_kind_fg and mode_style become callers and disappear","description":"ADR 004 clause 8 step 2. One face_style(Face)-\u003eStyle in the TUI. AxisColor and Axis::color leave improvise-core (clause 6: face_of(Axis) in protocol). mode_style decomposes into the five mode faces plus the text-entry chain. Carries clause 8's one declared exception: minibuffer.category-add becomes green (user, Q1).","status":"closed","priority":2,"issue_type":"task","assignee":"agent-faces","owner":"el-github@elangley.org","created_at":"2026-09-25T04:48:26Z","created_by":"agent-faces","updated_at":"2026-09-25T17:10:21Z","started_at":"2026-09-25T04:48:48Z","closed_at":"2026-09-25T17:10:21Z","close_reason":"Landed in PR #319 (merged 4ee5d4b1); post-merge review found the code sound (issuecomment-5836381716); gaps filed as nn4s.8-.12","dependencies":[{"issue_id":"improvise-nn4s.2","depends_on_id":"improvise-nn4s","type":"parent-child","created_at":"2026-09-25T04:48:26Z","created_by":"agent-faces","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-nn4s.1","title":"Faces step 1: Face, Theme, resolve and the default theme in improvise-protocol","description":"ADR 004 clause 8 step 1. Face vocabulary as semantic data in improvise-protocol (clause 6), derived from domain enums where one exists (clause 2); Colour = Default|Named|Indexed|Rgb (clause 4); six portable attributes (clause 5); inheritance resolved once by Theme::resolve(Face) (clause 3); serde JSON shape with face names as keys (clause 9). The default theme contains no Rgb. Faces land only where a migrated call site needs them; later steps add the rest.","status":"closed","priority":2,"issue_type":"task","assignee":"agent-faces","owner":"el-github@elangley.org","created_at":"2026-09-25T04:48:21Z","created_by":"agent-faces","updated_at":"2026-09-25T17:10:19Z","started_at":"2026-09-25T04:48:47Z","closed_at":"2026-09-25T17:10:19Z","close_reason":"Landed in PR #319 (merged 4ee5d4b1); post-merge review found the code sound (issuecomment-5836381716); gaps filed as nn4s.8-.12","dependencies":[{"issue_id":"improvise-nn4s.1","depends_on_id":"improvise-nn4s","type":"parent-child","created_at":"2026-09-25T04:48:21Z","created_by":"agent-faces","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-31oe.8","title":"create-view silently replaces an existing view of the same name and exits 0","description":"Found by the PR #313 review (issuecomment-5826870397): Workbook::create_view is an IndexMap::insert, so 'create-view X' when a view X exists overwrites it -- its axes, page selections, hidden items and collapse state -- with no message, and headless 'improvise cmd/script' exits 0. The repo-stats pipeline's interim create-view replaced a per-file view that folder import (#314) had built. Refuse (naming the view) unless an explicit replace is asked for; tests first in both the TUI path and headless. Related: improvise-31oe.2/31oe.3 (refusals for unknown names, in progress).","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-25T04:43:03Z","created_by":"Edward Langley","updated_at":"2026-09-25T04:43:03Z","labels":["headless","view"],"dependencies":[{"issue_id":"improvise-31oe.8","depends_on_id":"improvise-31oe","type":"parent-child","created_at":"2026-09-25T04:43:03Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-felo.8","title":"Semi-additive measures: a different summary per category","description":"USER 2026-09-25, 'yes, file these'. One summary per measure cannot express a semi-additive measure: holdings value is SUMMED across Account but takes LAST across Date; a stock price is AVG or LAST across Date but meaningless summed across Ticker. Add per-category overrides to a measure's aggregation, e.g. in '## Aggregation': '- Value: sum; Date: last' (grammar first). Decide the fold semantics when several categories are folded at once (apply per-category functions in a defined order -- e.g. the overriding categories innermost -- since sum-of-last and last-of-sum differ), how a collapsed group of the overriding category uses it, and totals. Depends on the ordered last/first ticket for the canonical examples. Interacts with ADR 009's incremental aggregates.","status":"open","priority":2,"issue_type":"feature","owner":"el-github@elangley.org","created_at":"2026-09-25T04:30:51Z","created_by":"Edward Langley","updated_at":"2026-09-25T04:30:51Z","labels":["core","formula"],"dependencies":[{"issue_id":"improvise-felo.8","depends_on_id":"improvise-felo","type":"parent-child","created_at":"2026-09-25T04:30:51Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-felo.7","title":"Ordered summary functions: last and first (closing/opening value)","description":"USER 2026-09-25: 'we probably should also be able to specify a summary function. e.g. stock price doesn't make sense to add' -- and on the proposal: 'yes, file these'. Today a measure's summary is one of sum/avg/min/max/count (AggFunc, improvise-formula ast.rs; per measure in Model::measure_agg, '## Aggregation' section '- Price: avg'), applied to every fold incl. collapsed groups. A stock price wants LAST (closing) or FIRST (opening) over a period. Those need an ORDER over the folded values; the natural one is the item order of the category being folded (e.g. months within a year of a date hierarchy, improvise-8jk6.3/felo.6). Decide: which category orders the fold when several are folded at once (the time-like one? an explicit per-measure 'along' category?), ties, and what last/first mean when the ordering category is on an axis rather than folded. Grammar first (agg_kind in improv.pest). Interacts with ADR 009 (PR #309): its integrated per-group state is an unordered multiset -- last/first need ordered state; raise it there. Test with a stock-price fixture.","status":"open","priority":2,"issue_type":"feature","owner":"el-github@elangley.org","created_at":"2026-09-25T04:30:50Z","created_by":"Edward Langley","updated_at":"2026-09-25T04:30:50Z","labels":["core","formula"],"dependencies":[{"issue_id":"improvise-felo.7","depends_on_id":"improvise-felo","type":"parent-child","created_at":"2026-09-25T04:30:50Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
@@ -159,7 +162,7 @@
|
||||
{"_type":"issue","id":"improvise-bqsl","title":":import in the TUI parses every file as JSON, so a CSV fails with a JSON parse error","description":"StartImportWizard (crates/improvise-command/src/ui/effect.rs, impl Effect for StartImportWizard) reads the path through app.storage and hands the bytes straight to serde_json::from_slice, so ':import sales.csv' refuses with 'JSON parse error' although the CLI's 'improvise import sales.csv' handles CSV via csv_parser. Two front doors, two answers to 'what can be imported' (design-principles §5: a derivation done twice). Fix: one shared 'bytes + file name -\u003e import records' function in improvise-io (CSV by extension, else JSON) that both the CLI's get_import_data and StartImportWizard call; csv_parser already has _bytes/_from variants over Storage (gotcha 2). Demonstrate first with a failing test: StartImportWizard over a MemoryFs holding a small CSV must open the wizard, not refuse. Consider whether :import should also take several paths (merge_csvs) — at least note the gap if not done.","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-25T00:50:39Z","created_by":"Edward Langley","updated_at":"2026-09-25T00:50:39Z","labels":["import"],"dependencies":[{"issue_id":"improvise-bqsl","depends_on_id":"improvise-8jk6","type":"parent-child","created_at":"2026-09-25T01:05:27Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-lpzu","title":"improvise import should accept a folder of CSV files","description":"Today 'improvise import data/*.csv' works only because the shell expands the glob: get_import_data (src/main.rs) merges several CSV paths via csv_parser::merge_csvs, which adds a 'File' field per row from the file stem. Passing the folder itself ('improvise import data/') fails, because a single path goes to read_to_string, which errors on a directory. Wanted: a directory argument expands to its *.csv entries in a DETERMINISTIC order (sorted by name, not read_dir order — see gotcha 18, the same CSV imported twice must give the same .improv), non-recursive, then goes down the existing merge path. Decide and test: an empty folder (refuse, naming the folder), a folder containing non-CSV files (skip them or refuse — state which), and a folder mixed with file arguments. The directory listing belongs behind Storage::list so the browser/OPFS host can reuse it (gotcha 2), not std::fs in main.rs.","status":"closed","priority":2,"issue_type":"feature","assignee":"agent-lpzu","owner":"el-github@elangley.org","created_at":"2026-09-25T00:50:39Z","created_by":"Edward Langley","updated_at":"2026-09-25T05:02:19Z","started_at":"2026-09-25T03:32:32Z","closed_at":"2026-09-25T05:02:19Z","close_reason":"Landed in PR #314; reviewed PASS at c43ee724","labels":["import"],"dependencies":[{"issue_id":"improvise-lpzu","depends_on_id":"improvise-8jk6","type":"parent-child","created_at":"2026-09-25T01:05:28Z","created_by":"Edward Langley","metadata":"{}"}],"comments":[{"id":"ba36392a-bcb8-5db3-b276-13dc05ca2451","issue_id":"improvise-lpzu","author":"Edward Langley","text":"2026-09-25: now needed by improvise-1xfb.5.1 -- the user's design is that code-maat writes its CSVs into a directory and improvise processes that directory. Note code-maat's outputs have DIFFERENT schemas per analysis (coupling: entity,coupled,degree,average-revs; age: entity,age-months; abs-churn: date,added,deleted,commits), so folder import must decide what heterogeneous headers mean (one merged model with a File category and sparse measures, vs refuse, vs one model per schema). Raised P3 -\u003e P2.","created_at":"2026-09-25T03:30:03Z"},{"id":"3aafd79b-1988-54fc-860d-318579349b5a","issue_id":"improvise-lpzu","author":"Edward Langley","text":"USER DECISION 2026-09-25, verbatim: 'Records are already sparse: the model can just leave null/empty values for columns not in a given CSV file.' So heterogeneous headers MERGE into one model (with the existing File category from merge_csvs), and a record simply has no value for a column its file lacks -- not refuse, not one model per schema.","created_at":"2026-09-25T03:32:18Z"},{"id":"91707f9f-2542-533b-b5d9-41d0980ab506","issue_id":"improvise-lpzu","author":"Edward Langley","text":"USER 2026-09-25, verbatim: 'The stats model should pre-create views per file (also dir import should do this: one view filtered by the file column and projected to the columns in that file)'. So folder import creates, per source file, a view named after the file: File fixed to that file (filtered), and only that file's own columns shown -- its categories on the grid axes, its measures visible, and other files' categories/measures out of the grid (categories on None, other measures hidden). The merged default view stays.","created_at":"2026-09-25T03:38:09Z"},{"id":"1a0b8382-9330-5788-b62e-6b41c886a881","issue_id":"improvise-lpzu","author":"Edward Langley","text":"USER 2026-09-25: (1) '(none)' position: 'I think leaving it out for now is fine, maybe a UI element somewhere that shows the number of records underneath the current view?' -\u003e filed separately. (2) single-CSV folder: 'yes, we might want to have some kind of update import eventually (don't plan this yet, but we'd want file identity for this)' -\u003e a folder with ONE CSV still gets the File category and its per-file view.","created_at":"2026-09-25T04:30:53Z"},{"id":"b586e154-47ae-513c-9312-a04eef63ffaa","issue_id":"improvise-lpzu","author":"Edward Langley","text":"USER 2026-09-25, verbatim: 'yeah, adding a single file shouldn't get the File category'. Confirms the PR #314 rule: a folder (even of one CSV) always merges with File; a lone CSV named directly imports alone, without File.","created_at":"2026-09-25T04:54:23Z"}],"dependency_count":0,"dependent_count":0,"comment_count":5}
|
||||
{"_type":"issue","id":"improvise-weoe","title":"pr-reviews --wait cannot see a plain PR comment, so it never wakes for the user's ADR approval","description":"scripts/pr-reviews --wait is satisfied only by a review or an inline comment whose commit is the current head (has_head_review). A thread comment (issues/{n}/comments) is attached to no commit, so by design it can never satisfy --wait. However, ADR approval arrives as exactly that: the user comments 'Approved' / answers questions on the thread (AGENTS.md 'ADR pull requests'). So a steward waiting on the approved tool sleeps through the one event it is waiting for, and must hand-poll the thread beside it. That is how the earlier #210 watcher missed the user's 19:50Z answers, and the current #210 steward is working around it the same way. Proposed: a --since \u003ciso8601\u003e (or --comments) mode in which a thread comment created after the given time satisfies the wait. Test it with the fake-gh fixture: a thread comment arriving mid-wait wakes it, and a thread comment older than --since does not.","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-24T23:22:01Z","created_by":"Edward Langley","updated_at":"2026-09-24T23:22:01Z","dependencies":[{"issue_id":"improvise-weoe","depends_on_id":"improvise-1xfb","type":"parent-child","created_at":"2026-09-25T01:05:50Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-m56i","title":"ADR 007 leaves formula identity unspecified: no formula-line grammar, formula id vs target item id, where a _Measure target's display lives, filter-value strings","description":"Found by the adversarial review of PR #210 (ADR 007, head 4830fa30). This is broader than improvise-i0n7 (which covers only the CategoryRow shape of _Measure formula-target rows).\n\nADR 007 does not say how a formula and its target are identified in the file or the model. An implementer hits these at once:\n\n1. **No grammar for the formula line.** Clause 5's grammar block gives header_decl, item_decl and grouped_item, but no `formula_line` or formula `target` rule for answer 5's F1 (`- Profit(profit) = revenue - cost [cat]`). `formula.pest`'s `formula = { SOI ~ target ~ \"=\" ~ expr ... }` has no id tag, and today the whole line is `formula_raw`.\n2. **Whose id is in the parentheses?** Answer 5 (Q2) is *formula* id placement. Answer 19's `Profit (profit)` is described as \"the target's id\". For a `_Measure` target these coincide (clause 1: the target's handle is its FormulaId). For a target outside `_Measure`, `Model::add_formula` creates a real item. Under answer 22 (ids globally unique), that item's ItemId and the formula's FormulaId must differ. So the file must reference the target item by id somewhere, and the ADR does not say where.\n3. **Where a `_Measure` target's display lives.** Clause 2 gives `display` to Category, Item and View, but not to Formula. km71's section says \"a target change becomes a display rename of the target\", and a `_Measure` target has no item to hold that display.\n4. **Which ItemId a formula cell's CellKey carries** for a `_Measure` target, which has no item. Also `RecordCell.measure: Option\u003cString\u003e` and `CellId::Derived { target: String }` (render.rs) name the target.\n5. **WHERE / IS filter values.** formula.pest has `filter_value = { string | pipe_quoted | bare_ident }`. Is a double-quoted `\"East Coast\"` in id-form text an id or a display? Answer 3 says every reference is an id, but a quoted string has no stated meaning under ids.\n6. **Resolving typed names at the panel edge.** If a typed display name is ambiguous (answer 19), or equals some other thing's id, which wins, or is it refused?\n","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-24T22:49:30Z","created_by":"agent-review-210","updated_at":"2026-09-24T22:49:30Z","labels":["adversarial-review","pr-210"],"dependencies":[{"issue_id":"improvise-m56i","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T01:04:54Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-m56i","depends_on_id":"improvise-sboz","type":"discovered-from","created_at":"2026-09-24T22:49:30Z","created_by":"agent-review-210","metadata":"{}"}],"comments":[{"id":"0c2ab9a3-bfb7-5273-89bb-99fc544e5a06","issue_id":"improvise-m56i","author":"agent-adr007","text":"agent-adr007 planning. This blocks improvise-cq1q.4 and improvise-cq1q.6. Items 1, 3, 4 and 5 are readings that can go in the ADR text: a formula line is '- \u003cdisplay\u003e(\u003cformula id\u003e) = \u003cid-form expr\u003e [\u003ccategory id\u003e]'; Formula carries the target's display; a formula cell's CellKey carries the FormulaId text under _measure, which is unique by answer 22; and a filter value is an item id. Items 2 and 6 are USER QUESTIONS. Item 2: add_formula creates a real item for a target outside _Measure (types.rs add_formula), so under answer 22 that item's id and the formula's id must differ. Recommendation: treat every formula target like a _Measure one, with ItemHandle::FormulaTarget(FormulaId) and no real item, so one id in the parentheses serves in every category. Item 6: the precedence when a typed name is both a display and another thing's id. Recommendation: display first, then id; an ambiguous display is refused and the refusal lists the candidates' ids.","created_at":"2026-09-25T04:51:33Z"}],"dependency_count":0,"dependent_count":2,"comment_count":1}
|
||||
{"_type":"issue","id":"improvise-m56i","title":"ADR 007 leaves formula identity unspecified: no formula-line grammar, formula id vs target item id, where a _Measure target's display lives, filter-value strings","description":"Found by the adversarial review of PR #210 (ADR 007, head 4830fa30). This is broader than improvise-i0n7 (which covers only the CategoryRow shape of _Measure formula-target rows).\n\nADR 007 does not say how a formula and its target are identified in the file or the model. An implementer hits these at once:\n\n1. **No grammar for the formula line.** Clause 5's grammar block gives header_decl, item_decl and grouped_item, but no `formula_line` or formula `target` rule for answer 5's F1 (`- Profit(profit) = revenue - cost [cat]`). `formula.pest`'s `formula = { SOI ~ target ~ \"=\" ~ expr ... }` has no id tag, and today the whole line is `formula_raw`.\n2. **Whose id is in the parentheses?** Answer 5 (Q2) is *formula* id placement. Answer 19's `Profit (profit)` is described as \"the target's id\". For a `_Measure` target these coincide (clause 1: the target's handle is its FormulaId). For a target outside `_Measure`, `Model::add_formula` creates a real item. Under answer 22 (ids globally unique), that item's ItemId and the formula's FormulaId must differ. So the file must reference the target item by id somewhere, and the ADR does not say where.\n3. **Where a `_Measure` target's display lives.** Clause 2 gives `display` to Category, Item and View, but not to Formula. km71's section says \"a target change becomes a display rename of the target\", and a `_Measure` target has no item to hold that display.\n4. **Which ItemId a formula cell's CellKey carries** for a `_Measure` target, which has no item. Also `RecordCell.measure: Option\u003cString\u003e` and `CellId::Derived { target: String }` (render.rs) name the target.\n5. **WHERE / IS filter values.** formula.pest has `filter_value = { string | pipe_quoted | bare_ident }`. Is a double-quoted `\"East Coast\"` in id-form text an id or a display? Answer 3 says every reference is an id, but a quoted string has no stated meaning under ids.\n6. **Resolving typed names at the panel edge.** If a typed display name is ambiguous (answer 19), or equals some other thing's id, which wins, or is it refused?\n","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-24T22:49:30Z","created_by":"agent-review-210","updated_at":"2026-09-24T22:49:30Z","labels":["adversarial-review","pr-210"],"dependencies":[{"issue_id":"improvise-m56i","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T01:04:54Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-m56i","depends_on_id":"improvise-sboz","type":"discovered-from","created_at":"2026-09-24T22:49:30Z","created_by":"agent-review-210","metadata":"{}"}],"comments":[{"id":"0c2ab9a3-bfb7-5273-89bb-99fc544e5a06","issue_id":"improvise-m56i","author":"agent-adr007","text":"agent-adr007 planning. This blocks improvise-cq1q.4 and improvise-cq1q.6. Items 1, 3, 4 and 5 are readings that can go in the ADR text: a formula line is '- \u003cdisplay\u003e(\u003cformula id\u003e) = \u003cid-form expr\u003e [\u003ccategory id\u003e]'; Formula carries the target's display; a formula cell's CellKey carries the FormulaId text under _measure, which is unique by answer 22; and a filter value is an item id. Items 2 and 6 are USER QUESTIONS. Item 2: add_formula creates a real item for a target outside _Measure (types.rs add_formula), so under answer 22 that item's id and the formula's id must differ. Recommendation: treat every formula target like a _Measure one, with ItemHandle::FormulaTarget(FormulaId) and no real item, so one id in the parentheses serves in every category. Item 6: the precedence when a typed name is both a display and another thing's id. Recommendation: display first, then id; an ambiguous display is refused and the refusal lists the candidates' ids.","created_at":"2026-09-25T04:51:33Z"},{"id":"e029ce2b-7fdd-5e1c-98a5-df1460a148db","issue_id":"improvise-m56i","author":"Edward Langley","text":"USER DECISION 2026-09-25 (item 2, formula target outside _Measure): 'Real item + separate formula id' -- add_formula keeps creating a real item for a non-_Measure target; the formula gets its own id; formula lines name the target by the item's id. (Chosen over the recommended 'no real item, FormulaTarget handle'.)","created_at":"2026-09-25T17:51:47Z"},{"id":"7e1c14e6-9d79-56c6-bcc5-0abcbb9eb3bb","issue_id":"improvise-m56i","author":"Edward Langley","text":"USER DECISION 2026-09-25 (item 6, a typed name matching a display and another thing's id): 'Display first; client holds it' -- match display first, then id; an ambiguous display is refused listing the candidate ids.","created_at":"2026-09-25T17:51:49Z"}],"dependency_count":0,"dependent_count":2,"comment_count":3}
|
||||
{"_type":"issue","id":"improvise-le4p","title":"ADR 007: an id 'format' cannot be written as a view reference — format_line wins the ordered choice, and id_ref has no quoting arm","description":"Found by the adversarial review of PR #210 (ADR 007, head 4830fa30), against gh/main ad217cc1.\n\nADR 007 clause 5 says an id \"never needs pipe quoting in an .improv declaration or reference\", and its grammar gives references `id_ref = { id_token }` with no pipe-quoted arm. That is false for one id: `format`.\n\nIn improv.pest, `view_entry = _{ format_line | prune_line | hidden_line | collapsed_line | axis_line }` is an ordered choice, and `format_line = { \"format: \" ~ rest_of_line ~ ... }`. So the axis line for a category whose id is `format` (`format: row`, `format: page, east`) matches format_line first and is read as the view's number format. The axis assignment is lost with no error. (`prune: row` and `hidden: row` fail their own rule and backtrack to axis_line, so only `format` collides.)\n\nToday this is masked by accident: `quote::KEYWORDS` contains `FORMAT` (a formula keyword, from d00cea24), matched case-insensitively, so `format_md` writes `|format|: row`. Under ADR 007, slugify folds to lower case (answer 16(b)), so any category displayed `Format` gets the id `format`, and `id_ref` cannot quote it.\n\nThe implementation must pick one: reserve `format` from slugify (suffix it like any collision), give `id_ref` a pipe-quoted arm, or reorder or disambiguate the view-section rules. A round-trip test with a category displayed `Format` on a row axis should fail first.\n","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-24T22:49:28Z","created_by":"agent-review-210","updated_at":"2026-09-24T22:49:28Z","labels":["adversarial-review","pr-210"],"dependencies":[{"issue_id":"improvise-le4p","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T01:04:54Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-le4p","depends_on_id":"improvise-sboz","type":"discovered-from","created_at":"2026-09-24T22:49:28Z","created_by":"agent-review-210","metadata":"{}"}],"comments":[{"id":"bc8717a4-50fc-52a0-86ed-84adf1f7793c","issue_id":"improvise-le4p","author":"agent-adr007","text":"agent-adr007 planning: a text fix, not a user question. In the new-version arm, try axis_line (which requires an axis keyword) before format_line. No number format is an axis keyword, so neither slugify nor id_ref needs to change. Folded into improvise-cq1q.1; the test is in improvise-cq1q.5.","created_at":"2026-09-25T04:51:35Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1}
|
||||
{"_type":"issue","id":"improvise-5jxt","title":"pr-reviews dies with 'jq: Argument list too long' on a PR with many comments (#210)","description":"scripts/pr-reviews passes the whole PR, inline-comment and issue-comment JSON to jq as --argjson arguments (fetch(), around line 109). On PR #210 (ADR 007, dozens of long comments) the argv exceeds ARG_MAX: ./scripts/pr-reviews 210 exits 126 with 'jq: Argument list too long', with or without --wait. So the approved review watcher cannot watch the busiest ADR PR at all, and a --wait over it fails in a way that is not exit 1 (timeout) or 0. Fix: feed the three documents via files or stdin (--slurpfile / --rawfile, or jq -s over a concatenated stream) instead of argv. Test: a fixture whose combined JSON exceeds getconf ARG_MAX.","status":"closed","priority":2,"issue_type":"bug","assignee":"agent-5jxt","owner":"el-github@elangley.org","created_at":"2026-09-24T22:07:30Z","created_by":"agent-adr007","updated_at":"2026-09-24T22:37:58Z","started_at":"2026-09-24T22:17:05Z","closed_at":"2026-09-24T22:37:58Z","close_reason":"Landed in main via #301.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-0s6.1","title":"Sanitize the repository for a public mirror (improvise/improvise)","description":"Prepare the repository for a public release, via a public mirror at improvise/improvise (user's plan, 2026-09-24).\n\nScope:\n1. Secret scan of the FULL history (gitleaks or trufflehog), every branch and tag that would be published. Rotate anything found; decide whether history must be rewritten.\n2. Content audit: list what becomes public and decide per item — .beads/ (issues.jsonl, interactions.jsonl), prolog-sessions/kb.pl, .claude/ and .agents/, context/ (agent notes), commit author emails, agent-posted PR/issue comments (these stay on the private repo; confirm nothing is copied).\n3. Choose mirror shape: true mirror of main vs. filtered/fresh history (e.g. drop .beads/, .claude/, prolog-sessions/, or squash to a single initial commit). Once public, the choice is effectively permanent.\n4. Workflows in the public repo: self-hosted runners must NOT be attached there. Disable Actions on the mirror initially, or add a GitHub-hosted-only CI. Disable or strip claude.yml (no CLAUDE_CODE_OAUTH_TOKEN there; if ever enabled, gate on author_association OWNER/MEMBER/COLLABORATOR).\n5. Mirror mechanism: push main only, private -\u003e public, via a narrowly scoped deploy key; never push agent WIP branches.\n6. Decide where Pages, releases (cargo dist), README badges and crates.io metadata (repository URL) point.\n7. Contribution flow: how outside PRs/issues on the mirror are brought into the private repo.\n\nAcceptance: a written audit (secret-scan result + public-content list with decisions), the mirror script/workflow in a PR, and the user's go-ahead before the first public push.\n","status":"open","priority":2,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-24T19:52:46Z","created_by":"orchestrator","updated_at":"2026-09-24T19:52:46Z","labels":["area:release","needs:user"],"dependencies":[{"issue_id":"improvise-0s6.1","depends_on_id":"improvise-0s6","type":"parent-child","created_at":"2026-09-24T19:52:46Z","created_by":"orchestrator","metadata":"{}"}],"comments":[{"id":"b8d881e5-7b7f-5471-bb86-fbe206b5b706","issue_id":"improvise-0s6.1","author":"Edward Langley","text":"Found 2026-09-25 while reading a code-maat age report: bank-info.improv was tracked and deleted in 1c2361b4 (2026-09-19) but is still in history, reachable from many branches; foo.improv (deleted 359c1577) and whatever.improv likewise. Check bank-info.improv's content in history before any public mirror -- if it holds real financial data it needs a history rewrite or a mirror cut from a clean point, which is the user's decision. Also on gh/main: a generated TAGS file (stale; still says MAX_CATEGORIES = 12) and DOLT_REMOTE.md on some branch.","created_at":"2026-09-25T03:18:02Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1}
|
||||
@@ -506,6 +509,11 @@
|
||||
{"_type":"issue","id":"improvise-pby","title":"2.2 Write the README","description":"Replace existing README.md with new one under 250 lines. Sections in order: Title, one-sentence pitch, inline demo GIF, why this exists (Lotus Improv reference), quick start, key bindings, installation (Nix/crates.io/prebuilt - no Homebrew), codebase overview, expectations disclaimer, license. No badges, TOC, or contributing guide.","status":"closed","priority":2,"issue_type":"task","assignee":"Edward Langley","owner":"el-github@elangley.org","created_at":"2026-04-09T04:07:32Z","created_by":"Edward Langley","updated_at":"2026-07-30T23:06:31Z","closed_at":"2026-04-09T07:02:52Z","close_reason":"README written with all 10 required sections, committed. License updated to Apache-2.0 per user request.","dependency_count":0,"dependent_count":2,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-ihv","title":"2.1 Add Nix tooling for asciinema, VHS, and cargo-dist","description":"Modify flake.nix to add pkgs.asciinema and pkgs.vhs to dev shell nativeBuildInputs. Add cargo-dist if packaged in nixpkgs. Add runtime deps for VHS (ttyd, ffmpeg) if needed. Verify tools work via nix develop --command. Add nix run app or shell script for demo recording workflow.","status":"closed","priority":2,"issue_type":"task","assignee":"Edward Langley","owner":"el-github@elangley.org","created_at":"2026-04-09T04:07:30Z","created_by":"Edward Langley","updated_at":"2026-07-30T23:06:31Z","closed_at":"2026-04-09T07:30:19Z","close_reason":"asciinema, vhs, cargo-dist added to flake.nix dev shell and verified. cargo-dist wrapper enables 'cargo dist' subcommand. scripts/record-demo.sh added.","dependency_count":0,"dependent_count":3,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-yk4","title":"Phase 2: README and demo artifacts","description":"The main launch work. README is 80% of the launch, demo artifacts are 20%. Covers Nix tooling for asciinema/VHS, writing the README, creating demo GIF and asciinema casts, and verifying all artifacts.","status":"closed","priority":2,"issue_type":"feature","assignee":"spot","owner":"el-github@elangley.org","created_at":"2026-04-09T04:05:49Z","created_by":"Edward Langley","updated_at":"2026-04-14T07:47:43Z","closed_at":"2026-04-14T07:47:43Z","close_reason":"All 5 subtasks closed; artifacts verified present and tracked in git (README.md, docs/demo.gif, docs/demo.tape, docs/casts/*.cast).","dependencies":[{"issue_id":"improvise-yk4","depends_on_id":"improvise-abz","type":"blocks","created_at":"2026-04-08T23:37:37Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-yk4","depends_on_id":"improvise-d4w","type":"blocks","created_at":"2026-04-08T23:37:36Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-yk4","depends_on_id":"improvise-ihv","type":"blocks","created_at":"2026-04-08T23:37:34Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-yk4","depends_on_id":"improvise-odx","type":"blocks","created_at":"2026-04-08T23:37:35Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-yk4","depends_on_id":"improvise-pby","type":"blocks","created_at":"2026-04-08T23:37:36Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":5,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-szi8","title":"Protocol version in the client/server handshake","description":"The user decided on PR #320 (ADR 005, question 9): \"yeah, accept and add a ticket for handshake version.\"\n\nProblem: the client/server protocol has no version handshake. A deployed browser page can talk to an improvise-ws-server built from a different commit, and nothing says so. ADR 005 changes how a cell value is spelled on the wire: exact values become strings such as \"12.50\" and \"10/3\", and inexact values become \"#i…\". Across such a version skew:\n- an old peer reading a new value refuses the whole frame, loudly;\n- a new peer reads an old peer's bare JSON number as inexact, silently. That old peer's typed 0.1 then compares unequal to every typed 0.1 on the new side, though both look identical.\n\nAsk: add a protocol version to the handshake (ClientFrame::Viewport / the Snapshot), and refuse or warn on a mismatch before any value crosses.\n\nAcceptance:\n- A mismatched peer is told so at the handshake, before any frame carrying values is exchanged.\n- A test pins both directions, old→new and new→old.\n","status":"open","priority":3,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-25T17:16:53Z","created_by":"agent-adr005","updated_at":"2026-09-25T17:16:53Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-nn4s.12","title":"minibuffer.* inherits the status badge through mode, so a theme omitting a prompt face draws a badge; decide overlay-vs-replace before theme upload","description":"Found in the post-merge review of PR #319 (merged head 3583359a). Needs a decision\nbefore improvise-nn4s.7 (theme upload) lands, and possibly the user's.\n\nAfter Codex's P2 fix, `minibuffer.\u003centry\u003e` inherits `mode.text-entry`, which inherits\n`mode`, as ADR 004 says. But `mode` is the status-line badge: `Named(Black)` on\n`Named(BrightBlack)`. The prompt is accent-on-`Indexed(235)`, bold. So the shipped theme\nstates every colour/weight attribute on all seven minibuffer leaves, and the parent\ncontributes nothing but slant, underline and reverse.\n\nClause 9 promises that a theme may safely *omit* a face and inheritance supplies it.\nUnder a replace-semantics upload, a user theme that omits `minibuffer.formula` gets the\nbadge colours: black on dark grey, not bold. It does not get a prompt. Two readings fix it:\n- (a) an uploaded theme OVERLAYS `Theme::shipped()` face by face, so omission falls back\n to the shipped leaf. That is a code decision for nn4s.7, but `Theme` should then say so.\n- (b) the ADR chain is revisited, so the prompt and the badge stop sharing a parent.\n That is the user's call, and it is a published-name/parent change (clause 9).\n\nNot a defect today: the only theme is the shipped one, and it reproduces the literals.\nThe ask is that nn4s.7 not ship without choosing (a) or (b), and a test that an uploaded\ntheme omitting a minibuffer face still draws a prompt.\n","status":"open","priority":3,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-25T17:09:04Z","created_by":"Edward Langley","updated_at":"2026-09-25T17:09:04Z","labels":["adversarial-review","needs-user","pr-319"],"dependencies":[{"issue_id":"improvise-nn4s.12","depends_on_id":"improvise-nn4s","type":"parent-child","created_at":"2026-09-25T17:09:04Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-nn4s.11","title":"Stale pointers to axis_fg/minibuf_fg/AxisColor after PR #319: web/style.css, repo-map, kb.pl","description":"Found in the post-merge review of PR #319 (merged head 3583359a).\n\nThese references still name what #319 deleted (`axis_fg`, `minibuf_fg`, `AxisColor`,\n`Axis::color`):\n- `web/style.css:677-679`, the comment over `#minibuffer.green`, says the class\n resolves \"exactly as `draw.rs::minibuf_fg` resolves it for the TUI\".\n- `context/repo-map.md` (orchestrator-owned): the How-to-find row \"Change an axis label,\n color or cycle\" (colors resolve in `draw.rs::axis_fg`), gotcha 26 (`color() -\u003e AxisColor`,\n `draw.rs::axis_fg`), and the File Inventory line for `view/axis.rs` (\"AxisColor\").\n `improvise-protocol/src/face.rs` has no inventory line.\n- `prolog-sessions/kb.pl` (orchestrator-owned, `scripts/kb` only):\n `location(axis_presentation, ...)`, which says the color/AxisColor live on Axis;\n `location(axis_color_resolution, ...)`, which says axis_fg is in draw.rs; and\n `invariant(semantic_palette_at_the_core_boundary, ...)`, whose precedents are\n MinibufColor -\u003e minibuf_fg.\n- `TAGS:1235` still lists `mode_style`. It is a generated file, so regenerate or ignore.\n\nAlso: improvise-nn4s.1 and improvise-nn4s.2 are still in_progress although #319 merged.\n","status":"open","priority":3,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-25T17:09:02Z","created_by":"Edward Langley","updated_at":"2026-09-25T17:09:02Z","labels":["adversarial-review","docs","pr-319"],"dependencies":[{"issue_id":"improvise-nn4s.11","depends_on_id":"improvise-nn4s","type":"parent-child","created_at":"2026-09-25T17:09:02Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-nn4s.9","title":"every_face_reaches_the_terminal_with_every_attribute_it_states checks only BOLD and uses terminal_colour as its own oracle","description":"Found in the post-merge review of PR #319 (merged head 3583359a).\n\n`src/draw.rs::face_tests::every_face_reaches_the_terminal_with_every_attribute_it_states`\ndoes not test what its name says:\n- It checks fg and bg against `terminal_colour`, which is the function under test, so a\n wrong slot mapping cannot fail it. One example is `AnsiSlot::White =\u003e Color::White`,\n the ratatui naming trap `context/code/tui.md` warns about.\n- Of the four modifiers it checks only BOLD.\n- The shipped theme states no italic, underline or reverse, so\n `the_shipped_theme_reproduces_every_migrated_literal` cannot see them either.\n\nInjected fault: in `face_style`, map `Slant::Italic` to `Modifier::UNDERLINED` and\n`reverse` to `Modifier::DIM`. `cargo test --workspace --no-fail-fast` stays fully green.\nThis matters before improvise-nn4s.6, whose whole mechanism is `reverse`.\n\nFix: build a synthetic `ResolvedFace` per attribute, or a non-shipped `Theme` that\nstates each one, and assert the exact `Modifier` bit. Pin `terminal_colour` against a\nliteral table of the 16 slots (ANSI 7 = ratatui Gray, 8 = DarkGray, 15 = White) rather\nthan against itself.\n\nSame pass, a nit: `Face::of_mode`'s `.unwrap_or(Face::ModeTextEntry)` and\n`draw_bottom_bar`'s matching fallback are unreachable. Remove them, or make them\n`expect`, so a disagreement between `TextEntry::of` and `of_mode` fails loudly instead\nof falling back silently.\n","status":"open","priority":3,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-25T17:08:58Z","created_by":"Edward Langley","updated_at":"2026-09-25T17:08:58Z","labels":["adversarial-review","pr-319"],"dependencies":[{"issue_id":"improvise-nn4s.9","depends_on_id":"improvise-nn4s","type":"parent-child","created_at":"2026-09-25T17:08:58Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-nn4s.8","title":"The wire half of ADR 004's category-add exception is unpinned: MinibufColor::Yellow on category_add passes the whole workspace","description":"Found in the post-merge review of PR #319 (merged head 3583359a).\n\nADR 004 clause 8's one declared exception has two halves. The TUI half is pinned by\n`the_category_add_prompt_draws_as_the_item_add_prompt_does`. The wire half is not.\n`AppMode::category_add` now sends `MinibufColor::Green`, and the browser's class comes\nfrom it (`web/app.js:311`).\n\nInjected fault: set `category_add`'s `color` in `crates/improvise-protocol/src/lib.rs`\nback to `MinibufColor::Yellow`. `cargo test --workspace --no-fail-fast` stays fully green.\nNo Playwright spec asserts the minibuffer class either. So the browser could go back to\nyellow while the TUI stays green, and nothing would notice.\n\nThere is a second hazard here. The TUI no longer reads `MinibufferConfig.color`: it\nderives the face from the mode variant. The browser still reads `color`. That means two\nderivations of one fact until improvise-nn4s.4 lands, and nothing holds them in step.\n\nFix, the smallest that discriminates: a protocol test asserting that for every\ntext-entry `AppMode`, `minibuffer().color` agrees with the shipped theme's\n`minibuffer.\u003centry\u003e` foreground. It must include category-add == item-add. It is deleted\nwith the field in nn4s.4.\n","status":"open","priority":3,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-25T17:08:55Z","created_by":"Edward Langley","updated_at":"2026-09-25T17:08:55Z","labels":["adversarial-review","pr-319"],"dependencies":[{"issue_id":"improvise-nn4s.8","depends_on_id":"improvise-nn4s","type":"parent-child","created_at":"2026-09-25T17:08:55Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-9xy9","title":"Cargo.nix has no freshness gate: dev-dependency test-support edges drifted on main unnoticed, and the nix-build job builds only #improvise","description":"Found by the adversarial review of PR #318 (head 689480bf).\n\nOn gh/main (c91cc055), Cargo.nix had drifted from the Cargo.toml files, and nothing noticed. Five dependency edges ask for improvise-core's or improvise-protocol's `test-support` feature:\n- the root crate's improvise-protocol;\n- improvise-command's improvise-core and improvise-protocol;\n- improvise-wasm-client's improvise-protocol.\n\nMain's Cargo.nix mentions `test-support` three times. PR #318 regenerated it with crate2nix for unicase, and four more `test-support` edges appeared that had nothing to do with that PR.\n\nWhy CI misses it: the `nix-build` job runs only `nix build .#improvise`. That builds the non-test crate graph, so a missing dev-dependency feature edge never fails. A missing *normal* dependency does fail, which is why #318 had to regenerate for unicase. No job checks that Cargo.nix is what crate2nix would generate from the current Cargo.lock and manifests.\n\nFix: a CI step, or a checkers suite, that regenerates Cargo.nix and diffs it (`crate2nix generate` then `git diff --exit-code Cargo.nix`). Alternatively, build the crate2nix test derivations. Either way, inject a stale edge and watch the check fail.\n","status":"open","priority":3,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-25T06:19:23Z","created_by":"Edward Langley","updated_at":"2026-09-25T06:19:23Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-8jk6.17","title":"Headless import suite misses three boundary faults: every bad formula named, CSV float measures, :import-json on CSV","description":"Found in the adversarial review of PR #321 at efaa5050. The headless import suite (tests/headless_cli.rs) does not see three faults at the CLI boundary, although the user treats that boundary as a product and context/code/cli.md section 13 states its contract. Each fault below was injected alone, and the whole workspace suite was run:\n\n1. **Every bad formula is named.** cli.md section 13 says: \"Every bad formula is named on stderr\". The headless test passes one bad and one good formula, so naming only the first bad one (the author's rival R5) is caught only by the io unit test `a_formula_that_does_not_parse_stops_the_build_and_is_named`. The fix is cheap: pass two bad `--formula`s in `an_import_formula_that_does_not_parse_refuses_names_it_and_writes_nothing`.\n2. **A CSV float measure.** With `csv_parser::number_in`'s f64 branch disabled (integers only), `1000.50` and similar measures are silently dropped. Only io unit tests failed (4 csv_parser tests and one wizard test). No headless or root integration test imports a CSV with a decimal measure and checks the value.\n3. **`:import-json` / ModelCommand::ImportJsonHeadless on a CSV.** With its CSV branch changed back to `ImportPipeline::new(Value::Array(recs.to_vec()))`, the JSON constructor, only `import_headless_refuses_records_that_collide` failed, and it failed only incidentally, because Revenue stopped being a measure. No test for this path asserts that a numeric CSV column is proposed as a Measure, or that a category's text is kept verbatim.\n\nThe wizard-only rivals R6 and R8 have no headless surface, so a unit test is the right level for them. They are not part of this ticket.\n","status":"in_progress","priority":3,"issue_type":"task","assignee":"agent-headless-import","owner":"el-github@elangley.org","created_at":"2026-09-25T06:15:00Z","created_by":"Edward Langley","updated_at":"2026-09-25T06:20:11Z","started_at":"2026-09-25T06:20:11Z","lease_expires_at":"2026-09-25T06:25:11Z","heartbeat_at":"2026-09-25T06:20:11Z","labels":["headless","import","tests"],"dependencies":[{"issue_id":"improvise-8jk6.17","depends_on_id":"improvise-8jk6","type":"parent-child","created_at":"2026-09-25T06:15:00Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-tpm8","title":"import --extract on a field with no detected date format is silently ignored and exits 0","description":"`improvise import --no-wizard --extract F:Year` on a field F with no detected date format does nothing and exits 0 without a word. Examples are a years-only column (`2023,2024,2025`, forced with `--time`), or a column where one value fails to parse.\n\nFound by the adversarial review of PR #322 at 6b910889 (years.csv, `--time Year --extract Year:Year --extract Year:Month`). The output was one flat category of the raw years, and there was no warning. That result is fine; the silence is not, because the user asked for a component and got none. The behaviour is not new with #322. Sibling of improvise-31oe.2 and improvise-8jk6.5 (other silent headless no-ops).\n\nExpected: a warning naming the field and saying no date format was detected, so the extraction was not applied. It could also be an error, matching how 8jk6.5 is resolved.\n","status":"open","priority":3,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-25T06:09:25Z","created_by":"Edward Langley","updated_at":"2026-09-25T06:09:25Z","labels":["headless","import"],"dependencies":[{"issue_id":"improvise-tpm8","depends_on_id":"improvise-8jk6.3","type":"discovered-from","created_at":"2026-09-25T06:09:25Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
@@ -548,7 +556,7 @@
|
||||
{"_type":"issue","id":"improvise-j4no","title":"TUI draw panics on a terminal 0 or 1 row tall (index outside of buffer)","description":"Found reviewing PR #303 (improvise-gtn7.1), pre-existing and identical at gh/main ae5cf408: rendering draw() into a TestBackend of height 0 (any width \u003e=1) or height 1 (width \u003e=3), with or without side panels, panics in ratatui-core buffer.rs:251 'index outside of buffer: the area is Rect { .. height: 0 } but index is (0, 0)'. A real terminal can be resized that small; the TUI loop would crash. Probe: loop w 0..=120, h in {0,1,3,5,24}, all 8 panel masks, press Right x12 and draw; 1904 failing combos at both commits, all at h\u003c=1. Fix: find the widget that writes without checking its Rect (likely a bar or the welcome/grid block) and guard, then pin with that sweep.","status":"open","priority":3,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-25T01:40:30Z","created_by":"Edward Langley","updated_at":"2026-09-25T01:40:30Z","labels":["tui"],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-j7ij","title":"No test notices a side panel that is open but never drawn: App::side_panel_open can drop a flag with the suite green","description":"Found reviewing PR #303 (improvise-gtn7.1). Injected: App::side_panel_open returns category||view, omitting formula_panel_open. cargo test -p improvise -p improvise-command stays green. Effect: the formula panel is toggled open (FormulaPanel mode reachable) but draw_content skips the whole side column, so the panel is invisible; grid and keypress path agree on full width, so gtn7.1's test cannot see it. Same hole pre-dates the PR (draw_content computed the OR inline). Fix shape: a draw-level assertion that each open panel's title/border appears in the frame, one panel at a time AND with all three open, asserting the direction the launch default (category open) cannot supply.","status":"open","priority":3,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-25T01:40:26Z","created_by":"Edward Langley","updated_at":"2026-09-25T01:40:26Z","labels":["tests","tui"],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-felo","title":"Epic: Core model, layout and formula evaluation","description":"improvise-core and improvise-formula: Model and DataStore correctness (07i0, nwgk, vwc7, 98pd), GridLayout prune and marker logic and its docs (dw2d), formula evaluation performance (9iyv), and ADR 005's exact-decimal arithmetic (l2b8). Out of scope: stable ids (the ADR 007 epic), .improv reading and writing (the import/export epic), and records mode and drill (their own epic).","status":"open","priority":3,"issue_type":"epic","owner":"el-github@elangley.org","created_at":"2026-09-25T01:04:12Z","created_by":"Edward Langley","updated_at":"2026-09-25T01:04:12Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-hexb","title":"ADR 007 clause 12: the regression test commits UNEDITED text (a no-op commit passes it), and which side holds the captured mapping is unstated","description":"Found by the delta review of PR #210 (ADR 007, `4830fa30..fcb8f539`). None of this blocks. Clause 12 resolves the contradiction it was written for. These are details the implementer of clause 12 will hit.\n\n1. **The required regression test can pass without exercising the mapping.** It says \"Commit the unedited text\". A host or client that treats an unedited commit as a no-op (the text equals what was shown, so send nothing, or `raw == was`, so write nothing) passes both assertions without resolving any name. That is the \"a rival that fails the setup\" vacuity shape. The test should change something the user typed that is not a name, for example `Revenue - Cost` → `Revenue - Cost * 1` (Codex's original scenario: \"edit only the constant/operator\"). Then assert that the stored formula references the original item's id.\n\n2. **Which side holds the captured mapping, and what `ReplaceFormula.raw` carries on the wire, is unstated.** Clause 12 cites `OpenedCell` (client-side, in `improvise-protocol/src/client.rs`) as the model. But it also says a name not in the mapping \"is resolved against the document at commit\", which only the host can do. So either:\n - `raw` travels as id-form text for the captured names and display text for new ones (a mixed rendering, which needs a rule for telling them apart, even though ids are globally unique); or\n - the mapping travels with the command; or\n - the host keeps a per-session open-editor record.\n\n The TUI (host-local) and the browser (remote) must reach the same answer. This overlaps improvise-m56i item 6 (resolving typed names at the panel edge).\n\n3. **Clause 11 (Migration)** does not list clause 12's capture step. The Consequences bullet \"the host must resolve display names typed there back to ids\" now describes only the names that are new to the edit.\n\n4. **Minor factual drift in the revised Context paragraph.** \"The only reader is one test\" is wrong: `category.rs`'s `add_item_returns_sequential_ids` and `add_item_duplicate_returns_same_id`, and `types.rs:1385`, all read the ids. `Category::add_item` returns the id on every call, not only on the idempotent path. The claim that matters, that no production caller uses the value, holds: `parse.rs:389` discards it.\n","status":"open","priority":3,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-25T00:21:24Z","created_by":"agent-review-210","updated_at":"2026-09-25T00:21:24Z","labels":["adversarial-review","pr-210"],"dependencies":[{"issue_id":"improvise-hexb","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T01:04:53Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-hexb","depends_on_id":"improvise-sboz","type":"discovered-from","created_at":"2026-09-25T00:21:24Z","created_by":"agent-review-210","metadata":"{}"}],"comments":[{"id":"7834165f-01be-5178-97e7-d15d6c21a9fc","issue_id":"improvise-hexb","author":"Edward Langley","text":"Second copy of item 1 (review of PR #302 at 95d4814b, Claude agent): prolog-sessions/kb.pl decision('improvise-sboz', a_formula_edit_maps_display_names_to_ids_when_the_editor_opens, ...) restates clause 12's required regression test verbatim, including 'commit unedited'. When this ticket amends the test (edit a non-name token, e.g. Revenue - Cost -\u003e Revenue - Cost * 1), that kb fact must be updated in the same change via scripts/kb replace, or the kb keeps the vacuous form after the ADR is fixed.","created_at":"2026-09-25T00:51:28Z"},{"id":"e17020bb-539a-562f-8101-9f0b977ec4fd","issue_id":"improvise-hexb","author":"agent-adr007","text":"agent-adr007 planning. Items 1, 3 and 4 go in the text fix improvise-cq1q.1. Item 2 is a USER QUESTION and blocks improvise-cq1q.9. Recommendation: the client holds the capture, as it holds OpenedCell, and resolves typed names against its own CategoryTreeCache, which clause 6 gives ids. The wire then carries only id-form text, and the host never resolves a display name. The TUI and the browser share the resolver in improvise-protocol.","created_at":"2026-09-25T04:51:34Z"}],"dependency_count":0,"dependent_count":1,"comment_count":2}
|
||||
{"_type":"issue","id":"improvise-hexb","title":"ADR 007 clause 12: the regression test commits UNEDITED text (a no-op commit passes it), and which side holds the captured mapping is unstated","description":"Found by the delta review of PR #210 (ADR 007, `4830fa30..fcb8f539`). None of this blocks. Clause 12 resolves the contradiction it was written for. These are details the implementer of clause 12 will hit.\n\n1. **The required regression test can pass without exercising the mapping.** It says \"Commit the unedited text\". A host or client that treats an unedited commit as a no-op (the text equals what was shown, so send nothing, or `raw == was`, so write nothing) passes both assertions without resolving any name. That is the \"a rival that fails the setup\" vacuity shape. The test should change something the user typed that is not a name, for example `Revenue - Cost` → `Revenue - Cost * 1` (Codex's original scenario: \"edit only the constant/operator\"). Then assert that the stored formula references the original item's id.\n\n2. **Which side holds the captured mapping, and what `ReplaceFormula.raw` carries on the wire, is unstated.** Clause 12 cites `OpenedCell` (client-side, in `improvise-protocol/src/client.rs`) as the model. But it also says a name not in the mapping \"is resolved against the document at commit\", which only the host can do. So either:\n - `raw` travels as id-form text for the captured names and display text for new ones (a mixed rendering, which needs a rule for telling them apart, even though ids are globally unique); or\n - the mapping travels with the command; or\n - the host keeps a per-session open-editor record.\n\n The TUI (host-local) and the browser (remote) must reach the same answer. This overlaps improvise-m56i item 6 (resolving typed names at the panel edge).\n\n3. **Clause 11 (Migration)** does not list clause 12's capture step. The Consequences bullet \"the host must resolve display names typed there back to ids\" now describes only the names that are new to the edit.\n\n4. **Minor factual drift in the revised Context paragraph.** \"The only reader is one test\" is wrong: `category.rs`'s `add_item_returns_sequential_ids` and `add_item_duplicate_returns_same_id`, and `types.rs:1385`, all read the ids. `Category::add_item` returns the id on every call, not only on the idempotent path. The claim that matters, that no production caller uses the value, holds: `parse.rs:389` discards it.\n","status":"open","priority":3,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-25T00:21:24Z","created_by":"agent-review-210","updated_at":"2026-09-25T00:21:24Z","labels":["adversarial-review","pr-210"],"dependencies":[{"issue_id":"improvise-hexb","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T01:04:53Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-hexb","depends_on_id":"improvise-sboz","type":"discovered-from","created_at":"2026-09-25T00:21:24Z","created_by":"agent-review-210","metadata":"{}"}],"comments":[{"id":"7834165f-01be-5178-97e7-d15d6c21a9fc","issue_id":"improvise-hexb","author":"Edward Langley","text":"Second copy of item 1 (review of PR #302 at 95d4814b, Claude agent): prolog-sessions/kb.pl decision('improvise-sboz', a_formula_edit_maps_display_names_to_ids_when_the_editor_opens, ...) restates clause 12's required regression test verbatim, including 'commit unedited'. When this ticket amends the test (edit a non-name token, e.g. Revenue - Cost -\u003e Revenue - Cost * 1), that kb fact must be updated in the same change via scripts/kb replace, or the kb keeps the vacuous form after the ADR is fixed.","created_at":"2026-09-25T00:51:28Z"},{"id":"e17020bb-539a-562f-8101-9f0b977ec4fd","issue_id":"improvise-hexb","author":"agent-adr007","text":"agent-adr007 planning. Items 1, 3 and 4 go in the text fix improvise-cq1q.1. Item 2 is a USER QUESTION and blocks improvise-cq1q.9. Recommendation: the client holds the capture, as it holds OpenedCell, and resolves typed names against its own CategoryTreeCache, which clause 6 gives ids. The wire then carries only id-form text, and the host never resolves a display name. The TUI and the browser share the resolver in improvise-protocol.","created_at":"2026-09-25T04:51:34Z"},{"id":"2849c32c-59f4-5d6e-b1cb-ef08af4cc8c6","issue_id":"improvise-hexb","author":"Edward Langley","text":"USER DECISION 2026-09-25 (item 2): the CLIENT holds clause 12's display-\u003eid capture (like OpenedCell) and resolves names against its id-bearing projection; the wire carries only id-form text; the host never resolves a display name.","created_at":"2026-09-25T17:51:49Z"}],"dependency_count":0,"dependent_count":1,"comment_count":3}
|
||||
{"_type":"issue","id":"improvise-8vs2","title":"ADR 007 states stale facts about main: fdk1's RenameRecordCoordinate has landed, CellId::Coordinate exists, wpxr (#281) is merged","description":"Found by the adversarial review of PR #210 (ADR 007, head 4830fa30). The ADR's text about the current code is stale against its own base, gh/main ad217cc1:\n\n1. **improvise-fdk1 has landed** (PR #221, ticket closed 2026-09-24). `ModelCommand::RenameRecordCoordinate { record: RecordHash, category: String, item: String }` exists in `improvise-protocol/src/reduce.rs`. Yet the ADR says:\n - its header, \"Unblocks: improvise-fdk1\";\n - Context, \"A records-mode coordinate rename has no wire form (improvise-fdk1)\";\n - \"What this means for improvise-fdk1\", which proposes the operation as new (`to: ItemRef`).\n\n The section should instead revise the landed variant's operands to `CategoryId` / `ItemRef`, as it does for km71's `FormulaId`.\n2. **`CellId::Coordinate { record, category }` already exists** (render.rs). \"`CellId::Coordinate` grows `{ record, category: CategoryId }`\" should read \"its `category` becomes a `CategoryId`\".\n3. **improvise-wpxr (PR #281) has merged**, and the ADR still calls it \"open\": \"If improvise-wpxr (PR #281, open) lands first\". `SetCell { view, cell, value }` and `ClearCell { view, cell }` are on main, so they are simply on the list.\n4. Minor: \"No production code reads either value\" (`Category.id` / `Item.id`) is slightly overstated. `Model::add_category` and `Category::add_item` return them on the idempotent path. No caller uses the value, which is the claim that matters.\n","status":"open","priority":3,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-24T22:49:31Z","created_by":"agent-review-210","updated_at":"2026-09-24T22:49:31Z","labels":["adversarial-review","pr-210"],"dependencies":[{"issue_id":"improvise-8vs2","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T01:04:53Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-8vs2","depends_on_id":"improvise-sboz","type":"discovered-from","created_at":"2026-09-24T22:49:31Z","created_by":"agent-review-210","metadata":"{}"}],"comments":[{"id":"5a61964c-18a7-5653-95b4-eb53e4685795","issue_id":"improvise-8vs2","author":"agent-adr007","text":"agent-adr007 planning: revision 8 fixed items 1 to 3. Item 4, the 'idempotent path' wording, remains and is folded into improvise-cq1q.1.","created_at":"2026-09-25T04:51:22Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1}
|
||||
{"_type":"issue","id":"improvise-p003","title":"ADR 007 namespace gaps: _Records/_Drill ids, _Index/_Dim items under global uniqueness, active_view, collapsed: by group name, CheckedBatch's create exemption","description":"Found by the adversarial review of PR #210 (ADR 007, head 4830fa30), against gh/main ad217cc1. These are places where the id namespace and the list of re-keyed state are incomplete:\n\n1. **Virtual views.** `view::VIRTUAL_VIEW_NAMES` is `[\"_Records\", \"_Drill\"]`. Answer 23 reserves `_` for virtual things, but clause 1 reserves only `_index`, `_dim` and `_measure`. Slugify's step 4 turns `_Drill` into `drill`, which collides with a user view named `Drill` or takes a gensym suffix. So a virtual view's id is neither reserved nor stable. They are never persisted, but they are addressed on the wire (`SwitchView`, and `_Drill` in improvise-obtu/t219).\n2. **`_Index` and `_Dim` items.** These are synthetic, such as record indices, and `_Dim` carries a column index (gotcha 1). Answer 22 makes ids unique across every kind. The ADR does not say whether these items take part. If they do, `_Index`'s `0` collides with a user item whose id is `0` (for example a display `0`).\n3. **`ViewState::active_view` and `Workbook::active_view`** are view names (`String`). Clause 6 lists cursors, expanded_cats and the view history as keyed by id, but not the active view itself.\n4. **`collapsed:` names a group.** `collapsed_line = \"collapsed: \" name \"/\" name` is category/group. Groups have no id (answer 9), but clause 2 says \"hidden and collapsed sets are keyed by ids\", and clause 5 lists `collapsed:` among references that carry the id. Only `[Coastal]` is exempted. So the collapsed set is (CategoryId, group name).\n5. **CheckedBatch's created-record exemption** (gotcha 43: a record that neither resolves nor is claimed, for `[AddItem(East), SetCell(East)]`). Clause 6 says a frame cannot address what it creates, so that exemption has no remaining producer. It should be removed or re-argued with the id vocabulary, not left as dead reasoning.\n","status":"open","priority":3,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-24T22:49:31Z","created_by":"agent-review-210","updated_at":"2026-09-24T22:49:31Z","labels":["adversarial-review","pr-210"],"dependencies":[{"issue_id":"improvise-p003","depends_on_id":"improvise-cq1q","type":"parent-child","created_at":"2026-09-25T01:04:55Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-p003","depends_on_id":"improvise-sboz","type":"discovered-from","created_at":"2026-09-24T22:49:31Z","created_by":"agent-review-210","metadata":"{}"}],"comments":[{"id":"26a29b15-8906-5014-a8cb-1725955f0b89","issue_id":"improvise-p003","author":"agent-adr007","text":"agent-adr007 planning: all five items are readings for improvise-cq1q.1. Item 2 (_Index/_Dim coordinates are positions outside the id namespace) is the ADR's reading and should be flagged to the user on that PR. Item 5 is implemented in improvise-cq1q.8.","created_at":"2026-09-25T04:51:36Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1}
|
||||
{"_type":"issue","id":"improvise-3d64","title":"renamed_away() says 'the row you were editing has changed' when only the edited cell's measure went (Opened::Gone on a value target)","description":"Codex on #274 (discussion_r4090469430), repeated on integration PR #299 at 8dd39fd5. Opened::Gone is also produced for a value target when the same record remains but the edited measure disappears or changes, yet resolve.rs renamed_away() / RECORD_RENAMED_AWAY says the ROW changed. Use target-neutral wording (the cell you were editing), and pin it with a test that reaches Gone through a measure change on a surviving record.","status":"open","priority":3,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-24T19:35:31Z","created_by":"agent-train2","updated_at":"2026-09-24T19:35:31Z","labels":["browser","pr-274"],"dependencies":[{"issue_id":"improvise-3d64","depends_on_id":"improvise-6jk","type":"parent-child","created_at":"2026-09-25T01:04:40Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
@@ -881,7 +889,7 @@
|
||||
{"_type":"issue","id":"improvise-l36","title":"3.2 Publish to crates.io","description":"After cargo publish --dry-run is clean and user confirms, run cargo publish. Verify crate appears at crates.io/crates/improvise and cargo install improvise works.","acceptance_criteria":"improvise is on crates.io and 'cargo install improvise' works from a clean machine. NEEDS THE USER: crates.io credentials, and their confirmation after 'cargo publish --dry-run' is clean. Note the workspace has grown to nine crates since this was filed — decide which are published and in what order, since a dependent cannot publish before its dependency, and improvise-io/protocol/command are all now real crates rather than internal modules.","notes":"DRY RUN ACTUALLY RUN, 2026-09-20 at 52507a6 (wave 2, WU-P). It is NOT clean,\nand the blocker is mechanical rather than a judgement call:\n\n error: failed to verify manifest at Cargo.toml\n all dependencies must have a version requirement specified when publishing.\n dependency `gp-tree` does not specify a version\n\ncargo stops at the FIRST offender, and gp-tree is not the only one. Every\ninter-crate dependency in this workspace is `{ path = \"...\" }` with no\n`version`, which crates.io forbids for normal dependencies:\n\n Cargo.toml (root) gp-tree, improvise-core, improvise-formula,\n improvise-io, improvise-command\n improvise-core improvise-formula\n improvise-formula (none)\n improvise-protocol improvise-core\n improvise-io improvise-core, improvise-formula\n improvise-gp gp-tree, improvise-core, improvise-formula\n improvise-command improvise-core, improvise-protocol,\n improvise-formula, improvise-io, improvise-gp\n\nDev-dependencies are exempt — cargo strips them on publish — so the root's\n`improvise-wasm-client`/`improvise-protocol` dev deps and improvise-wasm-\nclient's dev dep on the unpublishable improvise-ws-server are fine as they are.\n\nWHICH CRATES PUBLISH. `improvise-ws-server` and `improvise-worker-server` both\ncarry `publish = false` already, so the set is eight, and a dependent cannot go\nbefore its dependency. The only order that works:\n\n 1. improvise-formula (no intra-workspace deps)\n 2. improvise-core\n 3. gp-tree (no intra-workspace deps; can go any time before 4)\n 4. improvise-gp\n 5. improvise-protocol\n 6. improvise-io\n 7. improvise-command\n 8. improvise (the binary)\n\ncrates.io needs a minute or two to index each one before the next can resolve\nit, so this is eight sequential `cargo publish` calls, not one.\n\nTWO VERSION FACTS THE USER MUST DECIDE. `gp-tree` is at `0.1.0` while every\nother crate is at `0.1.0-rc2`; and a `-rc2` version requirement in a published\nmanifest pins consumers to a prerelease. If the intent is to publish 0.1.0\n(which improvise-3tj's tag implies), the version bump across ten manifests\nshould happen BEFORE the version requirements are written, or they will be\nwritten twice.\n\nNAME AVAILABILITY CHECKED: crates.io returns \"crate `improvise` does not exist\",\nso the name is free. Not checked: the other seven names.","status":"open","priority":3,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-04-09T04:07:58Z","created_by":"Edward Langley","updated_at":"2026-09-20T18:01:38Z","labels":["area:release","needs:user"],"dependencies":[{"issue_id":"improvise-l36","depends_on_id":"improvise-0s6","type":"parent-child","created_at":"2026-09-25T01:07:36Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-l36","depends_on_id":"improvise-11a","type":"blocks","created_at":"2026-04-08T21:09:29Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-l36","depends_on_id":"improvise-2fr","type":"blocks","created_at":"2026-04-08T21:09:28Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":2,"dependent_count":1,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-11a","title":"3.1 Configure cargo dist","description":"Run cargo dist init targeting x86_64-unknown-linux-gnu, aarch64-apple-darwin, x86_64-apple-darwin. Skip Windows and musl. Commit generated .github/workflows/release.yml and Cargo.toml additions. Test with v0.1.0-rc1 tag, verify builds, delete rc tag.","status":"closed","priority":3,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-04-09T04:07:55Z","created_by":"Edward Langley","updated_at":"2026-07-30T23:06:31Z","closed_at":"2026-04-11T07:49:57Z","close_reason":"Closed","dependencies":[{"issue_id":"improvise-11a","depends_on_id":"improvise-abz","type":"blocks","created_at":"2026-04-08T21:09:28Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":1,"dependent_count":3,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-0s6","title":"Phase 3: Distribution","description":"Configure cargo dist, publish to crates.io, tag v0.1.0 release. Produces prebuilt binaries for Linux x86_64 and macOS (Intel + Apple Silicon).","acceptance_criteria":"Closes when improvise-l36 and improvise-3tj close. Both need the user; nothing here is agent work. improvise-11a (cargo dist) is already closed.","notes":"CHECKED, wave 2 WU-P: STAYS OPEN, correctly — both children need the user and\nneither was touched. Nothing was published and nothing was tagged.\n\nWhat this pass DID do is make them ready, and one of them is not as ready as\nit looked. `cargo publish --dry-run` was actually run and FAILS: every\ninter-crate dependency in the workspace is `{ path = ... }` with no `version`,\nwhich crates.io forbids. cargo reports only the first offender (gp-tree), so\nthe dry run understates it — the full list, the publish order for the eight\npublishable crates, and the gp-tree/rc2 version disagreement are on\nimprovise-l36.\n\nimprovise-3tj's premise is already partly false and its note now says so: the\nREADME prebuilt-binaries link is not a placeholder, v0.1.0-rc2 carries real\ncargo-dist artifacts for all four targets, and what is actually missing is the\nv0.1.0 tag and the manifest bump that must precede it.","status":"open","priority":3,"issue_type":"feature","owner":"el-github@elangley.org","created_at":"2026-04-09T04:05:51Z","created_by":"Edward Langley","updated_at":"2026-09-20T18:05:07Z","labels":["area:release","needs:user"],"dependencies":[{"issue_id":"improvise-0s6","depends_on_id":"improvise-11a","type":"blocks","created_at":"2026-04-08T23:37:39Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-1xfb.5.2","title":"Bookkeeping nits from the PR #311 review","description":"Non-blocking findings from the #311 review (issuecomment-5826314337), for the next bookkeeping pass: (1) kb decision scroll_measured_where_drawn cites a tui.md heading imprecisely -- real heading is 'The grid's width is one answer, and a scroll is measured where it is drawn'; (2) repo-map calls the pre-#303 tile-bar code first_tile, a name #303 introduced; (3) category_cap_has_one_home says the literal 24 appears only in add_category_max_limit -- 'the only test' is exact; (4) two user quotes in kb clauses lack quotation marks and drop the final period; (5) three rewritten inventory rows keep stale line counts: import/wizard.rs 1967-\u003e2730, import_wizard_ui.rs 829-\u003e1316, draw.rs 1122-\u003e1189.","status":"open","priority":4,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-25T03:44:37Z","created_by":"Edward Langley","updated_at":"2026-09-25T03:44:37Z","labels":["docs","kb","tooling"],"dependencies":[{"issue_id":"improvise-1xfb.5.2","depends_on_id":"improvise-1xfb.5","type":"parent-child","created_at":"2026-09-25T03:44:37Z","created_by":"Edward Langley","metadata":"{}"}],"comments":[{"id":"a6c01ac1-0730-5e63-8bb6-54a42a2b1046","issue_id":"improvise-1xfb.5.2","author":"Edward Langley","text":"Also for the next bookkeeping pass (PR #315 review, issuecomment-5827266982): (6) repo-map gotcha 45 is stale -- apply_effects raises dirty outright only for WholeWorkbook; for CellData/LayoutGeometry only when observed content/config changed (app.rs:1025-1052); the 'omitting it is unrepresentable' claim needs rewording. (7) repo-map Wasm section cites improvise-fuj (closed) for 'the two effects that still bypass Storage'; the live bypasses are autosave and the headless writes, improvise-31oe.11, and neither is an effect. (8) PR #314's proposed repo-map lines (folder import row, gotcha 27 Filter now persisted in per-file views, gotcha 7 sparse records) and kb facts. (9) PR #317's proposed gotcha 44 rewording and new gotcha 51 once it merges.","created_at":"2026-09-25T05:24:21Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1}
|
||||
{"_type":"issue","id":"improvise-1xfb.5.2","title":"Bookkeeping nits from the PR #311 review","description":"Non-blocking findings from the #311 review (issuecomment-5826314337), for the next bookkeeping pass: (1) kb decision scroll_measured_where_drawn cites a tui.md heading imprecisely -- real heading is 'The grid's width is one answer, and a scroll is measured where it is drawn'; (2) repo-map calls the pre-#303 tile-bar code first_tile, a name #303 introduced; (3) category_cap_has_one_home says the literal 24 appears only in add_category_max_limit -- 'the only test' is exact; (4) two user quotes in kb clauses lack quotation marks and drop the final period; (5) three rewritten inventory rows keep stale line counts: import/wizard.rs 1967-\u003e2730, import_wizard_ui.rs 829-\u003e1316, draw.rs 1122-\u003e1189.","status":"open","priority":4,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-25T03:44:37Z","created_by":"Edward Langley","updated_at":"2026-09-25T03:44:37Z","labels":["docs","kb","tooling"],"dependencies":[{"issue_id":"improvise-1xfb.5.2","depends_on_id":"improvise-1xfb.5","type":"parent-child","created_at":"2026-09-25T03:44:37Z","created_by":"Edward Langley","metadata":"{}"}],"comments":[{"id":"a6c01ac1-0730-5e63-8bb6-54a42a2b1046","issue_id":"improvise-1xfb.5.2","author":"Edward Langley","text":"Also for the next bookkeeping pass (PR #315 review, issuecomment-5827266982): (6) repo-map gotcha 45 is stale -- apply_effects raises dirty outright only for WholeWorkbook; for CellData/LayoutGeometry only when observed content/config changed (app.rs:1025-1052); the 'omitting it is unrepresentable' claim needs rewording. (7) repo-map Wasm section cites improvise-fuj (closed) for 'the two effects that still bypass Storage'; the live bypasses are autosave and the headless writes, improvise-31oe.11, and neither is an effect. (8) PR #314's proposed repo-map lines (folder import row, gotcha 27 Filter now persisted in per-file views, gotcha 7 sparse records) and kb facts. (9) PR #317's proposed gotcha 44 rewording and new gotcha 51 once it merges.","created_at":"2026-09-25T05:24:21Z"},{"id":"735d27bf-0c67-587e-8965-12b1a4495c1a","issue_id":"improvise-1xfb.5.2","author":"Edward Langley","text":"Also for the next bookkeeping pass: ADR 004 step 1-2 (PR #319) made stale repo-map entries -- the 'Change an axis label, color or cycle' row, gotcha 26 (axis colour now Face::Axis in improvise-protocol/src/face.rs via draw.rs::face_style), the view/axis.rs inventory line, and face.rs needs an inventory line; kb facts location(axis_presentation), location(axis_color_resolution), invariant(semantic_palette_at_the_core_boundary) cite removed names (improvise-nn4s.11).","created_at":"2026-09-25T17:10:22Z"}],"dependency_count":0,"dependent_count":0,"comment_count":2}
|
||||
{"_type":"issue","id":"improvise-loqe","title":"improvise import --on-duplicate is silently ignored without --no-wizard","description":"Found reviewing PR #304 (improvise-cgqt) at 92233cff. --on-duplicate is documented as applying with --no-wizard, but clap accepts it on a wizard import too, where run_wizard_import ignores the whole ImportConfig (a pre-existing TODO). A user passing --on-duplicate sum without --no-wizard is asked anyway, with no warning that the flag did nothing. Either reject the flag without --no-wizard (clap requires) or pre-seed the wizard's policy.","status":"open","priority":4,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-25T01:55:40Z","created_by":"Edward Langley","updated_at":"2026-09-25T01:55:40Z","labels":["adversarial-review","cli","import"],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-hkhw","title":"ClientState::bootstrap keeps status_seen/status_owner, so a snapshot with the same status text and an equal EditorSession inherits the old document's status ownership","description":"Codex P2 on #298, originally on #279 (https://github.com/fiddlerwoaroof/improvise/pull/279#discussion_r4090596331). bootstrap (crates/improvise-protocol/src/client.rs) resets every cache and in_flight, but it leaves status_seen and status_owner (improvise-1x9h) alone before calling settle_status. When the snapshot's status_msg equals status_seen and EditorSession::of(view) equals the old owner (same mode, same selected cell, same seed), the ownership survives the rebase. status_stands then keeps a toast standing that should expire. The effect is cosmetic: only a toast's lifetime is wrong. Fix: clear both fields in bootstrap before settling. Regression: equal text and an equal EditorSession across a bootstrap; the default state satisfies the other cases.","status":"open","priority":4,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-24T19:05:35Z","created_by":"agent-review-298","updated_at":"2026-09-24T19:05:35Z","labels":["adversarial-review","browser","pr-279","pr-298"],"dependencies":[{"issue_id":"improvise-hkhw","depends_on_id":"improvise-6jk","type":"parent-child","created_at":"2026-09-25T01:04:42Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-dw2d","title":"core.md and walk_open_runs' doc call it 'the one reading of the markers', but prune_axis_items reads them a second time through open_at","description":"Found reviewing PR #250 at cfa8bf1e (improvise-e265). context/code/core.md (e265 section) and the doc comment on layout.rs walk_open_runs say it is 'the one reading of the markers'. prune_axis_items keeps its own reading: its input loop applies the same truncate/resize rules through open_at (OpenRuns), because it needs the header entries it moves, not only their positions. So core has two readers of the AxisEntry marker rules (three counting AxisFrame::groups on the wire type). Not a bug today: the enumeration judges prune's output through walk_open_runs, so a drift between the two fails pruning_keeps_every_surviving_rows_runs_at_every_level. Fix either the claim (the one reading for lookups; the pruner tracks its input by the same rules and the enumeration holds them together) or the code (prune reads via walk_open_runs over the undrained slice and clones items[at]).","status":"open","priority":4,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-24T06:53:15Z","created_by":"agent-review-250","updated_at":"2026-09-24T06:53:15Z","dependencies":[{"issue_id":"improvise-dw2d","depends_on_id":"improvise-e265","type":"discovered-from","created_at":"2026-09-24T06:53:21Z","created_by":"agent-review-250","metadata":"{}"},{"issue_id":"improvise-dw2d","depends_on_id":"improvise-felo","type":"parent-child","created_at":"2026-09-25T01:05:20Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
|
||||
@@ -68,3 +68,4 @@ __pycache__/
|
||||
# The release web capture, written to the repo root by
|
||||
# scripts/build-web-release.sh (improvise-oujb).
|
||||
/improvise-web.tar.gz
|
||||
.opencode
|
||||
|
||||
@@ -240,7 +240,10 @@ if [ "$pin_status" -eq 0 ] && command -v bd >/dev/null 2>&1; then
|
||||
DB_NAME="$(jq -r '.dolt_database // "improvise"' .beads/metadata.json 2>/dev/null || echo improvise)"
|
||||
ORIGIN="$(git remote get-url origin 2>/dev/null || true)"
|
||||
case "$ORIGIN" in
|
||||
git@github.com:*) ORIGIN="https://github.com/${ORIGIN#git@github.com:}" ;;
|
||||
git@github.com:*)
|
||||
ORIGIN="https://github.com/${ORIGIN#git@github.com:}"
|
||||
git remote add gh "$ORIGIN"
|
||||
;;
|
||||
esac
|
||||
ORIGIN="${ORIGIN%.git}"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user