# Changelog All notable changes to Aurora Admin are documented here, one entry per version bump. Starts at 2.1.0 — earlier history (the theming rebuild, window chrome/taskbar, Site Editor support, etc.) lives in `git log` and `PROJECT_STATUS.md`, not reconstructed here. ## [3.6.1] - 2026-07-11 ### Fixed - **Second round of misplaced `phpcs:ignore` fixes**, found via another fresh Plugin Check report after 3.5.1's fix. Turns out `WordPress.DB.DirectDatabaseQuery.DirectQuery`/`NoCaching` and `WordPress.DB.PreparedSQL.NotPrepared`/`InterpolatedNotPrepared` are attributed by PHPCS to *different* lines within the same multi-line `$wpdb->get_results()`/`get_row()` call — `DirectQuery`/`NoCaching` to the outer call's opening line, `NotPrepared` to whichever inner line the interpolated table name physically appears on. 3.5.1's fix moved every ignore comment down to cover the inner line (fixing `NotPrepared`) but broke coverage of the outer line (`DirectQuery`/`NoCaching` came back) for four calls in `MenuCreatorData.php` and `Notices.php`/`NoticesData.php` that hadn't already been collapsed to a single line. Fixed by collapsing those four remaining multi-line calls too — consistent with the single-line-call pattern used everywhere else in the 3.5.1 fix, which a single ignore comment reliably covers regardless of which sniff PHPCS attributes to that line. Re-verified with the automated scan plus live REST-dispatch functional round-trips. ## [3.6.0] - 2026-07-11 ### Changed - **Site Backup extracted into a standalone companion plugin,** at the user's explicit request, following the same pattern as File Manager (3.4.0) and Database Explorer (3.5.0) — proactive risk reduction, not a WP.org rejection: restoring a backup means writing arbitrary files back into the site, the same class of thing that's kept file-management functionality out of Aurora Admin proper. - The entire `admin/src/Backup/` tree (job/schedule storage, DB dumper, file archiver, the state-machine engine, wp_cron safety net, and all four storage providers — local, S3-compatible, Google Drive, plus the provider interface/factory) and all four Backup REST controllers moved, unmodified in behavior, into a brand-new sibling plugin: **Aurora Site Backup** (`aurora-site-backup.php`, own bootstrap, own REST namespace `aurora-site-backup/v1`, own Vite build). Since this plugin needs its own settings storage independent of Aurora Admin (S3 credentials, Google Drive OAuth tokens), a small standalone `Options\Settings` class was added, backed by its own `aurora_site_backup_settings` option — same minimal shape as Aurora Admin's own Settings class, just a separate store. - `CompanionPluginData.php`'s `PLUGINS` config array (already generalized in 3.5.0) gained a `site-backup` entry — no new PHP needed beyond the config line. The Settings → Add-ons screen now lists all three companion plugins. - Verified live via a full `rest_do_request()` dispatch: created a real backup job, stepped it through the DB-dump phase, and canceled it — end-to-end confirmation the extracted code still works, not just that it loads without fatals. - **Deployment gap, same as the other two companion plugins**: the `aurora-site-backup.zip` is not yet hosted at `https://aurora.auroradragon.studio/aurora-site-backup.zip` — the Add-ons "Install & Activate" button for it will fail with a fetch error until that's deployed (needs explicit go-ahead). ## [3.5.1] - 2026-07-11 ### Fixed - **Misplaced `phpcs:ignore` comments from the 3.3.2 compliance pass, found via a fresh Plugin Check report.** WPCS's `phpcs:ignore` suppresses violations reported on the exact next physical line — but several annotations added in 3.3.2 sat above the *opening* line of a multi-line `$wpdb->get_results()`/`get_row()`/`get_var()` call (e.g. `$rows = $wpdb->get_results(`), while PHPCS actually attributes the violation to whichever line further down inside that call contains the interpolated table-name/column tokens. Those suppressions were silently never taking effect. Fixed across `MenuCreatorData.php`, `NoticesData.php`, `Notices.php`, `BackupSchedules.php`, `BackupJobs.php`, `MediaFolders.php` (the largest cluster — collapsed most multi-line query calls to a single physical line so one ignore comment reliably covers every sniff PHPCS reports on that line, added a `MediaFolders::name_taken()` helper to replace an inline `while()` condition that couldn't otherwise be annotated), `DbDumper.php`, `AnalyticsData.php`, and `MediaData.php`'s `post__not_in` VIP-advisory comment. Also added a genuinely missing annotation on `Analytics::record()`'s `$wpdb->insert()` call. Verified with an automated scan confirming every `$wpdb->` call with dynamic table/query construction is now immediately preceded by its ignore comment. - **Real bug found during verification, unrelated to Plugin Check**: `MenuCreatorData::create()` built its internal follow-up "fetch the row I just inserted" request as `new \WP_REST_Request("GET", "", ["id" => $wpdb->insert_id])` — but `WP_REST_Request`'s third constructor argument doesn't populate what `get_param()` reads, so `get_one()` always received a null id and returned "Menu not found" instead of the newly created menu. Confirmed via a full `rest_do_request()` dispatch (not just a direct function call) that this reproduced through the real REST route. Fixed by building the request with `set_url_params()`, WP core's own documented pattern for this. - Confirmed the `compressed_files`/`hidden_files`/`unexpected_markdown_file` findings in the Plugin Check report (build zips, `.gitignore`, `.claude/`, `PROJECT_STATUS.md`) are scanner noise from scanning the whole development repository, not the actual submission zip — verified via `unzip -l` that none of those paths exist in `wporg-submission/aurora-admin.zip`. ## [3.5.0] - 2026-07-11 ### Changed - **Database Explorer extracted into a standalone companion plugin,** for the same reason and following the exact same pattern as File Manager in 3.4.0: direct database-query tooling (browsing tables, running free-form SELECT/SHOW/DESCRIBE/EXPLAIN queries from wp-admin) is treated by WordPress.org's Plugin Directory review the same way as file-management functionality — outside what it accepts in a directory-listed plugin. Rather than wait for a rejection to confirm it or delete the feature outright: - `admin/src/DatabaseExplorer/DatabaseExplorerData.php` and its Vue UI (plus the shared `AppListShell`/`AppListItem` list-detail layout it's built on) moved, unmodified in behavior, into a brand-new sibling plugin: **Aurora Database Explorer** (`aurora-database-explorer.php`, own bootstrap, own REST namespace `aurora-database-explorer/v1`, own Vite build). Works completely standalone, verified live via direct HTTP request with a server-generated auth cookie — the admin page renders its mount div and enqueued script correctly, and the REST namespace is registered and reachable. - `CompanionPluginData.php` was generalized from a single file-manager-only class into a small `PLUGINS` config array keyed by slug, so both File Manager and Database Explorer (and any future companion plugin) share the same install/activate/status REST routes and `Plugin_Upgrader`-based installer logic — adding a new companion plugin going forward is a config entry, not new PHP. - The **Settings → Add-ons** screen now lists both companion plugins. - **Deployment gap, same as File Manager**: the Database Explorer zip is not yet hosted at `https://aurora.auroradragon.studio/aurora-database-explorer.zip` — the Add-ons "Install & Activate" button for it will fail with a fetch error until that's deployed (needs explicit go-ahead, same as any other production deploy). ## [3.4.0] - 2026-07-11 ### Changed - **File Manager extracted into a standalone companion plugin.** WordPress.org's second review response was unambiguous: the entire category of file-management/code-editing functionality isn't accepted into the Plugin Directory, regardless of the restrictions added in v3.2.1 — the feature had to come out of Aurora Admin entirely for the submission to be reconsidered. Rather than delete the functionality: - `admin/src/FileManager/FileManagerData.php` and its three Vue components moved, unmodified in behavior, into a brand-new sibling plugin: **Aurora File Manager** (`aurora-file-manager.php`, own bootstrap, own REST namespace `aurora-file-manager/v1`, own Vite build). It works completely standalone — verified live with Aurora Admin fully deactivated. - The Theme Editor → File Manager redirect (`AppearancePages.php`) is gone; visits to `theme-editor.php` now fall through to WordPress core's own Theme Editor unmodified, which already respects `DISALLOW_FILE_EDIT` itself. - A new **Settings → Add-ons** screen offers one-click installation of Aurora File Manager, wrapping `Plugin_Upgrader::install()` — the same core API the native Plugins → Add New "Install Now" button uses — pointed at a fixed, non-client-supplied HTTPS zip URL. No settings/data are shared between the two plugins; Aurora Admin has zero remaining code that touches the filesystem beyond its own Site Backup archives. ## [3.3.2] - 2026-07-11 ### Fixed - **WordPress.org Plugin Check compliance pass** — a full re-run of the official Plugin Check tool against the whole codebase (279 findings across 26 files) surfaced several real gaps beyond the earlier v3.0.1/ v3.0.2/v3.2.1 passes, all now fixed: - `readme.txt`'s `Stable tag` had drifted to 3.2.1 while the plugin itself moved through 3.3.0/3.3.1 — a real mismatch that can prevent WordPress.org from serving the correct download to users. Now kept in lockstep with every version bump. - `Analytics::country()` read `$_SERVER[$h]` directly for an `empty()`/`strlen()` check before the existing sanitized copy was computed — fixed by sanitizing once up front and using that everywhere, rather than touching the raw superglobal at all. - `AuroraAdmin.php`'s `defined("ABSPATH") || exit()` guard sat after 49 `use` import statements — functionally fine, but far enough into the file that at least one automated file-access-protection scanner didn't detect it. Moved to immediately after the `namespace` declaration, this file's one deliberate departure from the rest of the codebase's "guard after imports" convention. - Roughly 230 `WordPress.DB.DirectDatabaseQuery`/`PreparedSQL`/ `UnescapedDBParameter` findings across every custom-table feature (Site Backup, Media Folders, Activity Log, Analytics, Menu Creator, Notices, Database Explorer) — verified each one is safe (internal `$wpdb->prefix`-based table names or already-validated/escaped identifiers, never raw user input) and annotated with the justified `phpcs:ignore` the review process expects, matching the standard already established for Database Explorer's query runner. - A handful of `fread()`/`fwrite()`/`fclose()` calls adjacent to an already-annotated `fopen()` were still individually flagged — WPCS suppression is strictly per-line, so each sibling call in Site Backup's file/S3/Google Drive streaming code now has its own annotation too. - Several `RuntimeException` messages interpolating internal (never user-supplied) values were flagged as unescaped output — annotated as exception messages, not HTML/browser output, the same false-positive class WPCS is known to flag on exception constructors. ## [3.3.1] - 2026-07-11 ### Fixed - **Settings page: nav and content now scroll independently.** Previously the left category list and the right content panel scrolled together as one long page — scrolling down the nav to reach a category near the bottom (e.g. "Modern Image Formats") and clicking it left the content panel's top scrolled out of view above the fold, requiring an extra scroll back up to actually see it. Both panels now have their own fixed-height scroll region, and switching categories resets the content panel's scroll position to the top regardless of the nav's own scroll position. ## [3.3.0] - 2026-07-11 ### Added - **Modern Image Formats** — a new Settings section that automatically generates a WebP or AVIF sibling for every size WordPress produces from a newly uploaded JPEG/PNG, and (optionally) rewrites `` markup into a `` element offering the modern format with the original as a fallback source. "Automatic" output format prefers AVIF, falling back to WebP, based on what the server's image editor actually supports (`wp_image_editor_supports()`) — gracefully generates nothing on a host that supports neither, rather than erroring. Purely additive: original JPEG/PNG files are always kept, nothing about the attachment's own metadata or file is changed, and generated files are cleaned up automatically when the attachment is deleted. Only affects new uploads — existing library images need a regenerate-thumbnails-style tool to pick up the feature. - Built entirely on documented WordPress core hooks (`wp_generate_attachment_metadata`, `delete_attachment`, `wp_editor_set_quality`, `wp_content_img_tag`, `wp_get_attachment_image_html`) and `WP_Image_Editor::save()`'s documented mime-type-conversion parameter — no third-party plugin code, no Composer dependency. - Verified live end-to-end: uploaded a real JPEG, confirmed a smaller AVIF sibling was generated and recorded in attachment metadata, confirmed a post embedding the image rendered a correct `` wrapper, and confirmed both the original and generated files were removed with no orphans left behind after deleting the attachment. ## [3.2.1] - 2026-07-11 ### Changed - **File Manager: PHP/executable editing removed.** Rejected from the WordPress.org Plugin Directory because File Manager let a `manage_options` user open and overwrite *existing* `.php` files in-place with immediate execution effect — no different in risk from WordPress core's own Theme/Plugin editor, which core lets hosts disable via `DISALLOW_FILE_EDIT` specifically because of that risk. File Manager previously blocked creating/uploading/renaming-to executable file types, but that same list wasn't enforced on *editing an existing* file of that type — closed now: opening or saving any file matching `BLOCKED_EXTENSIONS` (php, phtml, cgi, py, sh, and others) is refused. File Manager also now honors `DISALLOW_FILE_EDIT` (blocks all editing) and `DISALLOW_FILE_MODS` (blocks all create/upload/rename/delete/copy/ move/write operations), the same host-level lockdown constants WordPress core's own file-modification flows already respect. Browsing, downloading, and viewing file info are unaffected. ## [3.2.0] - 2026-07-11 ### Added - **Google Drive remote storage** for Site Backup — a second remote storage option alongside S3-compatible, via OAuth 2.0. Each site connects its own Google Cloud OAuth client (Client ID + Secret, pasted into Settings) since a free, publicly-distributed plugin can't ship a shared client secret; scope is the narrow `drive.file` (only files/folders Aurora itself creates). Resumable upload and `Range`-header chunked download against Drive API v3 mirror the bounded-per-step design S3's multipart upload already established — no changes to the job engine itself were needed, confirming the storage-provider abstraction's original design goal. Shipped code-complete and protocol-verified (request shapes checked against Google's published API reference, and live-tested against Google's real OAuth endpoints with placeholder credentials to confirm genuine API errors come back); a full live "Connect" round-trip is left for the user to verify once they have a real Google Cloud OAuth client and HTTPS on the target site. ## [3.1.0] - 2026-07-11 ### Added - **Site Backup** — a new free sub-app: full site backup (database + wp-content) and restore-in-place, on-demand or on a recurring daily/ weekly/monthly schedule, storing archives locally or on any S3-compatible remote (AWS S3, Backblaze B2, Cloudflare R2, DigitalOcean Spaces — one implementation via a configurable endpoint, no vendor SDK, SigV4 signing implemented directly and verified against AWS's own published test vectors). - Built on a new resumable job-processing engine (`BackupEngine` + `BackupJobs`) — the first background-job infrastructure in this codebase. Every backup/restore step is bounded (row/file/byte count and a wall-clock budget) so a multi-GB site never risks a single request's timeout or memory limit. Two trigger paths converge on the same step function: foreground polling from the Vue UI (fast feedback while a tab is open) and a `wp_cron` safety net (`BackupCron`) that keeps a job moving with no browser involved — closing the tab mid-backup, or a scheduled backup with nobody watching, both still reach completion. - Database export/import (`DbDumper`) — paginated dump with schema captured per-table (`DROP TABLE IF EXISTS` + `CREATE TABLE`, so restore is deterministic and re-runnable from scratch), chunked restore via a byte-cursor into the dump file. Aurora's own backup-tracking tables are excluded from what gets backed up — a real bug caught during testing, where restoring a snapshot of those tables was overwriting the very job row driving the restore mid-flight. - File archiving (`FileArchiver`) — a manifest of every file under `wp-content/` (excluding the backup storage directory itself and, structurally, `wp-config.php`, which lives outside `wp-content/` entirely), zipped/extracted in bounded batches via `ZipArchive`, reopened and appended to across isolated requests. Every extracted entry is containment-checked (zip-slip protection) before anything is written — verified live with a deliberately crafted malicious archive entry, confirmed rejected. - Local storage hardening — `wp-content/uploads/aurora-backups/` gets WordPress core's own `index.php`/`.htaccess` stub pair; the real protection is that every archive read goes through a `manage_options`-gated REST route streaming the file through PHP, never a guessable direct URL. - Recurring schedules with count-based retention — a scheduled backup prunes older ones (both the job row and its stored archive, local or remote) down to the schedule's configured retention count. - v1 deliberately does not include cross-domain migration (Duplicator's own headline feature — generating an `installer.php` for a totally different host) or restoring from an arbitrary uploaded archive (restore only ever sources from Aurora's own backup history) — both are real scope/risk decisions, not oversights. - Live-verified end-to-end on the dev site, including catching and fixing two real bugs along the way: a job's `phase` column wasn't updated to a clean terminal value on completion (stale "Archiving files…" label after a backup had actually finished), and a data-corruption bug where manually pre-escaping newlines before `$wpdb->_real_escape()` (which already does this) caused restored text fields to contain literal backslash-n characters instead of real line breaks. ## [3.0.2] - 2026-07-11 ### Fixed - Plugin header validation error from the WordPress.org submission checker: `Plugin URI` and `Author URI` were both `https://auroradragon.studio`, and WP.org requires them to be distinct (one identifies the plugin, the other the author). `Plugin URI` now points to `https://aurora.auroradragon.studio` (the plugin's own marketing site); `Author URI` stays on the studio's root site. ## [3.0.1] - 2026-07-11 ### Fixed - **WordPress.org Plugin Check compliance pass.** Ran the official Plugin Check tool ahead of submission and fixed every real issue it surfaced (257 flagged items, several dozen real): - `WidgetsData.php`: replaced the forbidden `wp_get_sidebars_widgets()` (internal-only core function) with a local helper reading the `sidebars_widgets` option directly. - `FileManagerData.php`: converted every raw filesystem call (`move_uploaded_file()`, `mkdir()`, `rename()`, `unlink()`, `rmdir()`, `is_writable()`, `readfile()`) to the `WP_Filesystem` API, which works across hosting setups that don't allow direct filesystem access. `move_uploaded_file()`'s upload-tampering check is preserved via an explicit `is_uploaded_file()` call before handing off to `WP_Filesystem::move()`. - `BugReportData.php`: `parse_url()` → `wp_parse_url()`. - `SubApps.php`: fixed a translation-function rule violation (page titles now wrapped in `__()` at definition instead of being passed as a variable to `__()` at the call site) and added justified nonce-check suppressions on two read-only routing checks. - Fixed missing `wp_unslash()`/sanitization on a handful of superglobal reads (`Analytics.php`, `Shell.php`, `AnalyticsData.php`) and added missing translator comments for two placeholder strings. - Annotated dynamic-table-name SQL queries across `MediaFolders.php`, `AnalyticsData.php`, `DatabaseExplorerData.php`, `NoticesData.php`, `MenuCreatorData.php`, and `ActivityLogData.php` with justified `phpcs:ignore` comments — every flagged table name comes from an internal `$wpdb->prefix`-based helper, never user input, and table identifiers can't go through `$wpdb->prepare()`'s value placeholders (WordPress core has no API for arbitrary custom tables). - Removed the stale `Domain Path: /languages/` plugin header (pointed at a folder that's never existed — see 2.x-era removal of the fake "13 languages" marketing claim). - All changes live-verified against the LocalWP install: File Manager's full create/rename/delete cycle exercised directly against the REST routes (folder create, file create, rename, soft-delete, empty trash), Widgets' sidebar-read endpoint, Database Explorer's table browsing, and Media Folders' filtered listing all confirmed working with zero console/PHP errors. ## [3.0.0] - 2026-07-06 ### Removed - **The entire licensing/monetization system.** Aurora Admin is being given away free on the WordPress.org plugin repository — every feature previously gated behind a paid license (Role Editor, Activity Log, Database Explorer, File Manager, Analytics, White Label, Full Admin Takeover, Media Folders) is now simply available to everyone, no key required. - Deleted `admin/src/Licensing/` entirely (`License.php`, `Integrity.php`, the file-tamper-detection system that only existed to protect license gating from bypass) and `bin/build-integrity-manifest.php`. - Deleted the "Pro feature" upgrade-prompt UI (`UpgradeNotice.vue`, `locked-feature.js`) and every lock badge in the sidebar. - Removed the License key field and the dev-only "Simulate active license" toggle from Settings — there's nothing left to license or simulate. - `license_key` and `dev_license_override` are actively purged from any site's stored settings on upgrade (same cleanup pattern used for the earlier Remote Site Switcher removal), not just hidden from the UI. - Report a Bug still works, now via a direct call to the feedback endpoint instead of going through the license-status layer. - This is a clean split from the commercial `auroraplugins.com` service (beta licenses, Pro purchases) — that server-side system is untouched and unaffected by this change, it's simply no longer wired into this codebase. ## [2.27.0] - 2026-07-06 ### Added - Restored the dev-only license override (`AURORA_ADMIN_DEV_MODE` wp-config.php constant + Settings > Developer > "Simulate active license" toggle), reversing the 2.25.0 removal. Requested explicitly for local testing on admin.local — lets Pro features be exercised without a real license key. **This must be stripped again before the next release/beta build** — same standing rule as before, now re-documented in `PROJECT_STATUS.md`. Only has any effect when `AURORA_ADMIN_DEV_MODE` is defined truthy in a given install's own `wp-config.php`, never set in the repo or any built zip. ## [2.26.0] - 2026-07-05 ### Added - "Report a Bug" page (Aurora sidebar section), a real WordPress admin page (`aurora-admin-bug-report`, registered via `SubApps.php` like Menu Creator/Admin Notices) with a single free-text field plus auto-filled, read-only diagnostics (plugin version, WordPress version, PHP version, active theme, last 4 characters of the license key — not the full key). Submits to a new `POST aurora-admin/v1/bug-report` REST route (`admin/src/Rest/BugReportData.php`), which forwards to a new `License::submit_bug_report()` method that calls the license server. - **License server** (`aurora-licensing` plugin, not this repo): added a `wp_aurora_bug_reports` table (DB version 3 → 4, same version-gated migration idiom) and a new public `POST /report-bug` REST route, storing the description plus whatever license key/domain/diagnostics fields were sent. Deliberately a plain `wpdb->insert` with no admin UI yet — reports are reviewable via `wp db query` for now; a dashboard can come later if report volume justifies it. - Verified live end-to-end (not mocked): submitted a real report through the actual Report a Bug page, confirmed it landed correctly in `wp_aurora_bug_reports` on the production server, then deleted the two test rows (one from an earlier curl smoke test) by exact ID. ## [2.25.0] - 2026-07-05 ### Removed - Dev-mode license override (`AURORA_ADMIN_DEV_MODE` wp-config.php constant, Settings > Developer category's toggle, and every call-site in `License::is_active()`/`is_feature_available()`) — this was a build-time-only escape hatch for testing paid features without a live license, marked in `PROJECT_STATUS.md` as "MUST BE REMOVED BEFORE A RELEASE BUILD" since it shipped in the plugin zip. Real beta testers are about to install real builds, so it had to go before that zip goes out. Paid features now gate purely on `License::is_active()` for everyone, no exceptions. ## [2.24.0] - 2026-07-05 ### Added - Beta tester license support, end to end. User asked for 10 new 30-day beta licenses plus a way to flag/display beta status in the plugin. - **License server** (`aurora-licensing` plugin on the live `auroraplugins.com` box, not this repo): added an `is_beta` column to `wp_aurora_licenses` (migrated in place via a `plugins_loaded`-hooked check, since the file was updated without re-activation — same version-gated migration idiom this plugin's own `ActivityLog.php` etc. use), a `wp aurora-lic generate-beta [--days=] [--max-sites=]` WP-CLI command (not a public REST route — generating free licenses has to stay admin/shell-only), and `is_beta` added to `/validate` and `/activate`'s response payloads. Generated the 10 requested keys (30-day expiry, 1 site each) — see the conversation for the actual key list; not duplicated here since this file ships to customers. - **Client** (this plugin): `License::is_beta_tester()` reads the server's `is_beta` flag (already flowing through the existing `status()` cache — no new caching logic needed). Settings > General's license status line now shows "Beta Tester license active — expires in N days (date)" instead of the normal "License active (plan)" text when a beta key is active. A beta license is a real, fully-functional `is_active()` license like any other — this is a display flag only, not a separate gating path. - Verified against the real production license server, not a mock: generated a real key via the new CLI command, activated it from the dev site's actual Settings page, confirmed the badge and expiry countdown render correctly, then cleared the key (deactivating it server-side via the existing `on_settings_updated` flow). ## [2.23.0] - 2026-07-05 ### Added - Full field-set parity for Writing/Reading/Discussion native Settings screens, closing the gap flagged in 2.22.0's audit. - **Writing**: `Default Post Category` and `Default Post Format` are now real dropdowns (populated from `get_categories()`/ `get_post_format_strings()`) instead of raw numeric IDs. Added `use_smilies` — registered in core's own REST settings schema but never actually added to this screen. - **Reading**: `Homepage`/`Posts Page` are now page-title dropdowns (via `get_pages()`) instead of raw page IDs, and only appear once "Homepage Displays" is set to "A static page" — matching native WordPress's own conditional reveal. New generic `showIf` field property in `NativeSettings.vue` (`{key, equals}`) drives this, reusable by any future field. - **Discussion**: went from 2 fields to full native parity (~24 fields: pingback/comment defaults, moderation rules, threaded-comment settings, pagination, three notification-email toggles, comment moderation/disallowed-keys blocklists, and avatar settings). Core's REST settings schema (`register_initial_settings()`) only ever exposed 2 of Discussion's real options — everything else needed a new dedicated controller, `DiscussionData.php`, doing raw `get_option()`/ `update_option()` reads/writes against an explicit key allowlist (same pattern as `PermalinksData.php`, generalized to many keys instead of one). - New `textarea` field type in `SettingsField.vue` for the two moderation-keys blocklist fields (multi-line plain text — nothing else on any settings screen needed one until now). - Verified live: dropdowns populate with real data, conditional fields show/hide correctly, and a real save-then-reload round trip on Discussion (toggled a setting off, saved, reloaded fresh, confirmed the change persisted) — not just an optimistic-UI check. ## [2.22.0] - 2026-07-05 Full plugin audit, requested after the plugin-delete crash raised the question of what else might be broken or incomplete. Checked every sidebar screen live (console + PHP error log at each stop), re-verified the license-activation claim from last session against the actual code, and closed the one remaining documented security gap. ### Fixed - **REST-bypass gap closed for Role Editor, Activity Log, and Database Explorer** — their REST controllers only checked `current_user_can('manage_options')`, not the license, so a direct REST call could reach paid functionality (including Database Explorer's raw SQL runner) with no license at all. Same fix already applied to File Manager/Media Folders earlier this session, just never circled back for these three. Verified every other paid-feature REST controller (Themes, Nav Menus, Widgets, Permalinks, Analytics, White Label) already had the check — this was the last of them. - **Permalinks screen was missing the standard structure presets** (Plain, Day and name, Month and name, Numeric, Post name) — only the raw "Custom Structure" text field existed, which is the one part of that screen almost nobody actually uses by hand. Added the five presets as radio options with live example URLs, auto-detecting which preset (or "Custom") matches the currently-saved structure. ### Verified (no changes needed) - Clicked through Dashboard, Posts, Pages, Media, Users, Comments, Plugins, all Aurora sub-apps (Menu Creator, Admin Notices, Role Editor, Activity Log, Database Explorer, File Manager), Media Upload, Add New User, Profile, all 5 native Settings screens, and several Aurora Settings categories (General, White Label, Analytics) — no fatals, no new console errors anywhere in the pass. - Confirmed License.php's activation/validation flow is real (calls `auroraplugins.com`'s REST API, caches with TTL, tamper-checks) — corrected a stale "still a placeholder" claim from earlier this session and removed the outdated doc line that caused it. ### Known gaps (not fixed — flagged for a decision, not silently patched) - Writing/Reading/Discussion native Settings screens expose only a handful of fields each, several as raw text/numeric-ID inputs where native WordPress has dropdowns (post category, post format, homepage page picker) — functional but poor UX, and Discussion in particular is missing most of its native moderation options. Larger effort than the Permalinks fix; needs new endpoints (categories list, post formats) and new field types, not just a values tweak. - Comments list's pagination footer shows "0 to 0 of 0" despite the "All" tab correctly showing a count — cosmetic, not investigated further. ## [2.21.2] - 2026-07-05 ### Fixed - Deleting a plugin from Aurora's Plugins page ("major issue" per user report) fataled the whole site with WordPress's generic "There has been a critical error" page instead of actually deleting anything. Root cause, found in the PHP error log: `PluginsData::delete_plugin()` called core's `delete_plugins()`, which internally calls `request_filesystem_credentials()` (WP_Filesystem setup) — that function lives in `wp-admin/includes/file.php`, which a REST API request doesn't load the way a normal wp-admin page load does, and `delete_plugin()` only required `plugin.php`. `update_plugin()` right below it already required `file.php` for the same underlying reason; `delete_plugin()` just never got the same fix. Verified live: deleted a real plugin (WP File Manager) end to end with no fatal, "Plugin deleted." message shown correctly, confirmed no new error logged. ## [2.21.1] - 2026-07-05 ### Fixed - Sidebar section accordion closed the wrong section on "hidden child" native screens (theme-install.php, media-new.php, user-new.php, ...). User: expand Appearance, click "Add New Theme", and the Appearance section collapsed while the "AURORA" section at the bottom opened instead — this was a different bug than the overlay-covers-the-sidebar issue fixed in 2.21.0, this was specifically about which section stays expanded. Root cause: `Sidebar.vue`'s accordion decides the open section by matching the current URL against each menu item's own submenu array (`MenuSerializer::serialize()`, scraped from WP's `$submenu` global) — but these screens are deliberately absent from `$submenu` (WP's own native left menu doesn't list "Add New Theme" as a link either, only "Themes"/"Customize"/"Editor"), so no section ever matched and the accordion fell through to its Aurora-expanded default. - WP core itself solves this exact problem for its own native menu highlighting via a `$parent_file` global, set directly in each of these pages' own PHP (`theme-install.php` sets `$parent_file = 'themes.php'`, `media-new.php` sets `'upload.php'`, `user-new.php` sets `'users.php'`) — reused that instead of hardcoding a list of "hidden child screen" mappings ourselves. `Shell::print_mount()` now reads it and passes it through as `activeParentFile`; `Sidebar.vue`'s `isSectionActive()` treats a menu item as active when its id matches, alongside the existing exact-URL checks. General fix — covers any current or future WP core (or third-party plugin) screen using this same convention, not just the three confirmed here. ## [2.21.0] - 2026-07-05 ### Changed - The theme-install.php "Preview & Install" overlay (clicking a theme card under Appearance > Themes > Add New Theme) is now framed like the block editor window instead of covering Aurora's sidebar/toolbar entirely. User reported "it closes the menu" — confirmed this is WP core's own native behavior (the fullscreen overlay covers the native admin menu too, on a totally vanilla install), not a regression, but agreed a framed treatment was worth the risk for visual consistency with how the block editor is already handled. - `.wp-full-overlay` (WP core's overlay component, shared with the Customizer — only `.theme-install-overlay` specifically is touched, not `.wp-full-overlay` generally) is reframed to Aurora's standard inset box: the overlay itself plus its two independently `position: fixed` children (`.wp-full-overlay-sidebar`, `.wp-full-overlay-main`) each need the same left/top/bottom math applied directly, since fixed descendants don't inherit an ancestor's padding-box positioning the way absolutely-positioned ones do. `margin-left` on `.expanded`/`.collapsed` mirrors WP core's own three responsive breakpoints with Aurora's frame offset added on top, so the sidebar collapse/expand slide animation keeps working unmodified. - Root-caused and fixed two real bugs found while building this: (1) opening the overlay adds `body.full-overlay-active`, and WP core's own CSS sets `body.full-overlay-active { visibility: hidden }` with only the overlay itself set back to visible — Aurora's entire shell inherited that hidden visibility too (nested in body), which is what silently erased the sidebar/toolbar instead of just having them covered. Fixed with one override re-enabling visibility for Aurora's own root specifically. (2) The overlay's own `height: 100%` (WP core) combined with our `top`+`bottom` insets was the exact same over-constrained-box bug already known from the block editor's `.interface-interface-skeleton` — same `height: auto !important` fix. - The traffic-light winbar needed a permanently-running `MutationObserver` rather than a one-shot check: the overlay doesn't exist in the initial DOM (WP core creates it lazily on first click), and every subsequent theme selection fully re-renders the overlay's contents, wiping out anything inserted into it — confirmed live (bar present after the first click, gone after picking a second theme) before switching to an idempotent "ensure inserted" observer callback. - Verified live end-to-end: opening via natural clicks (not just direct URL navigation), switching between themes with the prev/next arrows (triggers the content-destroying re-render), closing and confirming the sidebar/toolbar return correctly, no console errors at any step. ## [2.20.2] - 2026-07-05 ### Fixed - Appearance > Themes was missing two capabilities the native Themes screen has: browsing/installing themes from the WordPress.org directory, and uploading a theme zip. `AppearancePages.php`'s `load-themes.php` hijack never passed a link to `theme-install.php` (only `customizeUrl`), so there was no way to reach either from Aurora's Themes page. `Themes.vue` gains an "Add New Theme" button (next to Customize) linking to native `theme-install.php`, gated on `current_user_can('install_themes')` — that native screen already has both the WordPress.org browser (Popular/ Latest/Block Themes/Favorites/Feature Filter tabs, search) and an "Upload Theme" button built in, so neither needed rebuilding. Same reuse-native-flow-for-add pattern already used for Plugins' `addUrl` (`plugin-install.php`) and Media's `newMediaUrl` (`media-new.php`). ## [2.20.1] - 2026-07-05 ### Fixed - Font Library (`font-library.php`) rendered with a jarring mix of Aurora's dark frame around a completely white, near-unreadable content area (light text on a white card). WP 6.5+ built this screen on `@wordpress/ components`' newer "boot-layout" primitives instead of classic postbox markup, so `native-dark.css`'s existing postbox/card rules never matched its DOM. Added targeted dark-theme coverage for the screen's stable, hand-authored class names (`.boot-layout-container`, `.boot-layout__stage`, `.admin-ui-page`, `.admin-ui-navigable-region`, `.admin-ui-page__header`, `.font-library-page__tab-panel`, `.font-library__tabpanel-layout`, `.font-library__footer`, `.font-library__fonts-list-item`, `.font-library__font-card`) — deliberately not touching this screen's Emotion-generated hash classes (e.g. `css-qi2azi`), which aren't stable across WP builds. - Regenerated `admin/src/Licensing/integrity-manifest.json` and rebuilt `dist/aurora-admin-v2.zip` from a fresh staging copy — the protected files' checksums (`Integrity::is_tampered()`'s manifest) had gone stale after this session's edits to `aurora-admin.php`, `Shell.php`, and others. Also excluded `.claude/` from the staged copy — the internal tooling config had been getting bundled into the distributable zip. ## [2.20.0] - 2026-07-05 ### Added - Media Folders (paid — `License::PAID_FEATURES["media-folders"]`): a folder tree for the Media Library, duplicating the organizing capability of the FileBird Pro plugin at the user's request, with its own independent schema (`wp_aurora_media_folders` / `wp_aurora_media_folder_items`, distinct from FileBird's own `wp_fbv*` tables so both can coexist) rather than importing FileBird's data. - New `MediaFolders.php` model (create/rename/delete/reparent, one folder per attachment) + `MediaFolderData.php` REST routes (`/media-folders`, `/media-folders/assign`), every route double-gated (`current_user_can` + `License::is_feature_available`) per this project's established REST-bypass-hardening pattern. - `Media.vue` gains a folder sidebar (`MediaFolderTree.vue`): create, rename, delete, right-click context menu, drag-and-drop reparenting, and drag-and-drop (or a "Move to folder" fallback) to file media into folders. "All Media" and "Uncategorized" are pinned pseudo-folders. - The Media Library page itself stays free/ungated either way — only the folder capability is paid. Unlicensed users keep today's flat grid unchanged; the sidebar shows an inline `UpgradeNotice` instead of disappearing or locking the whole page — a new gating shape for this codebase (previously: whole sub-app free or whole sub-app paid). ### Fixed - `Shell::is_own_page()` didn't recognize `ScreenHijack`-based pages (Posts, Pages, Media, Users, Comments — they keep their native URL, no `?page=aurora-admin` to key off), so it misclassified them as native pages and loaded `native-dark.css`'s own-page exclusion incorrectly, letting that stylesheet's generic `a` color rule leak into the real Aurora UI. Invisible until now because no earlier hijacked page rendered a link relying on accent/contrast coloring — Media Folders' new "Enter a license key" CTA in the unlicensed sidebar was the first. `ScreenHijack::register()` now flags `is_hijack_active()` the moment any registered hijack activates for the current request, and `is_own_page()` checks it. ## [2.19.0] - 2026-07-05 ### Removed - Remote Site Switcher: the Settings > General "Remote Sites" repeater and "Remote Site Switcher Capability" field, and the toolbar dropdown they fed. User asked what it did; investigation found it was inert scaffolding — it collected and stored a remote site's URL/username/application password, but nothing anywhere in the codebase ever used the credentials (no REST calls, no proxying, no SSO). The toolbar "switcher" was just a plain link opening the remote site's own `/wp-admin/` in a new tab. User's call: "doesn't really do much for the software" — removed rather than built out, since a real cross-site integration wasn't the ask. - `License::PAID_FEATURES["remote-site-switcher"]` — the feature's paid gate, now dead code since the feature itself is gone. ### Added - `Settings::remove_retired_keys()` — actively deletes now-unused settings keys from the database on `init`, rather than leaving them inertly stored. Added specifically because `remote_sites` held a real secret (WordPress application passwords); once the field stopped being read or written, the safer move was to purge it, not just stop touching it. ## [2.18.0] - 2026-07-05 ### Added - Role Editor: an info icon next to every capability checkbox, with a hover tooltip explaining what it actually does — user asked directly, pointing at `level_0`/`level_1` as an example of capabilities with no explanation anywhere in the UI. New `capabilityDescriptions.js` covers all ~55 WordPress core capabilities (`edit_posts`, `manage_options`, `unfiltered_html`, etc.) plus the legacy `level_0`–`level_10` "user level" system specifically — a pre-2.0 permissions scheme WordPress still assigns to every default role purely for backward compatibility with old themes/plugins that check `current_user_can('level_N')` directly instead of a named capability; core itself hasn't checked a level in years. Any capability not in the dictionary (e.g. one added by another plugin) gets a generic "custom capability" fallback rather than showing nothing. Verified live: hovering `level_0`'s info icon on the Subscriber role shows the explanation described above. ## [2.17.2] - 2026-07-05 ### Fixed - `AppListShell.vue` (the shared master/detail layout used by Database Explorer, Role Editor, Activity Log, Admin Notices, and Menu Creator): the list pane and detail pane shared one page-level scroll instead of scrolling independently. Selecting an item near the bottom of a long list (e.g. Database Explorer's 40 tables) left the detail panel's newly-loaded content rendered off-screen above the current scroll position — looked like nothing happened until you scrolled all the way back up. Root cause: the shell only set a `min-height` on the outer flex container, so it just grew with content instead of giving each pane a capped height + its own scrollbar. Fixed by giving the container a real `height` and both panes `height: 100%; overflow-y: auto`, with a separate mobile-breakpoint treatment (stacked panes get natural height with their own `max-height` instead of squashing to 50/50). Verified live: scrolled Database Explorer's table list to the bottom, selected `wp_users`, confirmed its data appeared immediately without scrolling. ## [2.17.1] - 2026-07-05 ### Fixed - File Manager's right-click context menu never appeared for real users (it worked in earlier automated testing, which turned out to be triggering the bug's absence rather than confirming its fix). Root cause: right-clicking — especially via a trackpad's two-finger tap or Ctrl-click on macOS — can fire a trailing synthetic `click` event immediately after `contextmenu` on the same target. The menu's "click anywhere else closes it" listener was attached synchronously the moment the menu opened, so that trailing click closed it in the same tick it appeared — a real race condition, not a rendering bug. Fixed by deferring the outside-click listener's registration by one tick (`setTimeout(..., 0)`) so it only starts listening after the opening event has fully finished. Verified by simulating the exact race (`contextmenu` immediately followed by `click` on the same element, same tick) and confirming the menu stays open; also confirmed a genuine later click still closes it. ## [2.17.0] - 2026-07-05 ### Added - File Manager: **right-click context menu**, mirroring the toolbar exactly (same functions, just a different trigger) so the two can't drift out of sync. Right-clicking an item outside the current selection selects just that item first (standard file-manager behavior); right-clicking within an existing multi-selection leaves it intact so bulk actions still work from the menu. Right-clicking empty space shows folder-level actions (New Folder/File, Upload, Paste) instead. Closes on Escape or a click anywhere else. - File Manager: the plain `