diff --git a/PROJECT_STATUS.md b/PROJECT_STATUS.md deleted file mode 100644 index 1780ad8..0000000 --- a/PROJECT_STATUS.md +++ /dev/null @@ -1,2078 +0,0 @@ -# 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 by the old uixpress reference plugin's own 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 `