WP Manifestindependent plugin directory
manifest / admin / sidecar

OpenStation WP Registry grade B-. Medium-severity findings · 4 findingsWP RegistryMedium-severity findings · 4 findingsOpen the reportB-

OpenStation Gutenberg editor sidecar preview

★ 0stars
68release downloads
sidecar-openstation-1.1.0-2026-08-18latest · 2mo ago

Install

Download openstation.zip From the sidecar-openstation-1.1.0-2026-08-18 release on GitHub, Aug 18, 2026

Upload it under Plugins → Add New Plugin → Upload Plugin, then activate.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/regionallyfamous/sidecar/releases/download/sidecar-openstation-1.1.0-2026-08-18/openstation.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

WP OpenStation

A WordPress plugin that reimagines /wp-admin as a desktop operating system. Admin screens open as draggable, resizable, minimizable windows on a desktop, with a left-edge dock built from the admin menu. Purely opt-in per user — the classic admin stays untouched for everyone else, and deactivating the plugin restores vanilla Core exactly.

Zero Core patches. Every feature is wired through public WordPress hooks.

Demo


Contents


Current State

  • Per-user opt-in Admin-bar toggle sets the desktop_mode_mode user meta. A dedicated /openstation/ portal URL auto-enables OpenStation for first-time visitors (gated by openstation_portal_auto_enable) and the admin_init redirect sends opted-in users from /wp-admin/ to the portal (openstation_admin_redirect_to_portal).

  • Desktop shell Fixed-viewport desktop that overlays /wp-admin: wallpaper area, unified dock (placement picked in OS Settings — left / right / bottom, default bottom), right-column widget layer, and full windowing system. openstation_mode_init, openstation_shell_before / _after, and the openstation_shell_config filter are the main extension points.

  • Window system — iframe + native Iframe windows load admin pages with ?openstation_chromeless=1 (chromeless mode). Native windows render directly in the parent DOM via openstation_register_window() / wp.os.registerWindow() — multi-tab native windows are supported through openstation_register_window_tab(). Both types share drag, resize, minimize, maximize, close, fullscreen, and detach-to-new-tab.

  • Gutenberg Sidebar Window Gutenberg editor windows gain a title-bar launch chooser for Post settings, Block settings, and plugin sidebars discovered from Gutenberg's own complementary-area toggles. Choosing an entry opens it in a real sibling OpenStation window. The companion keeps an always-visible Sidebar panel selector directly below its title bar, so choosing another panel switches the existing companion in place without returning to the source window. While the pair is connected, Gutenberg's own Settings and plugin-sidebar buttons in the source editor also switch that same companion instead of reopening the parked source sidebar. The source stays floating and parks its sidebar; an ephemeral iframe-backed companion opens directly against its inline end at the measured Gutenberg sidebar width (320px fallback) and the same height. The shell shifts or minimally narrows the source only when the connected pair would otherwise leave the desktop. The companion loads the same post URL and hides its duplicate canvas so only Gutenberg's complementary area remains beneath narrow connected OpenStation chrome. This is an attached pair, not a full-screen split. A true sibling requires a second Gutenberg document, with a separate data registry, undo history, dirty state, and autosave lifecycle; OpenStation does not synchronize those two editor instances.

  • Dock One unified rail hosting every admin menu — core and plugin alike — plus shell-level system tiles. Placement (left / right / bottom) is the user's OS Settings preference. Core menus are ordered before plugin menus; per-item hiding via openstation_dock_placement ('hidden'). Per-item multi-window support via openstation_dock_item_multi. Letter-badge icon fallback for plugins without icon art.

  • Virtual desktops (“Spaces”) Multiple desktops per user, each with its own window set. Overview grid (zoom-out view) surfaces the Spaces switcher, thumbnails, and create/close controls.

  • Arrange & snap Admin-bar Arrange menu: Cascade, Tile, Overview, Snap to grid. Plugins contribute custom entries via openstation_arrange_menu_items and react to clicks via os.arrange.custom-action. Tile grid dimensions and snap cell size are both filterable.

  • Wallpaper registry Server- and client-side registration (openstation_register_wallpaper() / wp.os.registerWallpaper()). CSS presets + canvas (WebGL/2D) wallpapers with collision-aware surface data (wp.os.getWallpaperSurfaces()) for snow/rain/physics effects. In-panel renderEditor callback for custom controls, shared vendor-module loader (pixijs pre-registered).

  • Widgets Right-column floating cards, optionally draggable / resizable outside the column. openstation_register_widget() / wp.os.registerWidget(). Built-in clock. User placement persists per-user in localStorage.

  • Desktop icons Wallpaper-layer shortcuts via openstation_register_icon() — targets a registered native window or an admin URL.

  • AI Assistant + slash commands Cmd+K palette backed by an OpenAI agentic loop whose search_posts / search_pages / search_comments tools run WordPress's native keyword search. Admin-configured API key + model picker. The only automatic AI analysis is comment spam scoring (on comment save), which feeds the comments-window spam score; posts, pages, and terms are not analyzed. wp.os.registerCommand() adds slash commands with autocomplete (suggest()), confirm dialogs (ctx.confirm()), and full lifecycle hooks (before-run / after-run / error). Built-in /open [window] is extensible via os.open-command.items.

  • Palette registry Cmd+K cycles through all registered palettes (wp.os.registerPalette()) — the AI assistant is palette 0 by default; additional plugin overlays share the shortcut.

  • Cross-frame drag bridge Media-library attachments drag across iframe boundaries via coordinated postMessage. Site-wide toggle through the Extended Options REST endpoint.

  • Toast notifications Shell-level toasts rendered via the <os-toast> component. Plugins register their own tone/icon via the openstation_toast_types filter. Iframe pages raise a toast through the os-notification bridge message — it survives the iframe's own lifecycle.

  • OS Settings Native-window settings panel: wallpaper picker (with HD-only media filter), accent color swatches + custom gradient editor, dock size slider, AI platform config, and per-user default-on-startup window. Persisted via /desktop-mode/v1/os-settings.

  • Session persistence Full window stack (including desktops, focus, state) is debounce-saved to /desktop-mode/v1/session and restored without layout flicker. Viewport-shrink clamping keeps off-screen windows reachable.

  • postMessage bridge Typed messages for title changes, navigation (same-origin validated), focus, color-scheme sync, screen-meta panels (Screen Options / Help), external-link capture, iframe-ready handshake, and observability (iframe-error, iframe-network).

  • UI component library ~25 <os-*> web components (os-button, os-menu, os-panel, os-range-field, os-swatch, os-toast, os-tabs, …) available to plugin authors — rendered server-side via openstation_component() or imported in TS.

  • i18n Full gettext coverage across PHP and TypeScript; Spanish translation shipped. Strings go through wp.i18n (__, _x, _n, sprintf) directly — no shell-specific re-export.

  • Component registration API Stable openstation_register_* functions for windows, widgets, wallpapers, icons, and window tabs. All return true / WP_Error with documented error codes.

  • Public hook API Comprehensive PHP and JS hook surface — dock items, placement, multi-window, native-window lifecycle, widget lifecycle, wallpaper lifecycle + surfaces, window lifecycle, iframe observability, arrange actions, virtual-desktop transitions, palette registration, command lifecycle, batch close, AI prompt + model + post-type filters, accents, toast types, default wallpaper. See docs/hooks-reference.md and docs/javascript-reference.md.


