926396352fc7d5224bb1287b2e0500c532b798b6
1386 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 |
||
|
|
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 |
||
|
|
0cfbc6f373 |
kb: browser drill stages identically, because staging and the basis check answer different questions
User ruling. An agent proposed that a browser write through with ordinary SetCell commands rather than staging, since those are already basis-checked. The substitution does not hold: the basis check is anti-staleness coordination between clients and answers whether a picture is CURRENT; staging is an editing-session abstraction for one user and answers whether they want to KEEP what they typed. Write-through would silently remove the ability to abandon a drill -- invisible to any test that asks only whether an edit lands. The convergence that makes it cheap: clause 4 is delivered, so a staged commit is a batch of SetCells taking its atomicity from the transaction that already exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
e3fc2210b8 |
kb: the doctrine this wave's four agents earned
Twelve facts across four PRs. The ones that generalise furthest: a_completeness_check_closed_over_the_omitting_artefact_is_blind -- the parity guard asked whether every RenderModel field is painted, and its domain was RenderModel, the very thing that can omit a field. It reported a clean bill for a whole wave while totals and page_coords were painted by nothing. an_escape_hatch_is_where_a_new_check_goes_vacuous -- both bugs in a new guard were in its exemptions, never its check. '&state.grid' is a substring of '&state.grid.row_frame'. a_cursor_rule_copies_between_panels_only_if_the_list_is_homogeneous -- the obvious repair for uej8 is worse than the bug. a_ticket_can_be_one_level_too_high_and_its_fix_unimplementable -- g3no's stated fix could not work on the browser side twice over. deliberate_slack_can_become_load_bearing_with_nobody_editing_it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
05a3e3f490 |
docs: gotcha 38 said ViewBack left for a reason that was false
It claimed ViewBack/ViewForward were excluded from the wire as session-local residue, like ApplyAndClearDrill and AcceptWizardModel. But view_back_stack IS in ViewState -- the portable slice -- so by the view half's own derivation rule they were always Commands and were simply MISSING. The cost was a live divergence: effect::SwitchView pushes the leaving frame while reduce_view's arm set active_view and nothing else, so no client ever had a back history. That, not the drill commit the declined() comment blames, is why view-back was dead in a browser. Found by the agent on improvise-gd28 (PR #115) while checking the derivation for COMPLETENESS -- it had only ever been checked for correctness. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
507447c48a |
docs: the arguments the two commits' doc comments point at
Every `context/code/...` pointer added by this branch now names prose that exists. protocol.md §2 carries why `ViewBack`/`ViewForward` CLOSE a gap in the view half's derivation rather than widening it, why the fold and the effect had diverged, and why a guard was written and then deleted. command.md carries why `forget_vanished_views` and `adopt_deleted_views` do not subsume each other. A pointer to prose that does not exist reads as a promise the explanation does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
2a3a69d803 |
fix(protocol): a deleted view leaves every client's history
Found reviewing the commit before this one, and the defect is that commit's own: giving clients a back stack gave them dead frames. `effect::DeleteView` moved `active_view` off the doomed view and left both stacks alone. That was already wrong for the TUI -- `<` could reach a frame naming a view the document had lost -- and invisible only because no client had a stack at all. Worse, the guard the previous commit added made it a loop. `apply_step` corrected a dead landing by folding `SwitchView(default)`, which pushes the dead view straight back; `<` then popped it, was corrected, and pushed it again forever. `ViewState::forget_view` is the one home, called by `effect::DeleteView` for the host and reached by `Command::ForgetView` for every client. Which views vanished is OBSERVED by diffing the name set across `apply_command`, for the reason `adopt_cursor_epoch` watches a counter rather than a command variant. `Sessions::apply_step` is DELETED. The pruning makes a dead frame unrepresentable, so the guard had no reachable input, and a guard whose fault cannot be injected is indistinguishable from one that always passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
614d818e75 |
test: three self-review findings on the above
The code-review skill run against this branch, and the findings are about my own tests rather than the code. 1. The formula sweep's predicate is (target_category, target), and nothing discriminated the first half: a sweep on `target` alone passed every test I had written. Two categories may legally hold a target of the same name, so removing Metric/Profit must not take _Measure/Profit. Proved by injecting `retain(|f| f.target != item_name)` and watching it fail. 2. Both `cycling_an_axis_...` tests read the status off `last()`, which a resolver emitting the status and NOT the mutation also satisfies. Both now assert the mutation is first. 3. Two things the fix leaves that nothing guards, both filed and both written down rather than left in a PR body. improvise-nwgk: doctrine(a_status_line_composed_beside_a_mutation_lies_when_the_mutation_no_ops) asks a fallible mutation to RETURN whether it acted, and remove_item still returns unit -- the reachable lie is gone, a wire RemoveItem naming nothing is not. improvise-en4s: Dirty::of(CellData) reporting formulas: true was slack over-reporting and is now load-bearing, because a delete can take a formula out of the document; narrowing that arm would strand every client's formula panel forever. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
56b3058136 |
docs(browser): the arguments for the page bar, the totals and the delegation
Self-review findings, applied.
The word `Total` is now spelled twice — `src/ui/grid.rs` and
`web/shell.js::gridPlan` — which fails design-principles §5's own test: would a
third front end write this again? Yes. It is kept local on the `indicator()`
precedent (the collapse glyph is spelled in both ends while the FACT is shared)
rather than built into a protocol string table for one word, and it is now
NAMED as debt at both the call site and in `context/code/clients.md` instead of
being left for the next reader to find.
`frameCostOf` now counts the totals strip, and that is a change to a rule
`context/code/clients.md` already states — so the section says why the strip is
reserved where the inline group rows deliberately are not: it is present
exactly once, never interleaved with data, so counting it is neither the
feedback loop nor the ratchet that argument rules out. The page bar needs no
rule at all, being outside the box `extentOf` measures.
The evidence file claimed three injections into `dispatch.test.js` that had not
been run. They have been now, and the claim is rewritten to match what actually
failed rather than what was planned — the third was `navigable: true`
unconditionally, not the `position !== ""` reading the text described.
`doctrine(an_injected_fault_must_be_the_fault_you_named)` applies to the
description as much as to the fault.
Injections run and read, all against the served copy or the source:
gridPlan takes the table row index -> both round-trip tests fail
whole() drops its empty-string guard -> both "means nothing" tests fail
navigable: true -> the Filter test alone fails
grid bound to `click` -> 4 Playwright tests fail, including
the pre-existing improvise-0x9u guard
paintPages never called -> the page bar assertion fails
the tfoot block never runs -> the totals assertion fails
page_coords deleted from render_model -> the Rust page-bar test fails
totals_strip returns Vec::new() -> the Rust totals test fails, and the
new cache-parity guard reports it
PageAxis::cycle replaced by items.is_some() -> the cycle index test fails
Two residuals named rather than discovered later: the `Total` spelling above,
and the page bar being display-only — clicking an entry does nothing, because
composing a page change from a click needs a `click_page` on the wasm surface
that does not exist.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
|
||
|
|
b4bd6bcdc4 |
test(command): the global guard names the command, not merely one
Self-review under the code-review skill, two findings against this branch. **doctrine(binding_dump_diff) had not been run**, and it is the doctrine aimed at exactly this change: a green suite is not evidence for a keymap refactor. Diffed: 84 added lookups, 0 removed, 0 CHANGED -- no binding anywhere answers differently. 40 of the 84 are not a binding change at all but the widened probe space hitting ImportWizard's catch-all on F(3)..=F(12). `:` produced zero rows, because every mode that now inherits it already bound it locally: the duplication was real and consistent, which is also why nothing in the suite could have caught the two spines drifting apart. **The fallback arm was vacuous.** `shadowing_answer` returned `None` for the six non-shadowing modes, so the test asserted only that a global key resolved to SOME plain command -- "is this key bound", where it means "does this key reach the global". It now answers with the global's own command name, read off the global rather than restated, leaving Help's `?` as the only hand-written expectation because it is the only decision. Proved by injecting the rival fix rather than only the bug: giving TileSelect `Arc::new(global_keymap())` -- its own equal-but-separate copy -- leaves this test GREEN, since every key resolves to exactly the right command. Only `every_modes_keymap_chain_ends_at_the_same_global` fails, which is the clearest statement of why that one compares by pointer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
e2417414ad |
docs: the arguments the new comments point at, and the test evidence
context/code/core.md gets the two model rules (why removing a name removes the formulas targeting it, and why a required virtual category refuses) and the axis rule (why leaving the constraining axes forgets the page selection, and why the predicate is filters_data rather than 'is it Page'). context/code/clients.md gets improvise-koh6's decision in full -- including why the state-dependence rule is about Upstream and not about outcomes -- plus the cursor rule and the shared axis sentence. Evidence entries are module-qualified where they must be: three leaf names are answered by a test in both resolve.rs and panel.rs, and one in both model/types.rs and workbook.rs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
9d70f4fcf6 |
feat(protocol): the view stack is the client's own (improvise-gd28)
Two user rulings, 2026-09-21: the state drill manipulates (the view stack) is local to the client, and a drill is an ephemeral view. The first one is what this lands; the second is scoped in the hand-back. `effect::SwitchView` pushes the view it leaves onto `view_back_stack` and clears the forward stack. `reduce_view`'s arm set `active_view` and nothing else -- so a connected client's own history was empty however many views it visited, and `view-back` had no stack to pop. Both write the same portable field, and the fold is meant to be the mirror's copy of the effect. `reduce_view` now mirrors all three effects line for line, and `ViewBack` / `ViewForward` join the view half of `Command`. They carry no basis and dirty nothing: the stack is in `ViewState`, so this is UI, not document. The case a client-local stack creates is a frame naming a view somebody else deleted. `Sessions::apply_step` guards the landing exactly as `apply_switch` guards a switch, correcting the asker alone. Also: `drill_constraint` and `drill_snapshot` get one home in `improvise-core::view`, where the addressed-drill work will share them with `DrillIntoCell` rather than freezing a second record order (gotcha 18). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
a93d668e5e |
feat(browser): paint the page bar and the totals strip, and delegate the pointer
improvise-p7lh: `GridCache::page_coords` and `GridCache::totals` were projected to every session and folded correctly by every client, and NO front end read either. The shipped front page — `web/demo.improv` is `Quarter: page, Q1` — drew a grid filtered to Q1 with nothing saying so, and `[`/`]` resolve locally off that same `page_coords`, so a user could page through quarters watching every number change with no way to tell which one they were looking at. `RenderModel` gains `page_coords` and `totals`, fed straight off `state.grid`. The Page/Filter split is asked of `PageAxis::cycle` rather than re-derived from `items.is_some()`, which disagrees with it on an empty item list (gotcha 27). The totals are RELAYED, never summed in the client: the sum runs over every row including ones a viewport scrolled past (gotcha 44). `web/tests/render_surface.test.js` gains the direction that would have caught this when improvise-n10 added the fields. The existing guard is closed under what `RenderModel` chose to expose, so a cache field that never reaches it is invisible to it; the new one asks that every `pub` field of every widget cache is read somewhere the browser runs, or is named as debt with a reason. `grid.col_widths` is the one entry, pointing at improvise-pcje. improvise-b8ot: the five clickable surfaces moved from one listener per node, reallocated inside the render loops on every repaint, to one handler per stable delegation root — the user's ruling. The operands are carried on the node as data attributes and read back by a pure `cellAction`/`indexAction` in `web/shell.js`, so the handler is a LOOKUP rather than a second derivation of which data row was clicked: `gridPlan` counts data rows because a group header is not one, and walking the table back up would disagree the moment one appears. The editor's own keydown/blur stay direct — one transient node, one at a time, and `blur` does not bubble. All four documented behaviours a naive delegation loses are preserved and each is now asserted in `harness/tests/contract.spec.js`: mousedown rather than click (asserted by dispatching mousedown ALONE), `preventDefault` and only over a cell, the conditionally skipped repaint (asserted by the editor's survival), and closing a live editor before composing the click. Gates: 1598 passed / 0 failed / 7 ignored over 37 binaries (baseline 1595); node --test 47 over 6 files (baseline 36 over 5); Playwright 47 passed / 4 skipped (baseline 43 / 4); clippy, fmt and --check-evidence clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
544cbd79f3 |
test(command): the registration guard reaches prefixes and F-keys (improvise-piyp)
`every_bound_command_name_is_registered` had two blind spots, both found by
injection rather than by reading, and both inside the region improvise-636d
works in.
Its walk matched `Some(Binding::Prefix(_)) | None => {}`, so it never
descended into a prefix sub-keymap: everything under `g`, `y` and `Z` was
unchecked in every mode, and twice over for RecordsNormal, which reaches
Normal's three through `Keymaps::prefix_keymap`. And its key space was a
second copy of `tests_support::probe_keys_pub`, identical but for the two
F-keys the public one carries, so a name bound on any function key was
invisible here while `dump_every_binding` probed it.
Two unregistered names -- one under `g`, one on `F(5)` -- left the workspace
byte-identical at 1595/0/7. The positive control separating "untested path"
from "broken test" is the same bogus name on `Char('~')`, which fails.
The walk is now `reachable_names`, recursing into `Binding::Prefix` and
terminating because `prefix_keymaps_are_flat` says the nesting is one deep.
`probe_keys` DELEGATES to `probe_keys_pub` rather than restating it, which is
the part that matters: two lists of "the keys a test probes" is the same
disease as two keymap spines, and the F-key gap was the drift between them.
`probe_keys_pub` now covers F(1)..=F(12) rather than the two that happen to be
bound, since the point is to see a binding nobody expected.
Acceptance is the two injections failing SEPARATELY -- a fix for one need not
close the other -- and both do.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
|
||
|
|
7a4461667e |
fix(panel): five category-panel and axis defects, four of them P1
improvise-koh6 (P1, data loss): `N` is advertised on the browser's own help overlay and resolved `Upstream`, which is a black hole -- the DOM was byte-identical and the user's next keystrokes landed in NORMAL, where `i` opens an editor pre-filled with the cell under the cursor. Measured: 18,400 became the text 18400on. `search-or-category-add` is now answered in `composed` in BOTH arms -- the composable one builds the two commands the browser already has, the other declines out loud. So the host-needed list stays a property of the NAME, which is what `enter_in_an_empty_view_panel_resolves_locally_rather_than_upstream` forbids making state-dependent; the OUTCOME varying with state is what `cycle-axis-at-cursor` already does two arms above. improvise-g3no (P1): `Model::remove_item` did not drop the formulas targeting the item, so a `_Measure` row synthesised from a formula (gotcha 7) was re-synthesised the moment it was deleted and both front ends reported success. `remove_category` has always swept its formulas; the item level now does too, which is the missing half of that symmetry rather than a new rule. The optimistic status both front ends compose is now true, so no ordering change is needed. improvise-uej8 (P1): deleting a category's last item left the cursor on the NEXT category's header, one `d` from a `LayoutGeometry` change that rewrites every view's axis map. Both front ends now retreat one row when the row below is not a sibling item -- never unconditionally, or holding `d` would walk backwards. The formula and view panels do not have this shape: their lists are homogeneous, so a cursor that does not move cannot escalate. Also guards the three required virtual categories at the model boundary, which is where `d` at the panel's default cursor 0 was deleting `_Dim`. improvise-z336 (P2): `View::set_axis` never touched `page_selections`, so cycling a category off Page merely HID its filter and cycling back silently reinstated it. It now forgets the selection unless the new axis still constrains -- `Axis::filters_data()`, Page OR Filter, because gotcha 27's trap is answering this with `categories_on(Page)` and dropping the selection a drill put on `Filter`. improvise-25bf (P3): `cycle-axis-at-cursor` told the user nothing where the tile route said "Product -> None". Both routes now go through one home, `viewmodel::axis_change_status`. This also answers z336's secondary defect: the stale "Filter: X = Y" persisted precisely because the key that moved the axis wrote no status over it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
11320f771a |
feat(protocol): one global keymap every mode inherits from (improvise-636d)
`?` resolved in 2 of 17 modes: bound once on Normal and reached by RecordsNormal through the single inheritance edge. It was missing from all three panels, TileSelect, SearchMode, ImportWizard and Help itself. Meanwhile `bind_panel_toggles` had two call sites and `:` was written into two spines while `?` was in one — nothing kept the spines in agreement. `global_keymap()` binds `?`, `:` and the three panel toggles, and all seventeen modes are built `Keymap::with_parent(global)`. The answer to "which modes must not inherit" is **none**: `lookup_local` is exhausted before the parent, so the eight text-entry modes and SearchMode shadow every global key with their own `AnyChar`, and ImportWizard with `Any`. Only Help writes an override, and it is a decision — `?` closes the overlay. The global binds Char keys only, because `AnyChar` is what does the shadowing; F1 therefore stays on Normal although it runs the same command. `TextEntry::with_parent` is folded into `new`, which now takes the parent every text-entry mode has. Four guards: the chain ends at one global (Arc::ptr_eq, not by value), the global is Char-only and prefix-free, no mode binds a toggle of its own, and every global key resolves in every mode and types where it must. Each was proved by injecting its fault; evidence in context/test-evidence/protocol.md records the two cases where a neighbouring test stayed green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
523d57197c |
Merge pull request #111 from fiddlerwoaroof/wave/o-gp-fit
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
fit: a poisoned affine map is unrepresentable (pv3g, P1); a budget declines a column (e8no) |
||
|
|
eebf25c262 |
fix(gp): the CLI refuses an overflowing target instead of aborting (Codex P1)
ChatGPT Codex on PR #111, correct. improvise-pv3g's claim is that a finite input no longer aborts the process, and the two LIBRARY paths honoured the refusal while examples/fit_csv.rs turned it straight back into a panic with `expect`. A user with a 1e20 column still got a panic -- at a different site than before, but still a panic, from the entrypoint a person actually runs. Demonstrated first: a temporary test drove `run_fit` over an 8-row CSV at 1e20/4e20 and aborted at fit_csv.rs:399. `run_fit` now returns `Result<(), String>` and `main` prints the refusal and exits 1. Gotcha 37(a) read the right way round: the library returns data, the example owns every message, and that includes the refusals. `fit_and_report` is the split that makes it testable without a temp file, and the example carries `test = true` so its tests run under `cargo test --workspace`. A SECOND abort on the same path, found because the first "just under the overflow" fixture used 1e18 and blew up somewhere else: at that scale the map is finite, the fit lands, and `report_importance` dies with a capacity overflow in `"#".repeat((share / 5.0).round() as usize)`. The share is `inf` because `importance_ranking` computes `100.0 * imp[j] / total` -- multiplication first, so an importance over f32::MAX/100 overflows before the division that would have returned 100.0 (measured: imp=[3.7499995e36], total the same, share=inf). The bar width is clamped and the arithmetic is filed as improvise-k844 rather than fixed here: reordering it moves the bit patterns `misclass_rate_and_importance_are_bit_for_bit_what_they_were` pins, and this PR's headline evidence is that none moved. AGENTS.md's own rule -- a finding can be correct and still be wrong to fix in that PR. Three guards, each failing under its own injection: restoring the panic, refusing every map, and removing the clamp. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH |
||
|
|
05162fa231 |
Merge pull request #110 from fiddlerwoaroof/wave/n-browser-boot
browser: a boot failure is a state with a reason, not silence (improvise-1wft + 10) |