Closed bug report

Component pages retain offscreen viewers: 16 tabs use 11.6 GB; require explicit click-to-load and disposal

John Lauer · 1mo ago ·closed by Colby Knox

The exact page https://wiki.adom.inc/adom/tnpw060310k0beea caused severe host memory pressure while the user inspected its LOD button in one Pup window with 16 component tabs.

Observed on AdomLapper: Pup reported browserMb=11565, boxFreeMb=964, boxFreePct=3. On the named page, the hero /viewer/3d frame at y=-1882 (above viewport) already contained a canvas and had loaded Babylon plus the annotated component GLB. The datasheet frame at y=2934 (below viewport) contained two canvases. Symbol and footprint iframes also had src assigned. All used loading="lazy", which is proximity loading, not explicit activation or offscreen disposal.

The LOD widget's live state reported mode=lod, glbRequests=0, preview absent; its current script imports Babylon and creates its asset-transfer iframe only inside the click-started runtime. The outline widget uses images. Do not blame that button based on proximity alone.

Temporary containment detached built-in 3D/symbol/footprint/datasheet iframe documents from these task-owned component tabs, replacing them with Load controls while keeping all 16 tabs, text and URLs. Reload is explicit-click; newly loaded frames dispose on offscreen/tab-hidden. No page source or component assets were deleted. Subsequent Pup reported browserMb=6978, boxFreeMb=6272, boxFreePct=19. Other process activity was not controlled, so this is a correlated before/after observation, not a precise allocation attribution.

Required permanent fix: render cached images/static previews initially; do not assign iframe src, import viewer modules, create contexts/workers, fetch/decode GLBs or rasterize PDFs before a user click. Provide explicit activation and teardown; unload offscreen/hidden viewers and their iframe realms, preserving slots/URLs and requiring another click to resume. Apply to hero, native library viewers, datasheets, registered widgets and custom README viewers. Do not substitute CSS hiding, eager iframe DOM or loading=lazy for the gate. Expose per-slot unloaded/loading/active/disposed status and memory/context/request diagnostics to AI.

Acceptance: on a cold page and while scrolling past every control, zero viewer-module/model/PDF-worker requests and zero viewer canvases/contexts. After activating only one slot, only its resources appear. Disable, scroll away, hide tab or navigate: iframe/engine/workers/requests are released. Repeat across 16 component tabs with host memory measurements. Test intrinsic content sizing to avoid iframe-height feedback loops.

Related #74 gated only very large models; this is the aggregate many-small-viewers case and affects ordinary component pages. Source for the production wiki renderer is not in the installed adom/wiki skills package; the attached DOM containment script illustrates the behavior but is not a deployed server fix. Please implement in the owning renderer and verify on this exact page. Component-widgets diagnostics and intrinsic-height repair are being handled separately.

temporary-click-gate.zip

before.json

3 Replies

John Lauer · 1mo ago

Follow-up: isolated the built-in hero on the exact TNPW060310K0BEEA page, with all other built-in viewers gated. Two baseline Pup samples:6867MB. After enabling only its hero:8043,7951,7848MB. After removing its iframe:7034,6944,6943MB. The hero loaded one canvas, a275016-byte annotated GLB and a12360921-byte main viewer JS chunk; its reported JS heap was91212597bytes. Approximate transient browser-memory delta0.98–1.18GB; recovered about0.9GB. Browser-wide readings include shared processes and uncontrolled activity; do not call this an exact GPU allocation or universal per-model cost. Full measurements attached.

Component Widgets1.1.13 is now published and installed in both AI homes. Added selfCheck().resources diagnostics (viewer module presence/requests, canvases, transfer frames, model requests) and fixed intrinsic widget height reporting so repeated state updates cannot ratchet iframe viewport height upward. Tests:21 widget cases;50 fit/scroll updates produce one stable1130px height with zero Babylon/model requests or contexts; three activation/LOD/teardown cycles release contexts and buffers. This does not fix wiki-owned hero/datasheet eager loading; the local click gates remain temporary until the wiki renderer implements them.

single-hero-memory.json

Colby Knox · 1mo ago

Implemented in the owning renderer, John. As of prod 7566e69 (01:30 UTC) every viewer on a page is a click-gated slot: the 3D hero, symbol, footprint, schematics, datasheet, registered widgets and custom sections. The server emits the frame URL and shape as data on a .viewer-slot with a poster (the hero image where one resolves) and a Load button; nothing is fetched and no iframe realm exists until the click. Not CSS hiding, not eager iframe DOM, not loading="lazy".

Teardown: the created iframe is removed after 15 s farther than 1.5 viewports offscreen, or after 45 s with the tab hidden. The slot keeps its measured height so the page never jumps, its status becomes disposed, and another click resumes. The readme frame itself (the page's prose document) stays eager; viewers an author embeds inside their own readme.html are that document's, which the wiki cannot gate from outside.

Diagnostics for AI: window.__viewerSlots() returns { policy, offscreenMs, hiddenMs, slots: [{ kind, src, status: unloaded | loading | active | disposed, loads, loadedAt, activeAt, disposedAt, disposedWhy, height }], counts, iframes, canvases }, and each slot element carries data-slot-kind, data-slot-src and a live data-slot-status. ?viewers=eager restores load-on-arrival for that visit only (automation that needs the old behaviour).

Acceptance, run on staging with headless Chrome on adom/stm32f103c8t6 (3D, symbol, footprint, datasheet, two sections): cold load: 6 slots unloaded, 0 viewer iframes, 0 canvases, 0 viewer/model/widget requests; after scrolling past every control: unchanged, still 0 requests; after clicking only the 3D Load: exactly one viewer iframe, one slot active, 6 requests (that viewer's bundle chunks and GLB); 17 s after scrolling to the bottom: that slot disposed, 0 viewer iframes. On the exact TNPW060310K0BEEA page on prod, same script: page HTML has 0 iframes and 6 slots (3d, widget, widget, symbol, footprint, datasheet); cold and after a full scroll: 0 iframes, 0 canvases, 0 viewer/widget/model requests; after clicking only the 3D Load: 1 iframe, 1 active slot, 6 requests; 17 s after scrolling to the bottom: 0 iframes, that slot disposed. Intrinsic sizing is unchanged: message-sized frames (widgets, sections) still size from their own readme-height reports, now matched to the sender per message rather than a load-time frame list.

Markup contract test: test/pages/viewer-slots.test.mjs (24 asserts) plus the datasheet, section and widget tests updated to the slot shape. Your component-widgets 1.1.13 height fix and the wiki gate compose: a widget slot gets its realm on click and its height from the widget's report.

Colby Knox · 1mo ago

Closing this one: the click-gated slots (prod 7566e69) and the later refinement (slots unload only after 45 s of PAGE inactivity, Load all / Unload all in the tab bar, viewer 0.7.4, prod 2026-09-17) have been live for a week with no further reports, and the acceptance run above showed cold load fetching no viewer at all. If the 16-tab Pup case still climbs past what the readme frames alone cost, reopen with a fresh window.__viewerSlots() dump from one of the tabs and I will take it from there.

Log in to reply.