Still ahead

  • Mobile (phone OS) — purpose-built home-screen grid, full-screen apps, app switcher, gesture nav, bottom tab bar.
  • Tablet hybrid — split view, slide-over, horizontal dock. wp.os.mode = 'desktop' | 'tablet' | 'mobile' surface.
  • Cross-window drag & drop (the North Star) — extend the current drag bridge to Media → Gutenberg block insertion, with pluggable mime-type negotiation.
  • Polish — color-scheme-aware variables across all shell surfaces, View Transitions API animations, full a11y audit (ARIA, focus traps, keyboard nav).
  • …and a whole lot more hooks, filters, and actions — every new surface lands with its own extension points, so this list keeps growing.

See docs/architecture.md for how the pieces fit together and docs/hooks-reference.md for the hook surface (current and planned).


Repository layout

.
├── desktop-mode.php       # bootstrap: header, constants, require_once of includes/
├── includes/              # PHP subsystems
│   ├── helpers.php              admin-bar.php       ajax.php
│   ├── assets.php               render.php          portal.php
│   ├── session.php              default-window.php  components.php
│   ├── os-settings.php          extended-options.php
│   ├── accents.php              wallpapers.php      toast-types.php
│   ├── media-query.php
│   └── ai-copilot/              # AI assistant (OpenAI client, analysis, search, jobs)
├── assets/                # hand-authored CSS + JS build output
│   ├── css/  desktop.css, windows.css, dock.css, chromeless.css, variables.css
│   │          variables.css carries the OpenStation palette — every design
│   │          token the shell and the <os-*> kit read, declared once
│   ├── fonts/    Geist + Geist Mono (variable, SIL OFL 1.1)
│   ├── wallpapers/  the four brand desks: galaxy (default), space,
│   │          holomesh, pulsemesh
│   ├── desktop-themes/legacy/
│   │          the built-in "Desktop Mode (Legacy)" theme — a frozen snapshot of
│   │          every token at its pre-brand value; never regenerated.
│   │          Zip it with npm run package:legacy-theme
│   └── js/   Vite bundles (gitignored; regenerate with npm run build) — only
│             admin-bar.js and media-library-enhanced.js are hand-written and tracked
├── src/                   # TypeScript source — compiled by Vite
│   ├── desktop.ts / dock.ts / hooks.ts / commands.ts / palette-registry.ts
│   ├── ai-assistant/ + drag-bridge.ts / toast.ts / desktop-icons.ts
│   ├── native-windows.ts / built-in-commands.ts / public-api.ts / types.ts
│   ├── window/          # Window class — DOM, pointer, tabs, iframe bridge
│   ├── window-manager/  # stack, desktops, arrange, snap, overview
│   ├── wallpapers/      # registry, layer, surfaces, server sync, vendor loader
│   ├── widgets/         # registry, layer, frame, picker, storage
│   ├── settings/        # OS Settings panel sections
│   ├── ui/              # <os-*> web components
│   ├── modules/         # vendor-script lazy-loader
│   └── plugins/         # built-in demos (animated-logo-wallpaper)
├── docs/                  # developer-facing docs (source of truth for plugin authors)
├── extensions/            # bundled sibling plugins (see "Bundled extensions" below)
├── tests/                 # PHPUnit + Vitest
├── languages/             # .po / .mo (es shipped)
├── bin/                   # package-zip helpers
├── package.json           # devDeps (vite, typescript, vitest)
├── vite.config.js         # Vite lib-mode: src/desktop.ts → assets/js/desktop[.min].js (IIFE)
├── vitest.config.ts
└── tsconfig.json

