From 7f470ad86c946bbc236e1abd7d9196ab6c8322ce Mon Sep 17 00:00:00 2001 From: Dan Knauss <273554+dknauss@users.noreply.github.com> Date: Sun, 9 Aug 2026 19:58:48 -0600 Subject: [PATCH] docs(planning): record v1.5.0 as shipped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tag v1.5.0 on 694b1bf; GitHub Release published; SVN deploy dispatched manually and verified FROM SVN — trunk Stable tag 1.5.0, tags/1.5.0/ present and carrying the per-user code (not merely the right version string), assets intact. Marks REL-11 and Phase 26 complete, flips the milestone to shipped, and adds the v1.5 entry to MILESTONES.md. The milestone entry records what this release should actually be remembered for: NINE defects found by verification and review, almost none by the test suite as first written. Most instructive is the run of FOUR consecutive holes in Config::sanitize()'s per-user authorization path — client-only gate, then payload-scoped preserve, then raw-key matching, then item-cap starvation, plus a DELETE endpoint that bypassed all of them. Each fix was correct about the case in front of it and blind to the next, and after each one the path looked settled. It only resolved by COLLAPSING three mechanisms into one, not by adding a fifth guard. The transferable lesson: when a fix keeps needing another fix, the shape is wrong, not the coverage. Also corrects ROLE-02's known-limitation text, which still claimed the multisite exempt branch was untested — the dedicated multisite CI lane closed that. Caveats carried forward rather than dropped at the finish line: the #128 fixes shipped unreviewed, there was no human screen-reader pass, and 21-05's browser verification never happened. A milestone record that quietly drops its caveats is how the next cycle inherits a false premise — which this project has already had to correct once. Co-Authored-By: Claude Opus 5 --- .planning/MILESTONES.md | 32 +++++++++++++++ .planning/REQUIREMENTS.md | 6 +-- .planning/ROADMAP.md | 4 +- .planning/STATE.md | 86 ++++++++++++--------------------------- 4 files changed, 62 insertions(+), 66 deletions(-) diff --git a/.planning/MILESTONES.md b/.planning/MILESTONES.md index ecbb4ad..4425a47 100644 --- a/.planning/MILESTONES.md +++ b/.planning/MILESTONES.md @@ -1,5 +1,37 @@ # Milestones: Maestro +## v1.5.0 Per-User Visibility (Shipped: 2026-08-09) + +**Phases:** 21 (built under the v1.4 roadmap) + 26 (release). **Release tag:** `v1.5.0` (commit `694b1bf`) · GitHub Release + WordPress.org SVN `trunk`/`tags/1.5.0`, verified from SVN. + +**Delivered:** ROLE-02's **per-user half**. An admin can hide a menu item from named individuals as well as whole roles, on four independent axes (role × person, item × sub-items). Cosmetic only, like every other hide in Maestro: a hidden page still opens by URL for anyone authorized, and no capability changes. + +**Key accomplishments:** +- `items[slug].hidden_users` / `child_hidden_users` storage, sparse and capped at `MAX_HIDDEN_USERS` +- `is_hidden_for_current_user()` widened to independent OR'd terms, with the third reserved for the deferred `hidden_profiles` half — the seam stays a pure identity intersect and never touches a capability +- `resolved_hidden_roles()` **generalized** into a field-parameterized resolver rather than duplicated, so the user axis inherits the qualified-key, schema-v2 and Axis-1 guards from one implementation +- The feasibility note's §6 cosmetic-invariant guardrail made **enforcing** (`CosmeticInvariantUsersTest`) +- Four-group visibility popover with an async person picker on core's `wp/v2/users`, gated by `list_users` on both sides +- A multisite CI lane, closing the super-admin exemption's coverage gap + +**NOT in this milestone:** the cloned-role **"profiles"** half of ROLE-02, still a backlog item (`todos/pending/2026-08-02-cloned-role-hiding-profiles.md`). ROLE-02 remains **partially delivered**. Phase 22 (demo) and Phase 25 (toolbar polish) were optional inclusions that did not land. + +### What this milestone should be remembered for + +**Nine defects were found by verification and review; almost none by the test suite as first written.** Three surfaced during Phase 21's own browser checks — including a guardrail that could not detect a broken seam, because `current_user_can()` answers from a cached allcaps array. Two more came from Codex rounds on the plans, both of which I then hit as live bugs anyway. The adversarial security gate found two. The final ultrareview found three. + +Most instructive: **four consecutive holes in `Config::sanitize()`'s per-user authorization path**, each fix correct about the case in front of it and blind to the next — client-only gate → payload-scoped preserve → raw-key matching → item-cap starvation, plus a DELETE endpoint that bypassed all of them. After each fix the path looked settled. It was only resolved by *collapsing* three mechanisms into one normalized-key map with reserved capacity, rather than adding a fifth guard. + +The transferable lesson: when a fix keeps needing another fix, the shape is wrong, not the coverage. + +**Known limitations, carried deliberately rather than dropped at the finish line:** +- The final round of fixes (#128) is itself unreviewed — the ultrareview ran against the prior commit +- No human screen-reader pass on the person picker; axe is clean across empty and populated states, which is not the same claim +- 21-05 Task 5 (human browser verification) was never performed; the phase was accepted on automated evidence +- On multisite, super admins are exempt from the person axis only — deliberate, now tested both ways + +--- + ## v1.3.0 Slug-Resolution Hardening (Shipped: 2026-06-30) **Phases completed:** 2 phases (17–18), 6 plans. **Release tag:** `v1.3.0` (commit `884c6df`) · GitHub Release + WordPress.org SVN `trunk`/`tags/1.3.0`. diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 17fa60c..2e89bd1 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -28,7 +28,7 @@ backlog IDs without renumbering. ### Roles (cosmetic only) - [x] **ROLE-01**: A feasibility note determines whether per-user and/or cloned-role cosmetic menu hiding can be delivered **without** touching capabilities (stays cosmetic per the core value) within WordPress's role/user model, and specifies the storage shape + resolution seam. **Gates ROLE-02** — if it can't stay cosmetic, ROLE-02 defers. *(Complete 2026-07-05 — Phase 19. Verdict: **partial-go**; both branches clear the cosmetic-only bar. Storage: inline `items[slug].hidden_users` axis + a `profiles` registry compiling onto `items[slug].hidden_profiles`; seam: widen `is_hidden_for_current_user()`. Phase 21 unblocked, per-user first. See `phases/19-cosmetic-hiding-feasibility/19-FEASIBILITY-NOTE.md`.)* -- [~] **ROLE-02**: An admin can apply cosmetic menu-hiding rules scoped to a **specific user** (or a cloned role), intersected against that user's live roles. The rules never grant or remove a capability; a hidden page still loads by URL for a user who has the capability. *(conditional on ROLE-01)* — **PARTIALLY DELIVERED (Phase 21, v1.5).** The **per-user** half is complete: `items[slug].hidden_users` + `child_hidden_users`, resolved through the same single `is_hidden_for_current_user()` seam as the role axes, with the cosmetic invariant enforced by `tests/integration/CosmeticInvariantUsersTest.php` and the effect proven in a targeted user's real sidebar by `tests/e2e/specs/hidden-users.spec.ts`. The **cloned-role "profiles"** half is NOT delivered and remains a v1.5 backlog item — see `todos/pending/2026-08-02-cloned-role-hiding-profiles.md`; the seam is built as an OR of independent terms so `hidden_profiles` lands as a third term without rework. Known limitation: on multisite, network super admins are exempt from the per-user axis only (the role axes keep their v1.4.1 behaviour); that exempt branch is not covered by the single-site test suite. +- [~] **ROLE-02**: An admin can apply cosmetic menu-hiding rules scoped to a **specific user** (or a cloned role), intersected against that user's live roles. The rules never grant or remove a capability; a hidden page still loads by URL for a user who has the capability. *(conditional on ROLE-01)* — **PARTIALLY DELIVERED (Phase 21, v1.5).** The **per-user** half is complete: `items[slug].hidden_users` + `child_hidden_users`, resolved through the same single `is_hidden_for_current_user()` seam as the role axes, with the cosmetic invariant enforced by `tests/integration/CosmeticInvariantUsersTest.php` and the effect proven in a targeted user's real sidebar by `tests/e2e/specs/hidden-users.spec.ts`. The **cloned-role "profiles"** half is NOT delivered and remains a v1.5 backlog item — see `todos/pending/2026-08-02-cloned-role-hiding-profiles.md`; the seam is built as an OR of independent terms so `hidden_profiles` lands as a third term without rework. Known limitation: on multisite, network super admins are exempt from the per-user axis only (the role axes keep their v1.4.1 behaviour) — an intentional asymmetry, now covered by a dedicated multisite CI lane (`npm run test:php:multisite`). SHIPPED in v1.5.0 (tag `v1.5.0` on `694b1bf`, 2026-08-09). ### Editor UX @@ -60,7 +60,7 @@ first; neither blocks the cut. ### Release -- [ ] **REL-11**: v1.5 is cut and shipped — runtime zip builds clean, Plugin Check 0 errors, full PHP/JS/e2e suites green, tagged `v1.5.0`, deployed to WordPress.org SVN `trunk` following the v1.2/v1.3/v1.4 pipeline (**including the manual `wp-deploy.yml` dispatch**, which has never been automatic); changelog verified against the `v1.4.1..main` diff rather than the phase list; **screenshot 3 recaptured** to show the visibility popover's four groups instead of two; and the multisite super-admin exemption stated as a known limitation. +- [x] **REL-11**: v1.5 is cut and shipped — runtime zip builds clean, Plugin Check 0 errors, full PHP/JS/e2e suites green, tagged `v1.5.0`, deployed to WordPress.org SVN `trunk` following the v1.2/v1.3/v1.4 pipeline (**including the manual `wp-deploy.yml` dispatch**, which has never been automatic); changelog verified against the `v1.4.1..main` diff rather than the phase list; **screenshot 3 recaptured** to show the visibility popover's four groups instead of two; and the multisite super-admin exemption stated as a known limitation. --- @@ -96,7 +96,7 @@ Which phases cover which requirements. Populated during roadmap creation. | UX-13 | Phase 23 | ✅ Complete 2026-07-05 | | BUG-08 | Phase 23 | ✅ Complete 2026-07-05 | | REL-10 | Phase 24 | ✅ Complete 2026-08-04 (v1.4.0; patch v1.4.1 2026-08-05) | -| REL-11 | Phase 26 (v1.5) | Pending — ships Phase 21's per-user hiding | +| REL-11 | Phase 26 (v1.5) | ✅ Complete 2026-08-09 (tag v1.5.0 on 694b1bf; SVN verified) | **Coverage:** - v1.4 requirements: 11 total diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 592ba6d..6d5415a 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -10,7 +10,7 @@ - ✅ **R1 Third-Party Compatibility Research** — Phases 13–16 (completed 2026-06-29; non-versioned research — no plugin code, no release tag, no SVN deploy) → [archive](milestones/R1-ROADMAP.md) - ✅ **v1.3.0 Slug-Resolution Hardening** — Phases 17–18 (shipped 2026-06-30; release tag `v1.3.0`) → [archive](milestones/v1.3.0-ROADMAP.md) - ✅ **v1.4 Compatibility, Roles & Showcase** — Phases 19–24 (shipped 2026-08-04; release tag `v1.4.0`, patch `v1.4.1` 2026-08-05). Shipped **without** Phase 21 (ROLE-02, deferred to v1.5 under the Release Binding fallback) and Phase 22 (not reached; still open). -- 🚧 **v1.5 Per-User Visibility** — Phase 21 (built under the v1.4 roadmap, ships here) + Phase 26 (release). Phases 22 and 25 are **optional inclusions**: they ship in v1.5 if they land before the cut, and otherwise slip without blocking it. +- ✅ **v1.5 Per-User Visibility** — Phase 21 + Phase 26 (shipped 2026-08-09; release tag `v1.5.0` on `694b1bf`). Delivered ROLE-02's **per-user half**; the cloned-role "profiles" half remains a backlog item. Phases 22 and 25 were optional inclusions and did **not** land, per the fallback. ## Phases @@ -295,7 +295,7 @@ feature is built (Phase 21, 5/5 plans, 2026-08-08) but unreleased — v1.4.0 was cut before it landed. This milestone exists to get it to users. - [~] **Phase 21: Cosmetic Per-User Hiding** — built under the v1.4 roadmap (details in the v1.4 section above); ships here. Per-user half complete; cloned-role profiles remain a backlog item. -- [ ] **Phase 26: Release v1.5.0** — cut and ship to WordPress.org +- [x] **Phase 26: Release v1.5.0** — cut and shipped to WordPress.org 2026-08-09 (tag `v1.5.0` on `694b1bf`; GitHub Release + SVN `trunk`/`tags/1.5.0` verified from SVN) - [ ] *(optional)* **Phase 22: Slug-Resolution Showcase Demo** — ships in v1.5 if it lands before the cut - [ ] *(optional)* **Phase 25: Edit-Mode Toolbar Dark-Surface Polish** — ships in v1.5 if it lands before the cut diff --git a/.planning/STATE.md b/.planning/STATE.md index c855902..e8da43a 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -2,8 +2,8 @@ gsd_state_version: 1.0 milestone: v1.5 milestone_name: Per-User Visibility -status: in-progress -stopped_at: "v1.5.0 hold cleared — Gate 8 satisfied via ultrareview (3 findings, fixed in #128); ready to tag" +status: shipped +stopped_at: "v1.5.0 SHIPPED to WordPress.org (tag v1.5.0 on 694b1bf, SVN verified). Milestone recorded; next milestone not yet opened." last_updated: "2026-08-09T00:00:00.000Z" last_activity: "2026-08-08 — **Phase 21 (ROLE-02 per-user hiding) executed, 5/5 plans, awaiting the human-verify checkpoint.** Branch `phase/21-cosmetic-per-user-hiding`, PR #120, nothing merged. Delivered: `hidden_users` / `child_hidden_users` storage; the `is_hidden_for_current_user()` seam widened to independent OR'd terms (3rd term reserved for the deferred `hidden_profiles`); `resolved_hidden_roles()` GENERALIZED to a field-parameterized resolver rather than duplicated, so the user axis inherits the qualified-key, schema-v2 (#115) and Axis-1 guards from one implementation; a shared `Cascade` union; the §6 cosmetic-invariant guardrail made enforcing; editor model exposure as id+name pairs via one batched query; four-group visibility popover with an async person picker on core's `wp/v2/users`. Gate: unit 165/165 (218), integration 109/109 (257), JS 83/83, e2e 39 passed/28 capture-skipped/0 failed, WPCS clean, PHPStan 0, Plugin Check 0 errors on the ZIP (1 pre-existing readme warning). THREE bugs found by verification that no unit test would have caught: (1) the guardrail initially could NOT detect a broken seam — `current_user_can()` answers from a cached allcaps array, so `snapshot_caps()` now drops `$GLOBALS['current_user']` to force re-derivation; (2) the picker URL appended `?` unconditionally, 404ing on every PLAIN-PERMALINK site since `rest_url()` already carries a query string; (3) clicking a search result or chip closed the whole popover, because re-rendering detached the node before `placePopover()`'s outside-click handler ran. Ruling recorded 2026-08-08: the super-admin exemption covers the NEW user axis only, multisite-scoped (unscoped would make administrators un-hideable on single-site and contradict the locked self-target decision). Prior: 2026-08-05 — **v1.4.1 SHIPPED** (PR #116, tag on c6cdcbe; wp.org API confirms 1.4.1). Patch for the shared-slug propagation defect (#115): a bare top-level key no longer applies to a submenu row whose slug names a rendered top-level item. Two Codex P2 rounds on #115 — the first cut of the gate tested `$nk === $norm_parent` and missed submenus parked under an unrelated parent; widened to `isset( $top_rendered_matches[ $nk ] )`. Prior: 2026-08-04 — **v1.4.0 SHIPPED** (PR #113, tag on 482510c, GitHub Release + wp.org SVN trunk/tags/1.4.0/assets confirmed). Phase 24 release gates 8–11 run consolidated over the full v1.3.1..main diff; one defect found and fixed (multibyte truncation blanked labels) and one changelog overclaim corrected (shared-slug isolation is submenu-direction only). ROLE-02 (Phase 21) deferred to v1.5." progress: @@ -26,7 +26,7 @@ See: .planning/PROJECT.md (updated 2026-07-03) ## Current Position Milestone: **v1.5 Per-User Visibility** (active) — Phase 21 ✅ MERGED 2026-08-09 + Phase 26 (release, created 2026-08-09); Phases 22/25 optional inclusions. v1.4 SHIPPED (v1.4.0 + v1.4.1). -Phase: Phase 19 ✅, Phase 20 ✅, Phase 23 ✅ (shipped as v1.3.1). **Phase 21 (Cosmetic Per-User Hiding) ✅ COMPLETE — 5/5 plans, merged to `main` 2026-08-09 via PR #120 (merge commit, history preserved). Task 5 accepted on automated evidence rather than a human browser pass — see the note below.** +Phase: Phase 19 ✅, Phase 20 ✅, Phase 23 ✅ (shipped as v1.3.1), **Phase 26 (Release v1.5.0) ✅ SHIPPED 2026-08-09**. **Phase 21 (Cosmetic Per-User Hiding) ✅ COMPLETE — 5/5 plans, merged to `main` 2026-08-09 via PR #120 (merge commit, history preserved). Task 5 accepted on automated evidence rather than a human browser pass — see the note below.** Plan: Phase 21 delivered ROLE-02's **per-user half**: 21-01 storage → 21-02 seam + cascade → 21-03 guardrail → 21-04 editor + picker → 21-05 e2e + gate + docs, plus two rounds of Codex review fixes. The cloned-role **profiles** half stays deferred to the backlog todo. Next: **Phase 26 (Release v1.5.0)** — per-user hiding is on `main` but unreleased; Phase 22 (demo) and Phase 25 (toolbar polish) are optional inclusions that do not block the cut. Status: Phase 20's COMPAT-10 was reworked mid-checkpoint 2026-08-02: the boolean `cascade_hide` + "rides the parent hide" model built in 20-05/20-06 was found **inert** — WordPress core's `_wp_menu_output()` never renders a hidden parent's `