Skip to content

fix(frontend): hide Edit and Delete on system-managed groups - #1392

Merged
IAmJSD merged 1 commit into
LeanerCloud:mainfrom
IAmJSD:ui/issue-919
Oct 24, 2026
Merged

IAmJSD merged 1 commit into
LeanerCloud:mainfrom
IAmJSD:ui/issue-919

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator

Every group card offered Edit and Delete, including the seeded system-managed groups (Administrators, Purchaser, RI Exchanger, Standard Users, Read-Only Users), which the API always refuses with a 403. A refused Delete also showed only "Failed to delete group" and dropped the server's reason. This PR replaces Edit and Delete with a "System group" badge on those cards and includes the server's reason in the Delete error toast.

Refs #919
Partial: the cards only change once the API sends system_managed (auth.APIGroup has no such field on main yet), and the suggested guard on Service.UpdateGroup is backend work not done here. The Delete error message works today.

What changed

  • New frontend/src/groups/systemGroups.ts with isSystemManagedGroup and SYSTEM_GROUP_NOTE.
  • frontend/src/groups/groupList.ts: when group.system_managed is true, Edit and Delete are replaced by a muted "System group" badge with a title tooltip and screen-reader text ("Built-in group maintained by CUDly. It cannot be edited or deleted; change its members from the Users list."). Duplicate is kept.
  • frontend/src/groups/groupActions.ts: deleteGroup refuses a system-managed group before the confirm dialog, and a failed delete shows "Failed to delete group: ".
  • Tests: new src/__tests__/groups-system-managed-919.test.ts and tests-e2e/system-managed-groups.spec.ts.

Why this approach

The buttons are hidden rather than disabled because disabled buttons cannot be focused and their tooltips do not show reliably; the badge explains why on hover and to screen readers and keeps the card uncluttered. Duplicate stays because copying a seeded group into an editable custom one is the legitimate way to start from it. The state comes from the API's system_managed flag rather than a hardcoded list of seeded UUIDs, which would drift each time a migration locks another group (000059, 000096 and 000125 all changed the set). The helpers live in their own module so groupList and groupActions do not import each other.

Screenshots

Before is main (a14dc8f2), after is this branch. Both are headless Chromium at 1440x900 against the repo's mocked e2e API, with system_managed: true on Administrators and Purchaser in the mocked /api/groups and a 403 on DELETE for the toast.

Before After
system-group-cards before system-group-cards after
delete-refused-toast before delete-refused-toast after
  • Group cards: on main, Administrators and Purchaser offer Edit and Delete, which the API always refuses. On this branch they show a "System group" badge and keep only Duplicate; the custom Finance group is unchanged.
  • Refused delete: on main the toast says only "Failed to delete group". On this branch it includes the server's reason.

Verification

Run on this branch's head 53bdb80f on top of main a14dc8f2:

  • npm run lint: 0 errors (142 pre-existing warnings). Typecheck and build pass.
  • Jest: 110 suites, 3492 passed, 1 skipped. The new groups-system-managed-919.test.ts (system card has no Edit/Delete, keeps Duplicate, badge tooltip and sr-only text; custom cards unchanged; a false or missing flag means custom; deleteGroup refuses a system group without confirm or API call; server reason shown on a refused delete; generic fallback for an empty message) passes with --randomize.
  • Playwright via e2e-local.sh: 158 passed, including the new system-managed-groups.spec.ts.
  • Secrets scan clean (planted key flagged); scripts/test-git-secrets-allowlist.sh: 137 PASS.

Notes for review

  • Backend dependency: the live API does not send system_managed today. internal/auth/service_api.go APIGroup has no SystemManaged field and groupToAPIGroup does not copy it, so in production the cards stay unchanged until a small backend change adds SystemManaged bool `json:"system_managed,omitempty"` to APIGroup and sets it in groupToAPIGroup. The frontend type in api/types.ts already declares the field.
  • The guard the issue suggests on Service.UpdateGroup (internal/auth/service_group.go:30) is backend work and is not touched here.
  • Copy and visuals to check: the "System group" badge (badge-muted) and its tooltip text.
  • The Edit modal itself is not guarded (the button is gone); openEditGroupModal is shared with fix(frontend): a group holding a blank permission resource cannot be saved at all, with no explanation #213.

Overlaps

Note for the UI owner

This changes UI code that @cristim owns. IAmJSD reviewed the before/after screenshots and asked for it to be merged; any design point you disagree with can be a follow-up.

🤖 Generated with Claude Code

The API refuses edits and deletes of system-managed groups (seeded by
migrations: Administrators, Purchaser, RI Exchanger, Standard Users,
Read-Only Users, ...) with ErrSystemManagedGroup / 403, but every group
card offered Edit and Delete, so admins were handed actions that always
fail. When Delete failed, the toast also dropped the server's reason and
only said "Failed to delete group", while Edit already shows it.

Cards for groups with system_managed: true now show a muted "System
group" badge (tooltip plus screen-reader text explaining it cannot be
edited or deleted, and that membership is changed from the Users list)
in place of Edit and Delete. Duplicate stays, since it makes a separate,
editable copy. deleteGroup refuses such a group up front instead of
asking for confirmation, and a failed delete now reads
"Failed to delete group: <server reason>".

