Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .planning/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ should be made once.
| D2 | Whether a request-scoped, viewer-only, subtract-only capability filter fits SPEC's "never changes a capability", or the wording gets a carve-out | Phase 30 | **Decided 2026-10-23 (Dan):** approve the narrow viewer-only, subtract-only menu-construction exception; see [decisions](phases/30-view-as-role-preview/30-CONTEXT.md) |
| D3 | Label and form of the select-all control ("All other roles"; tri-state checkbox or a pair of links) | Phase 29 | **Decided 2026-10-23 (Dan):** a tri-state checkbox labelled "All other roles", as built in Phase 29 |
| D4 | Import behaviour for rules whose menu item is missing on the target site: keep dormant, or drop and report | Phase 31 | **Decided 2026-10-23 (Dan): drop and report.** Rules naming a menu item the target site does not have are not stored; the import result lists them so the user knows what was not applied |
| D5 | Release shape: one v1.9.0 after all three, or cut after Phases 29–30 and let Phase 31 follow | Before the cut | Open — recommended 2026-10-23: cut v1.9.0 after Phases 29–30 once #248/#251 pass Dan's deep review; Phase 31 ships as a follow-up release. Awaiting Dan's confirmation |
| D5 | Release shape: one v1.9.0 after all three, or cut after Phases 29–30 and let Phase 31 follow | Before the cut | Recommended 2026-10-23: cut after Phases 29–30, Phase 31 follows. 2026-10-24: release PR cuts **v1.9.0** from main after #260 (1.8.0 was never tagged, so its changes ship in 1.9.0). Decided when Dan approves that PR |
| D6 | Milestone version | Before the cut | **Decided 2026-10-22:** width v1.7.0, profiles v1.8, Client-Site Workflow v1.9 |

### Shared seams (build once, reuse)
Expand Down
9 changes: 9 additions & 0 deletions .planning/STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ progress:

# Project State

## Release prep — 1.9.0 (2026-10-24)

