Multisite: Site Spaces — another admin opens on its own desktop - #736
Merged
Conversation
…place Cross-admin activations (the Network Admin tile, its flyout rows, and same-origin links that leave the current admin inside a window) now navigate THIS tab to the other admin's shell instead of forcing a browser tab. Every admin keeps its own desktop under its own session key, so each side restores exactly as it was left: hopping between the network admin and a site reads as switching workspaces, spelled as a navigation. The shell stylesheet opts into cross-document view transitions, so on supporting browsers the two desktops crossfade (a gentle zoom-through, instant under reduced motion) instead of hard-cutting; chromeless iframes never opt in, so in-window navigations never transition. The browser-tab behavior survives as the side-by-side gesture: the dock routes modifier and middle clicks through hopToAdmin()'s split, and the bridge already yields modified clicks to the browser, whose native new-tab handling is the same thing. Cross-origin links (a subdomain or mapped site's admin) were never classed other-admin and keep opening tabs. SystemDockItem.onOpen and SubmenuItem.onSelect now receive the originating MouseEvent when there is one, so any navigating handler can honour the same gestures; handlers that ignore the argument are unaffected.
A cross-admin activation (the Network Admin tile and its flyout rows, a Sites-list Dashboard link inside a window) now opens the target in that admin's own Space: a virtual desktop scoped to it, created on first use and labelled after the site (or Network Admin), slid to with the desktops switcher's own animation, and reused by every later click for the same admin. The page is an ordinary iframe window on that desktop, so this exists exactly where framing does — a cross-origin admin opens a browser tab, and a modifier or middle click opens one anywhere. createSpaceOpener() owns the whole rule; the dock tile and the bridge route through the one opener. The desktop persists its admin as Desktop.scope, and three behaviors hang off it. Its windows persist: the per-admin session scoping makes one exception for a window whose URL scope equals its desktop's declared scope (same host required, and the stored scope must be a normalized admin-scope path). Closing the Space closes them, while windows of the shell's own admin still migrate to the neighbour. And its payloads are quarantined: an os-plugins-changed payload or an os-menu-signature fingerprint from a frame showing another admin is dropped, so the first plugins.php opened in a Space cannot repaint this dock with that admin's menu and Space navigations spend no refresh probes — os-updates-changed stays through, since updates are network-wide and its probe runs against this shell's admin. The admin-scope rule now has three deliberate copies — the bridge's inline one, src/admin-scope.ts for the shell, and PHP's openstation_admin_scope_of_path() — pinned against one shared URL table on both sides of the fence. Folds in the workspace-hop branch it was built on: the modifier gesture split, the event threading through onOpen/onSelect, and the shell's cross-document view-transition opt-in, which still animates the shell-to-shell navigations that remain (typed URLs, bookmarks).
Inside a Space the dock now shows THAT admin's menu — the network menu in the Network Admin Space, a site's own menu in its Space — and the shell's own menu on ordinary desktops, so a Space reads as that admin's desktop, dock and all. Before, the dock stayed the booted admin's menu on every desktop, and the foreign-payload quarantine that kept it that way was racy: when a Space window loaded an allowlisted page (plugins.php, themes.php) and the frame match missed by a beat, the other admin's payload leaked and repainted the shared dock — sometimes the network menu, sometimes the site's, never by design. A plain admin page emits only a menu signature, never its dock menu, so a Space's menu is harvested lazily the first time the user enters it: one hidden probe against that admin (the openstation_menu_refresh probe live refresh already uses, pointed at the Space's admin base), cached, and swapped in on every switch through applyDockItems() — a dock-only repaint. createSpaceDockController() owns it; the switch hook and a post-restore seed (a session can boot straight into a Space) drive it. Until the first harvest lands the previous dock stays, never an empty one; a harvest that lands after the user has left is cached without repainting; a failed one is retried on the next entry. Only the admin MENU is swapped. System tiles stay on every desktop, and the full-payload quarantine still stands: a foreign admin's native windows, widgets and the rest never register in this shell — its dock items are borrowed, and only to paint them.
From inside a Space, a click on a page of the shell's own admin (the main site's Dashboard row in the network Sites list, seen from the main site's shell) opened the window on the Space it was clicked from. The home admin has no Space, its desktop is the primary one, so the opener now slides home first when the active desktop is scoped, and opens the window there.
…ever repaints a Space Two faults behind "the Network Admin Space's dock turns into the site menu when a window opens there". The menu-refresh probe built its placeholder screen from a plain `admin` id, and WP_Screen reads a bare id's admin context off its suffix, so a network probe became a site request from that point on: `self_admin_url()` resolved every network menu slug against the site admin, and the dock harvested for a Space sent its Plugins tile to the site's `plugins.php`. The screen now carries the request's context (`admin-network`, `admin-user`). And a payload from the shell's own admin repainted the dock whatever desktop was showing. It now goes through the per-Space dock controller, which paints it only while the dock shows the home menu and otherwise keeps it for the next switch home.
# Conflicts: # docs/javascript-reference.md # includes/session.php # src/types.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Demo
OpenStation.Multisite.mp4
What it does
A cross-admin click now opens the target in that admin's own Space — a virtual desktop scoped to it. Click the Network Admin tile on a site's desktop and a "Network Admin" Space slides in (the desktops switcher's own animation) holding the network dashboard as a window; click a site's "Dashboard" row in the network Sites window and a Space named after that site slides in with its dashboard. One Space per admin, reused by every later click; Spaces and their windows persist across reloads, and closing one from overview closes its cross-admin windows. The dock follows the Space: inside the Network Admin Space it is the network menu, inside a site's Space it is that site's own menu, and back on your own desktop it is yours again. Your own desktop stays exactly where you left it.
The browser-tab behavior survives as the side-by-side gesture (cmd/ctrl/shift or middle click), and it remains the automatic fallback where a Space is impossible: a subdomain or mapped site's admin is another origin, and a window is an iframe WordPress refuses to serve cross-origin.
Supersedes #735 (closed unmerged): same clarified goal — "open the other admin in another Desktop with a subtle animation" — built on the Desktops feature instead of navigating between shells. The hop branch's machinery rides along: the modifier-gesture split, the
MouseEventthreading throughSystemDockItem.onOpen/SubmenuItem.onSelect, and the shell's cross-document view-transition opt-in (which still animates the shell-to-shell navigations that remain: typed URLs, bookmarks, a kept admin bar).Rationale
#704 sent every cross-admin click to a new browser tab; the follow-up idea was Spaces. With per-admin sessions (#728) plus the Desktops plumbing that already existed (
createDesktop,switchDesktopwith slide animation,closeDesktop, the overview bar), the missing piece was small and precise: teach a desktop which admin it hosts, and teach the three subsystems that care — the session sanitizer, the desktop-close path, and the menu-payload consumer.Implementation
Desktop.scope— an admin-scope path (/site2/wp-admin/,/wp-admin/network/).createSpaceOpener()insrc/multisite/spaces.tsowns the whole opening rule (find-or-create the Space, slide, open the window there; tab fallbacks); the dock tile and the bridge route through the one opener so the two entry points cannot disagree. A target in the shell's own admin never gets a Space: its desktop is the primary one, so from inside a Space the opener slides home first and opens the window there. The bridge posts a newos-iframe-other-admin-linkmessage for same-origin clicks that leave its admin (documented inbridge-protocol.md).openstation_session_window_url_ok()), same host required. The stored scope is validated as a normalized admin-scope path — its ownopenstation_admin_scope_of_path()fixed point, path-rooted (a full URL cannot sneak through). Enforced on write and on read; a window pointing at a dead desktop id loses the privilege with the remap.openstation_menu_refreshprobe live refresh already uses, pointed at the Space's admin base) — cached, and swapped in on every switch throughapplyDockItems(), a dock-only repaint (createSpaceDockController()insrc/multisite/space-dock.ts). Until the first harvest lands the previous dock stays (never an empty one); a harvest that lands after you've left is cached without repainting; a failed one retries on the next entry. Only the admin menu is swapped — system tiles stay on every desktop. A payload from the shell's own admin (a home-admin window living on a Space, or a probe spent while the user stood there) paints only while the dock shows the home menu; otherwise the fresh home items wait for the next switch home (applyHomeDockItems()). The harvest probe keeps the request's admin context: its placeholder screen isadmin-network/admin-user(openstation_menu_refresh_probe_screen_id()), becauseWP_Screenreads a bare id's context off its suffix and a plainadminscreen turned every network probe into a site request, so the harvested network dock pointed its tiles at site pages. This replaces the earlier "dock stays the booted admin" compromise, whose quarantine was also racy: a Space window loadingplugins.phpcould leak the other admin's payload and repaint the shared dock.frameSourceIsOtherAdmin()(insrc/boot/menu-refresh.ts) dropsos-plugins-changedpayloads andos-menu-signaturefingerprints from frames mounted on another admin — otherwise the firstplugins.phpopened in a Space repaints this dock with that admin's menu (the failure modemultisite.mddocuments), and every Space navigation spends a refresh probe.os-updates-changeddeliberately passes: updates are network-wide, and its probe runs against this shell's own admin.adminScope()(the bridge stays a self-contained plain script),src/admin-scope.tsfor the shell, and PHP'sopenstation_admin_scope_of_path()— pinned against one shared URL table (tests/vitest/admin-scope.test.tsand its row-for-row PHPUnit twin).Known quirk, inherited from Spaces generally: the boot entry window opens on the ACTIVE desktop, so reloading while standing in a Space puts your own admin's dashboard there; it migrates safely if the Space is closed.
Testing instructions
On a subdirectory multisite (plugin network-activated):
plugins.php, and the dock stays the network menu after it loads.