perf(plugins): rebuild the panel frame only when its baked theme values change

The revision keys the panel iframe, so every bump destroys the sandboxed
frame and its in-panel state. It counted root attribute mutations, but
--workspace-sidebar-live-width is written every rAF of a sidebar drag, so
dragging with a panel open blanked it ~60x/sec. Compare the two values the
shell actually bakes in instead.

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Neil
2026-07-27 00:27:27 -07:00
co-authored by Orca
parent 4580044593
commit 1eaa91184d
2 changed files with 57 additions and 3 deletions
@@ -67,4 +67,40 @@ describe('usePluginPanelThemeRevision', () => {
expect(revisions.at(-1)).toBe(before)
})
// The revision keys the panel iframe, so a bump here destroys in-panel state.
it('ignores root custom properties the panel shell does not bake in', async () => {
const { revisions } = renderProbe()
const before = revisions.at(-1)
// Written every rAF while dragging the sidebar divider.
for (const width of [240, 241, 242, 243]) {
document.documentElement.style.setProperty('--workspace-sidebar-live-width', `${width}px`)
await flushObserver()
}
expect(revisions.at(-1)).toBe(before)
})
it('ignores a class change that leaves the color scheme alone', async () => {
const { revisions } = renderProbe()
const before = revisions.at(-1)
document.documentElement.classList.add('theme-transition-disabled')
await flushObserver()
expect(revisions.at(-1)).toBe(before)
})
it('bumps once when a token settles on a new value', async () => {
const { revisions } = renderProbe()
const before = revisions.at(-1)
document.documentElement.style.setProperty('--background', '#111')
await flushObserver()
document.documentElement.style.setProperty('--background', '#111')
await flushObserver()
expect(revisions.at(-1)).toBe(before! + 1)
})
})
@@ -1,10 +1,28 @@
import { useEffect, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { buildPanelDesignTokenCss, currentPanelColorScheme } from './plugin-panel-design-token-css'
/** Changes whenever root theme classes/tokens may have changed. */
/** The exact shell inputs; anything else on the root must not rebuild the frame. */
function readPanelThemeSnapshot(): string {
return `${currentPanelColorScheme()}|${buildPanelDesignTokenCss()}`
}
/** Changes only when the values baked into the panel shell actually change. */
export function usePluginPanelThemeRevision(): number {
const [revision, setRevision] = useState(0)
// Why: the revision keys the panel iframe, so remounting it destroys in-panel
// state. Root style writes fire at rAF cadence during a sidebar drag, so
// compare the baked values instead of counting mutations.
const snapshotRef = useRef<string | null>(null)
useEffect(() => {
const observer = new MutationObserver(() => setRevision((current) => current + 1))
snapshotRef.current ??= readPanelThemeSnapshot()
const observer = new MutationObserver(() => {
const snapshot = readPanelThemeSnapshot()
if (snapshot === snapshotRef.current) {
return
}
snapshotRef.current = snapshot
setRevision((current) => current + 1)
})
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class', 'style']