Main after #260 (`d34a576`) is being cut as **1.9.0**. 1.8.0 was bumped in #242 but
never tagged or released, so 1.9.0 carries visibility groups (Phase 27), the width
follow-ups, "All other roles" (Phase 29), preview as a role (Phase 30) and the
desktop and mobile polish (#256–#260). Phase 31 (export/import) follows in a later
release, per the D5 recommendation; the release PR is Dan's confirmation of D5.
The POT is regenerated. Real iPhone/iPad checks of #260 remain Dan's.

## Desktop UX refinement — 2026-10-24

Phase 30 role-preview UI is on main through #251 (`9802065`). The desktop cleanup
Expand Down
16 changes: 16 additions & 0 deletions assets/maestro-logic.js
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,21 @@ function profileNameProblem( name, profiles, exceptId ) {
return taken ? 'taken' : '';
}

/** Build a deletion confirmation without promising visibility changes. */
function profileDeleteMessage( name, items, people, strings ) {
var template = items === 1 ?
( people === 1 ? strings.profileDeleteBoth : strings.profileDeleteItem ) :
( people === 1 ? strings.profileDeletePerson : strings.profileDeleteConfirm );
var message = template
.replace( '%1$s', function () { return name; } )
.replace( '%2$d', function () { return String( items ); } )
.replace( '%3$d', function () { return String( people ); } );
if ( items > 0 && people > 0 ) {
message += ' ' + strings.profileDeleteEffect;
}
return message;
}

/**
* How many items a profile hides, counted from the editor's assignments: an
* item hidden through both its own and its sub-items axis counts once.
Expand Down Expand Up @@ -728,6 +743,7 @@ var api = {
dropProfileCarry: dropProfileCarry,
profileNameProblem: profileNameProblem,
profileItemCount: profileItemCount,
profileDeleteMessage: profileDeleteMessage,
buildProfilesPayload: buildProfilesPayload,
carryProfileHides: carryProfileHides,
carrySeparators: carrySeparators,
Expand Down
79 changes: 78 additions & 1 deletion assets/maestro.css
Original file line number Diff line number Diff line change
Expand Up @@ -1483,6 +1483,83 @@ body.maestro-editing #adminmenu li.menu-top .wp-submenu {
@media ( hover: hover ) {
.maestro-toolbar .button:not(:disabled):not(.button-link-delete):hover {
background: var( --maestro-menu-accent, #2271b1 );
color: var( --maestro-menu-accent-text, #fff );
}
}

/* Dialog standard: shared geometry and one focus treatment, including controls
* supplied by WordPress. Native checkboxes/radios keep their familiar shapes. */
.wp-core-ui :is(.maestro-popover, .maestro-tour) {
--maestro-control-radius: 3px;
--maestro-focus-color: #2271b1;
}
.wp-core-ui :is(.maestro-popover, .maestro-tour) :is(button.button, button.button-link, button, input[type='text'], input[type='search'], input[type='number'], input:not([type]), select) {
border-radius: var(--maestro-control-radius);
}
.wp-core-ui :is(.maestro-popover, .maestro-tour) :is(button.button, button.button-link, button, input, select, [tabindex]):focus {
box-shadow: none;
outline: none;
}
.wp-core-ui :is(.maestro-popover, .maestro-tour) :is(button.button, button.button-link, button, input, select, [tabindex]):focus-visible {
outline: 2px solid var(--maestro-focus-color);
outline-offset: 2px;
}
/* Safari may consider programmatic focus after a tap focus-visible. Keep focus
* for containment without drawing a keyboard ring until keyboard input resumes.
* Text fields retain their focus indicator, including while typing by touch. */
.maestro-pointer-input .wp-core-ui :is(.maestro-popover, .maestro-tour) :is(button.button, button.button-link, button, [tabindex]:not(input):not(select):not(textarea)):focus {
box-shadow: none;
outline: none;
}

/* Toolbar icons retain their neutral/destructive colours in every state.
* Background indicates the open tool independently of pointer focus. */
.maestro-toolbar .button:not(:disabled):not(.button-link-delete):is(:hover, :focus, :active) {
color: #c3c4c7;
}
.maestro-toolbar .button:not(:disabled):is(:focus-visible, :active, [aria-expanded="true"]) {
background: var(--maestro-menu-accent, #2271b1);
}
@media (hover: hover) {
.maestro-toolbar .button.button-link-delete:hover {
background: var(--maestro-menu-accent, #2271b1);
}
}

/* Phone tools occupy two bounded rows, or one on short landscape screens.
* Each group scrolls horizontally rather than wrapping or shrinking targets. */
@media screen and (max-width: 782px), screen and (max-width: 960px) and (max-height: 500px) {
.maestro-toolbar {
flex-direction: column;
flex-wrap: nowrap;
align-items: stretch;
gap: 4px;
}
.maestro-toolbar .maestro-panel,
.maestro-toolbar .maestro-toolbar-right {
display: flex;
flex: 0 0 auto;
flex-wrap: nowrap;
align-items: center;
max-width: 100%;
min-width: 0;
gap: 4px;
margin: 0;
padding: 2px;
box-sizing: border-box;
overflow-x: auto;
overscroll-behavior-x: contain;
}
.maestro-toolbar .maestro-panel[hidden] { display: none; }
.maestro-toolbar .maestro-panel > *,
.maestro-toolbar .maestro-toolbar-right > * { flex-shrink: 0; }
.maestro-toolbar .maestro-toolbar-right > :first-child { margin-inline-start: 0; }
.maestro-toolbar .maestro-rename-input { flex: 0 0 128px; width: 128px; min-height: 44px; }
}
@media screen and (max-width: 960px) and (max-height: 500px) and (orientation: landscape) {
.maestro-toolbar { flex-direction: row; }
.maestro-toolbar .maestro-panel { flex: 1 1 auto; }
.maestro-toolbar .maestro-toolbar-right { flex: 0 0 auto; }
}
@media screen and (max-width: 782px), screen and (max-width: 960px) and (max-height: 500px) {
.maestro-toolbar .maestro-tour-help { display: none; }
}
37 changes: 29 additions & 8 deletions assets/maestro.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
n.dir = 'auto';
}
function removePopover( pop ) {
if ( pop.maestroAnchor ) { pop.maestroAnchor.setAttribute( 'aria-expanded', 'false' ); }
if ( pop.maestroCleanup ) { pop.maestroCleanup(); }
pop.remove();
}
Expand Down Expand Up @@ -831,7 +832,6 @@
if ( current ) {
var scheme = window.getComputedStyle( current );
bar.style.setProperty( '--maestro-menu-accent', scheme.backgroundColor );
bar.style.setProperty( '--maestro-menu-accent-text', scheme.color );
}

// Transient save-status — aria-live, empty at idle so no announcement fires.
Expand Down Expand Up @@ -1067,6 +1067,10 @@
var parent = menuWrap && hamburger && hamburger.offsetParent !== null ? menuWrap : document.body;
if ( bar.parentNode !== parent ) { parent.appendChild( bar ); }
}
bar.querySelectorAll( '.maestro-icon-btn, .maestro-vis-btn, .maestro-settings-btn, .maestro-profiles-btn, .maestro-preview-toggle, .maestro-tour-help' ).forEach( function ( button ) {
button.setAttribute( 'aria-haspopup', 'dialog' );
button.setAttribute( 'aria-expanded', 'false' );
} );
mountToolbar();
window.addEventListener( 'resize', mountToolbar );

Expand Down Expand Up @@ -1793,10 +1797,7 @@
var count = window.maestroLogic.profileItemCount( p.id, assignments() );
// Function replacers: a name is user text, and a "$&" in it would
// otherwise be read as a replacement pattern.
var question = I.profileDeleteConfirm
.replace( '%1$s', function () { return p.name; } )
.replace( '%2$d', function () { return String( count ); } )
.replace( '%3$d', function () { return String( p.users.length ); } );
var question = window.maestroLogic.profileDeleteMessage( p.name, count, p.users.length, I );
if ( ! window.confirm( question ) ) { return; }
deleteProfile( p.id );
renderList();
Expand Down Expand Up @@ -2815,6 +2816,9 @@
}

