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
@@ -2,12 +2,8 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { useRelevantRecordsGqlFields } from '@/object-record/record-field/hooks/useRelevantRecordsGqlFields';
import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams';
import { SIGN_IN_BACKGROUND_MOCK_COMPANIES } from '@/sign-in-background-mock/constants/SignInBackgroundMockCompanies';
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
export const useRecordIndexTableQuery = (objectNameSingular: string) => {
const showAuthModal = useShowAuthModal();
const params = useFindManyRecordIndexTableParams(objectNameSingular);
const { objectMetadataItem } = useObjectMetadataItem({
@@ -28,12 +24,11 @@ export const useRecordIndexTableQuery = (objectNameSingular: string) => {
} = useFindManyRecords({
...params,
recordGqlFields,
skip: showAuthModal,
});
return {
records: showAuthModal ? SIGN_IN_BACKGROUND_MOCK_COMPANIES : records,
loading: showAuthModal ? false : loading,
records,
loading,
hasNextPage,
queryIdentifier,
totalCount,
@@ -30,8 +30,6 @@ import { recordIdByRealIndexComponentState } from '@/object-record/record-table/
import { scrollAtRealIndexComponentState } from '@/object-record/record-table/virtualization/states/scrollAtRealIndexComponentState';
import { totalNumberOfRecordsToVirtualizeComponentState } from '@/object-record/record-table/virtualization/states/totalNumberOfRecordsToVirtualizeComponentState';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { SIGN_IN_BACKGROUND_MOCK_COMPANIES } from '@/sign-in-background-mock/constants/SignInBackgroundMockCompanies';
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState';
import { useAtomComponentSelectorCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorCallbackState';
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
@@ -41,8 +39,6 @@ import { isDefined } from 'twenty-shared/utils';
export const useTriggerInitialRecordTableDataLoad = () => {
const { recordTableId, objectNameSingular } = useRecordTableContextOrThrow();
const showAuthModal = useShowAuthModal();
const { findManyRecordsLazy } =
useRecordIndexTableLazyQuery(objectNameSingular);
@@ -155,40 +151,35 @@ export const useTriggerInitialRecordTableDataLoad = () => {
let records: ObjectRecord[] | null = null;
let totalCount = 0;
if (showAuthModal) {
records = SIGN_IN_BACKGROUND_MOCK_COMPANIES;
totalCount = SIGN_IN_BACKGROUND_MOCK_COMPANIES.length;
} else {
const newRecordIdByRealIndex = new Map(
store.get(recordIdByRealIndexCallbackState),
);
const newDataLoadingStatusByRealIndex = new Map(
store.get(dataLoadingStatusByRealIndexCallbackState),
);
const newRecordIdByRealIndex = new Map(
store.get(recordIdByRealIndexCallbackState),
);
const newDataLoadingStatusByRealIndex = new Map(
store.get(dataLoadingStatusByRealIndexCallbackState),
);
for (const [realIndex] of currentRecordIds.entries()) {
newDataLoadingStatusByRealIndex.set(realIndex, 'not-loaded');
newRecordIdByRealIndex.delete(realIndex);
}
store.set(recordIdByRealIndexCallbackState, newRecordIdByRealIndex);
store.set(
dataLoadingStatusByRealIndexCallbackState,
newDataLoadingStatusByRealIndex,
);
store.set(
recordIndexRecordIdsByGroupFamilyState(NO_RECORD_GROUP_FAMILY_KEY),
[],
);
const { records: findManyRecords, totalCount: findManyTotalCount } =
await findManyRecordsLazy();
records = findManyRecords;
totalCount = findManyTotalCount;
for (const [realIndex] of currentRecordIds.entries()) {
newDataLoadingStatusByRealIndex.set(realIndex, 'not-loaded');
newRecordIdByRealIndex.delete(realIndex);
}
store.set(recordIdByRealIndexCallbackState, newRecordIdByRealIndex);
store.set(
dataLoadingStatusByRealIndexCallbackState,
newDataLoadingStatusByRealIndex,
);
store.set(
recordIndexRecordIdsByGroupFamilyState(NO_RECORD_GROUP_FAMILY_KEY),
[],
);
const { records: findManyRecords, totalCount: findManyTotalCount } =
await findManyRecordsLazy();
records = findManyRecords;
totalCount = findManyTotalCount;
store.set(totalNumberOfRecordsToVirtualizeCallbackState, totalCount);
if (isDefined(records)) {
@@ -230,7 +221,6 @@ export const useTriggerInitialRecordTableDataLoad = () => {
recordIndexAllRecordIds,
recordIndexRecordIdsByGroupFamilyState,
store,
showAuthModal,
dataPagesLoadedCallbackState,
isRecordTableInitialLoading,
lastScrollPositionCallbackState,