-
Notifications
You must be signed in to change notification settings - Fork 41
example render ctx
Native windows registered via openstation_register_window() (or wp.os.registerWindow()) get a render callback. The callback receives a second ctx argument carrying the channel API and the rest of the window-scoped helpers — a close-bound AbortSignal, lazy resize/hide/show subscribers, and top-level markLoading/markReady aliases for the loading-overlay controls.
render: (
body: HTMLElement,
ctx: NativeRenderContext,
) => void | ( () => void ) | Promise< void | ( () => void ) >;Legacy unary callbacks (render: ( body ) => …) keep working — the second arg is optional and JS just ignores extras.
| Field | Type | Use |
|---|---|---|
signal |
AbortSignal |
Aborts when the window starts closing. Pass to wp.os.fetch( url, { signal } ) so in-flight requests cancel. |
onResize( cb ) |
( cb: ( w, h ) => void ) => () => void |
Subscribe to body-resize events for this window. Returns an unsubscribe; auto-detaches on close. |
onHide( cb ) |
( cb: () => void ) => () => void |
Fires when the window is minimized. Pause animations/intervals here. |
onShow( cb ) |
( cb: () => void ) => () => void |
Fires when the window is restored. Resume what onHide paused. |
markLoading() |
() => void |
Re-show the loading overlay (e.g. before a refetch). |
markReady() |
() => void |
Hide the overlay + fade body in. |
window.send( channel, payload? ) |
typed pub | Publish on this window's channel — every Window.on(channel, cb) subscriber sees it. |
window.on( channel, cb ) |
typed sub | Subscribe to messages sent FROM outside via Window.send(). |
markLoading / markReady exist both at the top level (ctx.markLoading()) and under ctx.window (ctx.window.markLoading()). The top-level shape exists for { markLoading, markReady, signal, onResize } destructuring; ctx.window is the original surface and stays.
window.openStationNativeWindows = window.openStationNativeWindows || {};
window.openStationNativeWindows[ 'my-feed-inbox' ] = async (
body,
{ signal, onResize, onHide, onShow, markLoading, markReady, window: ch },
) => {
const list = body.querySelector< HTMLElement >( '.feed' )!;
let paused = false;
let cursor: string | undefined;
async function loadPage() {
markLoading();
try {
const res = await wp.os.fetch(
'/wp-json/my-feed/v1/items?cursor=' + ( cursor ?? '' ),
{ signal },
);
if ( signal.aborted ) return;
const json = await res.json();
cursor = json.nextCursor;
renderItems( list, json.items );
} catch ( err ) {
if ( ( err as DOMException ).name === 'AbortError' ) return;
throw err;
} finally {
if ( ! signal.aborted ) markReady();
}
}
onResize( ( w ) => list.style.setProperty( '--feed-width', w + 'px' ) );
onHide( () => { paused = true; } );
onShow( () => { paused = false; loadPage(); } );
// Listen for parent → window pushes, e.g. someone clicked
// "Refresh" from a sibling toolbar.
ch.on( 'feed:refresh', () => { cursor = undefined; loadPage(); } );
await loadPage();
// Optional: a render-returned teardown still runs at close —
// useful for resources the framework can't track on its own.
return () => {
// No need to abort `signal` here — the framework already did.
};
};The framework stores the ctx's disposer on the Window instance and runs it pre-animation when the window closes:
-
controller.abort()fires onctx.signal. - Every
onResize/onHide/onShowsubscription is removed.
The user's render-returned teardown runs AFTER the closing animation. So async paths inside the teardown that branch on signal.aborted already see the flipped value.
Close is not the only unmount. The ⋯ menu's Reload row (and wp.os.windowManager.getById( id ).reload()) runs the same disposal on a native window and then renders again into an emptied body with a brand-new ctx. The ordering differs in one way that matters: on reload the render-returned teardown runs before the body is emptied, so a teardown that reads its own DOM still finds it. Everything the framework tracks — signal, ch.on, onResize/onHide/onShow — is torn down for you either way; anything you registered outside the body needs the teardown to return it, or you leak a copy per reload.
- Existing unary
( body ) => …callbacks: continue to work. JS ignores the extra arg. - Existing callers using
ctx.window.markLoading()(the original surface): still work — that surface is unchanged. -
WindowConfig.onResize(registration-time field): still fires alongsidectx.onResize. Use whichever fits your code shape — the registration-time field is an inline bag for plugins that prefer not to subscribe inside the render body.
-
native-windows.md— the registration end of the contract. -
window-loading.md— the spinner-overlay lifecycle thatmarkLoading/markReadydrive. -
react-to-window-events.md— observability hooks for code outside the render body.
This wiki is generated from the docs/ directory — edits made here are overwritten by the next sync.
To change a page, open a pull request against docs/.
Guides
- Development guide
- Releasing openstation
- Agents security model
- API Index
- Architecture
- Bridge protocol — wiring overview
- <os-*> component reference
- Native Desktop Host — Experimental
- Desktop themes
- Dock customization — two registries, one mental model
- The event-driven framework
- Files on the Desktop
- Folder sharing
- Getting Started
- Hooks Reference
- Icons
- JavaScript Reference
- The Living Tree — algorithm definition
- Mio
- Native Windows & Framework Interop
- Plugin compatibility layer
- Progressive Web App (PWA)
- Station Home
- Using openstation from your own plugin
Migration notes
- Migration: built-in activity channels move to the os/ namespace
- Migration: window, wallpaper and widget bundles load on demand
- Migration — the navigation model
- Migration: a native window's tabs move to the window chrome
All examples
- AI Agents — extend and invoke from a plugin
- wp.os.ai.ask() — programmatic AI Copilot
- Tune the AI model config
- Custom arrange-menu action
- Open a child window its owner can't cover
- Style a specific admin page inside the iframe
- Code Blue — register your plugin's log file
- Open a file in the Code editor (deep-link from any window)
- Connect to a window — title-bar button + iframe pub/sub
- Content changes — live-refresh every window listing your type
- Custom window chrome (Experimental)
- Register a custom unfocused-window effect
- Example: render a data table
- Real file storage — react to uploads, gate policy, share from PHP
- React to a window being set free onto the real desktop
- Cross-window devtools — instrumentation primitives
- Add a dock item with a badge
- Decorate the dock without forking the renderer
- Replace the dock rail entirely
- Retune the Drafts widget's AI writing assistant
- Gate OpenStation by role
- Iframe-initiated window opens
- Build a feed reader without the bookkeeping
- Inject data into openStationConfig
- Render a list without losing clicks — renderKeyedList()
- Example: layout primitives (body → panel → row → col)
- Use <os-*> components from a plugin that ships as a zip
- Restyle and drive Mio
- Add an action that works on a whole selection
- WP Explorer — custom post types and their folder
- Add an action button to a WP Explorer preview pane
- Example: native Posts window
- Example: native window with tabs
- Native windows
- Customize note → post conversion
- Send a notification
- OAuth relay — connect to an external service
- OS-file drop
- <os-flyout> — window-scoped sliding card
- Plugins window — extras
- Track who's around — wp.os.presence
- Example: progress bar
- PWA install — surface your own button
- React to window events
- Example: extend the Trash
- Register a slash-command
- Register a desktop theme from a plugin
- Register a game
- Example: register a desktop icon (Jorvy)
- Register a wallpaper
- Register a widget
- Related entities — extend the title bar's "Related" menu
- The native-window render ctx
- Programmatic folder sharing
- Share state across multi-bundle plugins — wp.os.createSharedStore()
- Example: loading spinner
- Add an opt-in card to Station Home
- Accept drops on your desktop icon
- Give a tile two icons, one per state
- Add a row to a window's ⋯ menu
- Example: window activity & the status ring
- Window controls
- Subscribe to window lifecycle events
- Window links — relate windows and restyle the ties (Experimental)
- Window loading state — spinner overlay & ready signal
- Show a banner at the top of a window
- Pulse a window's icon — Window.requestAttention()
- Register a custom window reveal
- Window slots
- Window themes
- Native window with bundle-bound config