Summary
activateTab() at packages/memtomem/src/memtomem/web/static/app.js:680-694 removes the .active class from previously-active panels but never re-applies hidden = true. After the first tab activation any panel that has been visited stays hidden = false for the rest of the session, relying solely on CSS display: none (from the absent .active class) to hide content.
Found by Playwright UX review of v0.1.34 prod (2026-05-02). See docs/reports/mm-web-prod-v0.1.34-playwright-review.md (originally classified as P0 cross-tab a11y leak; verification reclassified to P2 — modern browsers do exclude display:none from the a11y tree, but the DOM-state hygiene gap is real and Playwright snapshots do dump the leaked nodes).
Evidence
packages/memtomem/src/memtomem/web/static/app.js:680-694:
// Hide all panels
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active')); // 681
// ...
// Show panel
const panel = qs(`tab-${tabName}`);
if (panel) {
panel.hidden = false; // 694 — set on activate, never undone elsewhere
panel.classList.add('active');
// ...
}
The HTML (packages/memtomem/src/memtomem/web/static/index.html) starts with hidden on each inactive panel, but once activateTab() runs for a panel it strips hidden and never reapplies it on tab switch.
Why this is worth fixing even though display:none works
- DOM state accumulates; over a session every visited panel ends with
hidden=false. Test automation tools (Playwright browser_snapshot) walk the DOM and report stale content.
- Future CSS edits or a switch from
display:none to e.g. visibility:hidden would silently regress a11y. Belt-and-braces (hidden attr + class) is cheap.
inert could be added at the same time to also remove focus / pointer events from inactive panels.
Suggested fix
In activateTab(), change the deactivation pass to:
document.querySelectorAll('.tab-panel').forEach(p => {
p.classList.remove('active');
p.hidden = true; // belt-and-braces with the CSS-driven display:none
});
…and the activation block continues to set panel.hidden = false.
Optionally also toggle inert for keyboard / pointer isolation.
References
- Review:
docs/reports/mm-web-prod-v0.1.34-playwright-review.md
- Tracking umbrella: TBD
Summary
activateTab()atpackages/memtomem/src/memtomem/web/static/app.js:680-694removes the.activeclass from previously-active panels but never re-applieshidden = true. After the first tab activation any panel that has been visited stayshidden = falsefor the rest of the session, relying solely on CSSdisplay: none(from the absent.activeclass) to hide content.Found by Playwright UX review of v0.1.34 prod (2026-05-02). See
docs/reports/mm-web-prod-v0.1.34-playwright-review.md(originally classified as P0 cross-tab a11y leak; verification reclassified to P2 — modern browsers do excludedisplay:nonefrom the a11y tree, but the DOM-state hygiene gap is real and Playwright snapshots do dump the leaked nodes).Evidence
packages/memtomem/src/memtomem/web/static/app.js:680-694:The HTML (
packages/memtomem/src/memtomem/web/static/index.html) starts withhiddenon each inactive panel, but onceactivateTab()runs for a panel it stripshiddenand never reapplies it on tab switch.Why this is worth fixing even though
display:noneworkshidden=false. Test automation tools (Playwrightbrowser_snapshot) walk the DOM and report stale content.display:noneto e.g.visibility:hiddenwould silently regress a11y. Belt-and-braces (hiddenattr + class) is cheap.inertcould be added at the same time to also remove focus / pointer events from inactive panels.Suggested fix
In
activateTab(), change the deactivation pass to:…and the activation block continues to set
panel.hidden = false.Optionally also toggle
inertfor keyboard / pointer isolation.References
docs/reports/mm-web-prod-v0.1.34-playwright-review.md