Skip to content

Show a group's effective constraints, and where each comes from - #622

Open
barborico wants to merge 4 commits into
mainfrom
brynna/effective_constraints_panel
Open

barborico wants to merge 4 commits into
mainfrom
brynna/effective_constraints_panel

Conversation

@barborico

@barborico barborico commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Stacked on #621. Last PR in this series.

Urgency: 5 days; does not need to deploy alongside the rest of the stack
Expected review effort: MEDIUM

Motivation

Make it obvious what constraints apply to a group and why.

A group page lists the tags on it, which for a role is not the same question as what constrains it. A role's limits mostly arrive from the groups it is a member or owner of, and a tag applied to an app reaches every one of that app's groups. Someone asking why they cannot grant a year of access had to work that out from a tag list and a mental model of propagation.

What's Changing

A collapsed Effective constraints panel on the group detail page. Each row names a constraint in force, its coalesced value, and the tag it came from — with how that tag reached this group:

Origin shown as Meaning
direct the tag is on this group
via app Foo inherited from the group's app
via membership in Foo this role is a member of Foo, which carries the tag
via ownership of Foo this role owns Foo, which carries the tag

That last distinction is the point: it separates a constraint a reader could lift by untagging this group from one they cannot.

The panel reads effective_constraints off the group detail the page already fetched, so it costs no request of its own, and it collapses by default since most readers are not asking.

The collapsed panel:
image

A directly applied constraint on a group:
image

Propagated constraints on a role:
image

Inherited constraints on an app group:
image

One piece of copy cleanup that came with it

Ten places rendered a time limit as Math.floor(seconds / 86400) + ' days'. That prints "0 days" for any limit under a day — which reads as no access at all, where the true answer is an hour — and "1 days" for exactly one day.

The panel formats the same values, so leaving this alone would have had a dialog and the panel above it disagree on the same page about the same group. All ten now use one timeLimitLabel helper, which renders <1 day, 1 day, 7 days.

The tag form's seconds-to-days conversion is untouched — that one populates an editable number field rather than prose, and rounding there is a separate question.

Validation of Changes

13 new frontend tests. Ten cover the panel: the empty case renders nothing, the summary count, a time limit folded into the constraint column, a boolean rendered without a redundant "— Yes", the tag link and origin text, singular/plural/sub-day formatting, an unrecognised origin echoed rather than mislabelled as direct, and a source entry with sources omitted (optional per the API contract). Three cover timeLimitLabel directly.

The panel's test stands in plain DOM for MUI, as the other component tests here do — this repo's style engine cannot be loaded under vitest — while forwarding Link's component/to props to the real router link, so the link assertion exercises rendered output rather than a mock.

Verified in a browser:

  • A role that inherits a limit renders "Limit time of membership — 7 days" sourced "SOX-timelimit, via membership in Ledger-Admin" — the constraint is not on the role, and the panel says where it is from.
  • The group carrying those tags directly renders both of its constraints with origin direct.
  • The dialog on that same group now reads "limited to <1 day", matching the panel, where it previously read "limited to 0 days".

The guard at the call site is deliberate and measured, not redundant with the panel's own empty check: without it the wrapping grid item still contributes its own 24px of padding, adding a gap to every group that has no constraints — which is most of them.

958 backend / 79 frontend passing; ruff, ty, tsc, and prettier clean.

🤖 Generated with Claude Code

@barborico
barborico force-pushed the brynna/effective_constraints_panel branch from 6f7b6ae to faf6f53 Compare September 2, 2026 02:16
@barborico
barborico marked this pull request as ready for review September 2, 2026 02:39
@barborico
barborico force-pushed the brynna/effective_constraints_panel branch from faf6f53 to 572dff2 Compare September 2, 2026 19:53
@barborico
barborico force-pushed the brynna/effective_constraints_panel branch 2 times, most recently from 601f823 to c129dae Compare September 2, 2026 20:50
@barborico
barborico force-pushed the brynna/effective_constraints_panel branch from c129dae to 0d16b92 Compare September 2, 2026 21:11
@barborico
barborico force-pushed the brynna/effective_constraints_panel branch 2 times, most recently from 70acdbe to e83c857 Compare September 9, 2026 00:37
@barborico
barborico force-pushed the brynna/effective_constraints_panel branch 2 times, most recently from 72f2c09 to f91183d Compare September 9, 2026 22:31
@barborico
barborico force-pushed the brynna/effective_constraints_panel branch from f91183d to 5b3d3e5 Compare September 10, 2026 21:14
@barborico
barborico force-pushed the brynna/effective_constraints_panel branch from 5b3d3e5 to f98ca42 Compare September 10, 2026 23:28
@barborico
barborico force-pushed the brynna/effective_constraints_panel branch 2 times, most recently from 0fe866b to b7f92d5 Compare September 11, 2026 00:00
Base automatically changed from brynna/drive_dialogs_off_effective_constraints to main September 11, 2026 00:12
A group page listed the tags on it, which for a role is not the same
question as what constrains it: a role's limits mostly arrive from the
groups it is a member or owner of, and a tag on an app reaches every one
of its groups. Someone asking why they cannot grant a year of access had
to work that out from the tag list.

The panel names each constraint in force, its coalesced value, and the
tags it came from -- each with how it reached this group, so a reader can
tell one they could lift by untagging here from one they cannot. It reads
`effective_constraints` off the group detail, so it costs no request, and
it collapses by default since most readers are not asking.

Route the day formatting through one helper while adding it. Ten sites
rendered `Math.floor(seconds / 86400) + ' days'`, which prints "0 days"
for any limit under a day -- reading as no access at all, where the true
answer is an hour -- and "1 days" for exactly one. The panel would
otherwise contradict the dialog on the same page.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
barborico and others added 3 commits September 10, 2026 17:15
A source row named the site the constraint came through but only linked
the tag. The name is the more useful destination of the two: a reader
following one is asking what that group or app is, and had to go find it
by hand. An app origin links to the app rather than a group, which is why
the API names those fields for the origin instead.

A source whose app has since been soft-deleted still states its origin
and stays unlinked, having no name to point at.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rendering a flag as its name alone is only right because the API omits one
that is switched off. Say so, and name the helper that guarantees it: the tag
form writes all four boolean keys on every save, so the response would
otherwise be full of `False` flags and each would render as a restriction in
force.
The app renders MUI through Emotion, which vitest resolves, so the components
import under test and the stand-ins that stood for them are no longer needed.

Rendering the real accordion means its contents start collapsed and out of the
accessibility tree, so the assertions now open the panel the way a reader does
before looking at a row. That is the behaviour the panel actually has, and the
stand-ins could not express it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@barborico
barborico force-pushed the brynna/effective_constraints_panel branch from b7f92d5 to d79daa2 Compare September 11, 2026 00:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant