-
Notifications
You must be signed in to change notification settings - Fork 41
migration window tabs
Who this affects: plugins calling openstation_register_window_tab(), and anything styling or querying the <os-tabs> element that used to appear inside a multi-tab native window's body.
Who it does not: plugins using <os-tabs> for a tab group inside content (a switcher within one pane, a segmented view in a panel). That component is unchanged and remains the right tool for that job.
A native window with more than one registered tab used to have its body wrapped in <os-stack> + <os-tabs> + one <os-tabpanel> per tab. The strip was part of the window's content, sitting at the top of the body.
The strip is now built by the shell, in the window chrome, directly under the title bar. It is the same strip an admin-page window wears for its sub-pages, and it is built from the tab metadata the shell already receives, so the markup no longer carries one.
Before:
<div class="os-window__body">
<os-stack gap="12" padding="16">
<os-tabs value="main">
<os-tab value="main">Main</os-tab>
<os-tab value="acme/reports">Reports</os-tab>
</os-tabs>
<os-tabpanel for="main">…</os-tabpanel>
<os-tabpanel for="acme/reports">…</os-tabpanel>
</os-stack>
</div>After:
<nav class="os-window__tabs" role="tablist" aria-label="Jorvy sections">…</nav>
<div class="os-window__body">
<os-stack gap="12" padding="16">
<os-tabpanel for="main">…</os-tabpanel>
<os-tabpanel for="acme/reports">…</os-tabpanel>
</os-stack>
</div>openstation_register_window_tab() is unchanged: same signature, same arguments, same main_tab_padding and openstation_native_window_tab_wrap_padding controls over the wrap. Your tab still appears, in the same order, with the same label. Panes are still <os-tabpanel for="…"> and are still stamped hidden server-side so first paint is correct.
If your plugin only registers tabs and renders their panes, there is nothing to do.
The strip is no longer an <os-tabs> element, so it no longer emits os-tab-change.
// Before
document.querySelector( 'os-tabs' )
.addEventListener( 'os-tab-change', ( e ) => { … } );
// After — bubbles from the window element
windowEl.addEventListener( 'os-window-tab-change', ( e ) => {
e.detail.value; // the tab's value
} );os-window-tab-change bubbles, so a listener on document works too. See javascript-reference.md.
CSS or JS targeting os-tabs inside a native window body no longer matches anything. There is no drop-in selector to swap in, and that is deliberate: the strip is window chrome now, shared with every other window, and a plugin restyling it would be restyling the shell.
Retint it through the tokens the strip reads instead, which reach every window and every desktop theme: --os-tabs-bg and --os-tabs-bg-unfocused (the track), --os-tabs-color (inactive labels), --os-tabs-rail and --os-tabs-rail-width (the accent line), --os-tabs-radius, --os-tabs-slide. On a native window the active tab wears --os-window-bg, the same token that paints the body it belongs to.
If you were setting .value on the <os-tabs> element to switch panes, use the window instead:
const win = wp.os.windowManager.getById( 'jorvy' );
win.activateTab( 'acme/reports' );win.setTabs( entries, activeValue? ) declares tabs for a native window registered purely in JS. Server-registered tabs are declared for you.
Native windows were skipped when the shell built its tab strip, so a window that wanted tabs had to grow its own inside its body. That left two implementations of one design, drifting apart, and only one of them had the tab strip's look, its keyboard, or its accessibility wiring.
There is one now. A native window's tabs get the roving tabindex, the arrow keys, aria-controls paired both ways with each pane, and the same active-tab surface as every other window in the shell, for free.
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