Load permission properly on initial manageUserForm selection - #1531
Merged
joshunrau merged 4 commits intoSep 10, 2026
Merged
Conversation
joshunrau
requested changes
Sep 10, 2026
| // libui's `record-array` field resets itself to a single blank record whenever its `fieldset` | ||
| // changes identity, so an inline literal would discard the permissions it was seeded with on the | ||
| // next render of this component -- which a background refetch of either query triggers. | ||
| const additionalPermissionsFieldset = useMemo( |
joshunrau
approved these changes
Sep 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Show a user's existing permissions when the manage-user sheet is opened
Opening Manage Users → a user showed an empty permission row even when that user held
permissions. Reopening the sheet later showed them correctly, which made it look like a
loading problem.
It was not. The permissions loaded fine — they were erased immediately afterwards.
Root cause
libui's
RecordArrayFieldresets itself whenever itsfieldsetprop changes identity:UpdateUserForm declared that fieldset as an inline object literal in its JSX, so every
render handed the field a structurally identical object with a fresh identity, and the
field blanked itself. The form mounts with the correct values; the next render destroys them.
On a first visit that next render is guaranteed: queryClient sets no staleTime, so
useUsersQuery / useGroupsQuery background-refetch on mount. When the refetch lands,
groupsQuery.data is a new array, the effect rebuilds data, and UpdateUserForm re-renders.
On a later open the queries are already fresh, nothing refetches, and the values survive —
hence "it works the second time".
key={JSON.stringify(initialValues)} did not help: the serialized string is unchanged for the
same user, so the Form never remounted to re-seed itself.
Why this mattered beyond the empty row
The reset left the field holding [{ action: undefined, subject: undefined }], which the
schema's .transform collapses to []. An admin who opened the sheet and saved any change —
an email, a group — silently stripped that user's additional permissions, having never been
shown them. Data loss, not just a rendering glitch.
The fix
apps/web/src/routes/_app/admin/users/index.tsx — the permission fieldset is now useMemo'd
on [resolvedLanguage], the only input that can change it, giving it a stable identity across
re-renders.
Memoized at the fieldset level rather than around the whole content array on purpose:
groupOptions is rebuilt on every refetch, so a content-level memo keyed on it would keep
churning and re-introduce the bug.
Tests
Unit — apps/web/src/tests/record-array-initial-values.test.tsx pins both halves of the libui contract this fix depends on: a stable fieldset renders, keeps and submits its seeded records across a parent re-render; an unstable one discards them. Written against the libui contract rather than the route, following data-table-server-mode.test.tsx, because route files export only Route.
E2E — admin-management.spec.ts seeds a user holding read Subject, opens the sheet, asserts the selects show it, then forces a re-render through the UI (open the delete dialog, click No) and asserts they still do.
ApiClient.updateUser added to testing/src/support/api-client.ts: additionalPermissions is on the update schema and not the create one, so seeding such a user takes two calls.
The e2e test was confirmed to fail on the unfixed code with the exact reported symptom
(Expected "Read", Received "") and to pass with the fix.
Verification
pnpm lint — 33/33 tasks successful
pnpm test — 796 passed
pnpm test:e2e (chromium) — 146 passed
Closes issue #1527