kb: a pipe discards the exit code you meant to read
CI / Build all bundles (push) Has been cancelled
CI / Test and lint (host) (push) Has been cancelled
CI / wasm32 check (browser bundle crates) (push) Has been cancelled
CI / Browser JS unit tests (node --test) (push) Has been cancelled
CI / Knowledge base consistency (push) Has been cancelled
CI / Lint the harness crate (push) Has been cancelled
CI / Browser harness (OPFS contract + shell) (push) Has been cancelled
CI / Deploy to GitHub Pages (push) Has been cancelled
CI / Build all bundles (push) Has been cancelled
CI / Test and lint (host) (push) Has been cancelled
CI / wasm32 check (browser bundle crates) (push) Has been cancelled
CI / Browser JS unit tests (node --test) (push) Has been cancelled
CI / Knowledge base consistency (push) Has been cancelled
CI / Lint the harness crate (push) Has been cancelled
CI / Browser harness (OPFS contract + shell) (push) Has been cancelled
CI / Deploy to GitHub Pages (push) Has been cancelled
Fourth occurrence in one campaign, and this one was in a tool I wrote TO BE the reliable answer. scripts/pr-reviews --wait correctly exited 1 on a timeout, and my invocation ended in '| tail -20; echo', so the harness reported 0 — a review window that had EXPIRED read as a review that had ARRIVED, which is the dangerous direction. The earlier three: pr-watch reading tr's status through a pipe; a probe reporting EXIT=0 from a checker that had exited 1; and PIPESTATUS in zsh, where the array is lowercase pipestatus — the two shells differ, which is its own trap. The script's usage now says not to pipe it. Closes improvise-711n (,.2f yielded zero decimals — the spelling our own docs show) and improvise-x1ns (the preflight undercount ChatGPT Codex found on a PR I had already merged unread). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
This commit is contained in:
+2
-2
@@ -40,9 +40,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-x1ns","title":"encoded_width undercounts a forced category that has a missing coordinate","description":"Found by ChatGPT Codex reviewing PR #90 at 99cfb7f. THE REVIEW WAS NOT READ BEFORE THE PR MERGED — orchestrator process failure, see improvise-\u003cthis\u003e's sibling note. Re-verified on main at ce895ff: STILL LIVE.\n\nencoded_width (crates/improvise-gp/src/fit.rs:18) computes has_missing at line 32 and uses it ONLY in the numeric branch:\n\n true =\u003e 1 + usize::from(has_missing), // numeric: feature + .missing flag\n false =\u003e present.iter().collect::\u003cHashSet\u003c_\u003e\u003e().len().max(1), // categorical: present only\n\nbuild_categorical (crates/gp-tree/src/data.rs:359) maps every missing cell to MISSING_CAT and includes it in distinct(\u0026mapped), emitting an ADDITIONAL one-hot.\n\nSo a forced categorical column with N distinct present values plus one record lacking that coordinate passes the preflight as N and materializes N+1 columns. It is caught only by the LATER EXACT CHECK — which is precisely the allocation the preflight exists to prevent. At default max_records that is a large one-hot built before the error.\n\nThis is doctrine(guard_the_cost_not_the_result) defeated, and it is the SECOND time on this code path: improvise-zv8t's own adversarial round found forcing-by-name-while-budgeting-by-index, repaired as a data dependency. This one is the same class one level in — the two branches of one match disagree about what a column costs.\n\nNote the Wave K agent's own review had spotted a related shape (recorded as F5, \"build_categorical counts MISSING_CAT, the preflight does not — under-counts by one per category with an absent coordinate\") and recorded it as READ, NOT INJECTED and bounded by the exact check. Codex is right that \"bounded by the exact check\" is not a defence: the exact check runs after the allocation.\n\n## Acceptance Criteria\n- Count the missing level in the categorical branch.\n- A boundary regression with a FORCED NUMERIC CATEGORY containing a missing coordinate — the interesting case, because forcing is what makes a numeric column take the categorical branch.\n- Prove it by injection: without the fix, the preflight must under-report by exactly one and the test must fail naming that.\n- While there: gp.md §14 documents where the preflight and the exact check \"still differ\". Re-read it — it is the prose that made this look bounded.\n","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-21T17:13:05Z","created_by":"Edward Langley","updated_at":"2026-09-21T17:13:05Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-x1ns","title":"encoded_width undercounts a forced category that has a missing coordinate","description":"Found by ChatGPT Codex reviewing PR #90 at 99cfb7f. THE REVIEW WAS NOT READ BEFORE THE PR MERGED — orchestrator process failure, see improvise-\u003cthis\u003e's sibling note. Re-verified on main at ce895ff: STILL LIVE.\n\nencoded_width (crates/improvise-gp/src/fit.rs:18) computes has_missing at line 32 and uses it ONLY in the numeric branch:\n\n true =\u003e 1 + usize::from(has_missing), // numeric: feature + .missing flag\n false =\u003e present.iter().collect::\u003cHashSet\u003c_\u003e\u003e().len().max(1), // categorical: present only\n\nbuild_categorical (crates/gp-tree/src/data.rs:359) maps every missing cell to MISSING_CAT and includes it in distinct(\u0026mapped), emitting an ADDITIONAL one-hot.\n\nSo a forced categorical column with N distinct present values plus one record lacking that coordinate passes the preflight as N and materializes N+1 columns. It is caught only by the LATER EXACT CHECK — which is precisely the allocation the preflight exists to prevent. At default max_records that is a large one-hot built before the error.\n\nThis is doctrine(guard_the_cost_not_the_result) defeated, and it is the SECOND time on this code path: improvise-zv8t's own adversarial round found forcing-by-name-while-budgeting-by-index, repaired as a data dependency. This one is the same class one level in — the two branches of one match disagree about what a column costs.\n\nNote the Wave K agent's own review had spotted a related shape (recorded as F5, \"build_categorical counts MISSING_CAT, the preflight does not — under-counts by one per category with an absent coordinate\") and recorded it as READ, NOT INJECTED and bounded by the exact check. Codex is right that \"bounded by the exact check\" is not a defence: the exact check runs after the allocation.\n\n## Acceptance Criteria\n- Count the missing level in the categorical branch.\n- A boundary regression with a FORCED NUMERIC CATEGORY containing a missing coordinate — the interesting case, because forcing is what makes a numeric column take the categorical branch.\n- Prove it by injection: without the fix, the preflight must under-report by exactly one and the test must fail naming that.\n- While there: gp.md §14 documents where the preflight and the exact check \"still differ\". Re-read it — it is the prose that made this look bounded.\n","status":"closed","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-21T17:13:05Z","created_by":"Edward Langley","updated_at":"2026-09-21T17:56:36Z","closed_at":"2026-09-21T17:56:36Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-ac2z","title":"A host's none_cats leak into another view's grid: formula columns vanish","description":"A client on a view whose none_cats differ from the HOST's is projected a grid\nmissing that view's formula columns entirely. Not a wrong number -- absent\ncolumns.\n\nFOUND BY CORRECTING A FIXTURE, which is the part worth keeping. The wave-I\nhand-back for improvise-t6db reported this hazard as \"argued and NOT observed\",\non the strength of a fixture that was said to put Product on the None axis in\none view and on Column in another. It did not: add_category defaults a new\ncategory to Column and nothing moved it, so all three views had none_cats\n{_Index, _Dim} and the stated input to the hazard was absent. An unprobed path\nreported as a negative result. Setting Product to Axis::None explicitly made the\nproperty test fail on the first run.\n\nMECHANISM. App::rebuild_layout calls recompute_formulas(\u0026none_cats) for the\nHOST's view, populating Model::formula_cache. project::grid_in on the elsewhere\npath builds the other view's GridLayout against that same, stale cache. The\ncache is keyed by the coordinates the recompute used, so with Product on None in\nthe host's view the Total cells are keyed WITHOUT a Product coordinate; the other\nview, which has Product on Column, asks for (Region, Product, _Measure=Total),\nfinds nothing, and -- because prune_empty is on -- drops both Total columns.\n\nMEASURED, two-view fixture, formula Total = SUM(Revenue):\n\n host on Second, projecting Second 6 columns incl. Total x Widget/Gadget\n host on Default, projecting Second 4 columns NO Total columns at all\n\nTHE FIX IS NOT LOCAL. grid_in takes \u0026App and recompute_formulas needs \u0026mut Model,\nso grid_in cannot recompute for the view it is projecting. Candidates, none\nchosen:\n\n - Key formula_cache by view (or by none_cats), so one Model can hold more than\n one view's derived values.\n - Recompute on the session's behalf at a \u0026mut App boundary (Sessions::project\n is one) and cache per session.\n - Declare it out of scope: formulas are evaluated for the host's view and a\n client on another view sees the host's derivation. That is a decision, not\n the current state, which is an accident.\n\nNote this is the SAME SHAPE as improvise-t6db: the elsewhere path of grid_in is\nonly as correct as the inputs it can rebuild, and prune_empty was the first such\ninput. none_cats is the second, and it is worse, because prune_empty was one\nline and this one cannot be fixed where it is observed.\n\nGUARDED MEANWHILE by a_hosts_none_cats_leak_into_another_views_formula_columns\nin ui/project.rs, a characterization test that asserts the bug. Its message says\nso: if it fails, the bug is fixed, and the none_cats guard should come out of\na_clients_grid_does_not_depend_on_which_view_its_host_is_on at the same time.\n\n## Steps to Reproduce\n\n1. Two views over one document, both with prune_empty on, with a formula.\n2. Put a category on Axis::None in view A and on Axis::Column in view B.\n3. Host on B: grid_in(app, \"B\") draws the formula's columns.\n4. Host on A: grid_in(app, \"B\") does not.\n","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-21T11:31:45Z","created_by":"Edward Langley","updated_at":"2026-09-21T11:31:45Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-711n","title":"parse_number_format silently yields 0 decimals for the ,.2f spelling our own docs show","description":"Found by the adversarial review of PR #92, as a side finding while printing a test fixture's rendered cells and noticing they said \"1\" where the fixture had set format \",.2f\".\n\nparse_number_format (crates/improvise-core/src/format.rs:14) reads the decimal count as:\n\n fmt.rfind('.').and_then(|i| fmt[i + 1..].parse::\u003cu8\u003e().ok()).unwrap_or(0)\n\nFor \",.2f\" that takes everything after the dot — \"2f\" — and parses it as u8. It FAILS, so unwrap_or(0) yields ZERO DECIMALS. Silently.\n\n## Why this is not hypothetical\n- context/repo-map.md's own .improv format example documents `format: ,.2f`.\n- persistence/format.rs:109 writes `format: {}` with view.number_format verbatim, so whatever a user types round-trips into the file unchanged and is re-parsed the same wrong way on every load.\n- The trailing-f spelling is the Python/printf convention a user would most naturally reach for.\n\nSo a document whose author wrote the spelling our own documentation shows renders every number with zero decimals, and nothing reports it.\n\n## Steps to Reproduce\nSet a view's number_format to \",.2f\" and render any non-integer value. Expect 1.00; observe 1.\n\n## Acceptance Criteria\n- Decide which spellings are legal and say so in ONE place. The grammar is the source of truth for .improv, so if \",.2f\" is legal the parser must honour it; if it is not, loading must refuse it rather than silently reinterpreting it.\n- A test with the \",.2f\" spelling asserting the decimals actually applied. It must fail on today's code.\n- Whichever way it goes, the repo-map example and the format doc must agree with the parser — this bug is partly a documentation bug, and a fact stated twice is changed by grep.\n- Consider the neighbouring spellings while you are there: \".2\", \",.2\", \"0.00\", \"%.2f\".\n\nNOT verified: whether any shipped document or fixture uses the trailing-f spelling, and whether the import wizard can produce it.\n","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-21T11:29:53Z","created_by":"Edward Langley","updated_at":"2026-09-21T11:29:53Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-711n","title":"parse_number_format silently yields 0 decimals for the ,.2f spelling our own docs show","description":"Found by the adversarial review of PR #92, as a side finding while printing a test fixture's rendered cells and noticing they said \"1\" where the fixture had set format \",.2f\".\n\nparse_number_format (crates/improvise-core/src/format.rs:14) reads the decimal count as:\n\n fmt.rfind('.').and_then(|i| fmt[i + 1..].parse::\u003cu8\u003e().ok()).unwrap_or(0)\n\nFor \",.2f\" that takes everything after the dot — \"2f\" — and parses it as u8. It FAILS, so unwrap_or(0) yields ZERO DECIMALS. Silently.\n\n## Why this is not hypothetical\n- context/repo-map.md's own .improv format example documents `format: ,.2f`.\n- persistence/format.rs:109 writes `format: {}` with view.number_format verbatim, so whatever a user types round-trips into the file unchanged and is re-parsed the same wrong way on every load.\n- The trailing-f spelling is the Python/printf convention a user would most naturally reach for.\n\nSo a document whose author wrote the spelling our own documentation shows renders every number with zero decimals, and nothing reports it.\n\n## Steps to Reproduce\nSet a view's number_format to \",.2f\" and render any non-integer value. Expect 1.00; observe 1.\n\n## Acceptance Criteria\n- Decide which spellings are legal and say so in ONE place. The grammar is the source of truth for .improv, so if \",.2f\" is legal the parser must honour it; if it is not, loading must refuse it rather than silently reinterpreting it.\n- A test with the \",.2f\" spelling asserting the decimals actually applied. It must fail on today's code.\n- Whichever way it goes, the repo-map example and the format doc must agree with the parser — this bug is partly a documentation bug, and a fact stated twice is changed by grep.\n- Consider the neighbouring spellings while you are there: \".2\", \",.2\", \"0.00\", \"%.2f\".\n\nNOT verified: whether any shipped document or fixture uses the trailing-f spelling, and whether the import wizard can produce it.\n","status":"closed","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-21T11:29:53Z","created_by":"Edward Langley","updated_at":"2026-09-21T17:56:34Z","closed_at":"2026-09-21T17:56:34Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-e5pf","title":"Command::ChangeMode(Normal) is on the wire and reduce_view folds it without the records resolution","description":"WIDENED 2026-09-21 after a sweep of resolve.rs. The fault is ONE SUBSTITUTION AT THREE DECISION POINTS, not one command on the wire, and the first of the three composes a malformed write rather than declining.\n\n wherever the host asks layout.is_records_mode(),\n the client asks state.view.mode.is_records()\n\nCommand::ChangeMode(Normal) is HOW the client reaches the bad state; the substitution is WHY the bad state does damage. The two want fixing together, and the second decides whether enriching the projection is enough.\n\n## How the client reaches the bad state\nimprovise-sihr made the TUI settle AppMode::Normal against the layout the batch ends on (App::settle_normal_mode). The wire carries Command::ChangeMode(AppMode::Normal); improvise-protocol/src/reduce.rs:195 folds it as `view.mode = mode.clone()` with no resolution. improvise_protocol::resolve — re-exported as improvise_wasm_client::resolve, and the LIVE browser resolver; keys.rs holds only BrowserKey — emits it at resolve.rs lines 369, 692, 972, 1461, 1613 and 2045.\n\nDemonstrated with a lockstep-shaped probe (resolve + ClientState::fold against reduce_key; enter records with R, open a panel, Esc):\n left: Normal (client)\n right: RecordsNormal (host)\n\n## What the bad state then breaks\nresolve.rs:685 commit() early return — host counterpart CmdContext::is_records_mode() via CommitAndAdvance. The client is meant to DECLINE a records commit it cannot compose. With the mode wrong it does not decline: it composes typed_cell_command(id, \u0026text) and a Basis claim against a records grid, whose columns are [coords.., +cat, measures.., +] (gotcha 1). That is A WRONG WRITE, strictly worse than the black hole it was written to produce.\n\nresolve.rs:708 create_column() exit mode — picks Normal where it should pick RecordsNormal, re-entering the same wrong state after every column creation. Self-sustaining.\n\nresolve.rs:744 advanced() — host counterpart commit.rs:547, `at_bottom_right \u0026\u0026 ctx.is_records_mode()` -\u003e AddRecordRow. The client returns None to decline what it cannot name; with the mode wrong it clamps and re-enters, so TAB AT THE BOTTOM-RIGHT OF A RECORDS GRID SILENTLY STOPS APPENDING ROWS. That is the records-mode Tab contract failing silently.\n\n## Why the suite cannot see any of it\nlockstep.rs is blind to the class BY CONSTRUCTION. Its records coverage (typing_at, records_pair) sets host.view_state.mode = AppMode::records_editing() by ASSIGNMENT and seeds the client with client.view = app.view_state.clone() — it starts from a state where mode and layout ALREADY AGREE, and every divergence above requires them to disagree. A guard seeded on agreement cannot detect drift: the same vacuity shape as gotcha 36's bystander already sitting on the relayed value.\n\n## Acceptance Criteria\n- lockstep DRIVES THE KEYS into records mode and back out of a minibuffer rather than hand-placing the mode. Without this, any fix is unguarded.\n- The projection carries what the client needs to settle the mode (gotcha 47: where a client cannot compose the answer, ENRICH THE PROJECTION rather than moving resolution back).\n- All three decision points answer the same question the host answers.\n- Or: a written argument that ChangeMode(Normal) should not be on the wire, plus its removal — which still leaves the substitution at the three sites.\n\nDo NOT close this by having the client re-derive records-ness from the axes: that is a second derivation, improvise-edp's disease.\n","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-21T11:04:35Z","created_by":"Edward Langley","updated_at":"2026-09-21T11:29:10Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-9c9o","title":"A geometry effect right after a mid-batch SwitchView skips the cursor check entirely","description":"Introduced by PR #78 round 2. PASSES on pre-PR main (0947fd0) and on round-1 head (bcccfde); FAILS at d9cb16c.\n\nRound 1 compared frame hashes across a view change, which threw a cursor away spuriously. Round 2 added a 'was == self.view_name()' guard, which fixes that — but it also exempts the FIRST geometry effect after a mid-batch SwitchView from the check altogether, because there is no basis for the new view yet, so the code re-bases and skips.\n\n## Steps to Reproduce\n app.apply_effects(vec![SwitchView(\"Second\"), TransposeAxes]);\nBoth preconditions hold: the switch lands, and row_count() really changes. Yet cursor_epoch does not move and the cursor stays at (2,0) in a transposed grid.\n\n## Acceptance Criteria\n- The probe above lands as a guard.\n- The basis re-bases to the new view AT THE SWITCH rather than at the next geometry effect — i.e. the basis follows active_view changes, which SwitchView being ViewOnly currently prevents. Not a one-liner.\n\nWHY THE DIRECTION MATTERS: round 1 threw away a cursor that should have been kept; round 2 keeps one that should have been thrown away. Gotcha 35's asymmetry says under-reporting is the expensive side — a stale cursor has nothing in the stream to correct it, where an extra invalidation costs one redundant projection.\n\nReachability is the same narrow class as the bug it replaced: a . -chained : line, or any future Binding::Sequence composing the two. DrillIntoCell and EnterRecordsMode hit the pattern constantly but their target view is freshly created, so it is benign there. Merged on that basis rather than blocked.","status":"closed","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-21T05:15:07Z","created_by":"Edward Langley","updated_at":"2026-09-21T17:09:50Z","closed_at":"2026-09-21T17:09:50Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"improvise-iot6","title":"render_csv builds GridLayout::new, so CSV export carries rows the view prunes","description":"FOURTH instance of the GridLayout::new versus with_frozen_records trap, and the first one in production that a user can see.\n\ncrates/improvise-io/src/persistence/export.rs:33 builds GridLayout::new(\u0026workbook.model, view), which does NOT apply prune_empty. So :export in the TUI and the CLI export path both write rows the grid does not draw.\n\n## Steps to Reproduce\nFixture: Region {North, Service, South}, Revenue set for North and South only, prune_empty = true. The view draws 2 rows (assert this as the precondition).\n\nrender_csv output:\n Region,Revenue\n North,1\n Service, \u003c- pruned on screen, exported anyway\n South,1\n\n## Acceptance Criteria\n- render_csv honours the view's prune_empty, via the same path grid_in now uses.\n- A test whose precondition asserts the view really prunes (a row that is actually empty, pruning left ON in the view). A fixture that turns pruning off, or has nothing to prune, is vacuous — that exact vacuity was demonstrated while writing the improvise-t6db property.\n- None of export.rs's three existing tests mentions pruning; that is why this shipped.\n\nPre-existing, not a regression from PR #78. Found by the adversarial review of #78 while enumerating every GridLayout::new call site: three are outside test modules, two of those are test helpers, and this is the third.","status":"closed","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-21T05:14:59Z","created_by":"Edward Langley","updated_at":"2026-09-21T11:09:27Z","closed_at":"2026-09-21T11:09:27Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
|
||||
@@ -299,6 +299,7 @@ doctrine(cfg_test_and_pub_crate_are_different_walls,'A visibility plan written f
|
||||
doctrine(cargo_tree_shows_dev_edges_by_default,'Asking whether a feature leaked a dependency into a shipped build, a plain cargo tree -p improvise-protocol --target wasm32-unknown-unknown reports serde_json PRESENT -- because the default output includes dev edges. --edges normal is what answers the question, and it reports 0. Verified both ways by the orchestrator. Same class as grep_over_git_diff_is_silently_empty_under_an_external_differ: a command that looks like it answers the question, answering a neighbouring one, in the direction that reads as a finding rather than as a pass. A feature that adds a dependency to a wasm bundle crate must be checked with --edges normal, and per-crate, because the CI wasm job checks each crate separately and feature unification cannot reach across that.').
|
||||
doctrine(an_unreachability_claim_is_only_as_wide_as_the_routes_enumerated,'PR #98 predicted the composition-time mode drift itself and declared it unreachable BECAUSE the toggle route (R) is a stranding decline in the browser. True of that route. A VIEW SWITCH reached the same drift, because records-ness is auto-detected from the axes, so a switch changes it with nothing in the batch saying \"records\" -- found by ChatGPT Codex. The ENUMERATION of routes is the part nothing checks, so an unreachability argument is evidence about the routes you thought of and about nothing else. Same shape as a_mitigation_that_names_the_wrong_file_is_not_a_mitigation. The remedy is to close the CLASS by observation rather than to patch the route.').
|
||||
doctrine(a_precondition_derived_from_its_own_expectation_guards_nothing,'A loop test whose precondition is computed from the same tuple element as the assertion cannot be relaxed one iteration at a time: relaxing the assertion relaxes the guard too, and the test then fails for the wrong reason -- which reads exactly like the bug. Assert the two as SEPARATE facts. Corollary for check_the_checker over a loop: an injection that stops the loop at iteration 1 leaves every later iteration untested, so each direction must be reached deliberately. Found by the PR #98 author in its own bidirectional test while proving the second direction.').
|
||||
doctrine(a_pipe_discards_the_exit_code_you_meant_to_read,'FOURTH occurrence in one campaign, and the last one was in a tool written TO BE the reliable answer. scripts/pr-watch once read tr exit status through a pipe. An orchestrator probe reported EXIT=0 from a checker that had exited 1, because the pipeline ended in tail. Another read PIPESTATUS in zsh, where the array is lowercase pipestatus. And scripts/pr-reviews --wait correctly exited 1 on a timeout while the invocation \`... | tail -20; echo\` reported 0 -- so a review window that had EXPIRED read as a review that had ARRIVED, which is the dangerous direction. In bash and zsh the exit status of a pipeline is the LAST command, so any \`cmd | head\`, \`| tail\`, \`| grep\` discards it. Redirect to a file and read the file, or check PIPESTATUS[0] in bash / pipestatus[1] in zsh -- and note the two shells differ, which is its own trap. A gate whose status you sampled through a pipe has told you about the pager.').
|
||||
work_done('improvise-8tk',bug_fix,'ZZ/:wq quit even when the save failed, silently discarding the model. Save now aborts the effect batch on failure; save_to_current_path/1 returns Result<String,String>; three tests in ui/effect.rs.').
|
||||
work_done('improvise-907',bug_fix,'Import wizard step machine redesign: validation_error() gate at ReviewProposals (no-category and over-limit), history-stack previous() bound to Left on every step, and Done made a real confirmation step holding the built workbook. Superseded improvise-gfq, improvise-o1c, improvise-c6j.').
|
||||
work_done('improvise-6sq',wont_do,'Merging EditOrDrill and EnterEditAtCursorCmd into one EnterEdit{mode} is not safe: they differ in WHEN the cell value is read, not in a mode parameter. Added ui/app.rs::tests::i_and_o_diverge_on_an_empty_records_grid as executable evidence.').
|
||||
|
||||
@@ -16,6 +16,10 @@ exit 0 a review against the current head exists (or --wait was 0)
|
||||
exit 1 --wait elapsed with no review against the current head
|
||||
exit 4 usage or environment error
|
||||
|
||||
DO NOT PIPE THIS INTO head/tail. `$?` is then the pager's status, not this
|
||||
script's, and a --wait that timed out reads as success. That trap has cost
|
||||
this repo four separate diagnoses. Redirect to a file and read it instead.
|
||||
|
||||
A review may target an EARLIER head, which is why the head is compared rather
|
||||
than the count: a finding against an old commit may be fixed or still live, and
|
||||
only reading it tells you which.
|
||||
|
||||
Reference in New Issue
Block a user