fix(live): wire every persisted view toggle before Live init awaits - #135
Conversation
…125) #88 moved only "Multibyte only" ahead of Live init's awaits. Heat, inferred hops, Realistic, Color by hash, Favorites, Foreign, Matrix and Rain are still restored and wired after await fetch('/api/config/map') and await loadNodes(). test-issue-125-live-toggles-early-e2e.js holds /api/config/map (the test-live-multibyte-only-e2e.js technique) and checks each toggle while init is held and after it, plus the map effects (heat layer, matrix theme, rain canvas), the Heat/Matrix interlock and one listener after SPA round trips. Against master: 2 passed, 12 failed (every toggle shows its default during init and drops a click made then; the heat layer is built although Heat was switched off; Matrix saved or clicked ON leaves Heat enabled during init). test-issue-125-live-toggles-wiring.js is the structural guard (fails if the wiring moves behind an await, drops a toggle, or a second change listener appears): 0 passed, 4 failed on master. Registered in test-all.sh, the CI unit step and the Playwright step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TcZHooUiiknVWbECVWzk8
…125) wireLiveControls() restores and wires Heat, inferred hops, Realistic, Color by hash, Favorites, Foreign, Multibyte, Matrix and Rain from one table, synchronously right after the markup is written and before init()'s first await. A click during init is saved at once and kept. - Heat gets a module-level mirror (heatEnabled) like the others. - The Heat/Matrix interlock is one function, syncHeatToggleToMatrix(), applied from the first paint and on every Matrix change. - applyLiveControlEffects() runs after loadNodes(): it builds the heat layer only when Heat is on and Matrix off, applies the matrix theme and starts the rain. showHeatMap() is a no-op until the map exists, so a Heat click during init only records the choice. - Each mounted checkbox is wired once (data-live-wired); init() writes new markup on every mount. - Audio and the geo-filter/region controls are unchanged (audio needs a user gesture; the others stay hidden or inert until their fetches). Test fixes: Foreign's default is ON (live.js reads it as !== 'false'), and the structural test ignores full-line comments when looking for the first await. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019TcZHooUiiknVWbECVWzk8
|
CI-note: Den første Playwright-kørsel fejlede i Generated by Claude Code |
Independent review of
|
| Criterion | Result |
|---|---|
One idempotent wiring phase before the first startup await |
Met [F]: wireLiveControls() right after app.innerHTML; wiring test; mutant M1 (moved after await loadNodes()) fails the E2E 2/12 and the wiring test 3/1 |
| Restore state and handlers as soon as the markup exists | Met [F]: E2E with /api/config/map held; all nine toggles show the saved value, and a click is written once and kept |
| Separate state from map effects; apply queued effects later | Met [F]: applyLiveControlEffects() after loadNodes(); probe with Heat saved OFF and clicked ON during init gives a heat layer after init and 0 page errors |
| Keep Heat/Matrix exclusion, filters, WebSocket/replay, SPA remount | Met [F]: interlock mutants M4 and M8 are caught; probe with Heat+Matrix saved ON gives no heat layer; the existing Live E2Es are green (below) |
| Audio and geolocation handled separately | Met [F]: left untouched, as the issue allows |
| At most one handler per control after remount | Met [F]: E2E "three SPA round trips keep one listener per toggle" |
| Mutation check that fails if wiring moves behind the await | Met [F]: M1 above |
Test-first and mutants
- [F]
test-issue-125-live-toggles-wiring.js: master 0/4, commit A 0/4, head 4/0. - [F]
test-issue-125-live-toggles-early-e2e.jsagainst a local server with real Leaflet: master 2 passed, 12 failed; head 14 passed, 0 failed. - [F] Eight mutants: M1, M4, M7 (Heat handler does not update its mirror) and M8 are caught. M6 survives (finding 1). M2, M3 and M5 are equivalent in practice (finding 4).
Suites run locally
- [F] Unit, same on master and the head:
test-live.js110/0,test-live-multibyte-filter.js9/0,test-live-region-filter.js13/0,test-live-legend-helper.js5/0.test-live-anims.jsgives 3/2 on both (pre-existing, not in CI). - [F] E2E on the head, against a local Go server on a freshened and migrated
test-fixtures/e2e-fixture.db:test-live-multibyte-only-e2e.js10/0,test-issue-1136-live-region-e2e.js5/0,test-live-fullscreen-1572-e2e.js9/0,test-live-mql-leak-1180-e2e.js2/0,test-live-layout-1178-1179-e2e.js10/0.
Performance and security
- [F] Not a hot path. The same listeners as before, attached earlier;
applyLiveControlEffects()runs once per mount. - [F] No new DOM sinks, and no Go, API or DB changes.
mode=roand themap[string]interface{}rule are not affected.
Not verified
- Real mobile devices and staging. The E2E and my probes ran at 1400×900 only.
- WebSocket/replay behaviour with live traffic during a held init. It was covered only indirectly by the existing Live E2Es.
- I did not run
scripts/check-xss-sinks.shor eslint myself [T]. The PR's "Not verified" list is honest.
Relates to #125
Summary
#88 moved only the Multibyte toggle ahead of Live init's first
await. The other persisted view toggles were restored and wired only afterawait fetch('/api/config/map')andawait loadNodes(): Heat, Inferred hops, Realistic, Color by hash, Favorites, Foreign, Matrix and Rain.While init ran, those toggles showed their default state instead of the saved one. A click made in that window was neither saved nor kept.
Now every persisted toggle is restored and wired synchronously, right after the markup is written. The effects that need the map are applied once the map exists.
Upstream reference (read only):
Kpa-clawbot/CoreScope#1940.Plan and design
Autonomous run, so the plan is written here instead of waiting for approval (AGENTS.md rule 5).
2eb7fdbf).test-issue-125-live-toggles-early-e2e.jsholds/api/config/mapwith request interception. That freezes init at its firstawaitright after the panel is painted, the same techniquetest-live-multibyte-only-e2e.jsalready uses.test-issue-125-live-toggles-wiring.jsis a structural guard.6b276d08), all inpublic/live.js:wireLiveControls(): one table with restore and persist for all nine persisted toggles (Multibyte included). It is called right afterapp.innerHTML, before anyawait. It is idempotent per element (data-live-wired). Every mount writes new markup, so each mounted checkbox has exactly one listener.heatEnabled: a module-level mirror for Heat, like the other toggles.syncHeatToggleToMatrix(): the Heat/Matrix interlock. It existed twice on master; now there is one copy. It is applied from the first paint and on every Matrix change.applyLiveControlEffects(): runs afterloadNodes()(the queued effects). It builds the heat layer only when Heat is on and Matrix is off, applies the matrix theme, and starts Rain.showHeatMap(): a no-op until the map exists, so a Heat click during init only records the choice.hideHeatMap()is safe without a map.Where the fork differs from upstream
applyLiveControlEffects(), together with the Matrix interlock (heatEnabled && !matrixMode), instead of a separateif (heatEnabled) showHeatMap().Config and customizer (AGENTS.md rule 8). No new configurable values. The localStorage keys are unchanged.
Acceptance criteria
awaitwireLiveControls(); structural test: the call comes afterapp.innerHTMLand before the firstawaitapplyLiveControlEffects(); E2E: Heat clicked off during init means no heat layer after init; Matrix and Rain clicked on mean theme and canvas after inittest-live-multibyte-only-e2e.js10/10;test-issue-1136-live-region-e2e.js5/5;test-live-fullscreen-1572-e2e.js9/9;test-live-mql-leak-1180-e2e.js2/2;test-live-layout-1178-1179-e2e.js10/10wireLiveControls(); no automatic activationawait loadNodes()makes it fail (3 passed, 1 failed)Tests
test-issue-125-live-toggles-early-e2e.js2eb7fdbfhad Foreign's default wrong.live.jsreads it as!== 'false', so the default is on. That is corrected in the fix commit.test-issue-125-live-toggles-wiring.jsExisting suites, same results on master and this branch
test-live.jstest-live-multibyte-filter.jstest-live-region-filter.jstest-live-legend-helper.jstest-live-anims.jsStatic checks
eslint@8 --quiet public/live.js: 0 errors.scripts/check-xss-sinks.sh --diff origin/master: exit 0.Browser. All E2E runs used Playwright Chromium against a local server on the migrated fixture, at 1400×900. The sandbox blocks unpkg.com, so a local-only Playwright preload served Leaflet 1.9.4, leaflet.heat 0.2.0 and Chart.js from npm copies of the same versions. That preload is not part of this PR.
Registration. The wiring test is added to
test-all.shand the CI unit step. The E2E test is added to the CI Playwright step next totest-live-multibyte-only-e2e.js. Workflow triggers, permissions, jobs andKpa-clawbotguards are unchanged.Performance. Not a hot path. The same listeners are attached as before, only earlier.
Not verified
Overlap with my other open PRs
test-all.sh, at different places.public/live.js.🤖 Generated with Claude Code
https://claude.ai/code/session_019TcZHooUiiknVWbECVWzk8
Generated by Claude Code