perf: cut redundant DOM work in the section-map poller and difficulty updates - #9
Conversation
_smUpdate() ran at 5Hz and, on every single tick, did a fresh
querySelectorAll('.sm-block') + document.getElementById('sm-marker') and
then force-wrote style.opacity on every block regardless of whether the
active section had actually changed. Cache the marker/block elements in
_smRender() (rebuilt only when the section list changes, same as before)
and track the last-active index so a tick only touches the (at most two)
blocks whose highlight state actually flipped.
Separately, the 'difficulty:sections-updated' handler called a full
_smRender() on every event, tearing down and rebuilding every section
block's DOM (colors/labels/positions included) just to redraw a few
percentage "glass fill" indicators that were the only thing that changed.
Added _smUpdateDifficultyFills() to update each glass's fill height/title
in place, falling back to a full render only when the cached blocks don't
match the current sections.
Bumped plugin.json to 1.2.4 (patch) per the versioning rule in CLAUDE.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
ℹ️ Solid perf work — one gap on test coverage.
Reviewed changes
Two targeted performance optimizations to screen.js, no behavior change:
- Poller DOM ref caching:
_smRender()caches the marker element and block array once after building the bar;_smUpdate()reads from the cache and only touches the two blocks whose opacity actually changed, instead ofquerySelectorAll+forEachon every 200ms tick. - Incremental difficulty fills:
_smUpdateDifficultyFills()updates glass fill height/title in place ondifficulty:sections-updatedevents, avoiding a fullinnerHTMLrebuild of every section block. Falls back to_smRender()when cached blocks are stale.
Both changes preserve the idempotent guard, screen:changed mount/unmount, _smSeek seeking path, and window.highway data reads. Version bump 1.2.3 → 1.2.4 per spec.
Big Pickle (free) | 𝕏
…path Pullfrog's review on #9 flagged that neither new perf-optimization code path had test coverage: the in-place glass-fill update (vs. rebuild on size change vs. removal), and _smUpdate's assumption that _smRender() bakes opacity:0.5 into every block so only the flipped block needs a touch. Add targeted tests for both, injecting fake DOM state through the existing _setState/_getState test hooks. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Test coverage commit (7bedc3c) directly addressing the prior review's flagged gap:
- Opacity bake contract —
_smRendertest asserts exactly 3opacity:0.5matches in the rendered HTML (one per section), catching any removal of the baked-in default that the cached highlight depends on. - Cached highlight path — Injected mock
blockElswith specific values;_smUpdateonly touches the two blocks whose active state flipped ('1'/'0.5'), untouched blocks stayundefined. _smUpdateDifficultyFillsbranches — Three tests cover the in-place update (glass identity preserved, fill height/title updated, noinsertAdjacentHTML), size-change rebuild (old glass removed, new one inserted), and stale removal (glass removed when difficulty data is absent).
Assertions are specific (identity checks, exact values, counts) rather than truthy, so each test would fail if the optimization it targets was broken.
Big Pickle (free) | 𝕏

Summary
Two verified performance fixes, no behavior change:
_smUpdate()did redundant DOM work every 200ms regardless of change — it calleddocument.getElementById('sm-marker')and_smBar.querySelectorAll('.sm-block')fresh on every 5Hz poller tick, and force-wrotestyle.opacityon every block on every tick even when the active section hadn't changed since the last tick._smRender()now caches the marker element and block array once, right after building the bar (invalidated on_smRemove());_smUpdate()reads from the cache and only flipsopacityon the previous/new active block when the active index actually changes.difficulty:sections-updatedhandler always called a full_smRender()— every difficulty-data push from dynamic-difficulty tore down and rebuilt every section block's entire DOM purely to redraw a handful of small "glass fill" percentage bars. Added_smUpdateDifficultyFills(), which updates each glass element's fill height/title in place, falling back to a full_smRender()only if the cached blocks don't match the current section count.Both changes preserve every plugin-spec rule from CLAUDE.md: idempotent guard untouched,
screen:changedmount/unmount untouched, seeking still routed through_smSeek, section/difficulty data still read only viawindow.highway/the event bus, noMutationObserver/DOM polling introduced.Bumped
plugin.jsonversion1.2.3→1.2.4(patch) per the CLAUDE.md versioning rule.Testing
node -c screen.js— cleantests/screen.test.js— all 16 existing tests pass unchangedquerySelectorAll/getElementByIdnow run once per render rather than once per tick, and difficulty updates no longer trigger fullinnerHTMLrebuildsGenerated by Claude Code