@@ -86,6 +86,7 @@ Read JSDoc documentation directly from source:
8686
8787| Example | Pattern Demonstrated |
8888| ---------| ---------------------|
89+ | ` examples/shadertoy-server/ ` | ** Streaming partial input** + visibility-based pause/play (best practice for large inputs) |
8990| ` examples/wiki-explorer-server/ ` | ` callServerTool ` for interactive data fetching |
9091| ` examples/system-monitor-server/ ` | Polling pattern with interval management |
9192| ` examples/video-resource-server/ ` | Binary/blob resources |
@@ -173,9 +174,31 @@ app.onhostcontextchanged = (ctx) => {
173174import { useApp , useHostStyles } from " @modelcontextprotocol/ext-apps/react" ;
174175
175176const { app } = useApp ({ appInfo , capabilities , onAppCreated });
176- useHostStyles (app ); // Handles theme, styles, fonts automatically
177+ useHostStyles (app ); // Injects CSS variables to document, making var(--*) available
177178```
178179
180+ ** Using variables in CSS** - After applying, use ` var() ` :
181+ ``` css
182+ .container {
183+ background : var (--color-background-secondary );
184+ color : var (--color-text-primary );
185+ font-family : var (--font-sans );
186+ border-radius : var (--border-radius-md );
187+ }
188+ .code {
189+ font-family : var (--font-mono );
190+ font-size : var (--font-text-sm-size );
191+ line-height : var (--font-text-sm-line-height );
192+ color : var (--color-text-secondary );
193+ }
194+ .heading {
195+ font-size : var (--font-heading-lg-size );
196+ font-weight : var (--font-weight-semibold );
197+ }
198+ ```
199+
200+ Key variable groups: ` --color-background-* ` , ` --color-text-* ` , ` --color-border-* ` , ` --font-sans ` , ` --font-mono ` , ` --font-text-*-size ` , ` --font-heading-*-size ` , ` --border-radius-* ` . See ` src/spec.types.ts ` for full list.
201+
179202### Safe Area Handling
180203
181204Always respect ` safeAreaInsets ` :
@@ -189,6 +212,96 @@ app.onhostcontextchanged = (ctx) => {
189212};
190213```
191214
215+ ### Streaming Partial Input
216+
217+ For large tool inputs, use ` ontoolinputpartial ` to show progress during LLM generation. The partial JSON is healed (always valid), enabling progressive UI updates.
218+
219+ ** Spec:** [ ui/notifications/tool-input-partial] ( https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx#streaming-tool-input )
220+
221+ ``` typescript
222+ app .ontoolinputpartial = (params ) => {
223+ const args = params .arguments ; // Healed partial JSON - always valid, fields appear as generated
224+ // Use args directly for progressive rendering
225+ };
226+
227+ app .ontoolinput = (params ) => {
228+ // Final complete input - switch from preview to full render
229+ };
230+ ```
231+
232+ ** Use cases:**
233+ | Pattern | Example |
234+ | ---------| ---------|
235+ | Code preview | Show streaming code in ` <pre> ` , render on complete (` examples/shadertoy-server/ ` ) |
236+ | Progressive form | Fill form fields as they stream in |
237+ | Live chart | Add data points to chart as array grows |
238+ | Partial render | Render incomplete structured data (tables, lists, trees) |
239+
240+ ** Simple pattern (code preview):**
241+ ``` typescript
242+ app .ontoolinputpartial = (params ) => {
243+ codePreview .textContent = params .arguments ?.code ?? " " ;
244+ codePreview .style .display = " block" ;
245+ canvas .style .display = " none" ;
246+ };
247+ app .ontoolinput = (params ) => {
248+ codePreview .style .display = " none" ;
249+ canvas .style .display = " block" ;
250+ render (params .arguments );
251+ };
252+ ```
253+
254+ ### Visibility-Based Resource Management
255+
256+ Pause expensive operations (animations, WebGL, polling) when widget scrolls out of viewport:
257+
258+ ``` typescript
259+ const observer = new IntersectionObserver ((entries ) => {
260+ entries .forEach ((entry ) => {
261+ if (entry .isIntersecting ) {
262+ animation .play (); // or: startPolling(), shaderToy.play()
263+ } else {
264+ animation .pause (); // or: stopPolling(), shaderToy.pause()
265+ }
266+ });
267+ });
268+ observer .observe (document .querySelector (" .main" ));
269+ ```
270+
271+ ### Fullscreen Mode
272+
273+ Request fullscreen via ` app.requestDisplayMode() ` . Check availability in host context:
274+
275+ ``` typescript
276+ let currentMode: " inline" | " fullscreen" = " inline" ;
277+
278+ app .onhostcontextchanged = (ctx ) => {
279+ // Check if fullscreen available
280+ if (ctx .availableDisplayModes ?.includes (" fullscreen" )) {
281+ fullscreenBtn .style .display = " block" ;
282+ }
283+ // Track current mode
284+ if (ctx .displayMode ) {
285+ currentMode = ctx .displayMode ;
286+ container .classList .toggle (" fullscreen" , currentMode === " fullscreen" );
287+ }
288+ };
289+
290+ async function toggleFullscreen() {
291+ const newMode = currentMode === " fullscreen" ? " inline" : " fullscreen" ;
292+ const result = await app .requestDisplayMode ({ mode: newMode });
293+ currentMode = result .mode ;
294+ }
295+ ```
296+
297+ ** CSS pattern** - Remove border radius in fullscreen:
298+ ``` css
299+ .main { border-radius : var (--border-radius-lg ); overflow : hidden ; }
300+ .main.fullscreen { border-radius : 0 ; }
301+ ```
302+
303+ See ` examples/shadertoy-server/ ` for complete implementation.
304+
192305## Common Mistakes to Avoid
193306
1943071 . ** Handlers after connect()** - Register ALL handlers BEFORE calling ` app.connect() `
@@ -198,6 +311,7 @@ app.onhostcontextchanged = (ctx) => {
1983115 . ** Ignoring safe area insets** - Always handle ` ctx.safeAreaInsets `
1993126 . ** No text fallback** - Always provide ` content ` array for non-UI hosts
2003137 . ** Hardcoded styles** - Use host CSS variables for theme integration
314+ 8 . ** No streaming for large inputs** - Use ` ontoolinputpartial ` to show progress during generation
201315
202316## Testing
203317
0 commit comments