Files
aurora-wordpress-admin/PROJECT_STATUS.md
T
nixpanelandClaude Sonnet 5 b0b0a9999a Update project status doc for the theme system rebuild and window chrome
Documents this session's work: the new fixed-preset theme system
(gradients, the recurring "independently-painted gradient" seam bug
pattern), the macOS-style window chrome + taskbar feature, extended
native-page dark styling coverage, and the still-open sub-app permissions
bug.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-03 12:32:56 -05:00

26 KiB
Raw Blame History

Aurora Admin v2 — Project Status

Last updated: 2026-07-03 (evening session — theme system rebuild + window chrome)

What this project is

A ground-up rebuild of the "uiXpress" WordPress admin-theme plugin, relaunched as Aurora Admin. The original plugin's look/feel and feature set are the target, but no original code is reused — all Vue components, PHP classes, and build tooling are freshly written. This lives in aurora-admin-v2/ (the earlier in-place rebrand attempt, aurora-admin-working/, is superseded and no longer the active codebase).

Repo

Now on GitLab (self-hosted): ssh://[email protected]:2289/NixReaper/aurora-wordpress-admin.git (HTTPS also available). Repo root = the contents of aurora-admin-v2/ (admin/, app/, aurora-admin.php), not the whole uixpress/ folder — that folder has a bunch of dead-end attempts (aurora-admin-working, aurora-admin-distribution, aurora-admin-rewrite, aurora-admin-GLM, uixpress) that are debugging history, not part of the project.

.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, 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.
  • 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 <repo>/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/.

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.

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 <link> 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 <section class="aurora-card"> 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).
  • 5 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', ...) then immediately remove_submenu_page()'d on a late admin_menu hook — the register-but-hide trick: real page/capability/URL, but never appears in WP's native $submenu global (so MenuSerializer never sees them, nothing to filter there).
    • 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).
    • 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. Known bug, unresolved: unlike the other sub-apps, Menu Creator/Activity Log/Role Editor currently throw WordPress's native "Sorry, you are not allowed to access this page" for the logged-in admin user when navigated to directly, while Settings/Plugins/Dashboard work fine — likely a capability-check mismatch in how those three register their submenu pages vs. the working ones. Not yet root-caused; a background investigation task was spawned for it but not completed this session — check for it before re-investigating from scratch.
    • 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 and remote site passwords for use on non-admin-gated pages; Settings::get() (full) only used on the manage_options-gated settings page itself.
  • Global search: app/src/shell/components/SearchModal.vue, Ctrl+K, debounced wp/v2/search REST calls, gated on disable_global_search setting.
  • Remote site switcher: toolbar dropdown, gated on canSwitchSites (from remote_site_switcher_capability setting) + non-empty remote_sites.
  • 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 <iframe> pointing at a separate WP page — CSS can't reach into it; would need JS injection into the iframe's own document if that's ever worth fixing.

Window chrome (macOS-style title bar + taskbar)

Added this session, user-requested after seeing a competing plugin's demo. Every page gets a traffic-light title bar; minimizing persists across a full page navigation via a taskbar.

  • app/src/shell/windowChrome.js: initWindowChrome(adminUrl), called from SiteShell.vue::onMounted (skipped on the block editor — see below). Injects a .aurora-winbar div (3 dot buttons) as the first child of #wpbody-content — the one container shared by every page Aurora manages, native WP pages and all of Aurora's own Vue pages alike, since each Vue entry mounts its root div there. Exception: the Dashboard (DashboardTakeover.php) renders into a separate #aurora-admin-dashboard-root sibling above #wpbody (which it hides) to fully replace the native dashboard — #wpbody-content is empty/collapsed there. windowChrome.js detects this and instead wraps the bar + #aurora-admin-dashboard-root in a new .aurora-dashboard-frame div, giving the Dashboard the same card-frame treatment (--aurora-frame-bg, border, radius) every other page already gets from #wpbody-content itself.
  • Red (close): window.location.href = adminUrl.
  • Green (maximize): toggles html.aurora-maximized — hides .sidebar/ .toolbar, zeroes #wpcontent's margin-left and the block editor skeleton's left, so content fills the whole browser window. Toggle again (or minimize) to restore. Nothing persisted — resets on next page load.
  • Yellow (minimize): on the Dashboard itself, toggles html.aurora-minimized which just collapses #aurora-admin-dashboard-root down to the bar in place (nowhere further "home" to send it). On every other page, calls taskbar.js's setMinimizedWindow(title, url) (persists {title, url} in sessionStorage, tab-scoped) then navigates to the dashboard. Only one minimized window is tracked at a time — this is "minimize sends you home with a way back," not a real window manager.
  • SiteShell.vue reads taskbar.js's getMinimizedWindow() on every mount (so the taskbar shows up on whatever page you're on after minimizing) and renders a full-width docked bar at the bottom with one pill-shaped item; clicking it clears the sessionStorage entry and navigates back to the stored URL. html.aurora-has-taskbar (toggled alongside) reserves bottom margin on #wpbody-content/.aurora-dashboard-frame equal to --aurora-taskbar-h so page content stops above the bar instead of running underneath it. Went through two design iterations (small centered floating pill → back to a full-length bar per explicit user correction — the pill-shaped item stayed, just not as the whole bar).
  • Block editor exception: Gutenberg (post.php/post-new.php) doesn't render inside #wpcontent's normal box model at all — its .interface-interface-skeleton is position: fixed; inset: 0, anchored to the viewport independent of any margin/padding on ancestors. SiteShell.vue detects document.body.classList.contains('block-editor-page') and: (a) skips rendering Aurora's own <Toolbar> (the editor has its own, equivalent one — stacking both left a gradient seam between them), (b) skips initWindowChrome() entirely (#wpbody-content is invisible/ collapsed there, injecting into it would be dead clutter), (c) insets the skeleton itself via left/top in shell-frame.css so it doesn't render underneath the sidebar. If a future feature needs to reach the block editor's own chrome, its real container is .interface-interface-skeleton, not #wpbody-content — don't assume the normal page structure there.

