Fix five high-severity defects found in review - #61
Merged
Merged
Conversation
All five reproduced before the change and verified after, each with a test that fails without its fix. 1. A gesture never ended when the button was released off the canvas. mousedown, mousemove and mouseup were all bound to the svg, so letting go over the properties panel never reached _onUp: the gesture stayed live and the next move dragged or panned with no button held. Space+drag was the worst of them, panning the whole canvas under a free cursor, and Escape cleared only three of the nine gesture fields so there was no way out. Because _changed() lives inside _onUp, the move never reached undo or autosave either. Window-level listeners now finish a gesture wherever it ends, guarded so the svg handlers never run twice for one event, and Escape releases everything and commits what moved. Measured: a node went 520,440 -> 300,480 on a buttonless move, and pan 60,0 -> -200,-150. 2. Chip rows never committed. A chip fires no change event, so switching rate mode or gate routing reached the model but not the undo stack or autosave: reload and it was back, and undo instead reverted the previous genuine edit while dropping the switch for good. 3. _refreshResourceCount wrote the selected node's live count into the first number input of whichever diagram-rail panel was open, because a rail feature borrows the panel without clearing the node selection. A parameter set to 7 displayed 5 and counted up with the pool; one edit committed the wrong number. It also reached Design tests, which the report had not mentioned. 4. Copy share link pinned the tab to that snapshot. The hash outranks autosave in _initDiagram and nothing ever cleared it, so an hour of later work vanished on reload with an empty undo stack. The same trap caught anyone opening someone else's link and then working in that tab. The link now goes only to the clipboard, and a diagram adopted from a hash is written to autosave and the hash dropped, so a reload restores what is on screen. Embed mode keeps its hash and never touches the host's autosave. 5. A limited source lost its stock color through the .econ round-trip, leaving grey, so any outgoing colorFilter matched nothing: a 10-step run moved 20 units before and none after. The guard that caused it is load-bearing, since `limited` can appear later on the line and an unlimited source has its stock zeroed afterwards, so the color is stashed and applied once the line is read. The fallback mirrors the serializer, which emits `of X` only for a non-default color: reading an absent `of` as the node's resourceColor invented a color the file never held, which the kitchen-sink round-trip caught. 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.
Five high-severity defects from a fresh multi-agent review. Each was reproduced before the change, verified after, and carries a test that fails without its fix.
1. A gesture never ended when the button was released off the canvas
js/editor.jsboundmousedown,mousemoveandmouseupall to the svg, so letting go over the properties panel never reached_onUp. The gesture stayed live and the next move dragged or panned with no button held. Escape cleared only 3 of the 9 gesture fields, so there was no way out. And since_changed()lives inside_onUp, the move never reached undo or autosave either.Measured:
Space+drag was the worst case: the whole canvas pans under a free cursor, which reads as the app being broken.
Fixed with window-level listeners that finish a gesture wherever it ends, guarded on a live gesture and on the target being outside the svg so the existing handlers never run twice for one event. Escape now releases every gesture and commits what actually moved. The minimap already bound this way (
renderer.js _bindInteraction); the editor was the outlier.Verified no regression: an in-canvas drag still lands on the exact delta with exactly one undo entry (no double-application), marquee still selects, and a click still fires an interactive node mid-run.
2. Chip rows never committed
js/app-fields.js_chipRowcalledonChangebut never_commit(). A chip fires nochangeevent, so nothing else picked it up.Reload and the switch is gone. Worse, undo afterwards reverted the previous genuine edit while silently discarding the mode switch, with no way back.
_commit()is already a documented no-op when the snapshot is unchanged, so committing here is safe.3.
_refreshResourceCountclobbered rail panel inputsA diagram-rail feature borrows the properties panel without clearing the node selection, so this kept running and wrote the node's live count into the panel's first number input.
The display is wrong immediately; one edit on that field commits the wrong number. Confirmed it also reaches Design tests (200 -> 24), which the original finding had not mentioned. Guarded on
_activeFeature; verified the node panel still tracks live afterwards.4. Copy share link pinned the tab to that snapshot
_shareURLwrote the encoded diagram into the location hash, and_initDiagramreads#d=ahead of autosave. Nothing ever cleared it, so every later reload reverted to the moment of sharing, with_resetHistory()leaving an empty undo stack and the next commit overwriting the autosave that still held the newer work.The recipient variant needs no sharing at all: anyone who opens someone else's link and then works in that tab is caught the same way.
The link now goes only to the clipboard (or the existing prompt fallback), and a diagram adopted from a hash is written to autosave with the hash dropped, so a reload restores what is on screen. Verified links still load for a fresh visitor, and embed mode keeps its hash and never writes the host page's autosave.
5. A limited source lost its stock color through the
.econround-tripjs/dsl.jsskipped the colorMap for sources, soreconcile()refilled the stock as untyped grey and any outgoingcolorFiltermatched nothing.Nothing moves at all. The round-trip that
docs/ECONOMY_AS_CODE.mdcalls lossless was not.The guard causing it is load-bearing:
limitedcan appear later on the same line, and an unlimited source has its stock zeroed after the loop. So the color is stashed and applied once the line is fully read.The fallback matters. The serializer emits
of Xonly when the color is notDEFAULT_COLOR, so an absentofmeans the default, not the node'sresourceColor. Reading it asresourceColorinvents a color the file never carried, and the existing kitchen-sink round-trip test caught exactly that: the fixture has a limited source whose stock is default grey while itsresourceColoris orange.Files
js/editor.js— window-level gesture listeners,_gestureActive(), Escape releases everythingjs/app-fields.js—_chipRowcommits;_refreshResourceCountguards on_activeFeaturejs/app-export.js— share link no longer written to the address barjs/app.js— adopted hash diagrams persist to autosave, hash cleared (except embed)js/dsl.js— limited-source stock color restored after the token looptest/run.js,test/smoke.js— regression testsTesting
node test/run.js→ 237 passed, 0 failed (was 235)npm run smoke→ SMOKE PASSED, no console or page errors.econone fails while the kitchen-sink round-trip still passes, so it is not papering over the fixture.Generated by Claude Code