function placePopover( pop, anchorBtn ) {
if ( tour && tour.active ) { endTour(); }
pop.maestroAnchor = anchorBtn;
anchorBtn.setAttribute( 'aria-expanded', 'true' );
if ( isPhoneViewport() ) {
return placeMobilePopover( pop, anchorBtn );
}
Expand Down Expand Up @@ -2953,6 +2957,9 @@
var bar = document.getElementById( 'wpadminbar' );
var top = Math.max( ( v ? v.offsetTop : 0 ) + 8, bar ? bar.getBoundingClientRect().bottom + 8 : 8 );
var bottom = ( v ? v.offsetTop + v.height : window.innerHeight ) - 8;
// Keep the other tool buttons available even when this is a long sheet.
var toolbar = document.querySelector( '.maestro-toolbar' );
if ( toolbar ) { bottom = Math.min( bottom, toolbar.getBoundingClientRect().top - 8 ); }
pop.style.top = top + 'px';
pop.style.width = Math.max( 0, ( v ? v.width : window.innerWidth ) - 16 ) + 'px';
pop.style.height = 'auto';
Expand All @@ -2965,6 +2972,9 @@
}
function outside( e ) {
if ( ! pop.contains( e.target ) ) {
// Tool buttons replace this dialog through their own click handler.
// Other page controls remain inert while the phone sheet is open.
if ( e.target.closest( '.maestro-toolbar [aria-haspopup="dialog"]:not(:disabled)' ) ) { return; }
e.preventDefault();
e.stopPropagation();
}
Expand Down Expand Up @@ -3278,7 +3288,7 @@
var toggle = el( 'button', 'button maestro-preview-toggle' );
toggle.type = 'button';
toggle.setAttribute( 'aria-haspopup', 'dialog' );
iconButton( toggle, 'dashicons-visibility', I.previewAs );
iconButton( toggle, 'dashicons-welcome-view-site', I.previewAs );
toggle.addEventListener( 'click', function () { openPreviewDialog( toggle ); } );
wrap.appendChild( toggle );
return wrap;
Expand Down Expand Up @@ -3325,8 +3335,14 @@

// Touch can come from a tablet or touch laptop as well as a phone.
var lastPointerType = '';
document.addEventListener( 'pointerdown', function ( e ) { lastPointerType = e.pointerType; }, true );
document.addEventListener( 'keydown', function () { lastPointerType = ''; }, true );
document.addEventListener( 'pointerdown', function ( e ) {
lastPointerType = e.pointerType;
document.documentElement.classList.add( 'maestro-pointer-input' );
}, true );
document.addEventListener( 'keydown', function () {
lastPointerType = '';
document.documentElement.classList.remove( 'maestro-pointer-input' );
}, true );
function touchPointer() {
return lastPointerType === 'touch' || lastPointerType === 'pen';
}
Expand Down Expand Up @@ -3556,6 +3572,9 @@

function startTour() {
if ( tour.active ) { return; }
closePopovers();
var help = document.querySelector( '.maestro-tour-help' );
if ( help ) { help.setAttribute( 'aria-expanded', 'true' ); }
// A step the viewport hides (byViewport) is dropped before the tour
// starts, so "3 of 11" counts only what will be shown rather than
// jumping from 7 to 9 on a phone. A panel control hidden only for the
Expand All @@ -3575,6 +3594,8 @@
}

function endTour() {
var help = document.querySelector( '.maestro-tour-help' );
if ( help ) { help.setAttribute( 'aria-expanded', 'false' ); }
tour.active = false;
if ( tour.root ) { tour.root.remove(); tour.root = null; }
document.removeEventListener( 'keydown', onTourKeydown, true );
Expand Down
41 changes: 41 additions & 0 deletions docs/design/ui-standard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Dialog UI standard

All editor popovers and the guided tour use the shared rules at the end of
`assets/maestro.css`. Avoid per-dialog overrides of control geometry or focus.

- Buttons, text/number/search fields and selects use the 3px
`--maestro-control-radius` token. Cards keep their own radius; native
checkboxes and radio buttons keep their native shapes.
- Keyboard focus uses a 2px blue outline with a 2px offset, without a second
box-shadow ring. Pointer interaction hides button focus rings; the next
keyboard interaction restores them. Text fields retain visible focus.
- Touch entry keeps focus on Close (or the dialog container when no Close
exists), so opening a dialog does not open the software keyboard. Close
targets remain at least 44 by 44 CSS pixels. Never blur focus to hide a ring.
- Use sentence case for headings, labels and actions: “Visibility groups”,
“Add group”, “Reset all”. Preserve proper names such as WordPress and Dashicons.
- In explanatory text, reproduce a named control's label exactly, for example
“the menu item's Visibility settings”. Generic uses stay lowercase, as in
“visibility rules”. Use “people” in visible group membership labels.
- Explain only consequences supported by the current state. Deleting a group
with no assigned items or no members must not claim to change anyone's menu.
Other visibility rules may still apply, so never promise restored visibility.
- Keep complete translatable sentences in `includes/class-assets.php`.
Conditional consequences use separate complete sentences, not word fragments.

Regression coverage: dialog-standard E2E tests cover pointer/keyboard focus and
control radii across dialogs and phone orientations; profile deletion unit tests
cover all combinations of zero/nonzero item and member counts.

Toolbar tools preserve their icon colours on hover, focus and activation; use
background colour for state, with `aria-expanded` tied to the open dialog.
Phone tool groups scroll rather than wrapping beyond two portrait rows or one
short-landscape row; controls keep 44px touch targets.
The coach tips (`?`) launcher is omitted on phones, including short landscape
screens; it remains available on larger screens.

Shared label casing and deletion-message invariants are also enforced in
`tests/js/profile-delete-message.test.mjs`.
Use distinct glyphs for different tools: an eye for Visibility rules, a monitor
for Preview as, and a group of people for Visibility groups. Accessible names
remain explicit even when visible labels are hidden.
50 changes: 50 additions & 0 deletions docs/testing/iphone-issues.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# iPhone acceptance issues

## 1. Dialog focus, geometry and wording

- Orientation: portrait; landscape not yet reported by Dan.
- Reproduce: open Visibility groups from the toolbar.
- Actual: Close shows a focus border immediately after a tap, its radius differs
from Add group, and the help text refers to “Visibility controls”.
- Expected: shared button geometry, keyboard-only button focus indicators,
sentence case, and consistent references to named UI controls.
- Status: fix implemented; awaiting device verification.

## 2. Empty-group deletion claims restored visibility

- Orientation: portrait.
- Reproduce: create a group with no assigned menu items and no members; delete it.
- Actual: confirmation says “Those people will see the items again.”
- Expected: “Delete ‘Group name’? It hides 0 items from 0 people.” Only describe
removal of visibility rules when both counts are nonzero; never promise that
overlapping role, person or other group rules will stop hiding the items.
- Status: fix implemented; awaiting device verification.

## 3. Toolbar tool switching and icon colours

- Orientation: portrait; also test landscape.
- Reproduce: open Preview, then tap Visibility groups in the toolbar.
- Actual: Preview remains open; focus styling changes on the toolbar instead.
- Expected: the newly chosen tool replaces the dialog, and only the open tool
has an active background. Preserve icon colours in hover/focus/active states.
- Cause: the mobile sheet's document capture handler swallowed toolbar clicks.
- Status: fix implemented; awaiting device verification.

## 4. Toolbar wrapping crowds out the menu

- Orientation: portrait and landscape.
- Reproduce: select a menu item; rotate to landscape.
- Actual: three portrait rows or two landscape rows consume the editing area.
- Expected: at most two portrait rows and one short-landscape row, preserving
44px targets. Tool groups scroll horizontally when needed.
- Status: fix implemented; awaiting device verification.

## 5. Duplicate eye icons for unrelated tools

- Orientation: both; visible in the portrait and landscape reports above.
- Reproduce: select a menu item and compare Visibility with Preview as.
- Actual: both tools use an eye glyph despite performing different actions.
- Expected: retain the eye for Visibility rules; use a monitor with an eye for Preview as,
with distinct accessible labels and stable icon colours.
- Introduced: role preview in PR #251; retained by PR #260.
- Status: fix implemented; awaiting device verification.
13 changes: 12 additions & 1 deletion docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ the same control.

## Preview The Menu As A Role

To check what a role still sees, open **Preview as** using the eye button in the
To check what a role still sees, open **Preview as** using the screen button in the
editor toolbar, choose a role, then press **Preview**. Changing the selection
alone does not start a preview. Maestro saves anything still pending first; if
the save fails, the preview does not open.
Expand Down Expand Up @@ -218,3 +218,14 @@ the menu to the defaults generated by WordPress and active plugins.

Because the plugin stores sparse deltas instead of a rebuilt menu, reset is safe:
when the saved option is removed, the natural menu returns automatically.

### Phone toolbar and dialogs

On phones, the toolbar uses up to two rows in portrait and one row in short
landscape viewports. Swipe the item tools sideways if they do not all fit; touch
targets remain full size. Coach tips are available from the `?` button on larger
screens. Tap another tool to switch dialogs. The open tool has a coloured
background while its icon keeps its usual colour.

Dialogs share button shapes and keyboard focus indicators. Opening one by touch
keeps the keyboard closed; tap a text field when you want to type.
Loading
Loading