Skip to content

Load permission properly on initial manageUserForm selection - #1531

Merged
joshunrau merged 4 commits into
DouglasNeuroInformatics:mainfrom
david-roper:initial-load-permissions
Sep 10, 2026
Merged

Load permission properly on initial manageUserForm selection#1531
joshunrau merged 4 commits into
DouglasNeuroInformatics:mainfrom
david-roper:initial-load-permissions

Conversation

@david-roper

Copy link
Copy Markdown
Collaborator

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 RecordArrayField resets itself whenever its fieldset prop changes identity:

const fieldsetRef = useRef(fieldset);
useEffect(() => {
  if (fieldsetRef.current !== fieldset) {
    setArrayValue([createNewRecord()]);   // ← discards the loaded permissions
    fieldsetRef.current = fieldset;
  }
}, [fieldset]);

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

// 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(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add type

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useMemo

@joshunrau
joshunrau merged commit 84c67f1 into DouglasNeuroInformatics:main Sep 10, 2026
2 checks passed
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.

2 participants