fix(blocks): a run is drawn where the reader is standing, and a note lives on one level - #73
Merged
Conversation
…lives on one level Every path that reads App.states/App.transitions where it should read the view answers about a diagram that is not on screen, and does so silently, because the model stays perfectly consistent. Six surfaces were still doing that, and each is invisible on any machine without a block on it. The playback highlight failed as a whole rather than in part: getSimStepEdgeKeys built its key from a transition's own endpoints and updateSimCanvasHighlights looked its states up by their own ids, so from the first step a run touched a block every mark went at once while the verdict stayed correct. Both halves go through the projection now (viewEdgeKeyFor, visibleNodeIdFor), ids are resolved to drawn nodes before anything is compared, what resolves to nothing is dropped rather than guessed at, and trailUpTo's cache carries the scope. Marks reach one level in, since a box that lights up says "something in here" and nothing more. markPreviewRun writes onto elements drawPreview already built — __pvIndex and __pvEdgeD, stamped in loops it was already running — so nothing is rebuilt; the active edge is a second path rather than a class, and no travelling token goes in there. slideBlockPreview is one function with two callers now: blockPreviewKey is built from the members' positions, so Arrange, a paste, an undo, a nudge, a collision push and the JFLAP importer's spread all moved a box and left its diagram behind. pulseSimNode traces the node's own outline, so an arrival ring on a block is a rect rather than a circle sweeping half the diagram. drawnStateEl/drawnEdgeEl replace the same lookup written out per surface, and fix three more: a crossing edge selected with nothing on screen saying so, a note's anchors lighting its states and none of its edges, and the Language panel highlighting the opposite of the |Q| printed beside it. note.scope is the one thing here that was not a lookup bug. A note is written somewhere, and until it said so it was drawn at every level at once against a state that level does not draw. Absent means the top level, so no serializer changed and there is no migration; a note resolves its anchors against its own level rather than the reader's; ungrouping carries its notes to the parent while noteScopeOf is the safety net for every other way a block can vanish. And viewGraph() is cheap again: a cache hit recomputed a derived block size, falling through to two unindexed filters on the ordinary path, which a select-all over 2000 transitions measured at 617ms with eight blocks against 7ms without.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
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.
Every path that reads
App.states/App.transitionswhere it should read the view answers about a diagram that is not on screen — and it does so silently, because the model is still perfectly consistent. Six surfaces were still doing it, and each is invisible on any machine without a block on it.The playback highlight
It failed as a whole rather than in part.
getSimStepEdgeKeysbuilt its key from each transition's own endpoints andupdateSimCanvasHighlightslooked its states up by their own ids — both exactly right about the machine, and both naming things the canvas has no node for the moment a block is on it. So from the first step a run touched a block, every mark went at once — no trail, no active edge, no travelling token, no arrival pulse — while the verdict stayed correct throughout, which is what made it read as "the animations are broken".Both halves go through the projection now (
viewEdgeKeyFor,visibleNodeIdFor), and three details follow: ids are resolved to drawn nodes before anything is compared, since several states inside one block are one box; what resolves to nothing is dropped rather than guessed at; andtrailUpTo's cache carries the scope, because drilling in changes which node every step is shown by.It reaches one level in
A box that lights up says "something in here" and nothing more.
markPreviewRunwrites onto elements the preview already built —drawPreviewstamps__pvIndexand__pvEdgeDin loops it was already running — so nothing is rebuilt. The active edge is a second path rather than a class (a path per edge is a hundred elements per box), and no travelling token goes in there: at preview scale the dot would be larger than the states it travels between.Two more that moved
slideBlockPreview()is one function with two callers now.blockPreviewKeyis built from the members' positions, so every path that moved a box and ended in a full render rather than a drag frame left its diagram behind — Arrange, a paste, an undo, an arrow-key nudge, a collision push, the JFLAP importer'sspreadForBlocks.pulseSimNodeasks what shape it is landing on. A circle of radiusRgrowing 1.7× is right for a state and wrong twice over on a block.The four that were not playback
drawnStateEl/drawnEdgeElinjs/render.jsreplace the same lookup written out per surface, and fix:syncSelectionClasses, where a crossing edge sat selected with nothing on screen saying so and Delete took it anyway;highlightNoteAnchors, which lit a note's states and none of its edges; andlangHighlight/langHighlightSymbol, where hovering Q lit the handful of top-level states while the |Q| beside it counted every depth.A note lives on one level
The one thing here that was not a lookup bug. A note is written somewhere, and until
note.scopesaid so it was drawn at every level at once, positioned against a state that level does not draw — and dragging the box stranded it. Absent means the top level, so no serializer was edited and there is no migration (and deliberately not aSCHEMA_VERSIONbump —assertReadableSchemathrows on a document from the future). A note resolves its anchors against its own level, never the reader's, which is what keepspruneNoteAnchorsRemovinggiving one answer whoever asks. Ungrouping carries its notes to the parent;noteScopeOfis the safety net for every other way a block can vanish.And
viewGraph()is cheap againA cache hit recomputed a derived block size, falling through to
blockMembers()+blockChildren()— two unindexed filters — on whatinlineBlockmakes the ordinary path. This function is reached per edge label byedgeLabelsHidden(). A select-all over 2000 transitions measured 617ms with eight blocks against 7ms without.Tests
tests/sim-blocks.test.jsandtests/note-scope.test.jsare new;view-graph,block-renderandselectiongained cases. The perf fix is pinned on object identity rather than a timer, because an equal box rebuilt every call is exactly the walk being guarded against.npm test— 1830 passing, 0 failing.