Remove macOS window-chrome (traffic-light dots, minimize taskbar)

Strip the whole window metaphor as UI baggage: the traffic-light dots
injected at the top of every page, the minimize taskbar, and the
minimize/maximize state and all their CSS. windowChrome.js and taskbar.js
are gone; the only structural DOM the shell still does — wrapping the
takeover Dashboard in .aurora-dashboard-frame and reparenting its notices
into the card — moved to a focused dashboardFrame.js (initDashboardFrame()).

SiteShell.vue drops the taskbar rendering/state; shell-frame.css drops the
.aurora-winbar*/.aurora-taskbar*/minimized/maximized rules and the
--aurora-taskbar-h / --aurora-editor-winbar-h vars, and zeroes the editor
and theme-install frame offsets that reserved a top strip for the dots.
shell.js 16.4->13.4 kB. The editor "back to list" crumb lived in the bar
and was removed with it (Gutenberg's own W-logo still navigates back).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
NixReaper
2026-07-12 11:44:57 -05:00
co-authored by Claude Opus 4.8
parent dc2a4435c5
commit 53ca6f62b9
6 changed files with 51 additions and 495 deletions
@@ -31,7 +31,7 @@ defineProps({
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
/* Deeper than the generic --aurora-card-bg — the Dashboard is now /* Deeper than the generic --aurora-card-bg — the Dashboard is now
wrapped in its own frame (windowChrome.js's .aurora-dashboard-frame, wrapped in its own frame (dashboardFrame.js's .aurora-dashboard-frame,
using --aurora-frame-bg), and that's close in tone to --aurora-card-bg using --aurora-frame-bg), and that's close in tone to --aurora-card-bg
for most presets, so cards would nearly disappear into it otherwise. */ for most presets, so cards would nearly disappear into it otherwise. */
background: var(--aurora-card-bg-deep, var(--aurora-card-bg)); background: var(--aurora-card-bg-deep, var(--aurora-card-bg));
+4 -80
View File
@@ -3,8 +3,7 @@ import { ref, onMounted } from 'vue';
import Sidebar from './components/Sidebar.vue'; import Sidebar from './components/Sidebar.vue';
import Toolbar from './components/Toolbar.vue'; import Toolbar from './components/Toolbar.vue';
import { applyThemePalette, applyFontFamily } from '@/pages/settings/applyLiveTheme.js'; import { applyThemePalette, applyFontFamily } from '@/pages/settings/applyLiveTheme.js';
import { initWindowChrome } from './windowChrome.js'; import { initDashboardFrame } from './dashboardFrame.js';
import { getMinimizedWindows, removeMinimizedWindow } from './taskbar.js';
const props = defineProps({ const props = defineProps({
serverData: { serverData: {
@@ -25,21 +24,7 @@ const toggleMenu = () => {
localStorage.setItem('aurora-menu-collapsed', collapsed.value ? '1' : '0'); localStorage.setItem('aurora-menu-collapsed', collapsed.value ? '1' : '0');
}; };
// Every minimized window (taskbar.js persists the list in sessionStorage
// across the page loads minimizing triggers). Rendered as taskbar items at
// the bottom of every page since SiteShell mounts on all of them — clicking
// one navigates back to it and removes just that entry.
const minimizedWindows = ref([]);
const restoreWindow = (win) => {
removeMinimizedWindow(win.id);
window.location.href = win.url;
};
onMounted(() => { onMounted(() => {
minimizedWindows.value = getMinimizedWindows();
// Reserve bottom space for the taskbar (shell-frame.css) so page content
// stops above it instead of running underneath.
document.documentElement.classList.toggle('aurora-has-taskbar', minimizedWindows.value.length > 0);
const settings = props.serverData?.settings || {}; const settings = props.serverData?.settings || {};
// Every theme preset is its own self-contained dark-toned palette now — // Every theme preset is its own self-contained dark-toned palette now —
// no separate light/dark mode, so this class is always on. It's kept // no separate light/dark mode, so this class is always on. It's kept
@@ -49,7 +34,7 @@ onMounted(() => {
applyThemePalette(settings.theme_preset || 'indigo'); applyThemePalette(settings.theme_preset || 'indigo');
applyCollapsed(); applyCollapsed();
applyFontFamily(settings.font_family); applyFontFamily(settings.font_family);
initWindowChrome(props.serverData?.adminUrl); initDashboardFrame();
// Signal the shell is live: reveals the framed content and commits to // Signal the shell is live: reveals the framed content and commits to
// keeping native chrome hidden (the PHP failsafe stops watching once // keeping native chrome hidden (the PHP failsafe stops watching once
// aurora-ready is present). // aurora-ready is present).
@@ -73,74 +58,13 @@ onMounted(() => {
:server-data="serverData" :server-data="serverData"
@toggle-menu="toggleMenu" @toggle-menu="toggleMenu"
/> />
<div v-if="minimizedWindows.length" class="aurora-taskbar">
<button
v-for="win in minimizedWindows"
:key="win.id"
type="button"
class="aurora-taskbar__item"
@click="restoreWindow(win)"
>
<span class="aurora-taskbar__dot" />
{{ win.title }}
</button>
</div>
</div> </div>
</template> </template>
<style scoped> <style scoped>
/* Sidebar, Toolbar and the taskbar are all position:fixed, so this /* Sidebar and Toolbar are both position:fixed, so this wrapper has no
wrapper has no layout role — it just groups them under one mounted layout role — it just groups them under one mounted root. */
root. */
.site-shell { .site-shell {
display: contents; display: contents;
} }
/* Full-width docked bar, but inset with the same gap the sidebar floats
with (not flush against the browser edges or the window above it) and
fully rounded + shadowed so it reads as its own separate surface rather
than an extension of the content frame it sits under. Page content
reserves space above it via html.aurora-has-taskbar in shell-frame.css. */
.aurora-taskbar {
position: fixed;
left: calc(var(--aurora-sidebar-w) + var(--aurora-sidebar-gap) * 2);
right: var(--aurora-sidebar-gap);
bottom: var(--aurora-sidebar-gap);
height: var(--aurora-taskbar-h);
display: flex;
align-items: center;
gap: 8px;
padding: 0 16px;
background: var(--aurora-card-bg);
border: 1px solid var(--aurora-card-border);
border-radius: var(--aurora-radius-lg);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
box-sizing: border-box;
z-index: 200001;
overflow-x: auto;
transition: left 0.2s ease;
}
.aurora-taskbar__item {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
background: var(--aurora-bg-subtle);
border: 1px solid var(--aurora-border);
border-radius: 999px;
padding: 8px 18px 8px 10px;
color: var(--aurora-text);
font-size: 0.8125rem;
cursor: pointer;
}
.aurora-taskbar__item:hover {
border-color: var(--aurora-accent);
}
.aurora-taskbar__dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #febc2e;
flex-shrink: 0;
}
</style> </style>
+35
View File
@@ -0,0 +1,35 @@
/**
* Structural setup for the takeover Dashboard.
*
* DashboardTakeover.php renders the dashboard into a separate
* #aurora-admin-dashboard-root sibling (above the hidden #wpbody), so unlike
* every other page it never sits inside #wpbody-content and never got that
* element's card frame. Wrap it in .aurora-dashboard-frame so it gets the
* same card treatment, and pull the custom admin notices
* (Notices::render_for_dashboard(), printed as a sibling just before the
* mount point) into that frame so they render as part of the card.
*
* This is the only DOM restructuring the shell does — there is no window
* chrome (title bars, traffic-light controls, minimize taskbar); that whole
* macOS-window metaphor was removed.
*/
export function initDashboardFrame() {
const dashboardRoot = document.getElementById('aurora-admin-dashboard-root');
if (!dashboardRoot) {
return;
}
// Dedupe: if SiteShell re-mounts, don't wrap a second time.
if (dashboardRoot.parentNode.querySelector('.aurora-dashboard-frame')) {
return;
}
const frame = document.createElement('div');
frame.className = 'aurora-dashboard-frame';
dashboardRoot.parentNode.insertBefore(frame, dashboardRoot);
const notices = document.getElementById('aurora-admin-dashboard-notices');
if (notices) {
frame.appendChild(notices);
}
frame.appendChild(dashboardRoot);
}
+11 -172
View File
@@ -14,13 +14,6 @@
:root { :root {
--aurora-sidebar-w: 264px; --aurora-sidebar-w: 264px;
--aurora-toolbar-h: 56px; --aurora-toolbar-h: 56px;
/* SiteShell.vue's taskbar, shown only while a window is minimized
(html.aurora-has-taskbar toggles the bottom space reserved for it). */
--aurora-taskbar-h: 56px;
/* Height of the strip reserved at the top of the block editor's frame
for .aurora-winbar--editor's traffic-light dots (a sibling element,
not a child of the skeleton — see windowChrome.js). */
--aurora-editor-winbar-h: 44px;
/* Space between the sidebar card and the browser's left/top/bottom /* Space between the sidebar card and the browser's left/top/bottom
edges (Sidebar.vue) — the toolbar and #wpcontent offsets below add edges (Sidebar.vue) — the toolbar and #wpcontent offsets below add
the same amount so nothing overlaps the floating sidebar. */ the same amount so nothing overlaps the floating sidebar. */
@@ -170,12 +163,6 @@ html.aurora-ready #site-editor {
border-radius: var(--aurora-radius-lg); border-radius: var(--aurora-radius-lg);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
transition: left 0.2s ease; transition: left 0.2s ease;
/* Reserves a strip at the top for .aurora-winbar--editor's dots (a
sibling, not a child — see windowChrome.js) so they sit in their own
clear space instead of overlapping the editor's own logo/toolbar row.
border-box so the padding comes out of the top/left/right/bottom
inset box already defined above, rather than adding to it. */
padding-top: var(--aurora-editor-winbar-h);
box-sizing: border-box; box-sizing: border-box;
/* border-radius alone doesn't clip children — Gutenberg's own white /* border-radius alone doesn't clip children — Gutenberg's own white
canvas background ran straight past the rounded bottom corners as a canvas background ran straight past the rounded bottom corners as a
@@ -250,48 +237,6 @@ html.aurora-ready body.post-php #wpbody-content {
padding: 0 !important; padding: 0 !important;
} }
html.aurora-ready body.block-editor-page #wpbody-content > .aurora-winbar,
html.aurora-ready body.post-new-php #wpbody-content > .aurora-winbar,
html.aurora-ready body.post-php #wpbody-content > .aurora-winbar {
display: none !important;
}
/* SiteShell.vue's taskbar floats along the bottom edge (inset by
--aurora-sidebar-gap, same as the sidebar) only while a window is
minimized. A plain margin-bottom only guarantees a gap once the page is
scrolled all the way down — content taller than the viewport still
renders underneath the fixed taskbar on first paint, since a fixed
element doesn't reserve space from the rest of the page's flow. Instead,
cap the content box's own height to whatever's actually left between the
toolbar and the taskbar and let *it* scroll internally — the same way
the toolbar's reserved space at the top never lets content start behind
it, not just "eventually scroll clear of it". */
html.aurora-has-taskbar #wpbody-content,
html.aurora-has-taskbar .aurora-dashboard-frame {
/* #wpbody-content's base rule sets min-height: calc(100vh - toolbar-h -
40px) for the normal (no-taskbar) case — per spec, when min-height is
larger than max-height, min-height always wins and max-height is
silently ignored. Without this override the box kept sizing to that
taller min-height regardless of the taskbar, which is why the
max-height below appeared to do nothing. */
min-height: 0;
max-height: calc(
100vh - var(--aurora-toolbar-h) - 16px - 24px - var(--aurora-taskbar-h) -
var(--aurora-sidebar-gap) * 2
);
overflow-y: auto;
overflow-anchor: none;
}
/* The block editor's skeleton is already positioned with fixed insets
(top/left/right/bottom) rather than margin, so reserving taskbar space
is just moving its bottom inset in — no max-height/overflow trick
needed there, unlike the margin-based elements above. */
html.aurora-has-taskbar .interface-interface-skeleton,
html.aurora-has-taskbar #site-editor {
bottom: calc(24px + var(--aurora-taskbar-h) + var(--aurora-sidebar-gap) * 2) !important;
}
/* Theme installer preview ("Add Themes" > click a theme > full "Preview & /* Theme installer preview ("Add Themes" > click a theme > full "Preview &
Install" overlay, theme-install.php). WP core's own .wp-full-overlay Install" overlay, theme-install.php). WP core's own .wp-full-overlay
component (shared with the Customizer, but only .theme-install-overlay component (shared with the Customizer, but only .theme-install-overlay
@@ -355,86 +300,21 @@ html.aurora-ready .theme-install-overlay.collapsed {
html.aurora-ready .theme-install-overlay .wp-full-overlay-sidebar { html.aurora-ready .theme-install-overlay .wp-full-overlay-sidebar {
left: calc(var(--aurora-sidebar-w) + var(--aurora-sidebar-gap) * 2 + 24px) !important; left: calc(var(--aurora-sidebar-w) + var(--aurora-sidebar-gap) * 2 + 24px) !important;
top: calc(var(--aurora-toolbar-h) + 16px + var(--aurora-editor-winbar-h)) !important; top: calc(var(--aurora-toolbar-h) + 16px) !important;
bottom: 24px !important; bottom: 24px !important;
height: auto !important; height: auto !important;
border-top-left-radius: var(--aurora-radius-lg); border-top-left-radius: var(--aurora-radius-lg);
} }
html.aurora-ready .theme-install-overlay .wp-full-overlay-main { html.aurora-ready .theme-install-overlay .wp-full-overlay-main {
top: var(--aurora-editor-winbar-h) !important; top: 0 !important;
border-top-right-radius: var(--aurora-radius-lg); border-top-right-radius: var(--aurora-radius-lg);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
} }
/* macOS-style title bar (windowChrome.js). Most pages get it injected as
the first child of #wpbody-content — negative margins cancel the 32px
padding above so it spans edge-to-edge like a real window title bar. */
.aurora-winbar {
display: flex;
align-items: center;
gap: 8px;
}
.aurora-winbar--editor {
position: fixed;
/* Centered vertically within the skeleton's reserved top strip
(--aurora-editor-winbar-h), inset from its left edge to line up with
the padding every other title bar uses. */
top: calc(
var(--aurora-toolbar-h) + 16px + (var(--aurora-editor-winbar-h) - 12px) / 2
);
left: calc(var(--aurora-sidebar-w) + var(--aurora-sidebar-gap) * 2 + 24px + 20px);
height: 12px;
padding: 0;
background: transparent;
border: 0;
border-radius: 0;
box-shadow: none;
z-index: 100000;
transition: left 0.2s ease;
}
#wpbody-content > .aurora-winbar {
margin: -32px -32px 20px -32px;
padding: 14px 16px;
border-bottom: 1px solid var(--aurora-frame-border);
border-top-left-radius: var(--aurora-radius-lg);
border-top-right-radius: var(--aurora-radius-lg);
}
.aurora-winbar__crumb {
margin-left: 8px;
font-size: 0.75rem;
font-weight: 500;
color: var(--aurora-text-muted);
text-decoration: none;
white-space: nowrap;
}
.aurora-winbar__crumb:hover {
color: var(--aurora-text);
}
.aurora-winbar__dot {
width: 12px;
height: 12px;
border-radius: 50%;
border: none;
padding: 0;
cursor: pointer;
}
.aurora-winbar__dot:hover {
filter: brightness(1.15);
}
.aurora-winbar__dot--close {
background: #ff5f57;
}
.aurora-winbar__dot--minimize {
background: #febc2e;
}
.aurora-winbar__dot--maximize {
background: #28c840;
}
/* The Dashboard bypasses #wpbody-content (DashboardTakeover.php renders /* The Dashboard bypasses #wpbody-content (DashboardTakeover.php renders
it into a separate sibling — see windowChrome.js), so it never got the it into a separate sibling — see dashboardFrame.js), so it never got the
card frame every other page has. windowChrome.js wraps the bar + the card frame every other page has. dashboardFrame.js wraps the dashboard's
dashboard's mount point in this div to give it the same treatment. */ mount point in this div to give it the same treatment. */
.aurora-dashboard-frame { .aurora-dashboard-frame {
background: var(--aurora-frame-bg); background: var(--aurora-frame-bg);
border: 1px solid var(--aurora-frame-border); border: 1px solid var(--aurora-frame-border);
@@ -444,25 +324,19 @@ html.aurora-ready .theme-install-overlay .wp-full-overlay-main {
box-sizing: border-box; box-sizing: border-box;
/* Same unconditional height cap as #wpbody-content's base rule — this /* Same unconditional height cap as #wpbody-content's base rule — this
element never got it, since the Dashboard bypasses #wpbody-content element never got it, since the Dashboard bypasses #wpbody-content
entirely (a separate sibling — see windowChrome.js) and only had the entirely (a separate sibling — see dashboardFrame.js). Once html/body
taskbar-present variant below. Once html/body were locked to were locked to overflow:hidden (to kill a phantom document-level
overflow:hidden (to kill a phantom document-level scrollbar on scrollbar on another page), the dashboard's content past the first
another page), the dashboard's content past the first ~1500px became ~1500px became completely unreachable: no internal scroll here, and
completely unreachable: no internal scroll here, and no document no document
scroll left to fall back on either. */ scroll left to fall back on either. */
max-height: calc(100vh - var(--aurora-toolbar-h) - 40px); max-height: calc(100vh - var(--aurora-toolbar-h) - 40px);
overflow-y: auto; overflow-y: auto;
overflow-anchor: none; overflow-anchor: none;
} }
.aurora-dashboard-frame > .aurora-winbar {
padding: 14px 20px;
border-bottom: 1px solid var(--aurora-frame-border);
border-top-left-radius: var(--aurora-radius-lg);
border-top-right-radius: var(--aurora-radius-lg);
}
/* Custom admin notices (Notices::render_for_dashboard(), reparented in /* Custom admin notices (Notices::render_for_dashboard(), reparented in
here by windowChrome.js) — native-dark.css already themes .notice here by dashboardFrame.js) — native-dark.css already themes .notice
elsewhere via #wpbody-content, but that stylesheet isn't loaded on the elsewhere via #wpbody-content, but that stylesheet isn't loaded on the
Dashboard at all (Shell::is_own_page() treats index.php as one of Dashboard at all (Shell::is_own_page() treats index.php as one of
Aurora's own pages), so it needs its own copy of the same treatment Aurora's own pages), so it needs its own copy of the same treatment
@@ -476,41 +350,6 @@ html.aurora-ready .theme-install-overlay .wp-full-overlay-main {
margin: 16px 20px 0; margin: 16px 20px 0;
} }
/* Yellow dot: on the Dashboard itself there's nowhere further "home" to
send it, so minimize just collapses the frame down to the title bar in
place — everywhere else, minimize navigates to the dashboard instead
(see windowChrome.js) and SiteShell.vue's taskbar offers a way back. */
html.aurora-minimized #aurora-admin-dashboard-root {
display: none !important;
}
/* Green dot: hide Aurora's own chrome (sidebar/toolbar) and let the
content reclaim the full browser window, like restoring a real OS
window to fullscreen. */
html.aurora-maximized .sidebar,
html.aurora-maximized .toolbar {
display: none !important;
}
html.aurora-ready.aurora-maximized #wpcontent {
margin-left: 0 !important;
}
html.aurora-ready.aurora-maximized .interface-interface-skeleton,
html.aurora-ready.aurora-maximized #site-editor {
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
height: auto !important;
border-radius: 0;
}
html.aurora-maximized .aurora-winbar--editor {
top: calc((var(--aurora-editor-winbar-h) - 12px) / 2);
left: 20px;
}
html.aurora-maximized .aurora-taskbar {
left: 0 !important;
}
/* The settings page renders its own two boxed panels, so drop the outer /* The settings page renders its own two boxed panels, so drop the outer
content frame there — leaving just those panels on the dark backdrop. content frame there — leaving just those panels on the dark backdrop.
No background here: #wpbody-content is inset from the sidebar/gutter, No background here: #wpbody-content is inset from the sidebar/gutter,
-57
View File
@@ -1,57 +0,0 @@
/**
* Tracks every "minimized" window across page loads. Aurora is a normal
* multi-page WP admin (each nav is a full page load), so a real taskbar
* needs to survive navigation — sessionStorage (tab-scoped, clears when the
* tab closes) is enough for that. Stored as a list, not a single slot —
* minimizing a second page used to silently overwrite the first one's
* entry, so only the most recent minimize ever showed up in the taskbar.
*/
const STORAGE_KEY = 'aurora-minimized-windows';
export function getMinimizedWindows() {
try {
const raw = sessionStorage.getItem(STORAGE_KEY);
const parsed = raw ? JSON.parse(raw) : [];
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
function saveMinimizedWindows(list) {
try {
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(list));
} catch {
/* sessionStorage unavailable (e.g. private mode) — minimize just won't persist */
}
}
/** Adds (or, if the same page is already minimized, replaces) an entry. */
export function addMinimizedWindow(title, url) {
const list = getMinimizedWindows().filter((w) => w.url !== url);
list.push({ id: `${Date.now()}-${Math.random().toString(36).slice(2)}`, title, url });
saveMinimizedWindows(list);
}
export function removeMinimizedWindow(id) {
saveMinimizedWindows(getMinimizedWindows().filter((w) => w.id !== id));
}
/**
* Best-effort readable label for the current page, used as the taskbar
* item's title. Prefers the page's own on-screen <h1> over document.title —
* WordPress's title-tag lookup walks the $submenu global to find a page's
* title, which comes up empty for Aurora's sub-app pages (SubApps.php
* registers each one normally, then immediately remove_submenu_page()s it
* to keep it out of the native menu — that breaks WP's own title lookup,
* not just this). document.title is still the fallback for pages where
* that isn't an issue (Settings, native WP pages, the Dashboard).
*/
export function currentPageTitle() {
const heading = document.querySelector('#wpbody-content h1, #aurora-admin-dashboard-root h1');
if (heading && heading.textContent.trim()) {
return heading.textContent.trim();
}
const raw = document.title || '';
return raw.split('‹')[0].trim() || 'Window';
}
-185
View File
@@ -1,185 +0,0 @@
/**
* Adds a macOS-style traffic-light title bar to the top of the page content.
* Three cases, in priority order:
* 1. The Dashboard — DashboardTakeover.php renders it into a separate
* #aurora-admin-dashboard-root sibling, above #wpbody (which it hides),
* to fully replace the native dashboard. Gets wrapped in a new
* .aurora-dashboard-frame div to match the card treatment
* #wpbody-content already has everywhere else.
* 2. The block editor — gets only the traffic-light controls, overlaid on
* the editor frame without a full-width titlebar surface.
* 3. Everything else — native WP pages and all of Aurora's own Vue pages,
* since each mounts its root div inside #wpbody-content. The bar is
* inserted as its first child.
*
* Red closes (navigates back to the Aurora dashboard). Yellow minimizes —
* on any page other than the dashboard, that means "save where I am and
* send me home", with SiteShell.vue's taskbar offering a way back; on the
* dashboard itself (already home) it just collapses in place instead,
* since there's nowhere further back to send it. Green hides Aurora's own
* sidebar/toolbar so the content fills the browser window. Minimized pages
* are tracked as a list (taskbar.js), not a single slot, so minimizing
* more than one shows up as separate taskbar items rather than the latest
* silently replacing the previous one.
*/
import { addMinimizedWindow, currentPageTitle } from './taskbar.js';
function buildBar(adminUrl, isDashboard) {
const bar = document.createElement('div');
bar.className = 'aurora-winbar';
const makeDot = (variant, label, onClick) => {
const dot = document.createElement('button');
dot.type = 'button';
dot.className = 'aurora-winbar__dot aurora-winbar__dot--' + variant;
dot.setAttribute('aria-label', label);
dot.title = label;
dot.addEventListener('click', onClick);
return dot;
};
const root = document.documentElement;
bar.appendChild(
makeDot('close', 'Close', () => {
window.location.href = adminUrl || '/wp-admin/';
})
);
bar.appendChild(
makeDot('minimize', 'Minimize', () => {
if (isDashboard) {
root.classList.toggle('aurora-minimized');
root.classList.remove('aurora-maximized');
} else {
addMinimizedWindow(currentPageTitle(), window.location.href);
window.location.href = adminUrl || '/wp-admin/';
}
})
);
bar.appendChild(
makeDot('maximize', 'Maximize', () => {
root.classList.toggle('aurora-maximized');
root.classList.remove('aurora-minimized');
})
);
return bar;
}
function isBlockEditorPage() {
const path = window.location.pathname;
return (
document.body.classList.contains('block-editor-page') ||
path.endsWith('/post-new.php') ||
path.endsWith('/post.php')
);
}
// WP core adds a "post-type-{slug}" class to <body> on every post edit
// screen — the same signal used here to build a "back to the list" link,
// since Posts ("edit.php") and Pages ("edit.php?post_type=page") are now
// hijacked in place at their native URLs (see HijackedNativePages.php) and
// there's no single admin.php?page=... URL to hardcode anymore.
//
// The Site Editor (site-editor.php) also carries the "block-editor-page"
// body class isBlockEditorPage() matches on, but has no "post-type-*"
// class (it edits templates/template parts, not a post) — returning null
// here rather than guessing "Posts" is what keeps the crumb from showing
// up somewhere it doesn't make sense.
function editorBackLink(adminUrl) {
const match = [...document.body.classList].find((c) => c.startsWith('post-type-'));
if (!match) {
return null;
}
const postType = match.replace('post-type-', '');
const base = adminUrl || '/wp-admin/';
if (postType === 'post') {
return { label: 'Posts', href: base + 'edit.php' };
}
if (postType === 'page') {
return { label: 'Pages', href: base + 'edit.php?post_type=page' };
}
return { label: postType, href: base + 'edit.php?post_type=' + encodeURIComponent(postType) };
}
// theme-install.php's "Preview & Install" overlay (WP core's
// .wp-full-overlay component) needs a permanently-running observer, not a
// one-shot check, for two reasons: (1) core's own Backbone view creates it
// lazily the first time a theme is clicked, so it's missing from the
// initial DOM at initWindowChrome()'s call time; (2) every subsequent
// theme selection re-renders the overlay's contents from scratch (its own
// view.render() replaces all children to show the newly-selected theme's
// details), wiping out anything inserted into it — confirmed live: the bar
// was there after the very first click, gone after picking a second theme.
// Kept running for the page's whole lifetime rather than disconnecting
// after the first successful insert. Independent of the rest of this
// file's dedupe guard (a normal page winbar already exists in
// #wpbody-content here too; this is a second, additional bar for the
// overlay specifically, not a replacement for it).
function watchForThemeOverlay(adminUrl) {
const ensureInserted = () => {
const overlay = document.querySelector('.theme-install-overlay');
if (overlay && !overlay.querySelector('.aurora-winbar')) {
const bar = buildBar(adminUrl, false);
bar.classList.add('aurora-winbar--editor');
overlay.insertBefore(bar, overlay.firstChild);
}
};
ensureInserted();
new MutationObserver(ensureInserted).observe(document.body, { childList: true, subtree: true });
}
export function initWindowChrome(adminUrl) {
if (document.body.classList.contains('theme-install-php')) {
watchForThemeOverlay(adminUrl);
}
if (document.querySelector('.aurora-winbar')) {
return;
}
const dashboardRoot = document.getElementById('aurora-admin-dashboard-root');
if (dashboardRoot) {
const bar = buildBar(adminUrl, true);
const frame = document.createElement('div');
frame.className = 'aurora-dashboard-frame';
dashboardRoot.parentNode.insertBefore(frame, dashboardRoot);
frame.appendChild(bar);
// Custom admin notices (Notices::render_for_dashboard(), printed as a
// sibling right before the mount point) — reparented into the frame
// here too, so they render as part of the dashboard's own card
// instead of sitting outside it unstyled. Normally these reach the
// user via the plain admin_notices hook, but that renders inside
// #wpbody, which the Dashboard hides entirely to hide the *native*
// dashboard widgets — this is the replacement path for that one page.
const notices = document.getElementById('aurora-admin-dashboard-notices');
if (notices) frame.appendChild(notices);
frame.appendChild(dashboardRoot);
return;
}
if (isBlockEditorPage()) {
const bar = buildBar(adminUrl, false);
bar.classList.add('aurora-winbar--editor');
const back = editorBackLink(adminUrl);
if (back) {
const crumb = document.createElement('a');
crumb.className = 'aurora-winbar__crumb';
crumb.href = back.href;
crumb.textContent = '← ' + back.label;
bar.appendChild(crumb);
}
document.body.appendChild(bar);
return;
}
const wpbodyContent = document.getElementById('wpbody-content');
if (wpbodyContent) {
const bar = buildBar(adminUrl, false);
wpbodyContent.insertBefore(bar, wpbodyContent.firstChild);
}
}