398caf64624b45b88aa7b45d94960b2c11d861ce
1553 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
907a3aa4c7 |
fix(command): two P1s in the key path, and both were about silence
improvise-55l5 (P1): `KeymapSet::dispatch` spelled command construction `registry.interactive(name, args, ctx).ok()?`, so `None` carried two facts — *no binding matched* and *a binding matched but would not build* — and `App::dispatch_key` answers `None` with `Dirty::NOTHING` and no status. The class that reaches a user is `no cell at cursor`, returned by six registrations that read `ctx.cell_key()`: `clear-cell`, `yank-cell`, `paste-cell`, `drill-into-cell`, `commit-cell-edit` and `commit-and-advance-right`. It is state-dependent, so nothing checkable at registration time can see it. Every other constructor error is a keymap-authoring mistake that `every_bound_command_name_is_registered` already refuses at test time (gotcha 15), so surfacing them is free rather than the point. `None` now means no binding matched and nothing else; a construction failure becomes a status naming the binding. `an_unbound_key_stays_silent` is the control, and the rival that reports a failed *lookup* fails it. improvise-o9uq (P1, silent data corruption): `o` in records mode is `[add-record-row, enter-edit-at-cursor]`, and `AddRecordRow::execute` never placed the cursor. Appending a row changes `view_frame`'s hash, so the frame observation inside `apply_effects` invalidated the cursor to the origin mid-batch (gotcha 24) and the editor opened pre-filled with row 0's category coordinate; Enter then went through `rename_record_coordinate`, which by gotcha 1 moves every measure cell of that row. Demonstrated through `App::handle_key`: `East`'s Revenue cell became part of a record named `EastNorth`. The command now names the row it lands on, and the ORDER is the repair — the injected rival is the swap, not the omission, because `viewport_effects` before the `SetCell` is the bug verbatim. The arm where the record already exists stops claiming it added one; it is not made a refusal, because `CommitAndAdvance`'s Tab path seeds the same empty-coordinate row. Reasoning in `context/code/command.md`; evidence, injections and the two things this deliberately does not close in `context/test-evidence/command.md`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
260f6e0b48 |
test: the page-menu spec reads the demo under the decimal default
`harness/tests/pages.spec.js` arrived on `main` with #123 while this branch was open, and asserts `toHaveText("20,300")` against `web/demo.improv`. The merge was clean and the combination was wrong — design-principles §7's third case exactly: `git merge-tree` answers "can git combine these texts", not "does the combination do what both sides meant". CI caught it on the merge ref, which is the only place the two sides had ever been run together. The assertion is unchanged in what it discriminates: it exists so that a menu moving only the label fails, and `"20,300.00"` separates Q4 from Q1's `"18,400.00"` exactly as the old literal did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
faa2f28f29 | Merge remote-tracking branch 'gh/main' into fix/e0ex-decimal-default | ||
|
|
2329dc6204 |
fix: resolve a cell's format through the formula that owns it
Both findings from ChatGPT Codex's review of PR #125. Both are real, and both are the same mistake in two places: answering "whose format is this" from something other than the rule that answers "whose value is this". **1. `FormatPlan` indexed formats in a `HashMap` keyed by target.** A target category need not be `_Measure` — `[CustomCat]` is in the format's own documentation — so a key can carry coordinates two formulas claim, and iterating the map answered from whichever category hashed first: the displayed format was nondeterministic per process while the value was not. That is gotcha 18's shape. Worse, only *formatted* formulas were indexed, so a later formula's `FORMAT` reached a cell whose value an earlier unformatted formula produces. `FormatPlan` now holds every formula's `(target_category, target, Option<format>)` in the model's own order and takes the first coordinate match — `Model::formula_for`'s rule and order exactly. A match naming no format falls through to the view; it is a match, not a miss. The list is dropped entirely when no formula names a format, so the common document still pays one `is_empty` per cell. **2. A column total took its format from row 0.** `context/code/core.md` argued that was safe because "where `_Measure` is on the row axis a column is not a measure at all". Wrong: a formula's target category is any category, so a formula can own a row-axis item — the total then inherited a row-local override for a row it had summed away, and re-ordering the rows changed its spelling. New `GridLayout::column_key(col)` gives the coordinates that survive the row aggregation (page/filter constraints plus the column's tuple), and `col_formats` is resolved from that, once per column, outside the row loop. Three regression tests, each with the rejected version injected as the fault: - `the_formula_that_owns_a_cell_names_its_format_even_when_it_names_none` puts the same two formulas in both orders, so preferring the formatted one fails one half and ignoring formats fails the other. - `the_owner_is_the_one_model_order_names_and_not_the_one_a_hash_does` rebuilds the plan five times, since each `HashMap` gets its own `RandomState`. - `a_column_total_does_not_inherit_a_row_local_format` builds one grid with the owned row first and one with it last and requires the strips to agree. Every raw cell holds the same value per measure, so the totals are numerically identical and only the format can differ. `context/code/core.md`'s wrong paragraph is replaced rather than amended, and says what it got wrong. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
7470b61476 |
Merge pull request #123 from fiddlerwoaroof/ui/page-menu
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
Page bar: a menu with a down-arrow, 'Page' not 'Pag', and `[`/`]` on a document layer |
||
|
|
e8f66e6665 |
Merge pull request #124 from fiddlerwoaroof/fix/panel-bugs
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
fix: the formula panel's byte truncation panics on a Unicode formula (improvise-8l0c) |
||
|
|
9156b48f12 |
fix: the page menu must not claim a modified key (ChatGPT Codex, PR #123)
The menu's early return sat ABOVE `app.js`'s `metaKey` guard, so with the menu open `Cmd-R` reached `menuKey`, was answered `ignore`, and was then defaulted away by `if (!e.ctrlKey && !e.altKey) e.preventDefault()` — true for Meta. Reload was blocked. `Ctrl-j`/`Alt-Home` had the mirror fault and MOVED the menu, two lines below a comment saying Ctrl and Alt stay the browser's. Ordering plus one bail: the `metaKey` guard goes back above the menu branch, `pageMenuKeydown` returns on `ctrlKey || altKey`, and what remains claims unconditionally — every unmodified key, `ignore` included, which is the rule the menu exists to keep. The harness assertion installs its `defaultPrevented` probe AFTER load so it runs second and sees what the page decided, and ends on an unmodified `j` asserting `["j", true]` — without which "nothing was prevented" is satisfied by a page that claims nothing. Proved by restoring the reviewed code: it fails on `Control+j` moving the active row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
d00cea2477 |
fix: show decimals by default, and let a formula name its own format
improvise-e0ex. `Margin = Profit / Revenue` drew as `1`: `View::new` seeded
`,.0` — zero decimals — so a 0.66 ratio rounded half-away-from-zero. The cause
is the format, not the arithmetic; exact decimal math would not change the
rendering by one character, so nothing here touches the evaluator and gotcha 8
is unchanged.
Two halves, per the user's ruling:
* `View::new` seeds `,.2`. Revenue draws `18,400.00` and Margin `0.66`. The
noise on whole numbers is the cheaper error — a currency column with two
redundant zeroes is legible, a ratio with none is wrong — and a format cannot
be guessed from a value.
* A formula may carry its own format, which wins for its target; a raw measure
keeps the view's. `formula.pest` gains
`format_clause = ${ ^"FORMAT" ~ sp+ ~ format_spec }`, with `sp+` for `WHERE`'s
reason (a predicate is invisible to the grammar-walking generator) and
`FORMAT` added to `quote::KEYWORDS`.
`improv.pest` is deliberately unchanged: `formula_raw` already carries the
`## Formulas` line verbatim, so the format rides in the formula text with
nothing to add — and the generator, which reads `improv.pest`, sees no new
spelling to violate.
Resolution has one home. `FormatPlan::of(model, view)` replaces the
`(comma, decimals)` pair every render path threaded — `GridLayout::display_text`,
`viewport::grid_body`/`compute_col_widths`, `project::grid_in` and
`persistence/export.rs::render_csv` — rather than being added beside it, which
would be two answers to "what format is this cell". It is asked of the cell's
`CellKey`, so a cell aggregating over `_Measure` keeps the view's format by
construction. `GridBody::col_formats` carries the answer to the totals strip,
which is formatted on its own path.
Persistence is untouched except for metadata: `format_f64` is display,
`persistence/format.rs::format_number` writes round-trip precision, and the two
have no call between them. Newly created views write `format: ,.2`; existing
documents are unaffected, because `format_md` has always written the line
explicitly. `web/demo.improv` moves to `,.2` on both views.
The trap this sets is gotcha 34's: `format_md` writes `Formula::raw`, so a
`FORMAT` suffix round-trips whether or not the walker parsed it. The round-trip
test therefore asserts the parsed field — injecting a walker that drops the
clause fails that one test and nothing else in `improvise-io`, 242 tests
including the whole characterization suite and the grammar properties.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
|
||
|
|
364052b7cc |
docs: mode_style spells thirteen variants, not fourteen
`context/code/tui.md` claimed `mode_style`'s default-chrome arm spells out fourteen `AppMode` variants. It spells thirteen — Normal, RecordsNormal, FormulaEdit, FormulaPanel, CategoryPanel, CategoryAdd, ItemAdd, ViewPanel, ImportWizard, ExportPrompt, SavePrompt, Help, Quit — counted off `src/draw.rs` rather than taken on report. The match itself is sound and exhaustive; only the sentence about it was wrong. The arithmetic that makes the corrected count checkable now sits beside it: 13 + Editing/RecordsEditing + CommandMode + TileSelect = 17, which is `AppMode`'s variant count. Nothing in this repository checks a count written in prose, which is exactly why it survived — the same failure mode gotcha 38's derivation-count correction and gotcha 44's stale gap list record. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
0a920f1d54 |
fix: declare click_page_item on the wasm-surface ratchet
`web/tests/bundle/wasm_surface.test.js` pins the "handful of calls" the page reaches the client through, and its own comment says a new one has to come and say so. This is that. The reason it belongs on the list rather than above the seam: the page holds no `Basis`, so `SetPageSelection` cannot be composed there. It needs a built `web/pkg`, so it lives in `web/tests/bundle/` and is outside the `web/tests/*.test.js` glob the brief's gate names — it only ran in CI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
dd1194ef45 |
fix: one display-width-safe truncator, and the two panels that measured in bytes (improvise-8l0c)
`src/ui/formula_panel.rs` cut a formula by bytes and subtracted 1 from an unchecked width, so `|é| = 1` in a panel of outer width 7 panicked on a char boundary and an inner width of 0 panicked on the subtraction. Formula identifiers are pipe-quoted and may carry any character, so an ordinary resize crashed the TUI. The grid's existing width-measured `truncate` moves to `src/ui/text.rs` and the formula panel calls it, rather than becoming a third copy. The wizard's byte-measured one stays as `context/code/tui.md` argues. `prefix_by_width` replaces the old `chars().take` tail, which over-ran by a cell for a wide character at width 1. Searching for the class rather than the line found two siblings: the category and view panels placed a second string at `inner.x + name.len()`, a byte count used as a column. Neither panics, so the symptom is a gap that widens with every non-ASCII character in the name. Two rival fixes were injected and watched fail: a char-count truncator (passes both panic tests, over-runs the width on `日本語`), and repairing only the subtraction (still panics on the char boundary, and at width 1). Evidence: context/test-evidence/tui.md Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
a913390322 |
feat: the page bar becomes a control, and [/] reach the modes about the document
Three tickets on one subject: paging.
improvise-rw6l — clicking a page chip opens a menu of that axis's items, with
a down-arrow saying so. Almost nothing had to be derived: `PageAxis::items` was
already projected and `ModelCommand::SetPageSelection` was already on the wire.
The one gap was that `RenderPageAxis` dropped the names, keeping the position
and count improvise-p1ym needed for `[`/`]`. `items` is relayed INSIDE `cycle`
rather than beside it, so there is one predicate and not two: a Filter axis has
no cycle, therefore no arrow, no menu, no items, and composes nothing if a click
reaches it — all four off `PageAxis::cycle`, the one home of gotcha 27's split.
improvise-771e — the browser tile bar said "Quarter Pag". `short_label` is three
characters so the TUI aligns in a fixed-width terminal (gotcha 26); a browser has
no column to fit. `render.rs` asks `plain_label` instead; `short_label` is
untouched. Note what pinned the truncation was `harness/tests/drill.spec.js`,
not `tests/grid_frame_agreement.rs`, which does not mention the tile bar.
improvise-wiew — `[`/`]` move from Normal onto a new `document_keymap` between
the global and the modes whose subject IS the document (Normal, the three
panels, TileSelect; RecordsNormal through Normal). Help and the import wizard
take the global directly. Per the user's ruling: an overlay has a subject of its
own, and these are the first shared keys that MUTATE the document — under the
help overlay that is an invisible change to the grid it is covering.
Boundary crossings to flag, both outside the brief's stated write scope and
neither a wire change:
- `resolve.rs` gains `Click::PageItem` and `lib.rs` a `click_page_item`
binding. Composing the command anywhere else would put a derivation in the
page, which design-principles §5 forbids; `Command`/`ModelCommand` are
untouched.
- `improvise-command/src/command/keymap.rs` gains three tests. The two
existing guards were the mechanism that forced the wiew argument, so they
had to be answered where they live.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
|
||
|
|
246e8f4a63 |
docs(adr): nine, in all three places — finish the hex-count correction
The previous commit corrected clause 4's count from six to nine and left the migration step and the Consequences section saying six, so the ADR contradicted itself twice. Found by the third Codex review, and it is the same class as the stale count that review round caught in context/code/tui.md: when a number is stated in more than one place, changing it is a grep, not an edit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
9191718612 |
docs(adr): answer the Codex review — Indexed is a fourth arm, selection is per-surface
Three findings, all correct, all applied. 1. A contextual selection face cannot be resolved by `resolve(Face)`. Clause 3 fixed that signature while the vocabulary described one `selection` face "parameterised by the surface's accent" — the same Face cannot yield the grid's Cyan and the formula panel's Yellow. Resolved the way clause 2 already works: `selection` is a BASE face and each surface has a derived one. A context argument was rejected explicitly, because it is a second input three front ends would each have to supply correctly, which is the three-derivations hazard one level down. 2. `Indexed` could not be "a detail". The ADR defined Colour as exactly Default | Named | Rgb, promised the two existing Indexed greys would still render exactly, and said the shipped theme used only the first two. Folding an index into Rgb changes its meaning AND breaks that rule. `Indexed(u8)` is now a fourth arm, the browser resolves 16-255 through the xterm cube and ramp, and the shipped-theme rule is restated over the line that actually matters: palette-relative versus absolute. The shipped theme contains no `Rgb`. The rejected-alternatives entry now also says that dropping `Rgb` entirely is a coherent version of this ADR, since that would make the regression unrepresentable rather than defaulted-against. 3. "Build all bundles" was failing: the ADR was neither published nor a named omission. Added to SECTIONS in scripts/build-doc-site.mjs. The checked-in site/docs output is deliberately NOT regenerated here — assemble.sh builds into its own output directory, and site/ is a periodic snapshot rather than a per-commit artifact. Also corrects the hex count: nine stray values in style.css, six light and three dark, not six. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
ee0f57d227 |
Merge pull request #121 from fiddlerwoaroof/feat/oujb-web-in-release
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
feat: attach the assembled site to every release, and let the page name its build |
||
|
|
926396352f |
fix: seat the build stamp in #120's redesigned bar, and stop it faking a ticket id
Three things the merge with #120 (ui/shell-polish) actually required, none of which a clean auto-merge could have told us. MOVED, not preserved. #120 left <header>'s markup alone but gave every child explicit styling and a stated reading order -- "what this is, which document, which view, which mode, what keys, what the peer is doing". A build identifier is "what this is", so the span moves from after #status to beside <h1>. Keeping it at the tail was loyalty to my own first draft: it read as metadata trailing #status, and with #hint's `flex: 1 1 auto` fighting for the slack a 40-char commit SHA there is the thing that crowds the bar. STYLED, because unstyled is illegible-by-default in a design system. It takes `var(--dim)` -- the same token #hint and #status use, so it tracks both themes by construction rather than by a hand-picked colour -- at 0.72rem, and clamps to 14ch. Measured under Chromium on the merged tree: a tag renders whole (scrollWidth == clientWidth, 69px), a 40-char SHA truncates to a 13-character prefix with an ellipsis, and `header` does not overflow in either case (scrollWidth == clientWidth == 1280). Contrast of rgb(107,103,95) on rgb(244,241,234) is ~5.0:1, above AA for small text. Selecting the text still copies the whole SHA -- text-overflow is a rendering clamp, not a truncation. NOT AN ID. `improvise-web.tar.gz` puts the token `improvise-web` in the tree, and AGENTS.md's audit matches `improvise-[a-z0-9]{3,8}` -- so the artifact name resolved to no ticket and read exactly like a dead id, which AGENTS.md calls worse than one. `improvise-installer.sh` escapes only by being ten characters, which is luck rather than a rule, so the exclusion list gains `web` and the paragraph gains the general case. RELEASING.md (which arrived in this same merge, from improvise-gx5d) listed what a tag publishes and was made wrong by omission; it now names the web capture and says plainly that it is not what Pages is serving. Verified on the merged tree: 1631 cargo tests over 37 binaries, node --test 47 -> 50 (so #120's three new shell tests survived the merge rather than being silently dropped), 42 Playwright tests green under Chromium, and the stamp gate re-proved by injecting the missed substitution again -- a header rewrite does not carry the old fault injection. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
718f1ab2a9 |
docs(adr): thirteen, not fourteen — mode_style's default-chrome arm, counted
context/code/tui.md says fourteen variants take the default chrome. The arm lists thirteen, and 13 + Editing + RecordsEditing + CommandMode + TileSelect is the 17 AppMode variants. The ADR now says thirteen and names the stale claim, rather than inheriting it — a count in context/ is read as code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
c7f7ea49b4 |
docs(adr): ADR 004 — faces, so appearance is UI-agnostic data
Fixes a vocabulary and four constraints so incremental theming work cannot paint the repo into a corner. Proposed, not accepted: the user decides. The hard clause is the terminal palette. 123 of the app's 125 ratatui Color literals are NAMED colours, so the user's terminal theme applies to improvise for free today; a format carrying only RGB would end that silently. A colour attribute is therefore Default | Named(slot) | Rgb, the shipped theme states only the first two, and a browser resolves a slot through a 16-entry table that is where PR #120's palette meets the face system. The role vocabulary is derived from the call sites rather than designed, and two plausible readings of MinibufColor turn out to be wrong: cyan is the formula prompt and not search, and the green/yellow split does not fall on "editing vs prompting" — item-add is green while category-add is yellow. That contradiction is handed to the user as an open question rather than settled quietly, because the face names outlive the colours. Also records why the ticket's own cheapest first step does not survive the call sites: there is no three-way role split that fits the eight MinibufColor construction sites, so the honest move is a face per text-entry mode. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
1ac2a13b6c |
Merge main (#120 ui/shell-polish) into feat/oujb-web-in-release
#120 restyled the page and rewrote #backend-notice as a <details>; <header>'s markup is untouched, so #build-version lands where it was written. What the merge actually has to answer is the CSS, and the stamp gate after it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
f0687b1774 |
Merge pull request #120 from fiddlerwoaroof/ui/shell-polish
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
Browser shell polish: a dismissable notice, a raised minibuffer, and a styled page |
||
|
|
17a8ed66c6 |
Merge pull request #119 from fiddlerwoaroof/fix/release-preflight
ci: a release procedure, the preflight that enforces it, and the blind spot its own repair opened |
||
|
|
de24c65119 |
fix: the build stamp's check must not depend on a title attribute
Self-review of the previous commit. The assemble-stamp pattern spelled the whole opening tag, including the title text, so a purely cosmetic edit on ui/shell-polish -- which is rewriting this header right now -- would have failed the assembly with a message about a missing cache-bust stamp. `id` moves last in the tag and the pattern is `id="build-version">TOKEN<`, which still verifies the token is inside that element and nothing else. Re-proved discriminating by injecting the missed substitution: exit 1 with `assemble: refusing to ship; unstamped: .../app/index.html`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
7d12ada45d |
docs: strip the tag inventory; a verified fact still has a shelf life
v0.1.0-rc3 no longer exists anywhere -- not local, not gh, not origin, not gitea. Verified here independently before changing anything; the releases are untouched (rc1, rc2, and there never was an rc3 release). THREE MEASUREMENTS OF ONE FACT IN ONE SESSION, and only the first was careless: 1. present on all three remotes and locally (from aa4e901's commit message) 2. on gh alone (git ls-remote, this branch) 3. nowhere at all (git ls-remote, now) Reading 1 was asserted rather than asked, and correcting it was right. Readings 2 and 3 were each taken correctly, by two different people, and they disagree -- because the tag was deleted in between. So the lesson this section reached for was too weak. It is not "ask rather than assert"; it is that A REMOTE-SHAPED FACT HAS A SHELF LIFE EVEN AFTER YOU ASK. A verified answer decays, and prose cannot distinguish a stale line from a live one, so a reader following RELEASING.md at 2am has no way to tell which they are holding. WHAT CHANGES. The present-tense inventory goes, everywhere I put it: RELEASING.md's "It is on gh only; origin and gitea carry rc1 and rc2", the same clause's "the mirrors did not agree", tooling.md's "rc3 is on gh alone", and "pushed to gh" in both tooling.md and the script header -- that last one named a specific remote too, and we do not actually know which remotes the push reached. rc3 survives as a plainly historical example: it was pushed while every crate still said 0.1.0-rc2, dist refused, no release was built. Nothing about where it is now. WHAT STAYS. The instruction to ask -- `git tag -l`, `git ls-remote --tags <remote>` -- because that is the part that ages well: it tells the reader to look rather than to believe. And the record that aa4e901's claim was wrong when checked, which is durable precisely because its subject is a fixed text. Note which half of the earlier correction survived and which did not: "the commit message was wrong" is permanent; "the tag is on gh alone" was true and is now false. The keeper is the one whose subject cannot change. Both files now say explicitly that they record no inventory, so the next editor knows the absence is deliberate rather than an omission to helpfully fill in. The principle never decayed and is untouched: a wrong tag is left dead and the next version cut; a published tag is not moved. No version bumped, no tag cut, no tag deleted by me. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
90ccfb0154 |
feat: attach the assembled site to every release, and let the page name its build
improvise-oujb. A tagged release carried four native tarballs and nothing for the web; the site shipped only by ci.yml's pages-deploy, on a push to main. Pages keeps tracking main (user, 2026-09-22: "I like Pages showing the latest main for now"), so this is a CAPTURE, not a second deployment: the tarball is a self-hostable copy of the site as of the tag and a rollback target, not what a visitor to the published site is running. pages-deploy is untouched. Mechanism is dist's own [[dist.extra-artifacts]], so it survives `dist init`: `dist plan` under the pinned 0.30.4 lists improvise-web.tar.gz beside the four platform tarballs and `dist generate` leaves release.yml byte-identical. The build command is scripts/build-web-release.sh, which calls web/build.sh and web/assemble.sh -- the same two scripts ci.yml and a developer run, so there is no second way to build the bundle. Its toolchain pins come from scripts/web-toolchain-versions.env, which ci.yml's build job now reads too. `github-build-setup` was measured and does NOT help: it injects into build-local-artifacts only, which is the wrong job for a global artifact. The page now names the build it was assembled from in #build-version, stamped by web/assemble.sh with the same token as the bundles and verified by the same scripts/assemble-stamp.sh gate. An unassembled tree honestly reads "unstamped". Verified by running `dist build --artifacts=global` locally: the script ran, the site assembled, and dist placed improvise-web.tar.gz in target/distrib. Not verified: a real tag push, which would require cutting a tag. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
0930e9e360 |
Merge remote-tracking branch 'gh/main' into ui/shell-polish
# Conflicts: # context/test-evidence/clients.md |
||
|
|
52f615d14d |
fix(web): an error cell must not lose its colour to the dimming or the selection
Found by reviewing the restyling before pushing it, and demonstrated before it was fixed. `td.error` and `td:not(.editable)` used to set DIFFERENT properties — colour and opacity — so both applied and a dimmed error cell was a dimmed red. The restyling made the dimming a colour too, at the same specificity and below `td.error` in the file, so an error in an aggregate cell rendered plain grey: rgb(107, 103, 95) where the error colour is rgb(168, 50, 45). `td.selected` and `td.typing` did the same by setting `color: var(--fg)`. `#grid td.error` is the only place on this page where colour carries MEANING rather than emphasis, and it is the one a reader takes at face value: a circular reference or a `mixed` aggregate reading as an ordinary number is worse than it reading as nothing. So the dimming moves ABOVE the semantic colours, and a selection is a ring and a tint and never a colour. That is the class of defect a stylesheet has and a program does not — two rules that were independent become one property, and nothing in any language fails. Also: an expanded notice now caps at 40vh and scrolls. `main` is free to shrink to nothing, so on a short viewport opening it could have pushed the grid out entirely. The test is aimed at the STYLESHEET rather than at a document, because the question is which rule wins and a fixture per combination would be three documents testing three documents. It appends a <td> carrying each combination to the real page's real table and compares two controls taken the same way, rather than pinning absolute colours. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
d8eca0f0af |
docs: a shared failure signature is not a shared cause (my own overreach)
The paragraph added two commits ago said this version/tag disagreement "has been hit three times across two releases". I did not check that. What the tag history actually supports is narrower, and the gap is the same asserted-not-asked error the paragraphs on either side of it exist to correct -- which is why this is a commit rather than a quiet edit. WHAT IS VERIFIED. All three failed tag pushes carry the identical signature: plan failure with build-local-artifacts, build-global-artifacts, host and announce all skipped. Checked per job through `gh run view --json jobs` on each of the three runs, not inferred from the 14s/21s durations, which was how the claim got made. WHAT IS NOT. Only rc3's CAUSE was reproduced, by running dist plan against the tag. rc1's logs aged out at 90 days and cannot be read now, and its two failures sit on commits titled "feat: cargo dist info" and "chore: update version" -- initial setup, where plan could as easily have failed on an incomplete dist config as on a version/tag mismatch. A shared signature is evidence of a shared shape, never of a shared cause. So the honest claim is: this failure SHAPE has cost four pushes across two releases, and the one time anyone diagnosed it, it was the version/tag disagreement. That is ample reason for RELEASING.md and for a check before the irreversible step. It is not a claim that the same mistake was made three times, and the paragraph now says which half is which and names its own first draft as the error. Also dropped: an attribution of the earlier failures to a particular person, which the run list does not establish and which the argument does not need. No version bumped, no tag cut, no tag deleted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
b5a4d227fe |
docs: rc3 is on gh alone, not "all three remotes"
FOUND BY RUNNING THE COMMAND THE CLAIM IMPLIED. `git ls-remote --tags` against each of gh, origin and gitea: v0.1.0-rc3 exists on gh ONLY. origin and gitea carry v0.1.0-rc1 and v0.1.0-rc2 and nothing else. The claim originates in aa4e901's commit message and was repeated from there into context/code/tooling.md and, an hour ago, into RELEASING.md by me -- three copies of a fact nobody had asked a remote about. That commit message is superseded on this point and cannot be corrected, which is the case context/design-principles.md section 6 makes for not letting an argument live only in one; the correction is therefore recorded in tooling.md, naming the commit. The irony is local. The false claim sat two paragraphs from the sentence "an empty log is a statement about RETENTION, not about history", which is the same failure in the same section: a fact about a remote asserted instead of asked. Writing that sentence did not stop me repeating the neighbouring instance of it, which is the more useful half of this commit. What it changes and what it does not. The rule is untouched -- a published tag is not moved, and gh is the remote carrying the release machinery, so the preflight's reason for existing is unaffected. What moves is the SCOPE of any cleanup, from three remotes to one, and that is precisely the sort of detail a reader following RELEASING.md at 2am would act on. RELEASING.md now says which remote and tells the reader to run ls-remote rather than assume the mirrors agree -- they did not. No version bumped, no tag cut, no tag deleted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
8ad5c0806d |
feat(web): dismissable notice, a raised minibuffer, and a styled shell
Three things the user reported after looking at the deployed page, in the order they cost a visitor something. The deployment notice was ten dense lines of configuration reference — ?doc=, ?seed=, ?ws=, wss:// and reverse proxies — above the tile bar, the category tree and the grid on every visit: about 230px of an 820px window. It is now a <details>, so the collapsing is the BROWSER's (keyboard operable, and correct with app.js absent or still loading) and the only thing the page adds is remembering the answer. It starts COLLAPSED, and collapsing withholds nothing: the one fact a first visitor needs — the document lives in this tab, is saved to this browser's private storage and never leaves the machine — is the <summary>, which never collapses. What a stored value means is a decision and lives in shell.js; touching localStorage is a browser API and stays in app.js, wrapped, with every value that is not one of the two spellings read as "unanswered". The minibuffer was a 14px line in the page's bottom-left corner, up to 800px from the cell being typed into. It is now a raised bar over the grid. The CONTENT is untouched — RenderMinibuffer is built from `state.view.mode.minibuffer()`, the same semantic value the TUI draws — so this is position, weight and contrast, and there is no second derivation of what a prompt says. MinibufColor still resolves here exactly as draw.rs::minibuf_fg resolves it for the terminal. style.css stops being "legibility only": layered surfaces, one accent, a filled badge for the mode, chips for the two axis strips, one selection treatment, tabular numerals in the grid, and a focus ring on the one keyboard surface the page has. The palette is the landing page's, so the two read as one product. Asserted (harness/tests/contract.spec.js, Chromium): the notice starts collapsed and remembers, in BOTH directions across a reload; it opens from the keyboard, which the page's own keydown handler must not claim; the minibuffer is inside the viewport, OVERLAPS #grid — the assertion a static bottom bar cannot satisfy — and is larger than body text. Each was proved discriminating by injecting the fault and watching it fail. NOT asserted, and named as such in context/code/clients.md: colour, contrast, spacing, weight, the grouped and multi-level header DOM, either theme, and the localStorage-throws path. Those are argued and read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
50d93eb299 |
docs: a release procedure, and the overreach the tag history corrects
THE ROOT CAUSE WAS A MISSING DOCUMENT, NOT A MISSING CHECK. Nothing in this repo said "bump the version before you tag": release.yml is cargo-dist's autogenerated mechanism, README's Installation section describes the result, and plan.md Phase 3 carries one-time SETUP instructions from the launch plan. So the preflight added in the previous commits is the second half of the fix. RELEASING.md is the first, and it is a checklist rather than an explanation -- the reasoning stays in context/code/tooling.md section 4, because a checklist followed at 2am is not read for its argument and one carrying it does not get followed. Its own file rather than a README section because README Installation addresses someone INSTALLING and this addresses the one person PUBLISHING. THE DIAGNOSIS WAS UNDERSTATED AND IS NOW CORRECTED AT THE SOURCE. The earlier passage rested on a local `dist plan` run, which is the weaker half. v0.1.0-rc1 and v0.1.0-rc2 both carry COMPLETE published GitHub Releases from 2026-04-11 -- all four targets, .sha256 files, sha256.sum, dist-manifest.json, source.tar.gz and improvise-installer.sh. The workflow is not merely expected to work; it demonstrably ran end to end the last time a tag and the version agreed. Nearly the opposite was concluded from an empty `gh run list`: Actions logs age out at 90 days and those tags are five months old. An empty log is a statement about RETENTION, not about history -- the artefact a run published outlives the run, so ask the releases before asking the runs. AND THE MISTAKE IS RECURRENT, which is the real argument for writing a procedure down. `gh run list --workflow=release.yml --event=push` keeps the whole tag history: rc1 FAILED TWICE (14s each) before succeeding on the third push (3m25s), rc2 succeeded first time (3m9s), rc3 failed (21s). Three occurrences across two releases, all the same version/tag disagreement. A one-off justifies a note; a third justifies a document and a check before the irreversible step. The failure signature is recorded because it is cheap to read and easy to misread: `plan` fails in ~20s with the four jobs behind it skipped -- verified on the rc3 run, where plan is `failure` and build-local-artifacts, build-global-artifacts, host and announce are all `skipped` -- against ~3 minutes for a real build. The workflow ALSO triggers on pull_request, where it plans without a tag and passes in ~20s, so a green 20s run on a PR and a failed 20s run on a tag look alike at a glance and mean opposite things. Every claim above was read off `gh`, not inferred. Step 5 of RELEASING.md said "if plan succeeds and the four jobs skip" in a first draft; the run says plan FAILS, and it was corrected rather than kept as a plausible-sounding shape. No version bumped, no tag cut, no tag deleted, the rc1/rc2 releases untouched. Whether the stale rc tags are ever removed is left as the user's call and deliberately not made a step: it is not part of cutting a release, and the principle that covers it -- a published tag is not moved -- is stated. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
708d335fa7 |
ci: the preflight repair relocated its blind spot; close it
REVIEW OF THE PREVIOUS COMMIT, NOT NEW MACHINERY. The diagnosis it states was re-verified in both directions before anything here was written: `dist plan --tag=v0.1.0-rc2` gets past the tag/version check (stopping at the pinned cargo-dist-version, as expected) and `--tag=v0.1.0-rc3` reproduces the CI error verbatim, naming `--tag=v0.1.0-rc2` as the fix in its own help text. The workflow was never broken. Both claimed fault injections also reproduce exactly: the comparison replaced by `if false;`, and `expected` derived from the tag, each produce four failures and exit 1 -- exit code read unpiped, since a pager's status would mask it. WHAT THE TEST COULD NOT SEE. Disabling the dirty-tree guard outright left all six checks green and the script exiting 0. That guard is not decoration: it is what stops a tag naming a commit that was never tested, and its failure is irreversible in the same way the version mismatch is. The cause is the previous repair. Moving the negative cases to a clean throwaway checkout is what made the tag comparison reachable at all -- and it thereby removed the only circumstance in which the test could observe the tree guard. The guard whose PRECEDENCE caused the original vacuity became the one thing the suite could not see. The vacuity and its repair are not two events; the repair relocated the blind spot rather than closing it. So the new check is aimed the other way: a tag that MATCHES the version, run against a dirtied checkout, so the comparison passes and a refusal can only be the tree. Verified discriminating -- with the guard disabled it fails with exit 1, and the two faults are independent in both directions (comparison disabled leaves the tree check passing, guard disabled leaves the three tag checks passing), so the seventh check is not a restatement of the other six. Also recorded, rather than guarded: the `dist plan` second opinion can be disabled with the suite green, because every input the test feeds it has already been refused by the textual check. That follows from the arm being optional by design -- pinning it would mean installing a dist matching cargo-dist-version, the very mismatch the script tolerates on purpose. Stated so the silence is not mistaken for coverage. Two corrections of fact. The CI step lives in the `kb` job beside pr-watch-test.sh, where this repo keeps its check_the_checkers -- not in `test`; tooling.md now says so. And the awk version parser was probed rather than hardened: `version.workspace = true` yields empty, which the root path refuses loudly and the per-crate loop skips; a non-first [package] block and build metadata both parse correctly; a trailing comment on the version line mis-parses but fails CLOSED and prints the mis-parsed value in the refusal. No manifest in this workspace has one, so nothing was changed for it. No version bumped, no tag cut. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
d17ad0feb1 |
Merge pull request #117 from fiddlerwoaroof/fix/cfly-browser-view-history-key
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
Give the browser a key that reaches the view history (improvise-cfly) |
||
|
|
94a06efc94 |
Merge pull request #118 from fiddlerwoaroof/bd-updates
chore: update issues.jsonl |
||
|
|
c720565372 |
chore: update issues.jsonl
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
|
||
|
|
aa4e901bd2 |
ci: a release preflight, because the tag is already public when CI refuses
THE WORKFLOW WAS NEVER BROKEN. v0.1.0-rc3 was pushed to all three remotes while every crate still said 0.1.0-rc2; dist matches a tag against the root package version, refused with "This workspace doesn't have anything for dist to Release!", and the four jobs behind `plan` skipped. Proved in both directions locally: `dist plan --tag=v0.1.0-rc2` gets past the check and `--tag=v0.1.0-rc3` reproduces the CI error exactly. So this changes no trigger and no job graph. What it adds is a check that runs BEFORE the irreversible step. dist named the right answer in its own error text; the problem is when that answer arrives -- a tag is public and immutable by the time CI reads it, so the repair is a new version rather than an edit. scripts/release-preflight.sh refuses a dirty tree, compares the intended tag against the root package version, runs `dist plan` as a second opinion when dist is on PATH (and says so when it is not, since an absent dist must not read as a pass), and prints the tag-and-push commands only once it passes. THE TEST'S FIRST VERSION WAS VACUOUS AND THE INJECTION IS WHAT SHOWED IT. release-preflight-test.sh originally asserted only "did it refuse". Every negative case still passed with the tag comparison replaced by `if false;`, because the script refuses a DIRTY WORKING TREE first and this repo's tree is almost always dirty -- bd auto-exports .beads/issues.jsonl on nearly every command. Four of five checks were answering "is the tree dirty" while claiming to answer "does it catch a bad tag". It now runs the negative cases against a clean throwaway checkout AND asserts the refusal names the version mismatch, so a refusal for the wrong reason fails. Two rival faults were injected against the repaired version -- the comparison disabled, and `expected` derived from the tag itself -- and each produces four failures and exit 1. That is design-principles section 7's "a check that looks like the answer, answering a neighbouring question", found inside a checker written to prevent that class, within an hour of writing it. Recorded in context/code/tooling.md. No version is bumped and no tag is cut here, per the user's instruction to make the workflow work without releasing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
f34334cfca |
Merge pull request #116 from fiddlerwoaroof/docs/wave-corrections
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
docs: gotcha 38's stated reason was false, and four waves' worth of kb facts |
||
|
|
06f847bb66 |
docs: four more stale facts in repo-map, found by the h3y7 audit
An audit that had to read the code to answer a completeness question kept
tripping over the map. Each of these is a fact the map states that the tree
does not support, verified before applying:
- gotcha 3 said model_effects sends 28 ModelCommand variants. A brace-matched
count of the enum's top-level variants returns 24. Same staleness class as
the gotcha 38 arithmetic corrected in the previous commit.
- gotcha 43 named `reduce_full` twice in the present tense. It has been DELETED;
`reduce_key` is the survivor. Note the paragraph's own argument PREDICTED
this ("no production caller anywhere in the workspace, so pub(crate) would
have made it dead code") -- so the deletion vindicates the reasoning and
falsifies only the tense. Recorded that way rather than quietly renamed.
- the File Inventory's counts for ui/reduce.rs (1411 -> 1080), ui/effect.rs
(2731 -> 3034) and resolve.rs (2477 -> 2765) had drifted, two of them by
more than the 10% the inventory's own preamble warns about.
- "50+ effect types" is now 58, with the number that matters recorded beside
it: a brace-match over `impl Effect for` in effect.rs finds only 49, missing
8 simple_effect! expansions and SetTransientPrefix in command/keymap.rs. All
9 it misses are ViewOnly -- the half the ViewBack bug lived in. gotcha 38
warns that the naive awk OVER-reports; the brace-matcher it recommends
instead UNDER-reports, and nothing said so until now.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
|
||
|
|
5a3d1d1109 |
docs: gotcha 38's own arithmetic was stale in the same way (Codex P2)
The paragraph's head says 26 model effects and 24 ModelCommand variants; its tail still said "29 - SwitchView - these two = 26", which subtracts SwitchView and forgets ViewBack/ViewForward. So the corrected gotcha contradicted itself and the enum's own variant count. Verified independently before applying: a brace-matched count of ModelCommand's top-level variants in improvise-protocol/src/reduce.rs returns 24. Worth noting what this is: the correction landed in the same commit range as the sentence it contradicted, which is exactly the "one copy of an argument updated and not its siblings" failure gotcha 48 names. A paragraph about a derivation going unchecked for completeness had its own sum go unchecked. Found by ChatGPT Codex reviewing PR #116. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
6b28e58352 |
Give the browser a key that reaches the view history (improvise-cfly)
PR #115 put `Command::ViewBack`/`ViewForward` in the view half, made `reduce_view` fold all three view effects, and gave every client its own `view_back_stack`. `resolve.rs` was owned by another agent that wave, so `declined()` still refused `view-back`: the merged wave held the capability and no key, and `<` in a browser reported a feature that was fully supported. Delete the decline together with its comment, whose stated reason ("it commits a drill first, so composing it would commit another client's typing") was false by the time it shipped. `ApplyAndClearDrill` is off the wire, so a resolver cannot compose it; and no browser path stages a drill at all — `drill-into-cell` is declined and `edit_or_drill`'s `Aggregate` arm answers out loud. The divergence from the `Cmd` is confined to a state this client cannot enter. The asymmetry that IS real is a different one: `reduce_view`'s `stepped` is silent on an empty stack where `ViewNavigate` emits "No previous view" / "No forward view". `step_history` mirrors both arms, so a client that has nowhere to go hears the same sentence the TUI does. Found on the way and not predicted by the ticket: `view-forward` is bound in NO keymap, in either front end. The arm is added and tested by name, and the test asserts the absence of a binding (with `view-back`'s presence as the control) so that adding one has to come there and say so. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
d517b03dbf | Merge branch 'main' into docs/wave-corrections | ||
|
|
44b8819083 |
Merge pull request #115 from fiddlerwoaroof/wave/t-drill-wire
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
The view stack is the client's own (improvise-gd28) |
||
|
|
286c09960a |
kb: what the integration role learned that no branch could
Four doctrines from the wave q/r/s/t integration, each measured rather than
reasoned:
a_merge_prints_pop_whether_or_not_it_stashed -- git invites a stash pop on a
conflicted merge even with an empty autostash. Three occurrences, twice with
stash@{0} being another session's WIP, on a stack shared across nine worktrees.
a_missing_loose_ref_is_not_a_deleted_branch -- a watcher reading refs/heads
directly cannot tell a deleted branch from a packed one. Both happened in one
event.
rebuild_the_integration_on_the_new_base_and_diff_the_trees -- when three PRs
merged upstream mid-integration, rebuilding on the new base gave a BYTE-IDENTICAL
tree, which proves the upstream merges lost nothing rather than inferring it.
a_reported_sha_is_correct_and_short_lived -- three stale SHA tables in one
session; the remedy is a watcher, not diligence.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
|
||
|
|
df2c1fb37c | Merge branch 'main' into docs/wave-corrections | ||
|
|
fa1f572335 |
Merge remote-tracking branch 'gh/main' into wave/t-drill-wire
# Conflicts: # context/test-evidence/command.md |
||
|
|
9eb13119ae |
Merge pull request #114 from fiddlerwoaroof/wave/s-resolve-keys
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
fix: five category-panel and axis defects, four of them P1 (koh6, g3no, uej8, z336, 25bf) |
||
|
|
e3eef3c0a1 |
Merge pull request #113 from fiddlerwoaroof/wave/r-browser-page
browser: paint the page bar and the totals strip, and delegate the pointer |
||
|
|
96969c7bdc |
Merge pull request #112 from fiddlerwoaroof/wave/q-keymap
One global keymap every mode inherits from (improvise-636d, improvise-piyp) |
||
|
|
67c390687d |
fix(protocol): a refused switch is undone, not overwritten
ChatGPT Codex, PR #115, and it is this PR's own regression. Once `reduce_view` records history, the CORRECTION for a refused switch records history too: the asker folds `Missing` (back gains the view it left), then folds the correction (back gains `Missing`), while the guarded mirror holds neither. `<` afterwards lands on a view the document never had, and `forget_view` cannot help -- nothing vanished, the name was never real. Reachable rather than theoretical: no shipped client can invent a view name, but one can pick a name from its projected list that another client deletes before the frame arrives. Two changes. The mirror now folds what the client folded, refused or not -- a correction can only undo what BOTH ends hold, and guarding one end is what let them drift. And the correction is `[ViewBack, ForgetView(asked)]` rather than a `SwitchView` back, because `ViewBack` is the exact inverse of the push while a `SwitchView` pushes another frame and leaves the back stack one entry longer than it started. The residue is asserted, not hidden: the optimistic fold clears the forward stack and no correction can refill it, so a refused switch costs the redo history exactly as an accepted one does. The neighbouring test compared `active_view` alone and stayed green through all of it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
aeff017626 |
fix(panel): a singleton category's item reached the escalation anyway (Codex P1)
A ChatGPT Codex review of #114 found the cursor fix wrong for a category with exactly ONE item, and it is this PR's own argument turning against it: that is the configuration where the conditional retreat degenerates into the unconditional step-back the PR argued was worse than the bug. No sibling below, so it stepped back onto the category's own header -- one d from RemoveCategory. Reproduced on both paths first, asserting the CONSEQUENCE rather than a landing index: rebuild the post-deletion tree, seat the cursor where the command put it, and ask whether a second d composes a RemoveCategory. It did, on both. The enumeration is what makes the second fix total. CatTreeEntry has two variants and flatten_cat_tree emits a header then its items, so after deleting the item at i: below is a sibling -> the list closes up, stay; above is a sibling -> step back; NEITHER -> the category is now empty and both neighbours are Category rows, so there is no safe ADJACENT landing at all. That third case is structural, which is why the rule could not be patched in place. The landing is therefore the nearest Item row, preferring above then below -- never a header. Cost, stated rather than hidden: the cursor may cross into another category, and a held d then deletes one item per press, each with its own status line, walking upward. It never reaches RemoveCategory, which is the criterion the review asked for. Landing on the emptied category's own header was rejected even though RemoveCategory there is cheap -- an empty category has no cells, and a non-measure formula target IS an item, so no formulas. That is a harm-magnitude argument where the repo prefers the bad state unreachable. Residual, stated: if the deleted item was the only item in the whole tree there is no Item row anywhere and the cursor does not move. Every category is empty then, so no landing exists that would avoid it. Both fixtures are fenced so they cannot pass for the wrong reason. The browser one expands BOTH categories, because a first version expanded only the singleton's and the cursor fell off the shortened tree, where the delete arm answers that there is no category to delete -- green for a reason unrelated to the fix. The TUI one seats the two-item category FIRST, so the backward search (the preferred direction) finds a real landing. Injected the pre-review rule on both paths: each new test fails, and the two adjacent-case tests still pass. The singleton is the discriminating input and nothing else in the suite sees it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
8eb93628b2 |
kb: the view/server split exists so clients can differ, and that is a test
User: 'The whole point of the view / server split is that multiple clients can have different views of the model.' Recorded as the RATIONALE rather than a fact, because it is the single premise the day's four rulings follow from -- client-local view stack, ephemeral drill, client-only view switching, and identical staging. gotcha 29 already said 'the model is shared and the UI is not'; this says why, and converts the split from a fact into a test: does the proposal force two clients into the same view? Plus the two integration doctrines: an autostash instruction is not permission to pop (the stash stack is shared across six other sessions), and a capability and its key can land in different tickets. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |