Catalog: say why a disabled Admin Users switch is disabled (5217 stack 5/9) - #5263
Catalog: say why a disabled Admin Users switch is disabled (5217 stack 5/9)#5263nl0 wants to merge 6 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## stack/5217-4-search-ordering-visibility #5263 +/- ##
===========================================================================
+ Coverage 35.16% 35.70% +0.53%
===========================================================================
Files 741 741
Lines 23808 23823 +15
Branches 6431 6433 +2
===========================================================================
+ Hits 8373 8505 +132
+ Misses 13691 13577 -114
+ Partials 1744 1741 -3
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
733be45 to
a36a203
Compare
70469c2 to
9142571
Compare
c4ece23 to
b2b968c
Compare
Stack review pass — local code review, findings and dispositionsReviewed this layer only, diffed against its stack base Greptile: already reviewed this content. It delivered its pass as a summary Local review: five findings, three accepted, one accepted as a changelog Accepted1 — the reason was not reliably exposed to assistive tech. The carrier was a 2 — the reason was announced twice. MUI seeds its own 3 — the changelog entry, as a correction. 5 — the keyboard test did not exercise the keyboard path. It dispatched a Declined4 — collapsing Also in this passSwept this layer's comments back to the constraints they carry. The unit had Checked for a deletion in this layer that none of its commits explains: there is VerificationThe The four PRs above this one have been rebased onto the updated chain in order and |
b9b567d to
581fd40
Compare
A disabled switch with no stated cause is indistinguishable from a rendering bug, and two different causes — it is you, or it is a service user managed by the stack — rendered as the same mute, dead control in the same column. Each switch column now resolves a reason and derives `disabled` from whether there is one, so the guard and its explanation cannot drift apart. The reason reaches keyboard and screen-reader users too: a disabled switch fires no events and is out of the tab order, so the tooltip hangs on a focusable, named wrapper rather than on the switch itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
addresses review findings f29, f38
The reason carrier this unit adds is a `<span tabIndex={0}>`: focusable, and not
a `ButtonBase`. `constants/style` implements the Focus Ring Rule
(catalog/DESIGN.md §2, bound onto UI changes by catalog/CLAUDE.md) for
`MuiButtonBase` only, so the new element took keyboard focus with nothing drawn
on it — an invisible-focus defect on an element added by an accessibility fix.
It now carries the same 2px ring in the ground's counter-color, at the same
offset.
Also settles the suppression prefix the same unit introduced: this file used
`oxlint-disable-next-line` while 111 others in `app/` use
`eslint-disable-next-line` and two use the oxlint form. oxlint honors the eslint
comments — `npm run lint:oxlint` passes with this change — so the majority form
is what ships here. Worth a maintainer ruling if the intent is to migrate the
other 111 rather than to keep them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
addresses review findings f44, f27 The "explains why" half of the Admin Users entry, citing the PR that ships it. The read-only half lands with its own unit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The reason carrier was a bare `<span tabIndex={0}>`, which maps to role
`generic`; ARIA forbids `aria-label` there and name computation for such
nodes is not guaranteed, so the reason could stay mouse-only -- the very
failure the wrapper exists to prevent. It carries `role="group"` now, which
both permits the label and keeps the switch inside it exposed.
MUI seeds its own `title` on the child while the popper is closed, so the
span carried the reason as its name and again as its description. Suppress
it; the popper still supplies the description once open.
The keyboard test dispatched a synthetic focus event, which moves no focus
and opened the tooltip only because `matches(':focus-visible')` throws under
this jsdom and MUI falls back to a module-global keyboard heuristic. It now
takes real focus, primed with a keydown, and asserts `document.activeElement`
-- checked by flipping that global with a pointerdown, which fails the test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This unit shipped five multi-line blocks narrating why the change was made, into a file whose whole existing comment budget is four one-line notes. Keep only what the code cannot show -- why the wrapper exists, why the focus ring is restated off `MuiButtonBase`, why `isService` is tested before the SSO flag -- and drop the rest. The resolver comment also over-claimed: `disabled` is not structurally derived from the reason, both remain independent props. Say what is true -- each call site derives it -- rather than asserting an invariant the type does not enforce. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The entry quoted copy that is not in the code ("you can't deactivate
yourself" for "You cannot deactivate your own account"), named two of the
three reasons the change adds -- omitting the SSO one entirely -- and said
nothing about the Admin switch now refusing service users, which is a
behaviour change and not only an explanatory one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
581fd40 to
a633a1c
Compare
Description
In Admin → Users, a disabled Enabled or Admin switch said nothing about
why. Two different causes — "that row is you" and "that is a service user managed
by the stack" — rendered as the same mute, dead control in the same column, and a
dead control with no stated cause is indistinguishable from a rendering bug.
Each switch column now resolves a reason and derives
disabledfrom whetherthere is one, so the guard and its explanation cannot drift apart. The reason
reaches keyboard and screen-reader admins as well as mouse users: a disabled
switch fires no events and is out of the tab order, so the tooltip hangs on a
focusable, named wrapper rather than on the switch.
Review findings addressed
<span tabIndex={0}):focusable, and not a
ButtonBase.constants/styleimplements the Focus RingRule (
catalog/DESIGN.md§2, bound onto UI changes bycatalog/CLAUDE.md) forMuiButtonBaseonly, so the new element took keyboard focus with nothing drawnon it — an invisible-focus defect on an element added by an accessibility fix.
It now carries the same 2px ring, at the same offset, from the theme palette.
The review called this the strongest citation in its set; it was parked on rank,
not on merit.
oxlint-disable-next-linewhile 111 files inapp/useeslint-disable-next-lineand two use the oxlint form. oxlint honorsthe eslint comments, so the majority form ships here. Worth a ruling: if the
intent is to migrate the other 111 rather than keep them, say so and this flips.
Deliberate assumptions and recorded observations
The Admin switch now refuses every
isServiceuser, and putsisServiceahead of the SSO flag when choosing which reason to show. The GraphQL contract
this consumes does not declare that coupling; the registry that computes
isServiceandisAdminAssignmentDisabledis outside this repository, sonothing in the checkout proves the two always travel together. The guard is
written not to depend on it — refusing a service user is correct on its own
terms, and "managed by the stack" is the truer reason where both hold — but a
reviewer with registry access should confirm there is no service user for whom
admin assignment is legitimately allowed. The review graded this PLAUSIBLE
rather than CONFIRMED for exactly this reason.
site rather than resolved once beside the flags. The
whyEnabledDisabled/whyAdminDisabledsplit answers part of it. Out of scopehere.
accessible name.
For reference, the hardening review this stack comes from compared master
(
6167dd82) against the26.7.4 pin (
eda3016f) —linked rather than named, so the comparison is checkable.
Verification
12 tests. The focus-ring assertion reads the generated stylesheet for a
:focus-visibleoutline rule on the wrapper's own class, and was checked againsta version with the class removed.
Position in the stack
PR 5 of 9, based on
stack/5217-4-search-ordering-visibility.Part of the split of #5217 asked for in
f27. PR 6
edits the same file and stacks directly above; the two sets of hunks are disjoint
and share no symbol, so either order is correct. This one is first because its
fold-in is a present-tense accessibility defect on an element the diff adds.
TODO
🤖 Generated with Claude Code
Greptile Summary
This PR explains why Enabled and Admin switches are disabled for self-managed and stack-managed users.
Confidence Score: 5/5
The PR appears safe to merge, with no concrete blocking or independently actionable non-blocking issue identified.
The existing self and service-user restrictions remain enforced, while disabled controls gain mouse and keyboard-accessible explanations with focused test coverage.
Important Files Changed
Reviews (1): Last reviewed commit: "docs(changelog): entry for the disabled-..." | Re-trigger Greptile