chore: update issues

This commit is contained in:
Edward Langley
2026-09-25 00:08:54 -07:00
parent cf33a5d78a
commit 14141771e4
+6 -6
View File
@@ -1,7 +1,7 @@
{"_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","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-25T04:49:27Z","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.3","type":"blocks","created_at":"2026-09-25T04:50:50Z","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":2,"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":1,"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":0,"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.","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-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 +101,7 @@
{"_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":"{}"}],"dependency_count":0,"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-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}
@@ -114,8 +114,8 @@
{"_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.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.7","type":"blocks","created_at":"2026-09-25T04:50:59Z","created_by":"agent-adr007","metadata":"{}"}],"dependency_count":1,"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","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-25T04:50:03Z","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.4","type":"blocks","created_at":"2026-09-25T04:50:57Z","created_by":"agent-adr007","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"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}