Bundled extensions

The extensions/ directory hosts sibling plugins that build on OpenStation's public APIs. Each one is a standalone WordPress plugin (Requires Plugins: desktop-mode) installed from its own zip — run ./bin/package-extensions.sh to build one <slug>.zip per extension under dist/; see docs/RELEASE.md for the full packaging steps. (extensions/base/ is a shared base library for extension authors, not an installable plugin.)

  • Code Editor (desktop-mode-code-editor) — a Monaco-backed Code editor native window for browsing and editing files inside wp-content. Editing requires the edit_plugins capability and is disabled entirely when DISALLOW_FILE_EDIT is set.
  • Cron Manager (desktop-mode-cron-manager) — a Cron Jobs native window for browsing, editing, deleting, and running WP-Cron events. Gated by manage_options.
  • Popup Siege (desktop-mode-popup-siege) — a 90-second Breakout-style archive rescue game with OpenStation leaderboards, play-time tracking, and score-to-beat challenges. The deterministic game runtime is lazy-loaded on first play.
  • SOL Inbound Monologue (desktop-mode-feed-buddy) — an AIM-era RSS/Atom reader with a movable buddy-list widget, a native conversation-style reader window, per-user subscriptions and unread state, safe server-side feed discovery, and optional synthesized chimes.
  • phpMyAdmin (desktop-mode-phpmyadmin) — embeds a bundled phpMyAdmin install as a native window. Local environments only: the window registers solely when wp_get_environment_type() is 'local', because the bundled phpMyAdmin runs with auth_type=config and reuses the WordPress DB credentials — any visitor who finds the URL gets full DB access. The manage_options check only hides the shortcut from lower-privilege users; it does not gate the underlying URL.

How to run it

Quick install

Just want to try it? Grab the pre-built zip and upload it to any WordPress — Studio by WordPress.com, wp-env, or a hosted site. No Node, no build step.

  1. Download openstation.zip from the latest release (or pick a specific version from the releases page).
  2. In WP Admin: Plugins → Add New → Upload Plugin, choose the zip, and activate.
  3. Click the desktop icon in the admin bar's top-right corner. The admin reloads inside the desktop shell. Click the same icon again to return to classic admin.

Development setup

For hacking on the plugin: clone the repo, run the build in watch mode, and load it into a local WordPress via symlink so every save is one browser refresh away.

1. Install dependencies

npm install

2. Build the TypeScript bundle

The plugin uses Vite in library mode. esbuild handles transpile and minify, so builds finish in ~70 ms per bundle.

Full build — runs the PixiJS vendor-copy step plus every build:* target defined in package.json (one Vite bundle each; the scripts block in package.json and the entry map in vite.config.js are the authoritative target list):

npm run build

