🚩 fix: Non-Admin Org Recovery Follow-Ups (loader gate, switch completion, switcher UX) - #107
Closed
dustinhealy wants to merge 4 commits into
Closed
Conversation
Resolve the admin capability check in the _app route loader via ensureQueryData and read it with useSuspenseQuery, so the layout renders a decided state instead of flashing the full panel before the access-denied screen. Type the root route context so the loader can reach the query client. Complete an org switch entirely client-side: invalidate every query against the newly minted session and re-run the loaders, so the panel transitions to the dashboard without a hard reload and without leaving the user stranded on the recovery screen. Make the recovery screen legible: a single Continue action when the actor administers one org, a select-then-confirm dropdown when they administer several (selecting no longer switches on its own), and a named "Switching to X" interstitial during the transition. Show the placeholder in the empty dropdown, vertically center the screen, and prefetch the org list so the control is present on first paint.
The 403 branch preserves the authenticated session and returns accessDenied so the panel can offer org recovery, rather than clearing the session. Update the test that still asserted the removed clear-session outcome.
…nts, sort imports The switch transition already spans the mutation and refetch, so the minimum-interstitial timer is removed. Imports sorted per the repo script.
dustinhealy
force-pushed
the
agent/admin-org-access-recovery-fixes
branch
from
August 5, 2026 02:35
d620f64 to
d436d43
Compare
danny-avila
marked this pull request as ready for review
August 5, 2026 13:42
Keep the recovery screen reachable when the admin-org prefetch fails by letting that warm-up reject silently, so a backend hiccup no longer routes non-admin users to the error page instead of the access-denied screen. Guard the switch itself: disable the switch actions while a switch is pending, clear the interstitial if the post-switch invalidation or navigation rejects, and evaluate the interstitial before the empty-list short-circuit so it stays visible for the whole transition.
Contributor
Author
|
@codex review |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 7208a01. Configure here.
|
Codex Review: Didn't find any major issues. What shall we delve into next? Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Contributor
Author
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.

Summary
Follow-ups to the non-admin organization recovery flow on #106. Exercising that flow end to end surfaced two functional gaps in its current implementation and led to several UX refinements to the organization switcher.
Functional changes:
Switcher UX refinements:
Switching to <Org>...interstitial covers the transition.Change Type
What changed
_approuteloaderwithqueryClient.ensureQueryData, read it viauseSuspenseQuery, and share onecapabilitiesQueryOptionsbetween the loader anduseCapabilities.createRootRouteWithContext<{ queryClient }>) so the loader can reach the query client.invalidateQueries()(all) against the new session and re-run the loaders; no hard reload, no stale cross-org data.OrganizationSwitcher: single Continue button for one org; placeholder dropdown plus a separate Switch button for several (selection is inert); a full-screenSwitching to <Org>...interstitial; prefetch the org list in the loader.Testing
Test configuration
Verified end to end in an isolated container (
node:22): a stateful mock of the admin API (/api/admin/verify,/grants/effective,/orgs,/orgs/switch) with runtime toggles for zero / one / multiple admin orgs, driven by a Playwright script through a genuine login. AMutationObserverinstalled before app JS runs timestamps DOM markers, which is the only reliable way to catch a sub-frame chrome flash.1. Access-denied gate (before / after)
MutationObserver timeline at real timing. Base branch:
Loading (824ms) -> full dashboard (1316ms) -> Access denied (1360ms), a ~44 ms window with the panel on screen for a non-admin. This PR:Loading (661ms) -> Access denied (1192ms),CHROME_MARKERS=[].Base branch: the full panel renders while capabilities load, then is replaced by the access-denied screen
clip-base-flash.webm
With this change: neutral loading only, then the access-denied screen; no panel is shown.
2. Recovery screen on the base branch (#106)
Base branch: recovery screen is top-aligned, the dropdown trigger renders blank, and a single administered org still shows a dropdown.
Base branch: the dashboard appeared only after a manual reload.
clip-base-stuck-then-reload.webm
3. Recovery screen with this change
One administered org: a Continue button; screen vertically centered.
Multiple orgs: placeholder dropdown plus a separate Switch button; centered.
Multiple orgs: expanded dropdown.
Multiple orgs: selecting only fills the dropdown and enables Switch; no navigation yet.
4. Org switch transition (with this change)
A named
Switching to <Org>...interstitial covers the mutation, refetch, and rerouteclip-switch-flow.webm
Lands on the dashboard client-side, no reload.
5. No administered orgs (behavior unchanged, verified)
Only the Return to LibreChat link, no switcher.
Iterations, in order
Layout and placeholder: on the base branch the screen was top-aligned and the dropdown trigger rendered blank (section 2); this PR centers the screen and shows the placeholder (section 3).
Single administered org: the base branch always rendered a dropdown, so one org meant a one-item dropdown (section 2); this PR shows a direct Continue button (section 3).
Selecting vs switching: on the base branch selecting an org switched immediately, which also left the user on access-denied because the switch did not complete (section 2); this PR makes selection inert, adds an explicit Switch button (section 3), and completes the switch client-side through a named interstitial (section 4).
Local gates
eslint src/ --fix,prettier --write, andtsc --noEmitare clean forsrc/.Checklist
Behavioral verification used a reusable Playwright plus mock-backend harness (above) rather than committed specs. Porting it into
e2e/as regression specs (non-admin recovery, target-org authorization, no-admin fallback, switch transition) is a sensible follow-up and would satisfy the AI-1663 acceptance criterion for tests.