-
Notifications
You must be signed in to change notification settings - Fork 41
example content changes
The framework ships a generic content-change realtime
layer: any create / update / trash of a post, page, show_ui CPT,
comment, or WooCommerce order is broadcast to every window as
os.<type>.changed, and windows listing that type refresh
themselves — iframe list pages via the built-in soft reload, native
windows via their own subscriptions. This recipe shows what (little)
a third-party plugin needs to do to join in, on both sides.
If your content is a registered post type with show_ui => true and
your list screen is the standard edit.php?post_type=<type>, the
framework already covers you end to end: saves are recorded by the
wp_after_insert_post publisher, and the soft-reload matcher derives
your list page's type from its URL. Open two windows, save in one,
watch the other repaint.
A plugin with its own storage (custom table, settings blob) records mutations explicitly. One call per mutation:
/**
* After my plugin writes a row.
*/
function myplugin_after_save_item( $item_id, $is_new ) {
if ( function_exists( 'openstation_content_changes_record' ) ) {
openstation_content_changes_record(
'myplugin_item', // becomes os.myplugin_item.changed
$item_id,
$is_new ? 'created' : 'updated' // or trashed / untrashed / deleted
);
}
}That single call feeds every delivery path: the instant chromeless-footer broadcast (survives the form-POST → redirect via a per-user buffer) and the Heartbeat catch-all (other tabs, other users, REST/WP-CLI, ≤ one tick).
The generic soft-reload matcher only understands edit.php /
upload.php / edit-comments.php. If your list lives at
admin.php?page=myplugin-items, declare it:
add_filter( 'openstation_soft_reload_rules', function ( $rules ) {
$rules[] = array(
'topic' => 'os.myplugin_item.changed',
'path' => 'admin.php',
'query' => array( 'page' => 'myplugin-items' ),
// Keep the single-item editor out — a background body swap
// would destroy unsaved form state.
'queryAbsent' => array( 'action' ),
);
return $rules;
} );Now any os.myplugin_item.changed broadcast makes an open
admin.php?page=myplugin-items iframe refetch its own URL and swap
#wpbody-content in place — no spinner, no scroll jump. Re-bind any
custom JS after the swap by listening for os-soft-reloaded
on the iframe's document.
(This is exactly how the built-in WooCommerce HPOS orders rule works:
admin.php?page=wc-orders reacts to os.shop_order.changed,
with queryAbsent: [ 'action' ] protecting the order editor.)
Subscribe on the broadcast bus and refetch. Skip your own emissions
by source if your window also publishes:
const unsubscribe = wp.os.subscribe(
'os.myplugin_item.changed',
( { source, action, ids } ) => {
if ( source === 'myplugin-window' ) {
return; // our own mutation already refreshed the table
}
void refreshTable();
}
);
// Call unsubscribe() in your window-closed teardown.Payload contract: { source, action, ids } — source is 'admin'
(server-recorded), 'editor' (block-editor save), 'heartbeat'
(catch-all — MAY repeat a change a faster path already delivered, so
refreshes must be idempotent), or a client emitter's own id.
// Keep a high-churn internal type out of the realtime system.
add_filter( 'openstation_content_changes_should_record', function ( $record, $type ) {
return 'myplugin_log_entry' === $type ? false : $record;
}, 10, 2 );
// Mirror every recorded change into your own realtime channel (SSE, websocket).
add_action( 'openstation_content_change_recorded', function ( $type, $id, $action ) {
myplugin_sse_push( compact( 'type', 'id', 'action' ) );
}, 10, 3 );Full surface: hooks-reference.md → Content-change realtime layer.
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