The list endpoint does not serialize system_managed yet (auth.APIGroup
has no such field), so the cards only change once the backend sends it;
the Delete message fix works today.

Refs LeanerCloud#919

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD IAmJSD added triaged Item has been triaged priority/p3 Polish / idea / may never ship severity/low Minor harm urgency/this-quarter Within the quarter impact/few Limited audience effort/xs Trivial / one-liner type/bug Defect labels Oct 24, 2026
@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

This review includes 5 billable files and costs up to $1.25.

Or wait 59 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
Commits

Reviewing files that changed from the base of the PR and between 61be5b6 and 53bdb80.

Files selected for processing: 5
  • frontend/src/__tests__/groups-system-managed-919.test.ts
  • frontend/src/groups/groupActions.ts
  • frontend/src/groups/groupList.ts
  • frontend/src/groups/systemGroups.ts
  • frontend/tests-e2e/system-managed-groups.spec.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): APPROVE 53bdb80fe36a1c408a4354ffd2aaa90a1c31e34f

Blocking:

  • none
    Non-blocking:
  • The card change is inert in production until APIGroup/groupToAPIGroup send system_managed (internal/auth/service_api.go). This is disclosed, and "Refs fix(frontend): hide Edit and Delete on system-managed groups #919" is the correct claim; the delete-reason toast works today.
  • frontend/src/groups/groupList.ts:68: the badge sits among the action buttons as a non-focusable span with title plus sr-only text. That is accessible, but the title tooltip will not appear for keyboard users (the sr-only text covers screen readers).
    Attacked: Escaping: SYSTEM_GROUP_NOTE and the ids go through escapeHtml, and the error toast uses the server message through showError. Grepped every openEditGroupModal/deleteGroup entry point (only the card buttons, plus deleteGroup's own guard). Checked the Duplicate payload does not copy system_managed. Checked the frontend type already declares system_managed and that the Go types.go Group has the field but the API projection does not.

@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Local verification at 53bdb80f merged onto current main (15a0d38c; merge-tree 5086e3e4; Linux, Node, headless Chromium):

  • This branch's own checks passed at this head: lint, typecheck, build, the full jest suite, the full Playwright e2e suite, and the git-secrets checks (see Verification in the PR body).
  • The combined tree of every UI PR in this merge wave (tree 89dd34d8, main plus 46 PRs) passes npm ci, lint, typecheck, build, jest (1 skipped, 3914 passed, 3915 total), and the full Playwright suite (208 passed).
  • CI is green on 53bdb80f (CI Success, Run pre-commit hooks).
  • The independent review above approves this exact head.
  • Nothing was applied to any cloud.

@IAmJSD
IAmJSD marked this pull request as ready for review October 24, 2026 17:30
@IAmJSD
IAmJSD merged commit a229b81 into LeanerCloud:main Oct 24, 2026
29 checks passed
@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Marked ready and merged by IAmJSD's agent, at IAmJSD's request (IAmJSD reviewed the before/after screenshots; cristim is traveling), after the independent review and local verification above, all at 53bdb80f.

IAmJSD added a commit to IAmJSD/cloud-commitments-platform that referenced this pull request Oct 24, 2026
Conflicts:
- frontend/src/groups/groupList.ts: combined. Main (LeanerCloud#1392) hides Edit and
  Delete on system-managed groups and shows a "System group" badge
  instead; this branch turned Edit / Duplicate / Delete into icon buttons
  in a .row-actions group. Result: the badge replaces the pencil Edit
  button for locked groups, Delete (trash) is omitted for them, and
  Duplicate (copy) always shows, all inside .row-actions. Imports keep
  both iconSvg and main's systemGroups helpers.
- frontend/src/groups/groupModals.ts: import block. Kept iconSvg (used by
  the trash Remove-permission button) and main's groupFormValidation and
  permissionOptions imports; dropped ALL_ACTIONS/ALL_RESOURCES/Action/
  Resource, which main moved into permissionOptions.ts with the option
  builders.
- frontend/src/history.ts: imports keep both iconSvg and main's
  history-queue-link helpers. Retry button and retry lineage badge keep
  this branch's alert-triangle / rotate-cw icons with main's
  Number(p.retry_attempt_n) || 0 coercion (LeanerCloud#1318 escaping hardening).
- frontend/src/lib/history-filter-popover.ts: filter button keeps main's
  data-filter-scope attribute (LeanerCloud#1342) with this branch's filter icon in
  place of the ⛛ glyph.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IAmJSD added a commit that referenced this pull request Oct 26, 2026
…1513)

The dashboard hides Edit and Delete on a group whose system_managed flag is
true (#1392), but auth.APIGroup never carried the field, so the badge never
showed. groupToAPIGroup now copies it as `system_managed` (omitted when
false), matching frontend/src/api/types.ts.

Service.UpdateGroup relied on its only caller, UpdateGroupAPI, to refuse a
system-managed group. It now reads the stored row and refuses with
ErrSystemManagedGroup (403 at the handler) itself, sharing the check with
DeleteGroup via refuseSystemManagedGroup.

Refs #919

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/xs Trivial / one-liner impact/few Limited audience priority/p3 Polish / idea / may never ship severity/low Minor harm triaged Item has been triaged type/bug Defect urgency/this-quarter Within the quarter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant