diff --git a/CHANGELOG-legacy.md b/CHANGELOG-legacy.md
new file mode 100644
index 0000000..527b27b
--- /dev/null
+++ b/CHANGELOG-legacy.md
@@ -0,0 +1,1293 @@
+# 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 `