Fix the nine remaining defects found in review - #62
Merged
Merged
Conversation
Each reproduced before the change and verified after, with a test that fails without its fix. Step never refreshed the scrub slider, so its range sat one entry behind the history. Live, the thumb is already at max, so End and a rightward drag emitted no input event and the newest step could not be reached at all. Run/Pause and Reset already refreshed it. assertions was the one serialized Diagram field _clearAll missed, so design tests followed the user through File > New, every template load and the restored-session Discard, then into autosave, Save as JSON, the .econ export and the share link, where cli.js --check ran them against nodes that no longer existed. _exportFilename kept only ASCII, so a Chinese or Cyrillic name became a bare ".svg" that the browser renames to "svg.svg": every export of such a diagram collided on one filename, .econ lost its type to "econ.txt", and File > Save lost the name too. Unicode letters and digits are kept now, with a "diagram" fallback so the result can never begin with a dot. The Library's separate sanitizer is aligned on the same rule. Sparkline mapped v/max, putting anything below zero off the bottom of the canvas. A register holding a net or deficit, which is most of what a register computes, drew nothing at all under a false "max: 1"; a series crossing zero drew only its positive half, which looks like real data. The scale spans zero now, positive-only series are unchanged, and the caption reports the true extremes. The properties panel had no scrub-aware read path at all. The canvas and the timeline each had one, so scrubbing showed the replayed step beside a hero card still reporting the end of the run: two numbers for one node. The hero, the live readout and the sparkline now read the previewed entry, and the amount field stops mirroring while scrubbing so a past value cannot be typed back into the model. A node labelled with a .econ head keyword broke its own connections, because dslParse dispatches on tokens[0] before scanning for an arrow. Seventeen of the nineteen heads threw on reload; economy and assert matched their handlers and dropped the connection silently. Such a label serializes quoted now. Only the source side and only lowercase were ever affected, since default labels are capitalized. Trader flows were attributed backwards. The engine books what A pays under the incoming leg and what B pays under the outgoing one, so reading direction off sourceId/targetId called B's payment income, never credited either partner with what it received, and gave the trader two rows for resources it never held. Rebuilt from the real payer and payee, which the positional ins[i]/outs[i] pairing makes recoverable; both partners now account to a zero residual. Group and connection handles were painted below nodeLayer, so a node on a corner hid them while the press still resized. The hit-test precedence was never the bug and is unchanged; the handles move to an overlay above the content so the visible region and the hot region are the same again. Renaming a parameter onto an existing name overwrote the other and deleted this one. The check spans the whole shared store, params, custom variables, register labels and named state connections, since a duplicate collapses there whichever editor produced it. 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.
The nine medium and low findings left after #61. Each was reproduced before the change, verified after, and carries a test that fails without its fix.
Contained fixes
Step never refreshed the scrub slider (
js/app.js), so its range sat one entry behind the history. Live, the thumb already sits at max, so End and a rightward drag emit no input event and the newest step could not be reached at all. Run/Pause and Reset already refreshed it. Measured:max: 3against a history of 5.assertionswas the one serialized Diagram field_clearAllmissed (js/app.js), so design tests followed the user through File > New, every template load, and the restored-session Discard, then into autosave, Save as JSON, the.econexport and the share link, wherecli.js --checkwould run them against nodes that no longer exist. Three leaking paths, not the two originally reported._exportFilenamekept only ASCII (js/app-export.js), so a non-Latin name became a bare".svg"that the browser renames tosvg.svg:Every export of such a diagram collided on one filename,
.econlost its type toecon.txt, and File > Save lost the name too (9 call sites, not 5). The Library's separate sanitizer is aligned on the same rule.Charts and scrub
Sparkline mapped
v/max, putting anything below zero off the bottom of the canvas (js/charts.js). An all-negative series measured 0 rows of trace ink — the canvas held only a falsemax: 1caption. A series crossing zero drew only its positive half, which looks like real data. The scale spans zero now, positive-only series are unchanged, and the caption reports the true extremes rather than the zero-extended ones.The properties panel had no scrub-aware read path at all (
js/app-fields.js,js/charts.js,js/app.js). The canvas and the timeline each had one, so scrubbing showed the replayed step beside a hero card still reporting the end of the run: two numbers for the same node. Adding_renderProps()to_scrubTowould have changed nothing —_heroCard,_refreshResourceCountandSparkline.updateall read live model state. All three now read the previewed entry, and the amount field stops mirroring while scrubbing so a past value cannot be typed back into the model.Economy-as-code
A node labelled with a
.econhead keyword broke its own connections (js/dsl.js), becausedslParsedispatches ontokens[0]before scanning for an arrow. All 19 heads were broken: 17 threw on reload, andeconomyandassertmatched their handlers and dropped the connection silently. Such a label serializes quoted now; ordinary labels are still emitted bare. Only the source side, and only lowercase, was ever affected since default labels are capitalized.Trader flows were attributed backwards (
js/attribution.js). The engine books what A pays under the incoming leg and what B pays under the outgoing one, so reading direction offsourceId/targetIdcalled B's payment income, never credited either partner with what it received, and gave the trader two rows for resources it never held. Rebuilt from the real payer and payee, which the positionalins[i]/outs[i]pairing makes recoverable. With A paying 3 and B paying 1:-3 to Market, residual 1-3 to B,+1 from B, residual 0+1 from Market(inverted), residual 1+3 from A,-1 to A, residual 0+3 from A / -1 to BThe residual reaching zero for both partners is the proof the accounting balances.
Editor and panels
Group and connection handles painted below
nodeLayer(js/renderer.js), so a node on a corner hid them while the press still resized.elementFromPointat a group corner returnedns(a node) before andresize-handleafter.The reported cause was wrong and worth stating: "the handle hit test runs before
hitTest" is deliberate and correct — a selected item's handles must beat what they sit on, and reordering would make handles unreachable whenever a node overlapped. The defect was paint order. Note and chart handles were always fine because their layers already paint abovenodeLayer. Handles move to an overlay layer; the.conn-handles/.resize-handleswrapper classes are kept becauseapp-export.js:69strips them from exported SVGs by those selectors.Renaming a parameter onto an existing name overwrote the other and deleted this one (
js/app-props.js):{gold: 5, rate: 2}became{gold: 2}. The check now spans the whole shared store — params, custom variables, register labels and named state connections — since a duplicate collapses there whichever editor produced it. Verified a legitimate rename still works.Testing
node test/run.js→ 239 passed, 0 failed (was 237)npm run smoke→ SMOKE PASSED, no console or page errorsOne regression I caused and fixed: the full smoke run failed on the pre-existing
analysistest withtlShown: false. My new tests left the timeline drawer open, and that test togglesbtn-timelineunconditionally, so an already-open drawer made its click close it. Fixed by restoring the drawer in my own tests rather than editing the older one.Generated by Claude Code