Skip to content

Windowed mounting by default: memory tracks the visible window, not assignment length - #44

Merged
dqnykamp merged 5 commits into
Doenet:mainfrom
dqnykamp:windowed-mounting
Jul 13, 2026
Merged

Windowed mounting by default: memory tracks the visible window, not assignment length#44
dqnykamp merged 5 commits into
Doenet:mainfrom
dqnykamp:windowed-mounting

Conversation

@dqnykamp

@dqnykamp dqnykamp commented Jul 12, 2026

Copy link
Copy Markdown
Member

Fixes #35, fixes #36, and fixes #37. Requires @doenet/doenetml-iframe ≥ 0.7.20-dev.332 (windowed mounting with keepLive and report-callback host support, Doenet/DoenetML#1479).

Every item's viewer component stays mounted, but which items are booted is now governed by the iframe wrapper's windowed mounting policy:

The viewer's own render-scheduling machinery (itemsToRender/itemsVisible/checkRender/per-item IntersectionObserver) is deleted in favor of the wrapper's policy. Parking requires flags.allowSaveState or allowLocalState; without a persistence path viewers still mount lazily but stay live once booted, so no work can be lost.

New ActivityViewer props:

  • mountPolicy — overrides for maxLiveViewers / visibleMargin / parkDelayMs / flushTimeoutMs / maxConcurrentBoots;
  • useSharedCoreWorker — serve all documents' cores from a shared worker pool instead of one dedicated ~100 MB worker per document (default off);
  • working standaloneUrl / cssUrl / doenetmlVersion passthroughs — the standalone-bundle URL, its CSS, and a version override for every document's viewer;
  • doenetViewerUrl and doenetMediaUrl are threaded through ViewerActivitySingleDocActivity to each embedded DoenetViewer. Both are inner-viewer props (forwarded across the iframe boundary), not bundle URLs: doenetViewerUrl is the <ref> renderer's activity-link base (it was not being forwarded correctly), and doenetMediaUrl is the <image source="doenet:…"> media base ([Feature]: Expose doenetMediaUrl in assignment-viewer package DoenetML#1457), newly exposed so doenet: images resolve.

Tests

New cypress spec (against the real published dev-channel bundle) covering both modes end to end:

  • Paginated (5 docs): current+neighbor boot, pages beyond the window never do, iframe count stays ≤ 3; typing on page 1, paging to 4 (page 1 parks — iframe detached), and returning restores the typed work with no interaction.
  • Scroll (5 docs, budget 2): off-screen items never boot; scrolling boots the far items and parks the ones left behind (iframe count ≤ 2).

A separate spec asserts that doenetViewerUrl and doenetMediaUrl reach the embedded viewer — both are baked into the booted DoenetViewer's iframe srcdoc, confirming they thread all the way through.

The park/restore round trip also exercises DoenetML#1479's report-callback capture path in a real host. Full suite green (47 vitest + 14 cypress), lint/build clean.

🤖 Generated with Claude Code

dqnykamp and others added 2 commits July 12, 2026 23:22
…ssignment length

Implements Doenet#35, Doenet#36, and Doenet#37.

Every item's viewer component stays mounted, but which items are BOOTED
is now governed by @doenet/doenetml-iframe's windowed mounting policy:
an item only creates its iframe (a multi-MB standalone-bundle parse
plus a core worker) when near the viewport or within the pagination
window, simultaneous boots are capped page-wide, and at most
`maxLiveViewers` stay live — the rest are parked losslessly (state
flushed before the iframe is detached; restored, typed work intact, on
return). In paginated mode the current page and its neighbors are
marked `keepLive`, so page flips within the window stay instant while
hidden pages beyond it never boot at all (Doenet#35). A 20-question
assignment paged all the way through now holds ~3 live viewers instead
of 20 (Doenet#36); scroll-mode memory tracks the viewport (Doenet#37).

The viewer's own render-scheduling machinery (itemsToRender /
itemsVisible / checkRender / a per-item IntersectionObserver) is
deleted in favor of the wrapper's policy, which also brings the
boot-concurrency cap. Parking requires flags.allowSaveState or
allowLocalState (no persistence path ⇒ viewers still mount lazily but
stay live once booted, so no work can be lost).

New ActivityViewer props: `mountPolicy` (overrides for maxLiveViewers /
visibleMargin / parkDelayMs / flushTimeoutMs / maxConcurrentBoots),
`useSharedCoreWorker` (serve cores from a shared worker pool instead of
one dedicated ~100 MB worker per document), and working `standaloneUrl`
/ `cssUrl` / `doenetmlVersion` passthroughs — `doenetViewerUrl` was
silently ignored by the iframe wrapper and is kept as a deprecated
alias for `standaloneUrl`.

Requires @doenet/doenetml-iframe ≥ 0.7.20-dev.329 (windowed mounting
with keepLive and report-callback host support).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The committed package-lock.json recorded the root package's
@doenet/doenetml-iframe peerDependency as
`file:../DoenetML3/packages/doenetml-iframe` — a leftover from local
development against a sibling checkout. package.json declares it as
`^0.7.20-dev.329` (and the dependency actually resolves to the published
0.7.20-dev.329 from the registry), so regenerate the lockfile's mirror to
match. No dependency graph change; only the recorded range is corrected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dqnykamp
dqnykamp force-pushed the windowed-mounting branch from 0ac2752 to 70620f8 Compare July 13, 2026 04:24
`doenetViewerUrl` (the `<ref>` renderer's activity-link base) and
`doenetMediaUrl` (the `<image source="doenet:…">` media base,
Doenet/DoenetML#1457) are props of the inner DoenetViewer, forwarded
across the iframe boundary — not aliases for the standalone-bundle
`standaloneUrl`. Thread both through ActivityViewer -> Viewer -> Activity
-> SingleDocActivity to each embedded DoenetViewer, replacing the
incorrect `standaloneUrl ?? doenetViewerUrl` alias so `<ref>` links and
`doenet:` images resolve. Add a cypress spec asserting both URLs are
baked into the booted viewer's iframe.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dqnykamp and others added 2 commits July 13, 2026 00:27
Bump from dev.329 to the current `dev` dist-tag. The iframe's public type
surface is unchanged between the two, and lint/build/vitest plus the full
cypress component suite (windowed, park/restore, viewer-URL forwarding)
pass against the new bundle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`cypress/screenshots/` (and `cypress/videos/`) are generated by cypress
runs and should not be tracked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dqnykamp
dqnykamp merged commit d648bc1 into Doenet:main Jul 13, 2026
4 checks passed
@dqnykamp
dqnykamp deleted the windowed-mounting branch July 13, 2026 12:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment