# Aurora Admin v2 — Project Status _Last updated: 2026-07-11 (latest session — v3.6.0: Site Backup was extracted out of Aurora Admin into a new standalone sibling plugin, `aurora-site-backup`, at the user's explicit request ("pull out the backup as well and lets move that to an add-on") — the same proactive-risk- reduction reasoning as Database Explorer's 3.5.0 extraction, since restoring a backup means writing arbitrary files back into the site. The entire `admin/src/Backup/` tree (job/schedule storage, DB dumper, file archiver, state-machine engine, wp_cron safety net, all three storage providers) and all four Backup REST controllers moved unmodified into the new plugin. Since Backup's storage providers depend on a Settings class for stored credentials (S3 keys, Google Drive OAuth tokens), a small standalone `Options\Settings` class was added to the new plugin with its own `aurora_site_backup_settings` option, replacing the dependency on Aurora Admin's own Settings class. `CompanionPluginData.php`'s `PLUGINS` array (already generalized in 3.5.0) gained a `site-backup` entry — no new PHP route code needed. Verified live via a full `rest_do_request()` dispatch that created a real backup job, stepped it through the DB-dump phase, and canceled it — not just "loads without fatals" but actual end-to-end behavior confirmation. See "Site Backup — REMOVED..." below. Same outstanding deployment gap as the other two companion plugins: the zip isn't hosted yet. Previous session — v3.5.1: a fresh WordPress.org Plugin Check report showed the 3.3.2 compliance pass's `phpcs:ignore` annotations weren't actually suppressing anything on several multi-line `$wpdb->` calls — WPCS suppresses violations on the exact next physical line, but the annotations sat above the *opening* line of the call while PHPCS attributed the violation to a line further down inside it (where the interpolated table-name token actually appears). Fixed across MenuCreatorData.php, NoticesData.php, Notices.php, BackupSchedules.php, BackupJobs.php, MediaFolders.php (the largest cluster, mostly fixed by collapsing multi-line query calls to one physical line), DbDumper.php, AnalyticsData.php, and MediaData.php — verified with an automated scan that every dynamic-query `$wpdb->` call is now correctly covered. Also found and fixed a real, unrelated bug during verification: `MenuCreatorData::create()` built its "fetch the row I just inserted" follow-up request with `new WP_REST_Request("GET", "", ["id" => ...])`, but that constructor form doesn't populate what `get_param()` reads, so creating a menu always returned "Menu not found" — confirmed via a full `rest_do_request()` dispatch, fixed with `set_url_params()`. The `compressed_files`/`hidden_files`/`unexpected_markdown_file` findings in the report are scanner noise from scanning the whole dev repo, not the actual submission zip (re-confirmed via `unzip -l`). Submission zip rebuilt and re-verified after this pass. Previous session — v3.5.0: Database Explorer was extracted out of Aurora Admin into a new standalone sibling plugin, `aurora-database-explorer`, following the exact same pattern as File Manager's v3.4.0 extraction — direct database-query tooling (browsing tables, running free-form SELECT/SHOW/DESCRIBE/EXPLAIN queries) is treated by WordPress.org's Plugin Directory review the same way as file-management functionality, so it was pulled out proactively rather than waiting for a rejection. `CompanionPluginData.php` was generalized from a single file-manager-only class into a `PLUGINS` config array keyed by slug, so both companion plugins (and any future ones) share the same install/ activate/status REST routes. The Settings → Add-ons screen now lists both. A real production deploy (hosting the `aurora-database-explorer.zip` on aurora.auroradragon.studio) is still pending explicit user confirmation, not yet done — same outstanding gap File Manager's zip already has. Previous session — v3.4.0: WordPress.org's second review response said the whole file-management/code-editing *category* isn't accepted into the Plugin Directory, restrictions or not — so File Manager was extracted entirely out of Aurora Admin into a new standalone sibling plugin, `aurora-file-manager`, installable with one click from the new Settings → Add-ons screen (wraps core's own `Plugin_Upgrader`, same class the native "Install Now" button uses). See "File Manager — REMOVED from Aurora Admin in 3.4.0" below, specifically its final "Extraction into a standalone plugin" subsection. Prior versions this doc covers in detail, newest first: v3.3.2 (WP.org Plugin Check compliance pass, ~230 justified `phpcs:ignore` annotations across every custom-table feature), v3.3.1 (Settings page independent- scroll fix), v3.3.0 (Modern Image Formats — WebP/AVIF generation + `` rewriting, reimplemented against documented WP core hooks), v3.2.1 (closed the File Manager PHP-editing gap that triggered WP.org's *first* rejection), v3.2.0 (Google Drive remote storage for Site Backup), v3.1.0 (Site Backup itself — the first background-job/wp_cron infrastructure in this codebase), and v3.0.0 (made every previously-paid feature free, deleted the entire licensing/tamper-protection subsystem — everything earlier in this doc describing license gating is historical unless explicitly marked otherwise).)_ ## What this project is **Aurora Admin** — a modern, fast WordPress admin interface. Real replacement screens for the core admin (Posts, Pages, Media, Users, Comments, and more), a full theme system with dark mode, and admin power-tools, all freshly written from scratch (Vue components, PHP classes, and build tooling). ## Repo Now on GitLab (self-hosted): `ssh://git@git.auroradragon.studio:2289/NixReaper/aurora-wordpress-admin.git` (HTTPS also available). Repo root holds `admin/`, `app/`, and `aurora-admin.php`. `.gitignore` excludes `app/node_modules/` and `app/dist/` (build output — regenerate, don't version). To clone on a new machine: add an SSH key to the GitLab account, then `git clone` the URL above, then `cd app && npm install`. Claude Code conversation history does **not** sync across machines — it's local per project-folder-per-machine. Point a fresh session at this file to get back up to speed instead of relying on prior chat history. ## Workflow rules established - **Always verify live in Chrome** before declaring a fix done. Use the Chrome MCP tools (`tabs_context_mcp`, `navigate`, `computer`/screenshot, `javascript_exec`) to inspect real DOM/computed styles rather than guessing from source. - **Zip packaging**: on macOS, build via `npm run build` in `app/`, `rsync` the repo (excluding `node_modules`, `.git`, `/dist`) into a scratch staging dir, **run `php bin/build-integrity-manifest.php /aurora-admin` to generate `admin/src/Licensing/integrity-manifest.json` into the staged copy** (see "Tamper protection" below — this step is new, don't forget it or the shipped zip will have no manifest and Integrity::is_tampered() will treat it as a dev checkout, i.e. never lock anything), then `zip -r -X` it into `dist/aurora-admin-v2.zip` — macOS `zip` produces forward-slash paths natively, no path-separator corruption issue like Windows `Compress-Archive`/PowerShell had. **Always delete the existing zip first** (`rm -f dist/aurora-admin-v2.zip`) — `zip -r` merges into an existing archive rather than replacing it, so stale entries (old content hashes) silently survive otherwise. If building on Windows again, the old PowerShell `System.IO.Compression.ZipFile` + `.Replace('\', '/')` approach from earlier sessions still applies (the manifest-generation step would need a Windows-available PHP CLI too — LocalWP bundles one at `~/Library/Application Support/Local/lightning-services/php-*/bin/*/bin/php` on macOS; there's presumably an equivalent bundled binary on Windows). - **Fast local iteration**: the test site is LocalWP at `/Users/kris/Local Sites/aurora/app/public/wp-content/plugins/aurora-admin`. Instead of rezipping + manually reinstalling for every small CSS/JS tweak, hot-patch the built files straight into that install (`rm -rf app/dist && cp -R /app/dist app/dist`, or copy individual files like `admin/assets/native-dark.css`) and just reload the page in Chrome — near-instant feedback loop. Rezip properly (see above) once the change is confirmed, so the zip artifact matches what's live-verified. - User manually reinstalls the zip from a downloaded/attached copy when actually deploying — **the Chrome MCP `file_upload` tool cannot push arbitrary local files into the browser**, confirmed in an earlier session, so that step can't be automated. (Hot-patching, above, sidesteps this for local dev/verification.) - **Old build caching gotcha**: WordPress serves whatever plugin files were last installed — if the user says "didn't fix it," suspect a stale zip still installed before suspecting the code. Verify the live script hash via `document.querySelector('script[src*="shell"]')` + computed styles when in doubt (done successfully this session to prove a spacing change was live). - **Git**: local repo config only (`user.name`/`user.email` set per-repo, not `--global`). Commit after each user-confirmed-working change, not before. - Site under test: `http://admin.local/wp-admin/`. - **License server access**: `auroraplugins.com` runs on the same cPanel box as the git remote, `auroradragon.studio` (hostname `wyvern.auroradragon.studio`), but the git remote's SSH key (`~/.ssh/id_ed25519`) is scoped to git-only — no shell. Root shell access is a separate dedicated key, `~/.ssh/id_ed25519_auroradragon_root` (`ssh -i ~/.ssh/id_ed25519_auroradragon_root root@auroradragon.studio`). The license server itself is the `aurora-licensing` WordPress plugin at `/home/aurora/public_html/wp-content/plugins/aurora-licensing/aurora-licensing.php` (also present under `/home/plugins/public_html/...` — a second copy on the same box). **If SSH to port 22 suddenly times out**, something on the box (fail2ban or similar) appears to re-trigger a short block on rapid repeated connection attempts, including plain `nc`/probe connections — it clears on its own after roughly 1-2 minutes; wait it out with one careful attempt rather than retrying in a tight loop. ## Architecture - **PHP** (`admin/src/...`, namespaced `AuroraAdmin\...`), bootstrapped from `aurora-admin.php` (manual `require` list, dependency order matters), instantiated in `admin/src/App/AuroraAdmin.php`'s constructor. - **Vue 3** app (`app/src/...`), Vite multi-entry build (`app/vite.config.js` `rollupOptions.input`) → `manifest.json`, read by `admin/src/Utility/Assets.php::enqueue()` which also walks the manifest's `imports` chain to pull in shared-chunk CSS. Data reaches each Vue entry via a `data-aurora-admin` JSON attribute on a mount div (never `wp_localize_script`). - **Single global stylesheet**: `app/src/shell/shell-frame.css` — all `--aurora-*` CSS variables (incl. `.aurora-dark` overrides) and the only rules touching WP structural IDs (`#wpcontent`, `#wpbody-content`). No element resets/utility classes leak onto native WP pages. - **Immersive shell** (`admin/src/Shell/Shell.php` + `app/src/shell/SiteShell.vue` + `Sidebar.vue` + `Toolbar.vue` + `UserMenu.vue`): hides native WP chrome, mounts a Vue sidebar/toolbar on every admin page. - `MenuSerializer.php` converts WP's `$menu`/`$submenu` globals into JSON for the sidebar's native-menu section — explicitly skips the `aurora-admin` slug so Aurora's own pages aren't duplicated (they're represented in the sidebar's own branded nav section instead). - Sidebar has two sections: the WP-native menu list (icon-chip style, 28×28px rounded icon backgrounds, tight ~2px vertical padding per item) and a bottom "Aurora" section (collapsible, expanded by default, persisted via `localStorage['aurora-nav-expanded']`) styled like a **native WP submenu** (plain indented text via `.sidebar__sublist`/ `.sidebar__sublink`, no icons) — deliberately matches how e.g. Posts' own submenu (All Posts/Add Post/Categories/Tags) looks, per explicit user request. - Sidebar is `display:flex; flex-direction:column`, split into a `.sidebar__scroll` (flex:1, scrollable — holds brand/native-list/Aurora section) and a pinned `UserMenu` footer below it (not part of the scrollable area, so the account card is always visible). - `UserMenu.vue`: floating card that expands **upward** from a bottom-left trigger (avatar+name+chevron). Card contains: user header (avatar/name/ email), links (Edit Profile → `profile.php`, Visit site, Site Settings → `options-general.php`, Sign Out in red). No theme control here anymore — see "Theme system" below; the old light/dark/system 3-way swatch picker and the `dark_mode` setting it drove are both gone entirely. - Old top-right toolbar user-dropdown and sun/moon toggle button are **removed** — fully replaced by the bottom-left `UserMenu`. - Sidebar is **inset**, not flush against the browser edge: fixed at `top/left/bottom: var(--aurora-sidebar-gap)` (16px), fully rounded corners, `background: var(--aurora-card-bg)` — floats as its own card over the page gradient. `--aurora-sidebar-gap` is a shared CSS var (`shell-frame.css`) that the toolbar's `left` offset and `#wpcontent`'s `margin-left` both add too, so nothing overlaps when the gap changes. - **Native-menu accordion**: only one WP-native-menu section's submenu is open at a time (`Sidebar.vue`'s `openId` ref) — expanding one collapses whichever else was open, instead of the list growing indefinitely as more sections get expanded. Was previously a `Set` of independently toggleable ids (each section remembered its own open/closed state); replaced with a single id so opening any section closes the rest. Starts on whichever section matches the current page (each nav is a full page load in this multi-page admin, so this is computed once at mount, not reactively). The separate "Aurora" branded nav section below it keeps its own independent expand/collapse state (`localStorage['aurora-nav-expanded']`) — not part of this accordion. ## Skinned vs. replaced native pages (important distinction, ask before assuming) The user explicitly said they didn't want the "skinned" approach used without a real replacement behind it. As of this session, **every native list-table screen has been converted** — there's no more "just skinned" category left among the pages that had one: - **Actually replaced** — the native page never renders; a real custom Vue page (own REST controller, own list/detail UI) takes over entirely: Comments, Installed Plugins, Menu Creator, Admin Notices, Role Editor, Activity Log, Database Explorer, **Posts, Pages, Media Library, and Users** (the last four all added this session, in that order — Posts first as its own commit/version, then Pages/Media/Users together). - **Deliberately still native** (not a "skinning" gap — these are specific screens/actions, not list-tables, and rebuilding them wasn't in scope): the block editor (`post.php`/`post-new.php` — dark-themed chrome only, per "Window chrome" above), `media-new.php` (upload/drag-drop), `user-new.php` (account creation), individual edit screens reached from a list row (`user-edit.php`, comment/media edit), Settings/Profile pages, the Site Editor. If a new "why isn't X replaced" question comes up, check whether it's actually one of these deliberate exceptions before treating it as a gap. `PostsData.php`+`Posts.vue`, `PagesData.php`+`Pages.vue`, `MediaData.php`+`Media.vue`, `UsersData.php`+`Users.vue`, and `CommentsData.php`+`Comments.vue` all follow one shared REST/Vue pattern: - A REST controller in `admin/src/Rest/` with list (status/type/role + search + pagination, matching native filter tabs) and action endpoints (trash/restore/delete, or just delete for Media/Users, which don't have a trash state in the same way). Capability checks mirror WP core's own for that screen (`edit_posts`/`edit_pages`/`upload_files`/`list_users` to view; per-item `edit_post`/`delete_post`/`delete_page`/`delete_user` for actions). - A Vue page in `app/src/pages//` + entry in `app/src/entries/` + registered in `app/vite.config.js`, visually matching the Comments layout (filter pills, search, table with inline row actions, pagination) — Media is the one exception, using a thumbnail grid instead of a table, since that's what a media library actually needs. **As of this session, how they're mounted onto the page changed.** All five used to be a real registered `admin.php?page=aurora-admin-X` submenu that `SubApps::redirect_native_pages()` redirected direct visits to on `admin_init` — this had a proven bug class (see "Key bugs fixed": anything that redirects straight to the native URL, like a plugin's own post-activation redirect, lands on the untouched native page until the sidebar link is clicked again). They're now registered via `ScreenHijack::register()` (calls collected in `admin/src/Pages/HijackedNativePages.php`) instead: WordPress fires `load-{page}.php` before the native screen prints anything, so Aurora hooks that, checks capability/setting, buffers everything with `ob_start()` on `in_admin_header`, and on `admin_footer` discards the buffer (guarded by `ob_get_level() > 0`, since a screen that exited early via its own `wp_die()`/redirect may never have opened one) and prints Aurora's own mount div instead. **The URL never changes** — `edit.php`, `upload.php`, `users.php`, `edit-comments.php` are still the real URLs, so there's no separate slug to fall out of sync. Settings toggles (`modern_posts`/ `modern_pages`/`modern_media`/`modern_users`/`modern_comments`, all `default: true`) are unchanged and still gate this the same way. Their `SubApps::PAGES` entries, `redirect_native_pages()` branches, and `MenuSerializer::resolve_url()` rewrite branches were all deleted as dead code — the native sidebar links already point at the right (now hijacked) URL with no rewrite needed. **Plugins is the one exception, deliberately left on the old redirect mechanism** — `plugins.php` prints output in a way that can't be cleanly buffered (confirmed via a prior abandoned attempt at hijacking it the same way). It still has a real `aurora-admin-plugins` submenu under `plugins.php` and its own `redirect_native_pages()` branch in `SubApps.php`. This is phase 1 of a larger "full admin takeover" initiative — see the new section below. Two deliberate scope decisions worth remembering if asked "why doesn't X work": - **Media**: replaces the *library/browse* experience only — uploading (`media-new.php`, drag-drop/chunked uploads) was left alone entirely, same reasoning as leaving the block editor alone for Posts/Pages. - **Users**: deleting a user always reassigns their content to whoever performs the deletion, rather than exposing WP core's "delete this user's content too, or reassign to someone else" choice screen as a one-click REST action — the null-reassign path in `wp_delete_user()` is much more destructive than is safe to offer with one click, so it's not offered at all. `modern_posts`/`modern_plugins`/`modern_comments` were themselves each briefly **removed** earlier in this same session (when they had no implementation behind them) and re-added once real pages existed — if you ever see a settings field described as removed for "describing a feature that didn't exist," check whether it's since been rebuilt before assuming it's still missing. ## Full admin takeover initiative (in progress — plugin phase of "DragonPress") The user's long-term goal: Aurora Admin eventually becomes "DragonPress," a full WordPress core fork built around Aurora. That fork itself is **not** started and is explicitly out of scope until this plugin-level phase is finished. This phase's goal: when Aurora is active, **no native WordPress admin screen should ever be reachable** — everything gets replaced or wrapped by Aurora. Full plan lives in the approved plan file (not persisted in repo); summarized here for continuity. **Mechanism**: `ScreenHijack.php` (see above) is the new default for any screen being added. `SubApps.php`'s redirect mechanism is legacy, kept only for Plugins. **Sequencing** (see task list each session for live status): 1. ✅ `ScreenHijack.php` + migrate Comments (proof of concept) 2. ✅ Batch-migrate Posts, Pages, Media, Users 3. ✅ New `full_admin_takeover` master setting (default **false** — opt-in, unlike the existing `modern_*` toggles) + new `"admin-takeover"` license key gating everything below (this feature key, along with the entire licensing system, was removed in 3.0.0 — see "Licensing" below; the setting itself is unaffected and still opt-in) 4. ✅ Native Settings screens: General/Writing/Reading/Discussion/Permalinks — all 5 hijacked via one shared `NativeSettingsPages.php` + `NativeSettings.vue` (one Vue entry, driven by a per-screen field schema), reusing core's `/wp/v2/settings` REST route for everything except Permalinks (new `PermalinksData.php` — core has no REST route for rewrite rules). One gotcha worth remembering: core's REST property names don't always match the underlying option name (`title`→`blogname`, `description`→`blogdescription`, `url`→`siteurl`) — `get_option()` with the REST name silently reads empty; `NativeSettingsPages::OPTION_NAME_MAP` handles the translation for reads (writes go through the REST route itself, which already knows the mapping). Verified live: all 5 screens render real values on direct visit; at the time, Permalinks' Save also correctly 403'd without a real license (REST-level license check working independently of the page-level gate, per the "don't repeat the File Manager bug" requirement) — moot now that licensing is gone (3.0.0), but the REST route still checks `current_user_can()` directly rather than relying on the page-level lock alone. 5. ✅ Media Upload (`media-new.php`) + Add New User (`user-new.php`) — both Vue-only (no new PHP REST controllers), talking directly to core's `/wp/v2/media` and `/wp/v2/users` routes. Free, gated by the same `modern_media`/`modern_users` toggles as their list-view companions. Verified live: created a real user end-to-end. 6. ✅ Appearance/Tools batch. **Correction to this plan discovered during implementation**: Menu Creator (`admin/src/MenuCreator/`) manages Aurora's own admin sidebar menu, not WordPress's front-end nav menus — despite the similar name, completely unrelated features. Wiring `nav-menus.php` into it (as originally planned) would have been wrong; built a real, separate Nav Menus replacement instead (`NavMenusData.php` + `NavMenus.vue`: create/delete menus, add pages/posts/categories/custom links, reorder/indent items, assign to theme locations). Themes (`ThemesData.php` + `Themes.vue`: list/ activate/delete with screenshots) and Widgets (`WidgetsData.php` + `Widgets.vue`, deliberately simplified to reordering/moving/ deactivating existing instances — not adding new widget types, same "don't rebuild the real editor" reasoning as the block editor) also shipped. Theme Editor redirects to File Manager rather than getting a second in-browser file editor. Tools/Import-Export/Site Health left un-hijacked as planned (Shell already frames them). - Bug caught and fixed while testing: none of these pages' Vue components checked `res.ok` before using a fetch response — a 403 error body got rendered as if it were real data (iterated as an object in `v-for`, producing bogus placeholder rows). Fixed in all three; this class of bug is worth checking for in any future page that talks directly to `aurora-admin/v1` routes. - Environment note: this dev site's 3 installed themes are all block/ FSE themes (Twenty Twenty-Three/Four/Five), none of which register classic nav-menu/widget support — core itself refuses `nav-menus.php`/ `widgets.php` ("Your theme does not support...") regardless of this plugin. Verified the REST layer directly instead (full create/add- item/list/delete round trip); Themes was verified fully live since it doesn't depend on theme support. - **Gap found and fixed later** (user reported "missing a lot of features from the original"): `Themes.vue`'s initial build only covered list/activate/delete — no way to browse/install from WordPress.org or upload a theme zip, both present on the native screen. Fixed the same way as Plugins' `addUrl`/Media's `newMediaUrl`: link out to native `theme-install.php` (not hijacked, already has both a WordPress.org browser and an "Upload Theme" button built in) rather than rebuilding either. New "Add New Theme" button in `Themes.vue`, gated on `current_user_can('install_themes')`, `addThemeUrl` passed via `AppearancePages.php`'s existing `data_callback`. - **Follow-up** (user: "when you click add theme, it closes the menu"): clicking a theme card opens WP core's own fullscreen `.wp-full-overlay` preview/install modal, which covers the native admin menu on a totally vanilla install too — confirmed not a regression, but reframed it like the block editor window anyway per the user's call. See "Theme installer overlay framing" below for the full writeup — two real bugs (a `visibility: hidden` inheritance issue and the same over-constrained `height: 100%` + inset bug the block editor frame already had to work around) were found and fixed along the way. 7. ✅ Profile (`profile.php`) — user chose: reskin core fields only (`ProfileData.php` + `Profile.vue`, using core's `/wp/v2/users/me` REST route + a small custom password-change route), and fall through to the real native screen entirely whenever a plugin has hooked `show_user_profile`/`personal_options` (checked via `has_action()` at hijack time) rather than attempting to host that plugin's fields — a Vue SPA has no safe way to render arbitrary third-party HTML/JS. Verified live both ways with a test mu-plugin. Password change re-issues the auth cookie after `wp_set_password()`, same as core's own user-edit.php. 8. ✅ Block Editor reskin (`post.php`/`post-new.php`) — the last phase, and deliberately **not** a Gutenberg content-swap. Extended Shell's already-working block-editor framing (`shell-frame.css`'s `.interface-interface-skeleton`, `windowChrome.js`'s `isBlockEditorPage()` + `.aurora-winbar--editor` dots) with a "← Posts"/"← Pages" breadcrumb, resolved from the `post-type-{slug}` body class core adds to post edit screens (needed since Posts/Pages no longer have a fixed URL to hardcode after phase 1's migration). Bug caught while testing: the Site Editor shares the `block-editor-page` body class but has no `post-type-*` class — the crumb logic now explicitly returns nothing in that case instead of guessing wrong. Verified live on both the post editor (correct crumb) and Site Editor (no crumb, unaffected). **All 9 phases of the "full admin takeover" initiative are now shipped.** Network Admin (multisite) was explicitly deferred throughout — the dev/ target site isn't multisite and nothing else in the codebase references it. **Free vs. paid** — historical, moot since 3.0.0 (everything below is free now, see "Licensing"): Media Upload/Add User/Profile were always free (1:1 companions to already-free Posts/Pages/Media/Users). Appearance (Nav Menus, Widgets, Themes, Theme Editor redirect) and Native Settings screens were paid (`"admin-takeover"` feature key) — matched the "power admin tool" bucket (Database Explorer, Role Editor, etc.) at the time. Block editor reskin was always free (chrome around already-free Posts/Pages). Every REST controller in this group still checks `current_user_can()` directly in its own `permission_callback` rather than relying on page-level gating alone — that defense-in-depth pattern is unrelated to licensing and remains good practice. ## Theme system (rewrite — supersedes the old dark_mode 3-state model) Went through several rounds of user-directed rework this session, landing on: **10 fixed, named color presets + 2 flat neutrals ("Black & Grey", "White") — no custom color picker, no separate light/dark toggle.** Each preset is fully self-contained (its own coordinated backgrounds/cards/borders/text), not a hue applied on top of a shared light/dark base. - `app/src/pages/settings/themePresets.js`: `themePresets` array (10 hue-based presets like `{ id: 'indigo', label: 'Indigo', hue: 235 }`, plus `mono` and `white` with an explicit `palette` object instead of a hue). `buildPalette(preset)` returns all `--aurora-*` tokens — for hue presets, computed via HSL ramps; for the two flat presets, returned as-is. `resolvePreset(id)` looks up by id. - The main app background (`--aurora-app-bg`) is a **3-stop gradient**, not flat: `linear-gradient(135deg, vivid1 0%, vivid2 55%, near-black 100%)` — two bright, saturated stops of the same hue family carry most of the bar, dipping to a near-black anchor only in the last stretch. This exact ratio went through several corrections against a competing plugin's ("FleekDash") reference screenshots — a 50/50 dark-to-vivid split reads "washed out", two competing saturated hues ~70° apart reads "muddy"; this formula was the one the user confirmed as clean. If revisiting the gradient formula, re-check against those reference screenshots in the conversation history rather than re-deriving from scratch. - `--aurora-card-bg-deep`: a newer token, one step darker than `--aurora-card-bg`, used only by the Dashboard's `AuroraCard.vue`. Needed because the Dashboard got its own frame background using `--aurora-frame-bg` (see "Window chrome" below), which is close in lightness to `--aurora-card-bg` for most presets — cards would nearly disappear into the new frame without it. - `applyThemePalette(themePresetId)` / `applyFontFamily(family)` (`app/src/pages/settings/applyLiveTheme.js`) — shared between the settings page's live preview (`Settings.vue`'s `watch()`) and the real per-page-load application (`SiteShell.vue::onMounted`). Sets each token via `document.documentElement.style.setProperty`. - Google Fonts picker: `app/src/pages/settings/googleFonts.js` (curated ~70 family list, not the full catalog), searchable dropdown in `SettingsField.vue`'s `font-picker` field type, applied by injecting a Google Fonts CSS2 `` tag + setting `--aurora-font-family`. - **Recurring bug class this session — "independently-painted gradient"**: several elements (sidebar, toolbar, `#wpbody-content` on the settings page, `#wpbody-content` in general) each used to paint their *own* copy of `var(--aurora-app-bg)`. Since it's a gradient, each element computes it independently over *its own* bounding box — a narrow tall sidebar and a wide short toolbar get visibly different-looking slices of "the same" gradient, creating seams at their shared edges. Fixed by making `html`/`body` the *only* thing that paints the gradient (`background-attachment: fixed` so it doesn't scroll away from the fixed sidebar/toolbar), and leaving every other element transparent so that one gradient plane shows through. **If a new seam/mismatched-rectangle bug shows up, suspect this pattern first** — grep for `var(--aurora-app-bg)` outside `shell-frame.css`'s `html.aurora-ready, html.aurora-ready body` rule; it shouldn't be there. - **Dashboard takeover** (`admin/src/Pages/DashboardTakeover.php` + `app/src/pages/dashboard/Dashboard.vue`): hijacks native `index.php` via `in_admin_header`, hides the native `.wrap`, renders the Aurora dashboard in its place. 12-col grid; cards are single `
` elements with inline `grid-column: span N` (NOT wrapped — component attribute fallthrough for styles was unreliable). Never use unprefixed class names on Aurora components (`.card` collided with WP core once). - **Settings page** (`admin/src/Pages/SettingsPage.php` + `app/src/pages/ settings/Settings.vue`): owns the top-level "Aurora Admin" WP admin menu directly via `add_menu_page` (slug `aurora-admin`). Two-box layout: `.settings__nav` (category list) + `.settings__content` (fields), both boxed/bordered on a darker backdrop. Field schema is declarative in `app/src/pages/settings/settingsConfig.js`, rendered by `app/src/pages/settings/components/SettingsField.vue` (types: text, toggle, color, image, repeater, multiselect, segmented). `remote-sites` was a field type here — removed, see "Remote Site Switcher — removed" below. - **6 sub-apps** (`admin/src/Pages/SubApps.php` + one PHP feature-class pair each + one Vue page + one Vite entry each) — reachable only via the sidebar's Aurora nav section, not the native WP menu: - Registered via `add_submenu_page('aurora-admin', ...)` (or `'plugins.php'` for Plugins) and **left registered** in WP's native `$submenu` global — **no longer hidden via `remove_submenu_page()`**. An earlier version did call it (the standard register-but-hide trick) but that corrupts WP core's own `user_can_access_admin_page()` capability lookup, which partly depends on the page still being present in `$submenu` to resolve its required capability — removing it caused "Sorry, you are not allowed to access this page" for every user, including admins, on Menu Creator/ Activity Log/Role Editor/Comments. Root-caused and fixed this session by deleting `hide_from_native_submenu()` entirely; duplication in the sidebar's native-menu section is instead prevented in `MenuSerializer::serialize()` (see below), which has no such side effect. - All slugs prefixed `aurora-admin-*` so `Shell::is_own_page()`'s existing `strpos($page, "aurora-admin") === 0` check covers them for free (full custom dark theme, `native-dark.css` skipped). `MenuSerializer::serialize()` skips any submenu slug starting `aurora-admin-` (in addition to already skipping the `aurora-admin` top-level slug) so none of them are duplicated in the sidebar's native-menu section — needed because most are parented under `aurora-admin` (already skipped wholesale) but `aurora-admin-plugins` is parented under the real `plugins.php` menu. - Each page uses the shared two-pane layout `app/src/pages/_shared/AppListShell.vue` (generalized from Settings.vue's box-pair CSS) + `AppListItem.vue` for list rows. - **Menu Creator** (`admin/src/MenuCreator/`): DB table `aurora_admin_menus` (name/status/roles JSON/items JSON). Runtime `admin_menu` hook (priority `PHP_INT_MAX`) applies published configs matching the current user's roles — hides/relabels native menu items by slug. REST: `GET/POST /menus`, `GET/PUT/DELETE /menus/:id`, `GET /menus/native-items` (real WP menu list to build the editor from, via `MenuSerializer::serialize()`). Reordering is stored (`order` field) but not yet applied at runtime — CRUD + hide/relabel only so far. - **Admin Notices** (`admin/src/Notices/`): DB table `aurora_admin_notices` (title/message/type/roles JSON/pages JSON/dismissible/active). Runtime `admin_notices` hook renders active, role/page-matching rows via real WP notice markup. REST: `GET/POST /notices`, `PUT/DELETE /notices/:id`. - **Role Editor** (`admin/src/Roles/`): no custom table — wraps core `get_editable_roles()`/`WP_Roles` directly. Hard-codes the 5 default roles (`administrator, editor, author, contributor, subscriber`) as delete-protected (WP has no built-in check for this). REST: `GET/POST /roles`, `PUT/DELETE /roles/:slug`. - **Activity Log** (`admin/src/ActivityLog/`): DB table `aurora_admin_activity_log`. Hooked to `save_post` (guards `wp_is_post_autosave()`/`wp_is_post_revision()`), `before_delete_post`, `user_register`, `profile_update`, `wp_login`, `activated_plugin`, `deactivated_plugin`, `switch_theme`, `update_option_aurora_admin_settings`. REST: `GET /activity-log?action_type=&object_type=&search=&page=`. - **Plugins** (`admin/src/Rest/PluginsData.php` + `app/src/pages/plugins/Plugins.vue`): custom plugin management list (activate/deactivate/delete), also using `AppListShell.vue`. - **Comments** (`admin/src/Rest/CommentsData.php` + `app/src/pages/comments/`): newest sub-app, `edit_posts`-gated (not `manage_options` like the others). `SubApps::redirect_native_pages()` (`admin_init`) redirects the native `edit-comments.php` (when hit with no `?page=` param, i.e. a direct nav or an old bookmark/menu link) to `admin.php?page=aurora-admin-comments`. `MenuSerializer::resolve_url()` also maps the `edit-comments.php` slug straight to the Aurora URL so the native-menu-derived sidebar entry (for users who can `edit_posts` but not `manage_options`, so don't see the Aurora nav section) still lands on the Aurora page. The Dashboard's comments card (`DashboardData.php`'s `listUrl`) was updated to point at the same Aurora URL instead of the native one. - **Database Explorer** (`admin/src/DatabaseExplorer/`): no custom table — reads real `$wpdb` schema. `GET /db-tables` (SHOW TABLE STATUS), `GET /db-tables/:table` (columns + paginated rows — table name validated against the real `$wpdb` table list before use, never interpolated unchecked), `POST /db-query` (raw query runner, hard-restricted server-side to queries starting `SELECT`/`SHOW`/`DESCRIBE`/`EXPLAIN`, single-statement only, auto-`LIMIT 200` if none given). All 3 endpoints `manage_options`-gated. Identifier allow-listing + backtick-escaping is the actual safety boundary (`$wpdb->prepare()` only binds values, not identifiers). - All 5 backend classes + `SubApps.php` are `require`'d in `aurora-admin.php` and instantiated in `AuroraAdmin.php`. - **Settings persistence**: `admin/src/Options/Settings.php` — partial-payload save via `merge_on_save()` sanitize callback (merges into existing option, doesn't overwrite). `Settings::get_safe()` strips `license_key` for use on non-admin-gated pages; `Settings::get()` (full) only used on the manage_options-gated settings page itself. `Settings::remove_retired_keys()` (hooked to `init` alongside `register()`) actively deletes settings keys for features that no longer exist — currently just the removed Remote Site Switcher's `remote_sites`/`remote_site_switcher_capability`, since `remote_sites` stored a real secret (application passwords) that's no longer read anywhere. - **Global search**: `app/src/shell/components/SearchModal.vue`, Ctrl+K, debounced `wp/v2/search` REST calls, gated on `disable_global_search` setting. - **Scheduled content**: `app/src/pages/dashboard/cards/ScheduledContent.vue` calendar cells (squares, date top-left, count centered) are clickable — navigate to `edit.php?post_status=future&aurora_day=YYYY-MM-DD` if that day has scheduled posts, else show a toast ("no scheduled content"). Server side: `admin/src/Posts/ScheduledDayFilter.php` filters the `edit.php` query via `pre_get_posts` and prints an info notice. - **Analytics**: `admin/src/Analytics/Analytics.php` (first-party pageview tracker, own DB table `wp_aurora_admin_pageviews`, bot detection, gated on `enable_analytics`/`track_admins` settings) + `admin/src/Rest/AnalyticsData.php` (aggregation REST endpoint: totals, series, device/referrer/country breakdowns, bounce rate, active users). - **White label**: `admin/src/WhiteLabel/WhiteLabel.php` — custom favicon, rename plugin in Plugins list, rename top-level menu item, gettext find/replace via `text_replacements` setting. - **Native page dark styling**: `admin/assets/native-dark.css` (large static sheet, all rules gated `html.aurora-dark.aurora-ready`, scoped under `#wpbody-content`) + `admin/assets/native-enhance.js` (progressive JS enhancement — currently only `enhanceUpdates()`, which groups the flat Updates page sections into `.aurora-native-card` wrapper divs on `body.update-core-php`). This session also extended coverage to media/ thickbox modals, plugin/theme card grids, and the plugin-search filter bar (all previously untouched, so still native white regardless of theme). Known gap: the plugin "More Details" popup renders inside an `