Repository navigation
fix(core): stop restoring after a few rounds when another script keeps undoing it - #880
Conversation
…ainst another script
…s once, and spares ordinary use
✅ Deploy Preview for pine-design-system ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
…ep tags out of the format
…er specs to its module
pixelflips
left a comment
There was a problem hiding this comment.
A few notes, mostly on how the docs and JSDoc describe what happens after a restore gives up. The cap itself looks solid: only real writes count, the reset waits for a real task boundary, and ordinary use stays well under it.
…and warn once per kind
…once, and say dom morph
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4ded55e. Configure here.

Description
Pine puts back what a DOM morph strips from its components:
HostStylingKeeperrestores (fix(core): restore host-only components' classes and styles when something strips them #875, fix(core): restore the host classes and attributes ten more components depend on #876);hydratedand scope classes on every component, throughkeepPineHydrated(feat(core): keep stencil's hydrated class on every pine component through a dom morph #877).A morph strips once, so each of these restores once. But another script that undoes every restore, such as its own
MutationObserverstripping the same class, keeps both sides going forever. That all happens in microtasks, so the page never renders or takes input again.This caps them.
limitRestoreswraps a restore that returns whether it wrote anything:console.warnnaming the element, for example "pds-box: stopped restoring its host, as another script keeps changing it. Use props instead."MessageChannelmessage (with a timer as the fallback), so a later morph is still restored.Where the cap applies
keepHostStyling(everyHostStylingKeepercomponent).keepPineHydrated, capped per element, so a script fighting one component doesn't stop the others being restored.pds-dropdown-menuandpds-popover. Opening or closing writes the ARIA directly, outside the cap, so they recover on their next open or close.limitRestoressits inutils/keep-hydrated.ts, next to the other shared restore helpers. If the global script importedattributes.ts, the CDN loader would fetch that whole module.Docs: the nine component pages that say their host state "comes back if something removes it" now add that a script that keeps undoing it wins after a few rounds, with a console warning.
Size: the component chunks measure 349.51 KB against
main's 350 KB limit, so no change to the limit is needed.Fixes DSS-359
Type of change
How Has This Been Tested?
limitRestores: only writes count, it resets on the next task, it warns once with the element, and it passes arguments through.keepPineHydrated: gives up on a component another script keeps stripping, warns once, and still restores the others.pds-box's host,pds-dropdown-menu's trigger ARIA andpds-popover's trigger ARIA. The other script wins with one warning, and the page stays responsive. Apds-dropdown-menuin ordinary use logs no warning.pds-chip"subtle and solid sentiments" e2e tests, which fail onmaintoo.Checklist:
Note
Medium Risk
Touches global hydration and host-restore behavior used by many components; normal morph/restore paths should be unchanged, but edge cases with competing DOM scripts may leave Pine state or ARIA wrong until open/close or the next task.
Overview
Fixes a page-freeze bug where Pine’s
MutationObserver-based restores (host classes/styles, Stencilhydratedclasses, dropdown/popover trigger ARIA) could fight another observer that undoes each write in an endless microtask loop.Introduces
limitRestoresinkeep-hydrated.ts: only restores that actually write count toward a cap of 5 per task (reset viaMessageChannel/setTimeout). After that, Pine stops restoring, logs oneconsole.warnper element and restore kind, and leaves the competing script’s DOM state in place until something changes again.stencilClassesOfremembers Stencil scope classes so restores still work after repeated strips.Wires the cap through
keepHostStyling(HOST_RESTORE),keepPineHydrated(per-element), and trigger observers onpds-dropdown-menuandpds-popover.setAttributesIfChanged/restoreHostStylingnow return whether they wrote, so no-op observer callbacks don’t burn the budget.Docs on nine components note the give-up behavior; new unit and e2e tests cover responsiveness, single warnings, and normal use without false warnings.
Reviewed by Cursor Bugbot for commit d26171c. Bugbot is set up for automated code reviews on this repo. Configure here.