Document the free/paid feature-gating architecture

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
nixpanel
2026-07-03 20:00:17 -05:00
co-authored by Claude Sonnet 5
parent 6d502604d7
commit eb03d3dd6c
+97 -8
View File
@@ -1,6 +1,6 @@
# Aurora Admin v2 — Project Status
_Last updated: 2026-07-03 (latest session — universal content-area scroll cap, sidebar accordion, scroll-anchoring fix)_
_Last updated: 2026-07-03 (latest session — free/paid feature gating (licensing))_
## What this project is
@@ -438,6 +438,75 @@ page navigation via a taskbar.
block editor (`body.block-editor-page` is present on both), so the
existing block-editor handling covers it once `#site-editor` is framed.
## Licensing (free vs. paid feature gating)
Added this session. The split, as decided with the user: the whole shell/
theming product is free (it's the adoption hook); Role Editor, Activity Log,
Database Explorer, Analytics, White Label, and the remote site switcher are
paid (the "operational" tools an agency/serious site owner pays for). Menu
Creator, Admin Notices, Comments, and the Plugins page stay free.
- `admin/src/Licensing/License.php` is the single gate every paid feature
checks — `PAID_FEATURES` maps a feature id to a human label,
`is_active()` and `is_feature_available($feature)` are the two calls
everything else uses. **`is_active()` is a placeholder today**: any
non-empty `license_key` setting counts as licensed, no remote validation
at all. There's no license server yet — when one exists, only
`is_active()`'s internals need to change (ideally caching the remote
check in a transient so it isn't a network call on every admin page
load); every gated feature already funnels through this class rather than
reading the setting directly, so nothing else needs to change.
- **Sub-apps** (`SubApps.php`): `PAGES` now has a `"feature"` key per page
(`null` for the 4 free ones). Locked pages still fully register
(`add_submenu_page`, real capability, real URL — same as before) so nav/
bookmarks/capability checks all keep working; `render()` and
`maybe_enqueue()` just check `License::is_feature_available()` and swap in
a shared upgrade prompt (below) instead of the real feature's mount div/JS
bundle/REST data when unlicensed. This also means a locked page's
sensitive data (e.g. Database Explorer's schema access) never gets
fetched or serialized to the page in the first place.
- **Shared upgrade prompt**: `app/src/pages/_shared/UpgradeNotice.vue` (lock
icon, feature name, description, a CTA linking to
`admin.php?page=aurora-admin#license`) + a dedicated
`app/src/entries/locked-feature.js` Vite entry that mounts it onto
`[data-aurora-locked-feature]`. `SettingsField.vue` gives the license key
field `id="license"` so that CTA link scrolls straight to it (General is
the default-active settings category, so the anchor resolves on a fresh
page load too). Reused directly by the Dashboard's Analytics tab (below)
rather than building a second locked-state component.
- **Analytics** (paid): `Analytics.php`'s constructor returns immediately
(no hooks registered, no pageview tracking) when unlicensed.
`AnalyticsData.php`'s REST `permission_callback` also checks
`License::is_feature_available("analytics")`, so the endpoint 403s even
if someone hits it directly. The Dashboard's Analytics tab
(`Dashboard.vue`) renders `UpgradeNotice` in place of the real charts when
`useAnalyticsData`'s fetch comes back non-OK (the 403, in practice).
- **White Label** (paid): `WhiteLabel.php`'s constructor returns immediately
when unlicensed — favicon/plugin-rename/menu-rename/text-replacement
filters simply never register. Its settings fields stay visible either
way (so an admin can pre-configure before upgrading) — enforcement is
entirely server-side, nothing in the UI needs to disable inputs.
- **Remote site switcher** (paid): `Shell.php`'s `canSwitchSites` flag now
additionally requires `License::is_feature_available("remote-site-switcher")`,
alongside its existing `remote_site_switcher_capability` check.
- **Sidebar lock badges**: `Shell::aurora_nav()` adds a `"locked"` bool per
nav item (true for Role Editor/Activity Log/Database Explorer when
unlicensed); `Sidebar.vue` renders a small lock dashicon next to the label
when set. The link still navigates normally — `SubApps::render()` is what
actually shows the upgrade prompt there.
- **Settings page**: White Label/Analytics category subtitles and the
Remote Site Switcher Capability field's description now say "Pro feature
— requires a license" (`settingsConfig.js`) — a plain-text note only, no
functional gating in the settings UI itself (the features' own code is
the actual boundary).
- **Bug fix surfaced while testing this**: `RolesData.php`'s REST callback
called `get_editable_roles()` unqualified, which 500'd every time —
that function lives in `wp-admin/includes/user.php`, which WP only
auto-loads for real wp-admin page requests, not REST API requests. Fixed
with a `function_exists` guard + `require_once` before the call. Unrelated
to licensing, just happened to surface while verifying Role Editor's
unlock path.
## Key bugs fixed (root causes, in case they resurface)
- **`.card` global class collision**: original Dashboard cards used
@@ -536,15 +605,35 @@ over-constraint bug (`top`+`height`+`bottom` all set on a fixed-position box
silently ignoring `bottom`) that was making its frame run past the viewport
(pushed as commit `8153d43`).
This session: capped every native/Aurora page's content area to the
viewport with internal scrolling instead of letting tall pages grow past it
and scroll the whole document (see "Universal content-area height cap"
above), which surfaced and fixed two more bugs along the way — a Chrome
scroll-anchoring jump on Add Plugins, and a phantom document-level
An earlier pass this session capped every native/Aurora page's content area
to the viewport with internal scrolling instead of letting tall pages grow
past it and scroll the whole document (see "Universal content-area height
cap" above), which surfaced and fixed two more bugs along the way — a
Chrome scroll-anchoring jump on Add Plugins, and a phantom document-level
scrollbar with a large empty gap below the frame. Also made the sidebar's
native-menu sections a single-open accordion instead of letting multiple
stay expanded at once. All of it was live-verified in Chrome against the
LocalWP site (build hot-patched into
stay expanded at once (pushed as commit `73591e0`).
Most recently: added the free/paid feature-gating system end-to-end (see
"Licensing" above) — Role Editor, Activity Log, Database Explorer,
Analytics, White Label, and the remote site switcher are now paid; Menu
Creator, Admin Notices, Comments, Plugins, and the whole shell/theming
product stay free. Verified both directions live (entering/clearing a
license key immediately locks/unlocks the sidebar badges, the sub-app
pages, the Dashboard's Analytics tab, and the underlying REST endpoints),
and fixed an unrelated pre-existing 500 in Role Editor's REST endpoint
along the way (pushed as commit `6d50260`). The distributable zip
(`dist/aurora-admin-v2.zip`) was rebuilt fresh from source and should be
re-verified/re-zipped again after this doc update if anything changed since.
All of it was live-verified in Chrome against the LocalWP site (build
hot-patched into
`/Users/kris/Local Sites/aurora/.../aurora-admin/app/dist` for iteration —
the repo's source files are the actual edits; that copy is just the
compiled build artifact, not a separate edit surface).
**Not yet built**: an actual license-purchase/checkout flow or remote
validation server — today entering *any* non-empty text in the License key
field unlocks every paid feature (see "Licensing" above). This is fine for
internal testing but must be replaced with real validation before this
ships to real users, or the paid tier isn't actually gated.