fix(web): the nav may not say where you are until the rows agree (#754) - #757
Merged
Conversation
`show()` set `document.title` and `aria-current="page"`, then awaited the read. For the length of the fetch every sighted signal said "you are on Balances" over Positions' rows -- a stale figure under a fresh label, which is the one claim `_session_banner` refuses to make and the balances tiles carry an as-of stamp to prevent. THE CLAIM NOW WAITS FOR THE DOM THAT BACKS IT `commitNavigation` is the single place that says "you are here" -- the tab title and `aria-current` together -- and `paint` calls it after `rebuildInto`, on all three branches. A stopped view and a refused view are outcomes of this navigation too; leaving the nav on the previous route while the content reports this one is the same mismatch wearing different clothes. AND A CLICK IS STILL ACKNOWLEDGED, WITH A WEAKER CLAIM Deferring `aria-current` removes every immediate signal from a click, which on a slow route reads as a dead link and invites a second one. `data-pending` is that signal and is deliberately NOT `aria-current`: dotted rather than solid, muted rather than foreground. A pending link that looked like the current one would restore exactly the confusion the deferral removes. THE BUSY STATE WAS ALREADY NAVIGATION-ONLY `aria-busy` is raised in exactly one place and always was -- `show`, plus `index.html`'s initial value for the first paint. Neither the 15-second poll nor an SSE repaint raises it, so hanging the visual off it needs no new flag; a redundant `is_navigating` would be a second statement of one fact. Pinned by COUNTING the assignments rather than locating one. The content dims rather than clears (clearing costs a layout jump and claims more than is true) and takes `pointer-events: none`, because acting on a figure about to be replaced is the failure mode of a dimmed view left live. The bar is indeterminate: the server sends one response, not a stream, and a percentage would be a claim about something nothing counts. TWO VACUOUS TESTS, BOTH CAUGHT BY MUTATION The first cut of the show() assertion failed against a CORRECT implementation, because the new comment explains that it no longer sets `aria-current` and a raw substring scan cannot tell an explanation from an assignment. A test a docstring can break is one a docstring can satisfy; both scans now strip comments first. Worse, the CSS assertion was `"aria-busy" in _CSS`, which SURVIVED renaming the selector to `#content[data-nope="true"]` -- the comment above the rule still said the word. It asserts the selector against comment-stripped CSS now. Four mutants killed: commit-before-swap, the renamed busy selector, the dropped pending rule, and the removed keyframes. Reduced motion needed no new rule: the blanket query written before there was any animation to honour covers `keel-busy`, flattening it to a static bar rather than removing the signal. That comment is updated to say so, having claimed there was no animation in the file. 769 web + thinness tests pass. What no test here can prove is unchanged and listed in the module docstring: whether the dim is perceptible, whether a pending link reads as pending, whether any browser honours the query. Hand checks against a running `keel serve`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZZxmspQXe5qJ9FAsG13s6
…ed the view Review of this PR's own diff. Two defects, and the first meant half the feature did not exist. THE BAR NEVER DREW `#content[aria-busy="true"] #view::before` asks for a `#view` INSIDE `#content`. The shell nests it the other way -- `main#view > div#content` -- so the rule matched no document this client ever produces. The dim worked; the bar was inert from the first commit. `test_the_stylesheet_gives_the_busy_state_a_visible_form` stayed green throughout, because a selector's TEXT being present says nothing about whether it selects anything, and CSS has no other way to fail. The new test reads the nesting out of `index.html` and compares the rule against it, rather than against the stylesheet's own claim. `:has(> ...)` now, verified by re-running the original selector as a mutant. A RENDERER THAT THREW WOULD HAVE LEFT THE VIEW DEAD `paint` had no `try`/`finally`. `read` resolves on a transport failure rather than rejecting, but `mount` is a renderer and renderers throw; `paint` is called as `void paint(...)`, so nothing catches it. Before this branch a stranded `aria-busy` was harmless -- nothing styled it. As of this branch it carries `opacity: 0.55` AND `pointer-events: none`, so the same throw would leave the view dimmed with every control inside it dead: sort headers, status tabs, the timeline chip. The stale-but-usable view this change set out to improve on was strictly better than that. The flag now comes down in a `finally`. `commitNavigation` deliberately stays INSIDE the try: a render that threw is not an arrival, and claiming one would put the nav label back out of step with the rows, which is the whole bug. Nothing awaits between the `route !== current` guard and the block, so `current` cannot move under it and the flag lowered is always this route's own. AND CLEARING THE MARKER IS NOT THE SAME EVENT AS ARRIVING `commitNavigation` owned both, so a failed render left the clicked link marked pending for the life of the page. `clearPending` is separate and runs in the `finally`: the read being over is true on every path; arrival is not. `pointer-events: none` is KEPT, now that the flag is guaranteed to come down. Acting on a figure about to be replaced is the failure mode of a dimmed view left live, and that argument only holds while the dim is guaranteed to end. 6,322 passed / 3 skipped, ruff clean. Three mutants killed, one of them the shipped bug itself: the inside-out selector, the finally removed, and arrival claimed in the finally. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZZxmspQXe5qJ9FAsG13s6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #754.
show()setdocument.titleandaria-current="page", then awaited the read. For the length of the fetch every sighted signal said "you are on Balances" over Positions' rows — a stale figure under a fresh label, the one claim_session_bannerrefuses to make and the balances tiles carry an as-of stamp to prevent.The claim waits for the DOM that backs it
commitNavigationis now the single place that says "you are here" — tab title andaria-currenttogether — andpaintcalls it afterrebuildInto, on all three branches. A stopped view and a refused view are outcomes of this navigation too; leaving the nav on the previous route while the content reports this one is the same mismatch in different clothes.A click is still acknowledged, with a weaker claim
Deferring
aria-currentremoves every immediate signal from a click, which on a slow route reads as a dead link and invites a second one.data-pendingis that signal, and it is deliberately notaria-current: dotted rather than solid, muted rather than foreground. A pending link that looked like the current one would restore exactly the confusion the deferral removes.No
is_navigatingflag, because one already existsaria-busyis raised in exactly one place and always was —show, plusindex.html's initial value for the first paint. Neither the 15-second poll nor an SSE repaint raises it, so the visual hangs off it directly; a second flag would be a redundant statement of one fact. Pinned by counting the assignments rather than locating one.Content dims rather than clears — clearing costs a layout jump and claims more than is true — and takes
pointer-events: none, because acting on a figure about to be replaced is the failure mode of a dimmed view left live. The bar is indeterminate: the server sends one response, not a stream, and a percentage would be a claim about something nothing counts.Two of my own tests were vacuous, and mutation is what found them
The first
show()assertion failed against a correct implementation — the new comment explains that it no longer setsaria-current, and a raw substring scan cannot tell an explanation from an assignment. A test a docstring can break is one a docstring can satisfy.Worse: the CSS assertion was
"aria-busy" in _CSS, which survived renaming the selector to#content[data-nope="true"], because the comment above the rule still said the word. Both scans now strip comments first and assert the selector.Four mutants killed — commit-before-swap, renamed busy selector, dropped pending rule, removed keyframes.
Reduced motion needed no new rule
The blanket query — written before there was any animation to honour — already covers
keel-busy, flattening it to a static bar rather than removing the signal. Its comment claimed the file had no animation; updated to say what it now does.Checks
769 passed (
tests/web/+test_console_thinness.py), ruff clean.Unchanged and listed in the module docstring: no test here proves the dim is perceptible, that a pending link reads as pending, or that any browser honours the query. Those are hand checks against a running
keel serve.🤖 Generated with Claude Code
https://claude.ai/code/session_01KZZxmspQXe5qJ9FAsG13s6