Skip to content

Fix five high-severity defects found in review - #61

Merged
zntznt merged 1 commit into
mainfrom
claude/ui-ux-run-4qmy5r
Aug 28, 2026
Merged

zntznt merged 1 commit into
mainfrom
claude/ui-ux-run-4qmy5r

Conversation

@zntznt

@zntznt zntznt commented Aug 28, 2026

Copy link
Copy Markdown
Owner

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.js bound mousedown, mousemove and mouseup all 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:

node   520,440 -> 300,480   on a buttonless move   (undo stack unchanged)
pan    [60,0]  -> [-200,-150] on a buttonless move

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 _chipRow called onChange but never _commit(). A chip fires no change event, so nothing else picked it up.

after clicking the Dice chip:  model "dice"  /  saved baseline "fixed"  /  autosave "fixed"

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. _refreshResourceCount clobbered rail panel inputs

A 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.

diagram.params.rate = 7     field displayed 5 and counted up with the pool

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

_shareURL wrote the encoded diagram into the location hash, and _initDiagram reads #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.

sharer:     6 nodes -> 1 after reload, undo stack 0
recipient:  5 nodes -> 1 after reload

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 .econ round-trip

js/dsl.js skipped the colorMap for sources, so reconcile() refilled the stock as untyped grey and any outgoing colorFilter matched nothing.

10 steps before round-trip:  {"pool":10,"mine":10}
10 steps after round-trip:   {"pool":0,"mine":20}

Nothing moves at all. The round-trip that docs/ECONOMY_AS_CODE.md calls lossless was not.

The guard causing it is load-bearing: limited can 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 X only when the color is not DEFAULT_COLOR, so an absent of means the default, not the node's resourceColor. Reading it as resourceColor invents 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 its resourceColor is orange.

Files

  • js/editor.js — window-level gesture listeners, _gestureActive(), Escape releases everything
  • js/app-fields.js — _chipRow commits; _refreshResourceCount guards on _activeFeature
  • js/app-export.js — share link no longer written to the address bar
  • js/app.js — adopted hash diagrams persist to autosave, hash cleared (except embed)
  • js/dsl.js — limited-source stock color restored after the token loop
  • test/run.js, test/smoke.js — regression tests

Testing

  • node test/run.js → 237 passed, 0 failed (was 235)
  • npm run smoke → SMOKE PASSED, no console or page errors
  • Each new test fails without its fix. Notably the .econ one fails while the kitchen-sink round-trip still passes, so it is not papering over the fixture.

Generated by Claude Code

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
@zntznt
zntznt merged commit a986ab6 into main Aug 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants