-
Notifications
You must be signed in to change notification settings - Fork 43
example work area
The desktop area is not all reachable: the bottom dock pill floats over its lower band, and a window or panel that sizes itself against the whole area ends up with its last row of actions under the dock. The work area is the rectangle no shell chrome floats over, and the shell computes it once from the live dock geometry so nothing has to guess.
Three ways to read it, for three kinds of consumer.
wp.os.windowManager.open() takes a full WindowConfig, geometry included; wp.os.openWindow( id ) only opens a registered window and ignores geometry, so pin the rect through the manager:
document.addEventListener( 'os-init', () => {
const { rect } = wp.os.workArea.get(); // desktop-area-local: { x, y, width, height }
wp.os.windowManager.open( {
id: 'my-plugin-report',
title: 'Report',
url: 'admin.php?page=my-plugin-report',
x: rect.x + 24,
y: rect.y + 24,
width: Math.min( 1100, rect.width - 48 ),
height: rect.height - 48,
} );
} );rect is in the same coordinate space a window's x / y resolve in, so no conversion. The shell's own default placements (open, restore, cascade, tile) already do this; you only need it for geometry you pin yourself. Maximize and snap also stop at the work-area boundary and automatically reflow when the dock changes size, placement or behavior. For windows you register rather than open by hand, the os.window.geometry filter receives the same rectangle as ctx.workArea:
wp.hooks.addFilter( 'os.window.geometry', 'my-plugin/bottom-right', ( geometry, ctx ) => {
if ( ctx.baseId !== 'my-plugin-report' || ctx.hasSavedGeometry ) {
return geometry;
}
const { x, y, width, height } = ctx.workArea;
return {
...geometry,
x: x + width - geometry.width - 20,
y: y + height - geometry.height - 20, // above the dock, not under it
};
} );A surface that centres or fits something inside its own element (a canvas, a map, a graph) does not care where the desktop is, only which part of its box is covered. insetsOf( el ) answers that in the element's own pixels:
function fitGraph( host, bounds ) {
const inset = wp.os.workArea.insetsOf( host ); // { top, right, bottom, left } px of `host` outside the work area
const viewW = host.clientWidth - inset.left - inset.right;
const viewH = host.clientHeight - inset.top - inset.bottom;
const scale = Math.min( viewW / bounds.width, viewH / bounds.height );
camera.set( {
scale,
x: inset.left + viewW / 2 - bounds.cx * scale,
y: inset.top + viewH / 2 - bounds.cy * scale,
} );
}Zero everywhere while the host is fully inside the work area, including a maximized window. A window dragged below that boundary reports a bottom.
The insets are on #os-shell as custom properties, so a stylesheet can stop short of the dock without any JS:
.my-plugin-overlay {
position: absolute;
inset-block-start: calc( var( --os-work-area-inset-top, 0px ) + 16px );
inset-block-end: calc( var( --os-work-area-inset-bottom, 80px ) + 16px );
inset-inline-start: calc( var( --os-work-area-inset-left, 0px ) + 16px );
inset-inline-end: calc( var( --os-work-area-inset-right, 0px ) + 16px );
}--os-work-area-width / --os-work-area-height carry the rect's size for calc(). The fallbacks are what applies before the shell has measured once; 80px matches the bottom pill at its default size, which is the placement almost every user has.
The dock can move edges, change size, and collapse for the overview; the browser resizes. Subscribe once and repaint:
const off = wp.os.workArea.subscribe( ( { rect } ) => {
panel.style.maxHeight = `${ rect.height - 48 }px`;
} );
// or, without a handle to keep:
document.addEventListener( 'os-work-area-changed', ( e ) => repaint( e.detail ) );Both fire once per actual change and never on a same-numbers re-measure.
Only chrome that floats over the desktop area claims a band: today, the bottom dock pill, and whatever a custom dock-rail renderer floats over it (every .os-dock in the shell body is measured, using the edge nearest its centre so a wide bottom rail stays a bottom inset). A dock set to the Dynamic behavior (Preferences → Appearance → Desktop layout → Dock behavior) folds into a thin indicator line at its edge and claims nothing; the work area is then the whole desktop. A left or right dock is a flex sibling of the area, so the area is already narrower and the inset is 0. The admin bar sits above the shell in every mode, so the viewport rectangle is already below it.
There is no API for a plugin to claim a band of its own, and that is on purpose: a work area is only useful while few things carve it. If your plugin ships chrome that genuinely needs one, open an issue and say why.
Body-level popovers (context menus, tooltips, the dock's flyouts) position against the viewport and may open over the dock; they are transient chrome, not content, and the work area is not for them.
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
- The App Framework — a window in one PHP file
- Architecture
- Bridge protocol — wiring overview
- <os-*> component reference
- Data model — where OpenStation keeps its data
- 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
- Window-scoped MIO
- Mio
- Mobile — the phone layer
- Multisite
- Native Windows & Framework Interop
- OpenStation Network
- Plugin compatibility layer
- Progressive Web App (PWA)
- Station Home
- Using openstation from your own plugin
- Workspaces
Migration notes
- Migration: built-in activity channels move to the os/ namespace
- Migration — Code Blue becomes an App Framework app
- Migration — AI comment scoring leaves core
- Migration: window, wallpaper and widget bundles load on demand
- Migration — Posts, Pages, Users, User Edit, Plugins and Comments become App Framework apps
- Migration — the navigation model
- Migration — OpenStation Preferences becomes an App Framework app
- Performance settings move to Extended options
- Presence storage migration
- Migration — the Recycle Bin becomes an App Framework app
- Migration — the shell boots from its own screen
- Migration — Station Home becomes an App Framework app
- Migration: a native window's tabs move to the window chrome
- Migration — WP Explorer becomes the my-wordpress app
- Migration — WordPress package globals are no longer ambient
More
All examples
- AI Agents — extend and invoke from a plugin
- wp.os.ai.ask() — programmatic AI Copilot
- Tune the AI model config
- App layout recipes
- 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
- Edit a record with one form values map
- 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
- Repairable form edits with MIO
- Register a window companion
- Pin your app to the phone tab bar, and react to the mode
- Add an action that works on a whole selection
- WP Explorer — custom post types and their folder
- WP Explorer — add a column to the list view
- 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
- Ship a window as an .os.php app
- 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
- Observe stored-file cleanup failures
- 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
- Ship a workspace template