Fix eleven defects from the second review - #63
Merged
Merged
Conversation
Each reproduced before the change and verified after, with a test that fails without its fix. Two are regressions in last week's own work. A mid-run save dropped the authored colour mix whenever the total happened to be unchanged. b868dcc gated both the amount and the colour map behind one amount comparison, so a balanced loop, income and spend at matching rates, recorded neither: the total still read 20 at the moment of the write and the reload rebased {orange:20} to the {orange:14, green:6} that was in transit. The sameMap check written to catch exactly that drift sat inside the guard and never ran. The two drift independently, so they are tested independently now; at-rest files still carry neither field. A sparkline built while the timeline was scrubbed started live. 642ee0d taught _updateSparklines to push the scrub index into sparklines it already knew about, but a card built during a scrub, which is what selecting another node to see its earlier value does, created one at null. The hero read the replayed value over a trace of the whole run, the same card contradicting itself. The smoke test added with that fix selects before scrubbing, so it could not have caught this. A trader paying out of a delay or a queue created resources from nothing. Their contents are mirrored by an internal queue that releases on its own schedule; takeResources drew the count down and left it intact, so the same units were handed over and released again. Measured: a delay partner grew the system total from 25 to 36 over twelve steps while its queue owed 2 against a count of 1. Nothing in transit is available to trade, so it no longer pays. Receiving is untouched. Shift+Tab was a dead end on the canvas. Forward Tab past the last node released, backward from the first computed -1, clamped to 0 and reselected forever after preventDefault had eaten the key. Both ends release now. Adding or deleting a row in the Params or Checks rail rebuilt the panel and destroyed the focused button, dropping focus out of the panel. Add lands on the new row's name box, delete on the panel's add button. The mobile timeline drawer was 150px against 173 to 195px of content, so the entire scrub row sat below the fold with between zero and three usable pixels, and neither html, body nor the drawer scrolls. It fits its content now: measured 0px visible before and 39px after at three phone sizes, with the slider hit-testable. The concept guide had no responsive rule at all, so a 230px nav that will not shrink left a 69px article column on a 320px screen, roughly one word per line with the long words running past the modal edge. Stacked below 768px: 69px becomes 299px and the overflow goes to zero. Note text wrapped only on spaces and was never clipped, so a pasted URL was one 57-character word painted 193px past the note in near-black, over whatever sat to the right. Long words break, and the text is clipped to the note as a backstop, since counting characters cannot predict wide or CJK glyphs. Exported SVG and PNG dropped the class-only styles: no rule for .n-caption at all, so a converter's recipe rendered black on the dark background at 1.09 to 1 contrast and nearly twice its intended size, and font-family reached only elements that already carried the attribute, so node labels, badges and connection pills fell back to the browser serif. The gate guide described two of the three routing modes and claimed a gate stores nothing, both untrue since All mode: there the weights are not shares, each output takes its own weight in units. The panel's percentage rows and its weight help said the same thing and are corrected with it. The Library article pointed at a File menu entry that does not exist, and on touch the whole file-controls container is hidden anyway. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SH7ouoNymubArm7VEgALHg
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.
Everything a second multi-agent review turned up. Each was reproduced before the change, verified after, and carries a test that fails without its fix. Two are regressions in last week's own work, which is the main argument for having pointed one of the review's angles at that diff.
Regressions in the recent fixes
A mid-run save dropped the authored colour mix when the total happened to be unchanged (
js/model.js:493). Commitb868dccgated bothinitialResourcesandinitialColorMapbehind one amount comparison. In a balanced loop, income and spend at matching rates, the total still reads 20 at the moment of the write, so neither field was recorded and the reload rebased the authored mix to whatever was in transit:The
sameMapcheck written to catch exactly that drift sat inside the guard and never executed. The amount and the mix drift independently, so they are tested independently now. At-rest files still carry neither field, verified byte-identical.A sparkline built while the timeline was scrubbed started live (
js/app-fields.js:105). Commit642ee0dtaught_updateSparklinesto push the scrub index into sparklines it already knew about, but a card built during a scrub, which is what selecting another node to see its earlier value does, created one atscrubIndex: null. The hero read the replayed value over a trace of the whole run. The smoke test added with that fix calls_onSelectbefore_scrubTo, so it could not have caught this.Engine correctness
A trader paying out of a delay or a queue created resources from nothing (
js/engine.js:980). Their contents are mirrored by an internal queue that releases on its own schedule;takeResourcesdrew the count down and left it intact, so the same units were handed to the partner and released again later.Nothing in transit is available to trade, so a delay or queue no longer pays on a trader leg. Receiving is untouched:
_givealready routes an arrival through the queue properly. All three now conserve exactly.Keyboard
Shift+Tab was a dead end on the canvas (
js/editor.js). Forward Tab past the last node released; backward from the first computed-1, clamped back to0and reselected the same node forever, afterpreventDefaulthad already eaten the key. Both ends release now. (The reviewer called this a WCAG 2.1.2 keyboard trap; the skeptic corrected that — forward Tab does release, so it is a focus-order defect, not a trap.)Adding or deleting a row in the Params or Checks rail dropped focus out of the panel (
js/app-props.js,js/app-analysis.js). The handler calls_renderProps(), which tears down#props-contentand destroys the button that had focus. Add now lands on the new row's name box; delete lands on the panel's add button. The pool stepper buttons already mutate in place and keep focus, so this was an omission, not a house convention.Mobile
The timeline drawer was 150px against 173-195px of content (
css/style.css:1481), so the entire scrub row sat below the fold and neitherhtml,bodynor the drawer scrolls. Measured at three phone sizes: 0px visible before, 39px after, with#tl-rangehit-testable where it previously returned nothing.The concept guide had no responsive rule at all (
css/style.css:995). A 230px nav withflex-shrink: 0left a 69px article column on a 320px screen, roughly one word per line with long words running past the modal edge. Stacked below 768px: 69px → 299px, overflow 58px → 0.Render and export
Note text wrapped only on spaces and was never clipped (
js/renderer.js:887). A pasted URL is one 57-character "word" painted 193px past the note in near-black, andnoteLayersits abovenodeLayerso it covered whatever was to the right. Long words break now, and the text is clipped to the note as a backstop, since counting characters cannot predict wide or CJK glyphs.Exported SVG and PNG dropped the class-only styles (
js/app-export.js:81). There was no rule for.n-captionat all, so a converter's recipe rendered black on the exported dark background (1.09:1 contrast) at nearly twice its intended size; andfont-familyreached only elements that already carried the attribute, so node labels, badges and connection pills fell back to the browser serif.Documentation accuracy
The gate guide described two of the three routing modes and claimed a gate stores nothing (
js/kb.js:56). Both are untrue since All mode, where the weights are not shares at all: each output takes its own weight in units, and the gate keeps what it cannot move. The panel's percentage rows (75%) and its weight help text said the same wrong thing and are corrected alongside, so the UI and the guide agree.The Library article pointed at a File menu entry that does not exist (
js/kb.js:682), and on touch.file-controlsis hidden entirely, so both routes it named were absent there.Testing
node test/run.js→ 242 passed, 0 failed (was 239)npm run smoke→ SMOKE PASSED, no console or page errors, 5 new assertionsOne error I introduced and the suite caught: the gate help-text fix referenced
node.gateModeinside_connProps, where the variable issrc. That threw aReferenceErrorand failed the smoke run; fixed and re-verified rather than worked around.Generated by Claude Code