-
Notifications
You must be signed in to change notification settings - Fork 41
migration navigation
The dock, the sidebar and the desktop no longer decide for themselves where a thing belongs. One model in src/nav/ answers that question once and every surface renders the answer.
Read this if your plugin registers a native window with a dock tile, reads or writes the OpenStation Preferences state, implements a dock rail renderer, or points users at the Apps & Plugins settings tab.
The per-item placement map moved from a rail name to a region:
Before (itemVisibility) |
After (navPlacement) |
|---|---|
'dock' |
'rail' |
'desktop' |
'desktop' |
'both' |
'both' |
'hidden' |
'hidden' |
'rail' resolves to the sidebar for a WordPress admin menu while the split layout is on, and to the dock for everything else. Storing the region rather than the rail is what lets a layout switch be a re-render instead of a data migration.
dockOrder became navOrder and lost the dock: / desktop: id prefixes, which named tiles synthesized onto the opposite rail. Nothing synthesizes copies any more: an item is one item wherever it is painted.
Existing users keep their arrangement. openstation_sanitize_os_settings() reads the old fields when the new ones are absent, converts, and writes the result back on the next save. Nothing to run, and no sweep over user meta.
No alias ships. Reading wp.os.getOsSettings().itemVisibility returns undefined, and writing it through updateOsSettings() is ignored. Read navPlacement instead.
Every default now comes from the item's kind rather than from the registration path that produced it:
| Kind | Default |
|---|---|
| A WordPress admin menu | a rail |
| A plugin admin menu | a rail |
| An app: a registered desktop icon, a native window's launcher | the desktop |
| An OpenStation control: Mio, Overview, System, Trash, Exit | a rail |
This is the fix for a class of bug rather than a preference change. An app registered twice — a native window with a dock tile and a desktop icon, which the two APIs explicitly allow — used to get two defaults, one per surface, and the surfaces disagreed until the user picked a value explicitly. Games shipped that way: Preferences read the default off the icon and said "On the desktop" while the dock read it off the window and painted a tile.
Nav items are now collapsed by id (and by the window an icon names) before anything renders, so an app registered any number of ways is one row with one answer.
openstation_register_window( 'my-window', array(
'title' => __( 'My window', 'my-plugin' ),
'template' => 'my_plugin_render',
'placement' => 'dock',
'nav_kind' => 'app', // default; 'control' is for OpenStation's own
) );nav_kind decides the launcher's default placement and which dock zone it sits in. Plugins want 'app' and get it by saying nothing.
placement still accepts 'dock' and 'none', but it is now a proposed default rather than a render instruction. 'dock' puts the launcher on a rail even though apps otherwise default to the wallpaper, which is where it has always been; the user's Navigation pick then wins over it, and a running window gets a tile whatever either says. Nothing to change in existing registrations.
A rail paints WordPress's admin menus, then apps (plugin menus, app launchers, and any running window with no home of its own), then OpenStation's controls, with a divider between each adjacent pair of non-empty zones. Zone membership is derived from the kind, so a tile cannot be dragged into another zone.
The visible consequence: a plugin's native-window launcher now sits beside the plugin menus with no divider between them, instead of behind the divider with the station's own controls.
The shell's write path for a rail's contents. It takes the three zones as arrays of a DockEntry union, because a zone can mix menu-derived and system-derived tiles:
setZones( {
core: [ { type: 'menu', item }, … ],
apps: [ { type: 'menu', item }, { type: 'system', item }, … ],
controls: [ { type: 'system', item }, … ],
} );Optional. A renderer that does not implement it is driven through replaceItems + appendSystemItem / removeSystemItem exactly as before. It loses two things: the zone boundaries, which it had no way to paint anyway, and reordering of system tiles, since that path only adds and removes them (menu-derived tiles still reorder, because replaceItems takes the whole list). Nothing to do unless you want either.
One related change for every renderer: mount() now receives an empty items array. The shell fills the rail through the controller on the same turn mount() returns, so a rail's contents come from exactly one place. Read fullMenu for the whole admin menu.
// Before
{ id, title, icon, affinity: 'core' | 'plugin', placeable }
// After
{ id, title, icon, navKind: 'app' | 'control', placeable, locked }affinity described where a tile came from and had stopped selecting a rail. navKind describes what the tile IS, and selects its zone and its default.
wp.os.appendSystemTile( item ) lost its second affinity argument; put navKind on the tile instead.
-
wp.os.getNavItems()— every navigable thing the shell knows about, whatever surface it is on. -
wp.os.getNav()— the computed result: the dock's three zones, the sidebar, the wallpaper, and the ids present only because their window is open.
The settings tab id changed from apps-icons to navigation. wp.os.openOsSettings( { tabId: 'navigation' } ) is the new address; the old id no longer resolves. Rows are grouped by kind, and the rail option names the rail the user is actually looking at.
- A running window always has a tile. An app whose launcher lives on the wallpaper, or is hidden entirely, gets a transient tile in the dock's apps zone while its window is open, and loses it when the window closes. Previously a hidden item stayed tileless, leaving its window unswitchable with nowhere to minimize into.
- So does a window nothing on a rail answers for. A native window opened programmatically now gets a tile while it is open. Two things are excluded, because the rail already represents them: admin pages, which are reachable through their menu's tile and its hover peek, and any window a system tile's submenu row opens — the System tile carries OpenStation Preferences, so Preferences lights that tile rather than adding one.
- Games defaults to the desktop, which is what its Preferences row always claimed. A user who never touched the setting will see the dock tile go away.
- Dragging is scoped to a zone. It always effectively was; now it is structural rather than a consequence of the sort order.
- Exit OpenStation cannot be moved or hidden, and no longer appears in the preferences list.
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