Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const state = vi.hoisted(() => ({
org: undefined as { orgId: string | null } | undefined,
orgLoading: false,
orgError: false,
orgFetching: false,
actionCalls: [] as string[],
actionParams: [] as unknown[],
actionResult: {
Expand Down Expand Up @@ -38,6 +39,7 @@ vi.mock("@agent-native/core/client/org", () => ({
data: state.org,
isLoading: state.orgLoading,
isError: state.orgError,
isFetching: state.orgFetching,
}),
}));

Expand All @@ -53,6 +55,7 @@ beforeEach(() => {
state.org = undefined;
state.orgLoading = false;
state.orgError = false;
state.orgFetching = false;
state.actionCalls = [];
state.actionParams = [];
state.actionResult = { data: undefined, isPending: false, isError: false };
Expand Down Expand Up @@ -94,6 +97,21 @@ describe("OrganizationIdentityCard", () => {
expect(state.actionCalls).toEqual(["list-organization-state"]);
});

it("waits instead of flashing the error while the active org is still in flight", () => {
// Deleting an org invalidates every query at once, so `useOrg()` keeps
// naming the outgoing org while it refetches. The branding read then 403s
// for an org the caller just left - that is the wrong question, not an
// unreadable organization.
state.org = { orgId: "org_deleted" };
state.orgFetching = true;
state.actionResult = { data: undefined, isPending: false, isError: true };

const markup = renderToStaticMarkup(<OrganizationIdentityCard />);

expect(markup).not.toContain("organizationSettings.brandingLoadFailed");
expect(markup).toContain("skeleton");
});

it("scopes the branding request to the active organization", () => {
// An unscoped query key lets the next organization render the previous
// one's cached branding while refetching, which the editor would seed its
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export function OrganizationIdentityCard() {
data: orgInfo,
isLoading: orgLoading,
isError: isOrgError,
isFetching: isOrgFetching,
} = useOrg();
// Personal scope owns this surface: the framework Team card below already
// renders "create an organization", so an org-scoped branding fetch here
Expand Down Expand Up @@ -66,16 +67,23 @@ export function OrganizationIdentityCard() {
return role === "admin" || role === "owner";
}, [members, email, organization?.ownerEmail]);

const loadFailed = (
<Card>
<CardContent className="py-6 text-center text-sm text-muted-foreground">
{t("organizationSettings.brandingLoadFailed")}
</CardContent>
</Card>
);

// A failed load must not look like "this org has no branding", and an
// unreadable organization must not look like not having one.
if (isOrgError || isError) {
return (
<Card>
<CardContent className="py-6 text-center text-sm text-muted-foreground">
{t("organizationSettings.brandingLoadFailed")}
</CardContent>
</Card>
);
if (isOrgError) return loadFailed;
if (isError) {
// Deleting or switching an org invalidates every query at once, so while
// `org-me` is still in flight `orgInfo` names the outgoing organization
// and this failure means "asked about the wrong org". It settles on its
// own; flashing the error this surface exists to remove is worse.
return isOrgFetching ? <Skeleton className="h-64 w-full" /> : loadFailed;
}
if (orgLoading) return <Skeleton className="h-64 w-full" />;
if (!hasActiveOrg) return null;
Expand Down
Loading