Key bugs fixed (root causes, in case they resurface)

  • .card global class collision: original Dashboard cards used class="card", colliding with WP core's .card { max-width: 520px }. Fixed by renaming to .aurora-card* everywhere. Never use unprefixed class names on Aurora components.
  • Grid-column not applying on wrapped cards: settled on unwrapped <section class="aurora-card" :style="{gridColumn: ...}"> — component boundaries ate the style binding in earlier attempts.
  • Two-box settings layout: took two wrong iterations before matching the user's reference image. Re-confirm layout intent against a screenshot before assuming.
  • Redundant outer frame border on settings page: fixed with a body.toplevel_page_aurora-admin override in shell-frame.css.
  • Dashboard submenu defaulting collapsed: Sidebar.vue's normalize() URL helper didn't map the bare /wp-admin/ root to index.php, so the Dashboard section's active-state check (and thus its default-expanded state) never matched on the dashboard itself. Fixed by normalizing empty/ query-only paths to 'index.php' + path.
  • Aurora top-level menu appearing twice: SettingsPage.php's add_menu_page('aurora-admin', ...) naturally appears in WP's native $menu global. Since it's now also shown in the sidebar's dedicated Aurora nav section, MenuSerializer::serialize() explicitly skips slug aurora-admin to avoid the duplicate.
  • Browser automation can't install plugins: the Chrome MCP file_upload tool only accepts files already "shared with the session" (attachments/ connected folders) — local build output on disk doesn't qualify. Confirmed this session; the user must manually reinstall the zip every time (though see "Fast local iteration" above for the hot-patch workaround used for verification during dev).
  • Independently-painted gradient seams (sidebar/toolbar/settings-page #wpbody-content each showing a visibly different slice of "the same" background gradient): see the theme-system section above — root cause was multiple elements each painting their own copy of a gradient-valued var(--aurora-app-bg) instead of one element painting it and the rest staying transparent.
  • Block editor content rendering underneath the sidebar: Gutenberg's .interface-interface-skeleton is position: fixed; inset: 0, completely bypassing the margin-left that pushes every other page's #wpcontent over — see the "Window chrome" block editor exception above for the full mechanism and the fix.
  • zip -r merging into a stale archive: rebuilding the deployable zip without first deleting the old one left old-hash asset filenames mixed into the new zip (macOS zip -r appends/merges rather than replacing). Always rm -f the zip before rebuilding.

Not-yet-done (deferred, no active request)

  • Full field sets for settings categories beyond General/Analytics/White Label (original plugin had more).
  • Extending native-enhance.js/native-dark.css coverage to more native surfaces — the block editor's own chrome, Widgets screen, Customizer. Media/thickbox modals and plugin/theme card grids were added this session; the plugin "More Details" popup specifically can't be reached (it's an <iframe> — see native page dark styling notes above).
  • Porting the real license-key activation flow (built previously in the old aurora-admin-working project, not yet ported to v2 — currently just a text field).
  • Menu Creator: stored item order field isn't applied at runtime yet (hide/relabel work; reordering doesn't).
  • Unresolved from this session: Menu Creator/Activity Log/Role Editor throw "Sorry, you are not allowed to access this page" for the admin user — see the Plugins sub-app bullet above. A background investigation task was spawned (task_f8e56f60 at spawn time) but its outcome isn't recorded here — check whether it landed before re-investigating.
  • No automated tests anywhere in the project — all verification has been manual, live-in-Chrome, per the established workflow rule above.

Current state

No task in progress. This session rebuilt the entire theming system (fixed color presets replacing the old light/dark toggle, gradient backgrounds, several "independently-painted gradient" seam bugs), added the macOS-style window chrome + taskbar feature end-to-end, and extended native-page dark styling coverage. All of it was live-verified in Chrome against the LocalWP site and is pushed to origin/main (commit 4a547d2 at session end — a single large commit covering the whole session's work, since it wasn't broken into incremental commits along the way). The one known open issue is the Menu Creator/Activity Log/Role Editor permissions bug noted above.