Writes one assets/js/<target>.js / .min.js pair per target, including:

  • assets/js/desktop.js / .min.js — main shell bundle (loaded based on SCRIPT_DEBUG).
  • assets/js/iframe-bridge.js / .min.js — opt-in bridge that gives any same-origin iframe access to wp.os.iframe.*.
  • assets/js/recycle-bin.js / .min.js — Recycle Bin native window.
  • assets/js/posts-window.js / .min.js — Native Posts window (the <os-table> replacement for the edit.php iframe; opt-in per user via OS Settings → Features).

Development watch — auto-recompiles the unminified bundle on save:

npm run dev

Leave it running in a separate terminal; refresh the browser after each save. Set define( 'SCRIPT_DEBUG', true ) in wp-config.php so WordPress picks up the unminified bundle during development.

3. Load into a local WordPress

You need a running WordPress to load the plugin into. Pick whichever is easier.

Studio, wp-env, or a hosted WP

Run npm run package to build a zip from HEAD (with correct 0644 / 0755 permissions), then follow the Quick install steps 2–3 to upload and activate it. Re-package and re-upload after each change.

If you changed source, run npm run build before npm run package — the Vite output is gitignored, and bin/package.sh splices the built files into the zip from your working tree.

npm run package:legacy-theme builds dist/desktop-mode-legacy-theme.zip — the built-in Legacy desktop theme as an installable theme ZIP. The plugin registers Legacy from code either way; the zip is for handing it to someone else or forking it into a theme of your own.

Clone wordpress-develop and symlink

Gives you the full dev loop: npm run dev rebuilds on save, a browser refresh picks it up.

# clone Core's Docker-based dev host alongside this repo
git clone https://github.com/WordPress/wordpress-develop.git
cd wordpress-develop
npm install

# symlink this plugin into the WP plugins directory
ln -s "$(pwd)/../alcazaba-plugin" src/wp-content/plugins/desktop-mode

# boot + install WordPress
npm run env:start      # nginx + PHP + MySQL in Docker
npm run env:install    # installs WordPress

Site: http://localhost:8889 Admin: http://localhost:8889/wp-admin/ Credentials: admin / password

Stop the environment with npm run env:stop (from the wordpress-develop directory). Activate the plugin per Quick install steps 2–3.


Requirements

  • WordPress 6.0+
  • PHP 7.4+

For plugin authors

This plugin is built to be extended. Every significant behavior is hookable — drop an icon on the desktop, add a dock item, gate OpenStation by role, react to window events, or register a native window, all from your own plugin with zero patches here.

See docs/ — the developer documentation index.

Quick links:

License

GPLv2 or later. See LICENSE.

Releases

19 releases. Each count is every asset in that release; expand a row for the breakdown.

Tag
Published
Assets
Downloads
Aug 18, 2026 2mo ago
openstation.zip
10
Aug 18, 2026 2mo ago
openstation.zip
5
Aug 18, 2026 2mo ago
openstation.zip
1
Aug 18, 2026 2mo ago
openstation.zip
5
Aug 18, 2026 2mo ago
openstation.zip
4
Aug 18, 2026 2mo ago
openstation.zip
2
Aug 18, 2026 2mo ago
openstation.zip
5
Aug 18, 2026 2mo ago
openstation.zip
2
Aug 18, 2026 2mo ago
openstation.zip
3
Aug 18, 2026 2mo ago
openstation.zip
3
Aug 18, 2026 2mo ago
openstation.zip
8
Aug 18, 2026 2mo ago
openstation.zip
3
Aug 18, 2026 2mo ago
openstation.zip
1
Aug 18, 2026 2mo ago
openstation.zip
2
Aug 17, 2026 2mo ago
openstation.zip
6
Aug 17, 2026 2mo ago
openstation.zip
4
Aug 17, 2026 2mo ago
openstation.zip
3
Aug 17, 2026 2mo ago
pr-2-72d0772abc2d7f2e6eefe1e45a2f51654ea3d461.zip +9 assets
0
pr-2-72d0772abc2d7f2e6eefe1e45a2f51654ea3d461.zip0
pr-1-6ecfd759c7480e7dad72aafb19885974677a1832.zip0
pr-3-b8a2f04c8e69a4dfc533578d0ba081ee81664224.zip0
pr-4-be94c7b225e27b775d6476bd3c2453f6466ee54c.zip0
pr-4-005b5d7c1b05278b13554788ce38e8e4855d972f.zip0
pr-4-c30c91889bf0a2950553118d4e26f286b1b9c87d.zip0
pr-4-fe74662f532ccff4d0b32e9fe1aac4ac26d0f53f.zip0
pr-4-0cb154e03bb878a7073016446038335b7060ddb7.zip0
pr-4-1c10df0cdb737739dcc5d88f6ce31989a553d788.zip0
pr-4-4f509daa043dfa18f98fb41b2c9fa8701a587b81.zip0
Aug 17, 2026 2mo ago
openstation.zip
1
B- grade

