The first pass had admin/users sharing the address book profile partial,
which surfaced Contact/Address/Organization/About tabs that ignored the
admin-specific context. Separates the admin quick-view so it mirrors the
edit-page tab structure while staying read-only.
- `UserProfileViewService::buildAdminProfile()` extends the base profile
with `admin_extras` (active, email_verified_at, last_login_at,
last_login_provider, created, modified).
- `templates/partials/app-admin-user-profile.phtml` is the admin-only
render path: Master data · Organization · Security · About. Status
badge under the name; timestamps locale-formatted.
- `pages/admin/users/view-fragment($id).php` / `(none).phtml` switched
to the admin service method + partial.
- Drawer CSS aligns the aside with the main section's inline padding so
the content strip does not visually step in/out.
Address book is unchanged — still uses `buildProfile()` + the base
partial. Two i18n keys added (`Login provider`).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces a reusable core detail-drawer primitive that slides in from the
right and loads any view via a `*-fragment(none).phtml` endpoint. Bundles the
address book list overhaul that is its first consumer.
Core additions:
- `app-detail-drawer.js` — generic drawer with stepper, focus trap, body
scroll-lock, URL-hash deep-linking, session-expiry detection
- `app-fragment-init.js` — auto-wires tabs/lookups/confirm/file-upload/
fslightbox inside injected HTML; consumers do not re-initialize components
- `app-focus-trap.js` — shared focus-trap + refcounted scroll-lock, used by
both filter-drawer and detail-drawer
- `getHtml()` in `app-http.js` + `SessionExpiredError`; drawer reloads the
page on auth redirect instead of rendering the login form in the panel
- `DetailDrawerFragmentContractTest` enforces that every `initDetailDrawer`
consumer ships matching `*-fragment($id).php` + `*-fragment(none).phtml`
Address book list:
- Grid collapses from 9 columns to 4 (identity / context / phone / actions)
with a two-line identity cell (avatar + name + email)
- Tenant register tabs above the grid using the `app-list-tabs` partial;
tenant filter wired via hidden toolbar field so grid.js forwards it on
every data fetch
- Profile body extracted to a shared partial so the full-page view and the
new drawer fragment share the same markup
- New i18n keys for the drawer/list labels
Also refactors `app-filter-drawer` to reuse the shared focus-trap and
scroll-lock instead of maintaining its own copy, and documents the
detail-drawer convention in CLAUDE.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>