From 3398ed49b401bc0bf059f3c2aaf6f6a54bcae23d Mon Sep 17 00:00:00 2001 From: Jonathan Hefner Date: Mon, 19 Jan 2026 16:47:46 -0600 Subject: [PATCH] Complete "Pausing computation-heavy widgets" pattern documentation Add `IntersectionObserver` pattern for pausing expensive operations (animations, WebGL, polling) when widgets scroll out of view. - Add `visibilityBasedPause` example to `patterns.tsx` with play/pause callbacks and `onteardown` cleanup - Document pattern in `patterns.md` with link to MDN docs Co-Authored-By: Claude Opus 4.5 --- docs/patterns.md | 6 ++++++ docs/patterns.tsx | 31 +++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/docs/patterns.md b/docs/patterns.md index aff095c..ef072c4 100644 --- a/docs/patterns.md +++ b/docs/patterns.md @@ -68,6 +68,12 @@ To persist widget state across conversation reloads (e.g., current page in a PDF > **Note:** For model-visible state (informing the LLM about what the user is viewing), use {@link app!App.updateModelContext updateModelContext} instead. Widget state persistence is for UI state that should survive page reloads but doesn't need to be seen by the model. +## Pausing computation-heavy widgets when out of view + +Widgets with animations, WebGL rendering, or polling can consume significant CPU/GPU even when scrolled out of view. Use [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) to pause expensive operations when the widget isn't visible: + +{@includeCode ./patterns.tsx#visibilityBasedPause} + ## Lowering perceived latency Use {@link app!App.ontoolinputpartial ontoolinputpartial} to receive streaming tool arguments as they arrive, allowing you to show a loading preview before the complete input is available. diff --git a/docs/patterns.tsx b/docs/patterns.tsx index 6002bfc..86afa7e 100644 --- a/docs/patterns.tsx +++ b/docs/patterns.tsx @@ -158,6 +158,36 @@ function persistWidgetState(app: App) { //#endregion persistData } +/** + * Example: Pausing computation-heavy widgets when out of view + */ +function visibilityBasedPause( + app: App, + container: HTMLElement, + animation: { play: () => void; pause: () => void }, +) { + //#region visibilityBasedPause + // Use IntersectionObserver to pause when widget scrolls out of view + const observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + animation.play(); + } else { + animation.pause(); + } + }); + }); + observer.observe(container); + + // Clean up when the host tears down the widget + app.onteardown = async () => { + observer.disconnect(); + animation.pause(); + return {}; + }; + //#endregion visibilityBasedPause +} + /** * Example: Supporting both iframe & MCP Apps in same binary */ @@ -183,5 +213,6 @@ void hostStylingVanillaJs; void hostStylingReact; void persistWidgetStateServer; void persistWidgetState; +void visibilityBasedPause; void iframeAndMcpApps; void migrateFromOpenai;