Goal
Resolve the remaining MEDIUM-severity findings from #40 (the View Transitions audit). The HIGH-severity findings (Partytown, MobileMenu) and one of the MEDIUMs (GardenFilters typo) have already shipped via PRs #41, #42, and the recent GardenFilters refactor work — verified against current main.
This plan covers three independent surgical fixes. Each can be its own PR.
Scope (consolidated, MEDIUM only)
1. Resize-listener leak / non-reattachment in scrollama and gsap components
Problem: window.addEventListener("resize", handleResize) is attached at module scope (so only on first document load), but the cleanup in astro:before-swap removes it. After the first SPA navigation, resize-driven re-layout silently stops working on these pages.
Files affected:
src/components/unique/ScrollingImages.astro:90-103
src/components/unique/gsap-basics/TweenSpinningBox.astro
src/components/unique/gsap-basics/TweenBlueRedBox.astro
src/components/unique/gsap-basics/TweenRedBigBox.astro
src/components/unique/gsap-basics/TweenReverseSpinningBox.astro
src/components/unique/gsap-basics/GsapScroller.astro
src/components/unique/TweenBox.astro
src/components/mdx/ScrollyTalkSection.astro (overlaps with item 2)
Fix shape: Move the resize addEventListener inside the astro:page-load handler (or convert to the onPageLifecycle helper at src/utils/viewTransitionLifecycle.ts) so that attach + cleanup are paired per page-load. Prefer onPageLifecycle for consistency with the rest of the codebase.
Acceptance: Visit a page using one of these components, navigate away via SPA link, navigate back, then resize the window — the component re-lays-out correctly. CDP listener count for resize on window does not grow with navigations.
2. ScrollyTalkSection: per-<img> load listeners leak
File: src/components/mdx/ScrollyTalkSection.astro:177-184
Problem: Each page-load attaches a load handler to every .scrolly-img. The cleanup removes the window resize listener but nothing for the per-image ones. Listeners survive on persisted DOM subtrees.
Fix shape: Either keep an array of (element, handler) pairs to remove on cleanup, or attach with { once: true } since handleResize only needs to fire when the image first loads.
Acceptance: No accumulating load listeners on .scrolly-img elements after multiple navigations to/from a scrolly-talk page.
3. Twitter widgets.js redundancy and re-append churn
Files: src/layouts/Layout.astro:108, src/components/mdx/TweetEmbed.astro:18-43
Problem: Layout includes a global <script async src="https://platform.twitter.com/widgets.js"> inside <body>, which is replaced on every navigation but doesn't actually re-execute (good) — making it dead weight on pages with no tweets. Meanwhile TweetEmbed.astro manually removes and re-appends the same script on every astro:page-load, causing redundant network work and a minor race risk on fast nav.
Fix shape (pick one):
- Preferred: Remove the global
<script> from Layout.astro:108. In TweetEmbed.astro, change the page-load handler to: if window.twttr exists, call window.twttr.widgets.load(); otherwise inject the <script> once.
- Alternative: Wrap the global
<script> in transition:persist so it survives navigations, and have TweetEmbed only call window.twttr.widgets.load().
Acceptance: Tweet embeds render correctly on the first page that contains one and after subsequent SPA navigations to other pages with tweets. Network panel shows widgets.js requested once per session, not per navigation.
Out of scope
The following findings from #40 are deliberately excluded from this plan:
Notes for the implementer
- Follow the existing
onPageLifecycle helper pattern at src/utils/viewTransitionLifecycle.ts whenever lifecycle is involved. Always return a cleanup function.
- Each item above should be a separate PR — they touch different files and can ship independently.
- After each fix, manually verify the acceptance criterion in a real browser (not just dev-server build).
Goal
Resolve the remaining MEDIUM-severity findings from #40 (the View Transitions audit). The HIGH-severity findings (Partytown, MobileMenu) and one of the MEDIUMs (GardenFilters typo) have already shipped via PRs #41, #42, and the recent GardenFilters refactor work — verified against current
main.This plan covers three independent surgical fixes. Each can be its own PR.
Scope (consolidated, MEDIUM only)
1. Resize-listener leak / non-reattachment in scrollama and gsap components
Problem:
window.addEventListener("resize", handleResize)is attached at module scope (so only on first document load), but the cleanup inastro:before-swapremoves it. After the first SPA navigation, resize-driven re-layout silently stops working on these pages.Files affected:
src/components/unique/ScrollingImages.astro:90-103src/components/unique/gsap-basics/TweenSpinningBox.astrosrc/components/unique/gsap-basics/TweenBlueRedBox.astrosrc/components/unique/gsap-basics/TweenRedBigBox.astrosrc/components/unique/gsap-basics/TweenReverseSpinningBox.astrosrc/components/unique/gsap-basics/GsapScroller.astrosrc/components/unique/TweenBox.astrosrc/components/mdx/ScrollyTalkSection.astro(overlaps with item 2)Fix shape: Move the resize
addEventListenerinside theastro:page-loadhandler (or convert to theonPageLifecyclehelper atsrc/utils/viewTransitionLifecycle.ts) so that attach + cleanup are paired per page-load. PreferonPageLifecyclefor consistency with the rest of the codebase.Acceptance: Visit a page using one of these components, navigate away via SPA link, navigate back, then resize the window — the component re-lays-out correctly. CDP listener count for
resizeonwindowdoes not grow with navigations.2. ScrollyTalkSection: per-
<img>loadlisteners leakFile:
src/components/mdx/ScrollyTalkSection.astro:177-184Problem: Each page-load attaches a
loadhandler to every.scrolly-img. The cleanup removes thewindowresize listener but nothing for the per-image ones. Listeners survive on persisted DOM subtrees.Fix shape: Either keep an array of
(element, handler)pairs to remove on cleanup, or attach with{ once: true }sincehandleResizeonly needs to fire when the image first loads.Acceptance: No accumulating
loadlisteners on.scrolly-imgelements after multiple navigations to/from a scrolly-talk page.3. Twitter widgets.js redundancy and re-append churn
Files:
src/layouts/Layout.astro:108,src/components/mdx/TweetEmbed.astro:18-43Problem: Layout includes a global
<script async src="https://platform.twitter.com/widgets.js">inside<body>, which is replaced on every navigation but doesn't actually re-execute (good) — making it dead weight on pages with no tweets. MeanwhileTweetEmbed.astromanually removes and re-appends the same script on everyastro:page-load, causing redundant network work and a minor race risk on fast nav.Fix shape (pick one):
<script>fromLayout.astro:108. InTweetEmbed.astro, change the page-load handler to: ifwindow.twttrexists, callwindow.twttr.widgets.load(); otherwise inject the<script>once.<script>intransition:persistso it survives navigations, and haveTweetEmbedonly callwindow.twttr.widgets.load().Acceptance: Tweet embeds render correctly on the first page that contains one and after subsequent SPA navigations to other pages with tweets. Network panel shows
widgets.jsrequested once per session, not per navigation.Out of scope
The following findings from #40 are deliberately excluded from this plan:
main.Notes for the implementer
onPageLifecyclehelper pattern atsrc/utils/viewTransitionLifecycle.tswhenever lifecycle is involved. Always return a cleanup function.