Security

OpenStation 1.1.0 · audited by WP Registry

Medium-severity findings.

4 medium
Audited release
1.1.0
Findings
4
Worst severity
medium
Content hash
f5e4a37ee692bc7c35736615…

Findings

  • medium Comment-stats REST returns approved comments on private posts to any logged-in user

    information_disclosure

    GET /desktop-mode/v1/comment-stats/<id> is gated only on is_user_logged_in(). The callback allows any approved comment (or the author's own) without current_user_can('read_post', $comment->comment_post_ID). It then returns the full comment body (rendered and raw), parent-comment excerpt, up to 20 reply excerpts, and a parent-post object including title, status, type, permalink, and author. Approved comments on private or password-protected posts are therefore enumerable by comment ID (sequential) to a Subscriber. Core's comments REST controller refuses this case via read_post; this dossier route does not. Email/IP stay behind moderate_comments.

    includes/my-wordpress/comment-stats.php:26-78,130-158

    Recommendation

    After loading the comment, require current_user_can('read_post', (int) $comment->comment_post_ID) (and treat a missing parent as 404). Apply the same gate to parent-comment and reply rows. Mirror WP_REST_Comments_Controller.

  • medium User-footprint REST leaks unpublished post/page counts and revision activity

    information_disclosure

    GET /desktop-mode/v1/user-footprint/<id> is reachable by any logged-in user, including Subscriber, with no OpenStation opt-in required. Timeline rows correctly drop unpublished titles unless read_post passes, but totals.posts and totals.pages COUNT every status except auto-draft/inherit/trash (so draft/private/pending/future inventory of any author is visible). totals.updates and the daily updates heatmap count revisions on those unpublished parents. Sibling user-stats.php already strips unpublished counts unless list_users or self-view; footprint never applied that split. A Subscriber can therefore map how much unpublished work each user has and when they were editing it.

    includes/my-wordpress/user-footprint.php:59-78,188-208,438-477

    Recommendation

    Reuse the user-stats $can_see_private (list_users or self) split: unpublished totals and the updates heatmap must be publish-only (or omitted) for everyone else. Keep the existing per-row read_post filter on the timeline.

  • medium Games user-search is an email oracle for any OpenStation user

    information_disclosure

    When the admin opt-in Games extended option is on, GET /desktop-mode/v1/games/users/search is allowed for any logged-in user with OpenStation enabled (default Subscriber can flip that via the admin-bar nonce on profile.php or GET /openstation/). WP_User_Query search_columns include user_email, user_login, display_name, and user_nicename. The JSON omits the email but returns id, display name, nicename, and avatar for matches — so searching an address confirms the account and identifies the person. The files share-picker documents this exact risk and requires edit_posts; the games sibling was left on read. Games default off, but the route is shipped and becomes live the moment an admin enables the feature.

    includes/games/rest.php:524-572

    Recommendation

    Drop user_email from search_columns (search display_name / nicename only). Optionally require the same edit_posts gate as /files/users/search, or restrict results to users who themselves have OpenStation enabled.

  • medium AI search excerpts password-protected posts and comments without a read_post check

    information_disclosure

    POST /desktop-mode/v1/ai/search allows any logged-in user with read who has enabled the per-user AI toggle (OS Settings, itself gated only on OpenStation enabled — which a Subscriber can turn on). search_posts/search_pages query post_status=publish, which includes password-protected posts, and pass post_content through a 300-char excerpt to the model and UI. search_comments / search_comments_by_post pull approved comments (and parent titles) with no parent read_post / post_password_required check; build_entity repeats the excerpt. Live only when WordPress 7 AI client + a configured provider exist, but then it is a content oracle for protected bodies and comments on private posts.

    includes/ai-copilot/search.php:392-405,459-510,1917-1948

    Recommendation

    Skip posts where post_password_required() is true unless the caller already unlocked them or can edit_post. For comments, require current_user_can('read_post', $comment->comment_post_ID) before including body or parent title. Consider requiring edit_posts for /ai/search rather than read.

WP Registry hashes the installable build and reports on that exact bytes-for-bytes copy. Embargoed findings are withheld until they are disclosed, so a clean verdict means nothing public is outstanding. WP Manifest does not audit code itself.