Replace sign-in mocked metadata with hardcoded BackgroundMock (#20308)

## Summary

When the user is logged out, we render the auth modal on top of a sample
table to make the empty page feel alive. So far this was achieved by
**loading a full set of mocked object / field / view / navigation-menu
metadata into the runtime metadata store** and then mounting the real
`RecordTable` and `AppNavigationDrawer` behind the modal. This had a few
downsides:

- Significant bundle weight pulled in for unauthenticated users (mocked
GraphQL fixtures + the real `RecordTable` virtualization stack).
- Plenty of code paths that had to know about the "showAuthModal" case
(`useRecordIndexTableQuery`, `useTriggerInitialRecordTableDataLoad`,
`MainContextStoreProvider`, `IsMinimalMetadataReadyEffect`...).
- Any change to metadata-store internals or to the record-table runtime
risked breaking the logged-out background.

This PR replaces the entire flow with a small, self-contained
`BackgroundMock` component tree that **does not consume any metadata**
and **does not load any mocked metadata at runtime**.

### What changed

- New module under `sign-in-background-mock`:
- `BackgroundMockPage` + `BackgroundMockViewBar` + `BackgroundMockTable`
+ `BackgroundMockTableRow` render a hardcoded "Companies" table that
visually mirrors the real one.
- `BackgroundMockNavigationDrawer` renders a hardcoded sidebar with
People / Companies / Opportunities / Tasks / Notes (with their standard
colors).
- Hardcoded constants in `BackgroundMockCompanies.ts`,
`BackgroundMockColumns.ts`, `BackgroundMockNavigationItems.ts`.
- `MinimalMetadataLoadEffect` no longer calls `loadMockedMetadataAtomic`
for unauthenticated users — it just doesn't load anything.
- `IsMinimalMetadataReadyEffect` now reports ready immediately when
there is no access token pair, so the skeleton loader doesn't hang
waiting for metadata that will never come.
- `MainContextStoreProvider`, `useRecordIndexTableQuery`, and
`useTriggerInitialRecordTableDataLoad` drop their `showAuthModal`
branches — the real `RecordTable` is no longer mounted behind the modal.
- `DefaultLayout` and `NotFound` now lazily load `BackgroundMockPage` /
`BackgroundMockNavigationDrawer` instead of the deleted
`SignInBackgroundMockPage` / `SignInAppNavigationDrawerMock`.
- Removed: `SignInBackgroundMockPage`, `SignInBackgroundMockContainer`,
`SignInBackgroundMockContainerEffect`, `SignInAppNavigationDrawerMock`,
`SignInBackgroundMockColumnDefinitions`,
`SignInBackgroundMockCompanies`, `SignInBackgroundMockViewFields`.

`useLoadMockedMetadata` and `preloadMockedMetadata` are kept on purpose:
Storybook decorators (`ObjectMetadataItemsDecorator`,
`WorkflowStepDecorator`) still rely on the mocked metadata fixtures, but
**production** unauthenticated runtime no longer touches them.

### Visual parity

Side-by-side at 1440×900 on `/sign-in`:

**Before** (loads mocked metadata + real RecordTable):

![before](https://github.com/user-attachments/assets/before-placeholder)

**After** (purely hardcoded BackgroundMock):

![after](https://github.com/user-attachments/assets/after-placeholder)

## Test plan

- [ ] `npx nx typecheck twenty-front`  (passes locally)
- [ ] `npx nx lint:diff-with-main twenty-front`  (oxlint + prettier
clean)
- [ ] `npx jest useRecordIndexTableQuery` 
- [ ] Manually verify `/sign-in` renders the table + nav drawer behind
the modal
- [ ] Manually verify `/not-found` still renders the background
- [ ] Verify CI: storybook, unit tests, e2e tests
This commit is contained in:
Charles Bochet
2026-05-06 11:49:24 +02:00
committed by GitHub
parent 26874c3603
commit ee6c0ef904
26 changed files with 975 additions and 2132 deletions
@@ -28,21 +28,24 @@ export const IsMinimalMetadataReadyEffect = () => {
);
useEffect(() => {
if (!hasAccessTokenPair) {
setIsMinimalMetadataReady(true);
return;
}
const hasActiveWorkspace = isWorkspaceActiveOrSuspended(currentWorkspace);
const areObjectsLoaded = metadataStore.status === 'up-to-date';
const areViewsLoaded = metadataStoreViews.status === 'up-to-date';
const isReady = !areObjectsLoaded
? false
: !hasAccessTokenPair ||
(isDefined(currentUser) && (!hasActiveWorkspace || areViewsLoaded));
if (!areObjectsLoaded) {
setIsMinimalMetadataReady(false);
return;
}
const isReady =
isDefined(currentUser) && (!hasActiveWorkspace || areViewsLoaded);
if (isReady) {
setIsMinimalMetadataReady(true);
}
@@ -2,70 +2,41 @@ import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState';
import { useLoadMinimalMetadata } from '@/metadata-store/hooks/useLoadMinimalMetadata';
import { useLoadMockedMetadata } from '@/metadata-store/hooks/useLoadMockedMetadata';
import { useLoadStaleMetadataEntities } from '@/metadata-store/hooks/useLoadStaleMetadataEntities';
import { metadataLoadedVersionState } from '@/metadata-store/states/metadataLoadedVersionState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useEffect, useState } from 'react';
import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace';
type LoadedState = 'none' | 'mocked' | 'real';
const computeDesiredLoadState = (
hasAccessTokenPair: boolean,
isActiveWorkspace: boolean,
): LoadedState => {
if (hasAccessTokenPair && isActiveWorkspace) {
return 'real';
}
return 'mocked';
};
export const MinimalMetadataLoadEffect = () => {
const hasAccessTokenPair = useHasAccessTokenPair();
const isCurrentUserLoaded = useAtomStateValue(isCurrentUserLoadedState);
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
const metadataLoadedVersion = useAtomStateValue(metadataLoadedVersionState);
const [lastMetadataLoadData, setLastMetadataLoadData] = useState<{
state: LoadedState;
version: number;
}>({ state: 'none', version: -1 });
const [lastLoadedVersion, setLastLoadedVersion] = useState<number>(-1);
const { loadMinimalMetadata } = useLoadMinimalMetadata();
const { loadMockedMetadataAtomic } = useLoadMockedMetadata();
const { loadStaleMetadataEntities } = useLoadStaleMetadataEntities();
const isActiveWorkspace = isWorkspaceActiveOrSuspended(currentWorkspace);
const desiredLoadState = computeDesiredLoadState(
hasAccessTokenPair,
isActiveWorkspace,
);
const shouldLoadRealMetadata = hasAccessTokenPair && isActiveWorkspace;
useEffect(() => {
if (!isCurrentUserLoaded) {
return;
}
const versionChanged =
metadataLoadedVersion !== lastMetadataLoadData.version;
if (!versionChanged && lastMetadataLoadData.state === desiredLoadState) {
if (!shouldLoadRealMetadata) {
return;
}
setLastMetadataLoadData({
state: desiredLoadState,
version: metadataLoadedVersion,
});
if (metadataLoadedVersion === lastLoadedVersion) {
return;
}
setLastLoadedVersion(metadataLoadedVersion);
const performLoad = async () => {
if (desiredLoadState === 'mocked') {
await loadMockedMetadataAtomic();
return;
}
const result = await loadMinimalMetadata();
if (result?.staleEntityKeys && result.staleEntityKeys.length > 0) {
@@ -76,13 +47,10 @@ export const MinimalMetadataLoadEffect = () => {
performLoad();
}, [
isCurrentUserLoaded,
hasAccessTokenPair,
isActiveWorkspace,
desiredLoadState,
lastMetadataLoadData,
shouldLoadRealMetadata,
lastLoadedVersion,
metadataLoadedVersion,
loadMinimalMetadata,
loadMockedMetadataAtomic,
loadStaleMetadataEntities,
]);