Take the walk time off the map, and let the frame loop stop - #94
Merged
Conversation
Two of #51's done-whens that came back, caught by the #45 audit and filed as #75. The pill. drawTarget took a `label` and app.js handed it "4 min walk" at the midpoint of the dashed line. The minutes come out of walkMinutes, which multiplies the straight-line metres by DETOUR = 1.3, so the pill answered for a path 30% longer than the line it was pinned to: the drawing and the label disagreed and nothing on screen said which to believe. The option is gone from drawTarget rather than left unused, so there is no seam for it to come back through, and js/map.js now draws no text at all. The number still appears on the row and on the room screen, where nothing is drawn beside it to contradict it. The loop. render() re-requested a frame on its first line whatever was on screen; #75 counted 290 callbacks in 2 seconds on a settled list with nothing selected, all painting the same pixels. render() now returns whether anything is still moving, and createFrameLoop in js/map.js asks for the next frame only when it says so. The flyover is the only thing that keeps itself going. Everything else that changes what the map shows wakes the loop for one frame: settle, frame, reframe, showAsk, showRoom, useOrigin, the basemap arriving, boot, resize, and the pan and zoom gestures. Two of those needed care. frame() wakes before its guards, because a tap that does not move the camera still changes which footprint is lit, and it returns early after a hand pan. --bar-h is watched with a MutationObserver on the body, because the install rail changes the band the map centres in and no event announces it. Tests: the drawTarget label test becomes the case that it draws no text on either length of line, plus a source check that neither half of the seam is left. Six new tests drive the loop through a fake requestAnimationFrame and count frames: one wake on a settled draw costs one frame and 120 display frames after it cost none, a moving draw keeps going, a stopped loop restarts, three wakes inside one frame buy one frame, and a wake raised from inside the draw is not swallowed. 673 tests, 0 fail. The shell's gzip figure in sw.js is recomputed over the changed files: 85,856 bytes with gzip 1.12, against 85,719 from node's zlib at the same level. Not done: docs/media/room.webp still shows the pill, and the README alt text still describes it. Re-shooting needs Chrome for scripts/shoot.mjs, which this machine does not have. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xiX95r6nnUUmhD3KwMnWM
The comment said the loop was pumped 600 times; the loop is pumped 120, which is the two seconds of 16.7 ms display frames the same comment describes. 600 was a draft number that outlived the draft. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xiX95r6nnUUmhD3KwMnWM
This was referenced Sep 2, 2026
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.
Closes #75.
Both halves of #75, and nothing else.
1. The pill is gone
drawTargettook alabelandjs/app.jshanded it`${state.selected.walk} min walk`to paint at the midpoint of the dashed line. The minutes come out ofwalkMinutes, which multiplies the straight-line metres byDETOUR = 1.3, so the pill stated the time to walk a path 30% longer than the line it was pinned to. The drawing and the label disagreed and nothing on screen said which to believe.The
labeloption is removed fromdrawTargetrather than left unused, so there is no seam for it to come back through:js/map.jsnow calls neitherfillTextnormeasureText. The walk time still appears on the row and on the room screen, where nothing is drawn beside it to contradict it.2. The loop stops
render()re-requested a frame on its first line whatever was on screen. #75 counted 290 callbacks in 2 seconds on a settled list with nothing selected, every one painting the same pixels.render()now returns whether anything is still moving, and a newcreateFrameLoop(request, draw)injs/map.jsasks for the next frame only when it says so.requestis injected rather than read offwindow, which is what makes the loop testable under node. The flyover is the only draw that keeps itself going — and underprefers-reduced-motioneven that returns false, becausetis pinned to 0 and every frame computes the same view.Everything else that changes what the map shows wakes the loop for a single frame. The call sites, enumerated:
boot()campus.json.thenrenderpaints nothing whilestate.basemapis nullsettle()frame(r)reframe()showAsk()showRoom()frame()below it runs only for a ranked row; a room opened from a link is not oneuseOrigin()onPan/onZoomresizesurface()reallocates the backing store and the band moves with the heightMutationObserverondocument.bodystylejs/install.jswrites--bar-hwhen the install rail mounts or is dismissed, which changes the band the map centres in; no event announces it, and--bar-his the only inline style anything sets on the bodyEvery render input was walked to build that list:
basemap,campus,view,settled,selected,buildings,origin,accuracy,originIsGuess,screen, the canvas size, and the rail height.Tests
npm test: 673 tests, 669 pass, 0 fail, 4 skipped (main was 666 / 662 / 0 / 4).origin/main'sjs/map.js: it drawsfillText("1 min walk", 246.4, 219.0)there and fails, and draws nothing here.fillText/measureTextinjs/map.js, nolabelin thedrawTargetcall injs/app.js.createFrameLoopthrough a fakerequestAnimationFramethat queues callbacks and pumps them one display frame at a time. The one the issue is about: one wake on a settled draw costs one frame, and 120 pumped display frames after it cost none. Also that a moving draw keeps going and then stops, that a stopped loop restarts when something wakes it, that three wakes inside one frame buy one frame, and that a wake raised from inside the draw is not swallowed.js/app.jsasks for a frame in exactly one place, the request handed tocreateFrameLoop.All six loop tests fail on
origin/main— the module has no such export.Also in this diff
sw.js's shell gzip figure.scripts/test/sw.test.mjsholds it to within 1% of the files, and the added comments and the loop put it 1.8% out. Recomputed over the changed shell: 85,856 bytes with gzip 1.12, against 85,719 from node's zlib at the same level, so the two tools sit 0.16% apart as that comment says they do.Not done
docs/media/room.webpstill shows the pill, and the README alt text for it still says "labelled 4 min walk". Re-shooting needs Chrome forscripts/shoot.mjsand this machine has none, so the screenshot and its alt text are untouched rather than made to disagree with each other. They need onenode scripts/shoot.mjsrun.sw.jsis not re-stamped.SHELL_CACHEstill readsvacant-shell-74f888c, so this needsnpm run stampbefore it deploys. Left alone deliberately: several lanes are open at once and the last merge showed what happens when each re-stamps for its own branch.🤖 Generated with Claude Code
https://claude.ai/code/session_017xiX95r6nnUUmhD3KwMnWM
Generated by Claude Code