fix: metadata store lifecycle during sign-in, sign-out, and locale change (#18901)

## Summary

Fixes metadata lifecycle bugs during sign-in, sign-out, and locale
change:

- **Cross-tab sign-out**: Broadcasts sign-out via `BroadcastChannel` so
other tabs clear their session gracefully instead of hitting stale-token
errors
- **SSE teardown on sign-out**: Handles `UNAUTHENTICATED`/`FORBIDDEN`
errors in the SSE event stream effect instead of throwing unhandled
errors
- **Locale switch resilience**: `invalidateAndReload` now invalidates
collection hashes instead of clearing the store to empty, so components
never see 0 metadata items during the reload transition
- **Sign-in background mock**: Uses non-throwing
`objectMetadataItemFamilySelector` instead of hooks that throw on
missing metadata
- **View name placeholders**: Guards against `undefined` `viewName`
during metadata transitions (the minimal metadata query doesn't include
`name`)
- **Session cleanup**: Selective `localStorage` clearing
(`clearSessionLocalStorageKeys`) preserves metadata keys;
`clearAllSessionLocalStorageKeys` for full clears
- **Metadata reload API**: New `useMetadataStoreActions` hook as the
high-level API for metadata lifecycle operations (`applyMockedMetadata`,
`invalidateAndReload`, `loadMockedMetadataAtomic`)

## Test plan

- [ ] Sign out on Tab A → Tab A shows sign-in page with no console
errors
- [ ] Tab B (logged in) receives cross-tab broadcast and redirects to
sign-in
- [ ] No "Forbidden resource" SSE errors in console during sign-out
- [ ] Change language in Settings > Experience → no crash, metadata
refreshes in background
- [ ] Sign back in after sign-out → metadata loads correctly, app is
functional
- [ ] Re-sign-in after locale change → correct locale is preserved
This commit is contained in:
Charles Bochet
2026-03-24 15:57:53 +01:00
committed by GitHub
parent 2317a701bd
commit 611947e031
36 changed files with 394 additions and 160 deletions
@@ -5,7 +5,7 @@ import {
} from '~/generated-metadata/graphql';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
import { useUpdateMetadataStoreDraft } from '@/metadata-store/hooks/useUpdateMetadataStoreDraft';
import { type FlatFieldMetadataItem } from '@/metadata-store/types/FlatFieldMetadataItem';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
@@ -21,7 +21,7 @@ export const useCreateOneFieldMetadataItem = () => {
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const { addToDraft, applyChanges } = useMetadataStore();
const { addToDraft, applyChanges } = useUpdateMetadataStoreDraft();
const createOneFieldMetadataItem = async (
input: CreateFieldInput,
@@ -7,7 +7,7 @@ import {
} from '~/generated-metadata/graphql';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
import { useUpdateMetadataStoreDraft } from '@/metadata-store/hooks/useUpdateMetadataStoreDraft';
import { type FlatFieldMetadataItem } from '@/metadata-store/types/FlatFieldMetadataItem';
import { type FlatObjectMetadataItem } from '@/metadata-store/types/FlatObjectMetadataItem';
import { splitViewWithRelated } from '@/metadata-store/utils/splitViewWithRelated';
@@ -26,7 +26,8 @@ export const useCreateOneObjectMetadataItem = () => {
const client = useApolloClient();
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const { addToDraft, replaceDraft, applyChanges } = useMetadataStore();
const { addToDraft, replaceDraft, applyChanges } =
useUpdateMetadataStoreDraft();
const createOneObjectMetadataItem = async (
input: CreateObjectInput,
@@ -3,7 +3,7 @@ import { DeleteOneFieldMetadataItemDocument } from '~/generated-metadata/graphql
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
import { useUpdateMetadataStoreDraft } from '@/metadata-store/hooks/useUpdateMetadataStoreDraft';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
import { recordIndexGroupAggregateFieldMetadataItemComponentState } from '@/object-record/record-index/states/recordIndexGroupAggregateFieldMetadataItemComponentState';
import { recordIndexGroupAggregateOperationComponentState } from '@/object-record/record-index/states/recordIndexGroupAggregateOperationComponentState';
@@ -22,7 +22,7 @@ export const useDeleteOneFieldMetadataItem = () => {
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const { removeFromDraft, applyChanges } = useMetadataStore();
const { removeFromDraft, applyChanges } = useUpdateMetadataStoreDraft();
const setRecordIndexGroupAggregateOperation = useSetAtomComponentState(
recordIndexGroupAggregateOperationComponentState,
@@ -2,7 +2,7 @@ import { useMutation } from '@apollo/client/react';
import { DeleteOneObjectMetadataItemDocument } from '~/generated-metadata/graphql';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
import { useUpdateMetadataStoreDraft } from '@/metadata-store/hooks/useUpdateMetadataStoreDraft';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { CombinedGraphQLErrors } from '@apollo/client/errors';
@@ -16,7 +16,7 @@ export const useDeleteOneObjectMetadataItem = () => {
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const { removeFromDraft, applyChanges } = useMetadataStore();
const { removeFromDraft, applyChanges } = useUpdateMetadataStoreDraft();
const deleteOneObjectMetadataItem = async (
idToDelete: string,
@@ -5,7 +5,7 @@ import {
} from '~/generated-metadata/graphql';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
import { useUpdateMetadataStoreDraft } from '@/metadata-store/hooks/useUpdateMetadataStoreDraft';
import { type FlatFieldMetadataItem } from '@/metadata-store/types/FlatFieldMetadataItem';
import { lastFieldMetadataItemUpdateState } from '@/object-metadata/states/lastFieldMetadataItemUpdateState';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
@@ -25,7 +25,7 @@ export const useUpdateOneFieldMetadataItem = () => {
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const { updateInDraft, applyChanges } = useMetadataStore();
const { updateInDraft, applyChanges } = useUpdateMetadataStoreDraft();
const setLastFieldMetadataItemUpdate = useSetAtomState(
lastFieldMetadataItemUpdateState,
@@ -5,7 +5,7 @@ import {
} from '~/generated-metadata/graphql';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
import { useUpdateMetadataStoreDraft } from '@/metadata-store/hooks/useUpdateMetadataStoreDraft';
import { type FlatObjectMetadataItem } from '@/metadata-store/types/FlatObjectMetadataItem';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
@@ -22,7 +22,7 @@ export const useUpdateOneObjectMetadataItem = () => {
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const { updateInDraft, applyChanges } = useMetadataStore();
const { updateInDraft, applyChanges } = useUpdateMetadataStoreDraft();
const updateOneObjectMetadataItem = async ({
idToUpdate,