-
Notifications
You must be signed in to change notification settings - Fork 41
example revisions
Core's revision browser is a whole admin screen, and the block editor can only reach it by navigating the editor away from itself. On a desktop it is simply another window.
Every post / page / CPT editor window — Gutenberg and classic, any post type that declares revisions support — carries a "View revisions (N)" row in its title-bar ⋯ menu. Picking it opens revision.php as its own desktop window, placed beside the editor and tied to it by a window link: the two are one relation group, so the desktop draws a spline between them, and focusing either raises and highlights the other.
The row appears exactly where there is history to browse. A draft with no revisions has none; the block editor's save-watcher refetches the identity after every save, so the row shows up the moment the first revision exists — no reload. The count is re-read on every menu open, so it counts up while the window stays open.
Both ends are open: a PHP filter rewrites or suppresses the browser per post, and a JS filter reshapes the window before it opens.
add_filter( 'openstation_window_revisions', function ( $revisions, $post ) {
if ( 'acme_contract' !== $post->post_type ) {
return $revisions;
}
// NOTE: the shell only accepts SAME-ORIGIN URLs — a cross-origin
// rewrite hides the row entirely.
return array(
'url' => admin_url( 'admin.php?page=acme-history&contract=' . $post->ID ),
'count' => acme_contract_version_count( $post->ID ),
);
}, 10, 2 );The unfiltered url is revision.php?revision={newest}; count is the total wp_get_post_revisions() will list (autosaves included, matching Core's own revisions meta box and the block editor's revisions panel). Both are empty for attachments, post types without revisions support, posts with no revisions yet, and users lacking edit_post. A malformed return is sanitized back to array( 'url' => '', 'count' => 0 ) — the identity is validated as a unit client-side, and one bad key would discard the whole thing.
To hide the row everywhere:
add_filter( 'openstation_window_revisions', function () {
return array( 'url' => '', 'count' => 0 );
} );The spline exists because the revision browser announces itself as a child of the post it belongs to. A custom history screen gets the same treatment by announcing the same shape:
add_filter( 'openstation_window_content_identity', function ( $identity, $screen ) {
if ( ! $screen || 'acme-history' !== ( $_GET['page'] ?? '' ) ) {
return $identity;
}
$post_id = absint( $_GET['contract'] ?? 0 );
if ( ! $post_id ) {
return $identity;
}
return array(
'type' => 'acme/history',
'id' => $post_id,
'label' => sprintf( 'History of %s', get_the_title( $post_id ) ),
'root' => array( 'type' => get_post_type( $post_id ), 'id' => $post_id ),
);
}, 10, 2 );A ref with root joins that root's group as a child — see window-links.md for the full direction semantics.
wp.hooks.addFilter(
'os.revisions.window-config',
'my-plugin/big-revisions',
( config, { editorWindowId, content } ) => {
console.log( 'opening revisions for', content.type, content.id );
return { ...config, initialState: 'maximized' };
},
);The default config is { id: 'revisions-{type}-{id}', baseId: <same>, url, title, icon: 'dashicons-backup', content: { type: 'revisions', id, root: { type, id } }, …placement }. An invalid return (missing id or url) is ignored with a console warning and the default opens.
About the placement. On a first open — nothing remembered for this window, a desktop-width viewport, a measurable editor — the shell computes geometry that leaves the editor visible: to its right if there is room, to its left if not, otherwise the corner diagonally opposite the editor's own. Once the user moves or resizes the window, the window manager's remembered geometry wins and the shell stops arranging. It is deliberately not a snap: snapped windows report no rect to the window-link frame, so the tidiest-looking arrangement is the one that would cost the spline. Override any of it by returning your own x / y / width / height from this filter.
document.addEventListener( 'os-revisions-opened', ( e ) => {
const { editorWindowId, revisionsWindowId, content } = e.detail;
// e.g. park a note, start a timer, log the comparison
} );
// Same payload on the hook bus:
wp.hooks.addAction( 'os.revisions.opened', 'my-plugin/track', ( detail ) => {
console.log( detail.revisionsWindowId );
} );const ref = wp.os.relations.get( windowId );
ref?.revisionsUrl; // → string | undefined
ref?.revisionCount; // → number | undefinedReference: hooks-reference · javascript-reference · window links · window ⋯ menu rows
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 — the shell boots from its own screen
- Migration: a native window's tabs move to the window chrome
- Migration — WordPress package globals are no longer ambient
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
- Revisions in their own window — extend or redirect "View revisions"
- 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
- Place something where the user can reach it — wp.os.workArea