# Technical Orientation

> Repository layout, Nx workspace conventions, tech stack, key entry points (main.ts, app.module.ts, index.tsx), and how the backend, frontend, and shared packages relate to each other. Starting point for any new contributor.

- Repository: twentyhq/twenty
- GitHub: https://github.com/twentyhq/twenty
- Human wiki: https://grok-wiki.com/public/wiki/twentyhq-twenty-7ed82e5a21f6
- Complete Markdown: https://grok-wiki.com/public/wiki/twentyhq-twenty-7ed82e5a21f6/llms-full.txt

## Source Files

- `README.md`
- `CLAUDE.md`
- `nx.json`
- `package.json`
- `tsconfig.base.json`
- `packages/twenty-server/src/main.ts`
- `packages/twenty-front/src/index.tsx`
- `packages/twenty-server/src/app.module.ts`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [packages/twenty-front/src/index.tsx](packages/twenty-front/src/index.tsx)
- [packages/twenty-front/src/modules/ui/utilities/state/jotai/jotaiStore.ts](packages/twenty-front/src/modules/ui/utilities/state/jotai/jotaiStore.ts)
- [packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createAtomState.ts](packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createAtomState.ts)
- [packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createAtomFamilyState.ts](packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createAtomFamilyState.ts)
- [packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createAtomWritableFamilySelector.ts](packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createAtomWritableFamilySelector.ts)
- [packages/twenty-front/src/modules/object-record/record-store/states/recordStoreFamilyState.ts](packages/twenty-front/src/modules/object-record/record-store/states/recordStoreFamilyState.ts)
- [packages/twenty-front/src/modules/auth/states/tokenPairState.ts](packages/twenty-front/src/modules/auth/states/tokenPairState.ts)
- [packages/twenty-front/src/modules/metadata-store/states/metadataStoreState.ts](packages/twenty-front/src/modules/metadata-store/states/metadataStoreState.ts)
- [packages/twenty-front/src/modules/apollo/services/apollo.factory.ts](packages/twenty-front/src/modules/apollo/services/apollo.factory.ts)
- [packages/twenty-ui/src/theme-constants/themeCssVariables.ts](packages/twenty-ui/src/theme-constants/themeCssVariables.ts)
- [packages/twenty-ui/src/theme-constants/ThemeProvider.tsx](packages/twenty-ui/src/theme-constants/ThemeProvider.tsx)
- [packages/twenty-ui/package.json](packages/twenty-ui/package.json)
</details>

# Technical Orientation

This page is a contributor orientation for the **twenty-front** React application: how its ~55 feature modules are organized, how global state flows through a custom Jotai layer, how Apollo Client's GraphQL cache relates (and does not relate) to that state, and what conventions govern styling and internationalization. It also covers the `twenty-ui` shared component library and the `metadata-store` module that mirrors backend schema on the client.

Read this page before touching any frontend code. The patterns here—state factory functions, the metadata-store/Apollo split, the Linaria CSS variable system—appear everywhere and are easy to misunderstand from code alone.

---

## Module Directory Layout

All feature code lives under `packages/twenty-front/src/modules/`. There are approximately 55 modules; the table below covers the ones most likely to be entered first.

| Module | Responsibility |
|---|---|
| `apollo/` | ApolloClient factory, link chain, token renewal, optimistic effects |
| `auth/` | Authentication state, sign-in/up flows, token management |
| `command-menu/` | Cmd+K command palette; component-instance-scoped state |
| `context-store/` | Per-view contextual selection and focus state |
| `metadata-store/` | Client-side cache of all backend schema/config (Jotai, localStorage) |
| `navigation/` | Navigation drawer, last-visited tracking, mobile bar |
| `object-metadata/` | Object-definition hooks and derived selectors |
| `object-record/` | Record fetching, the Jotai record store, field editing |
| `settings/` | Workspace settings pages and their data layer |
| `ui/` | App-specific UI primitives, Jotai utilities, theme hooks |
| `views/` | View definitions, filters, sorts, groupings |
| `workflow/` | Workflow builder UI and state |

The `ui/utilities/state/jotai/` subtree is the cross-cutting infrastructure all other modules depend on for state. Understanding it is prerequisite to reading any module's `states/` directory.

---

## Jotai State Architecture

### The Single Global Store

One Jotai store is created at application startup and reset on logout:

```ts
// packages/twenty-front/src/modules/ui/utilities/state/jotai/jotaiStore.ts
import { createStore } from 'jotai';
export let jotaiStore = createStore();
export const resetJotaiStore = () => {
  clearAllSessionLocalStorageKeys();
  jotaiStore = createStore();
  return jotaiStore;
};
```

Sources: [packages/twenty-front/src/modules/ui/utilities/state/jotai/jotaiStore.ts:1-12]()

### Factory Functions — the Atom Abstraction Layer

Rather than calling `atom()` directly, all state is created through factory functions in `ui/utilities/state/jotai/utils/`. Each factory returns a typed wrapper object with a `.type` discriminant so that helper hooks can dispatch uniformly.

| Factory | Atom kind | Persistence options |
|---|---|---|
| `createAtomState` | Single writable atom | memory, localStorage, sessionStorage, cookie |
| `createAtomFamilyState` | Keyed Map-backed atom family | memory or localStorage |
| `createAtomSelector` | Read-only derived atom | — |
| `createAtomWritableFamilySelector` | Read-write derived family selector | — |
| `createAtomComponentState` / `createAtomComponentFamilyState` | Component-instance-scoped atoms | memory |

`createAtomState` uses `jotai/utils`'s `atomWithStorage` when a persistence backend is requested, and plain `atom()` otherwise. The cookie variant uses a custom `createJotaiCookieStorage` adapter.

Sources: [packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createAtomState.ts:27-40]()

### Canonical Atom Examples

**Simple persistent atom — auth token** (stored in a cookie):

```ts
// packages/twenty-front/src/modules/auth/states/tokenPairState.ts
export const tokenPairState = createAtomState<AuthTokenPair | null>({
  key: 'tokenPairState',
  defaultValue: null,
  useCookieStorage: {
    cookieKey: 'tokenPair',
    validateInitFn: (payload) => Boolean(payload['accessOrWorkspaceAgnosticToken']),
  },
});
```

Sources: [packages/twenty-front/src/modules/auth/states/tokenPairState.ts:4-12]()

**Keyed family atom — record store** (one slot per record ID):

```ts
// packages/twenty-front/src/modules/object-record/record-store/states/recordStoreFamilyState.ts
export const recordStoreFamilyState = createAtomFamilyState<
  ObjectRecord | null | undefined,
  string   // familyKey = recordId
>({ key: 'recordStoreFamilyState', defaultValue: null });
```

Sources: [packages/twenty-front/src/modules/object-record/record-store/states/recordStoreFamilyState.ts:4-10]()

**Writable family selector — single record field** (reads/writes into the family above):

```ts
export const recordStoreFamilySelector = createAtomWritableFamilySelector<
  unknown,
  { recordId: string; fieldName: string }
>({
  key: 'recordStoreFamilySelector',
  get: ({ recordId, fieldName }) => ({ get }) =>
    get(recordStoreFamilyState, recordId)?.[fieldName],
  set: ({ recordId, fieldName }) => ({ set }, newValue) =>
    set(recordStoreFamilyState, recordId, (prev) => ({ ...prev, [fieldName]: newValue })),
});
```

### State Hook Conventions

| Hook | Returns |
|---|---|
| `useAtomState(state)` | `[value, setter]` |
| `useAtomStateValue(state)` | Read-only value |
| `useSetAtomState(state)` | Write-only setter |
| `useAtomFamilyStateValue(state, key)` | Value for a family key |
| `useSetAtomFamilyState(state)` | `(key) => setter` |
| `useAtomComponentState(state)` | Component-instance-scoped `[value, setter]` |

These hooks all wrap Jotai's `useAtom` / `useAtomValue` / `useSetAtom` and dispatch on the wrapper object's `.type` field.

---

## The metadata-store Module

### What It Caches

`metadataStoreState` is a `createAtomFamilyState` keyed by `MetadataEntityKey` and persisted to **localStorage**. It stores 24 entity collections that mirror backend schema and configuration:

```ts
// packages/twenty-front/src/modules/metadata-store/states/metadataStoreState.ts
export const ALL_METADATA_ENTITY_KEYS = [
  'objectMetadataItems', 'fieldMetadataItems', 'indexMetadataItems',
  'views', 'viewFields', 'viewFilters', 'viewSorts', 'viewGroups',
  'viewFilterGroups', 'viewFieldGroups',
  'pageLayouts', 'pageLayoutTabs', 'pageLayoutWidgets',
  'logicFunctions',
  'navigationMenuItems', 'commandMenuItems', 'frontComponents',
  'webhooks', 'roles', 'roleTargets',
  'rowLevelPermissionPredicates', 'rowLevelPermissionPredicateGroups',
  'agents', 'skills', 'agentChatThreads',
] as const;
```

Each entry is a `MetadataStoreItem` with `current`, `draft`, `status` (`'empty' | 'draft-pending' | 'up-to-date'`), and hash fields for cache validation.

Sources: [packages/twenty-front/src/modules/metadata-store/states/metadataStoreState.ts:8-36]()

### Load and Sync Lifecycle

```text
App boot
  └─ MinimalMetadataLoadEffect (render-null effect component)
       ├─ calls useLoadMinimalMetadata()
       │    └─ Apollo client.query() [network-only] → FIND_MINIMAL_METADATA
       │         → compares returned collectionHashes vs. stored hashes
       │         → stale keys → useLoadStaleMetadataEntities() → full fetch
       │         → writes results into metadataStoreState Jotai atoms
       │              (Apollo cache is NOT used for storage)
       └─ MetadataStoreSSEEffect (listens to server-sent events)
            ├─ create/update → addToDraft({ key, items, collectionHash })
            ├─ delete → removeFromDraft({ key, itemIds, collectionHash })
            └─ applyChanges() → promotes draft → current
```

`useInvalidateMetadataStore()` clears all `currentCollectionHash` values, triggering a reload on next render.

### Apollo Cache vs. Jotai metadata-store

These two systems handle completely separate concerns and do not sync with each other:

| | Apollo `InMemoryCache` | Jotai `metadataStoreState` |
|---|---|---|
| **Caches** | Workspace record data (contacts, companies, etc.) | Schema / configuration (object definitions, views, roles, navigation) |
| **Fetch policy** | `cache-and-network` (default) | `network-only` (bypasses Apollo cache) |
| **Persistence** | In-memory only, reset on page reload | `localStorage`, survives reload |
| **Invalidation** | Apollo `cache.evict()` / optimistic effects | `useInvalidateMetadataStore()` |
| **SSE updates** | Optimistic effect patches on mutations | `MetadataStoreSSEEffect` applies drafts |

Apollo Client is used as a *transport* for metadata queries — `client.query()` sends the request, but the response is immediately written into Jotai atoms rather than stored in `InMemoryCache`.

---

## Apollo Client Configuration

### Link Chain

The `ApolloFactory` class (in `packages/twenty-front/src/modules/apollo/services/apollo.factory.ts`) builds a link pipeline:

```text
ErrorLink
  → AuthLink (setContext — injects Authorization, X-Schema-Version, X-App-Version headers)
  → [extraLinks]
  → [loggerLink in debug mode]
  → RetryLink (max 2 retries, 3 s initial delay; skips auth/413 errors)
  → StreamingRestLink
  → RestLink
  → UploadHttpLink
```

Sources: [packages/twenty-front/src/modules/apollo/services/apollo.factory.ts:1-44]()

### Token Renewal

A module-level `renewalPromise` variable is shared across all `ApolloFactory` instances so that concurrent `UNAUTHENTICATED` errors from the `/graphql` and `/metadata` clients collapse into a single renewal request (using RxJS `switchMap`). Max retries: 3, with 1 s backoff.

### Two Apollo Clients

| Client | Endpoint | Created in |
|---|---|---|
| Main (workspace data) | `/graphql` | `useApolloFactory` hook |
| Metadata | `/metadata` | `ApolloProvider` component, with captcha refresh link |

Both clients share the same token renewal logic through the module-level `renewalPromise`.

---

## Feature Module Patterns

### object-record

The record module owns the Jotai record store (`recordStoreFamilyState`, keyed by record ID), record-list fetching hooks, field editors, and optimistic mutation helpers. It is the largest module in the codebase.

Key internal shape:
- `record-store/states/` — atoms and selectors for individual record field access
- `record-filter/` — filter query builders
- `record-sort/` — sort state
- `hooks/` — `useCreateOneRecord`, `useUpdateOneRecord`, `useDeleteOneRecord`, etc.

### auth

All authentication state lives in `modules/auth/states/`. Most atoms are memory-only; `tokenPairState` is the only one persisted to a cookie. The auth module also owns `currentWorkspaceState`, `currentWorkspaceMemberState`, and `currentUserState`.

### navigation

Navigation state is minimal: `currentMobileNavigationDrawerState` (`'main' | 'settings'`), and two family atoms tracking last-visited object and view per object. Components include `AppNavigationDrawer`, `MainNavigationDrawer`, `SettingsNavigationDrawer`, and `MobileNavigationBar`.

### command-menu

Uses the **component instance context** pattern (`createComponentInstanceContext()`), allowing multiple independent palette instances scoped to different parts of the UI. State is not global — it is keyed to a context instance ID.

### workflow

The workflow builder module owns its own Jotai atoms for step selection, canvas position, and execution state. It follows the same `createAtomState` / `createAtomFamilyState` patterns as other modules.

### settings

Settings pages are grouped under `modules/settings/` with data fetched via Apollo and workspace configuration written back through mutations. Settings state is largely ephemeral (form state in component atoms) rather than global.

---

## twenty-ui Shared Component Library

`packages/twenty-ui` is the design system consumed by `twenty-front`. It is published as a private monorepo package with sub-path exports.

### Directory Structure

```text
packages/twenty-ui/src/
  display/       avatar, chip, icon, tag, tooltip, typography, status, callout …
  feedback/      loader, progress-bar
  input/         button, code-editor, color-scheme
  layout/        card, modal, section, animated-expandable-container
  navigation/    link, menu, navigation-bar, notification-counter
  theme-constants/  CSS variable references, ThemeProvider, generated CSS files
  utilities/     animation components, dimension helpers
  testing/       Storybook decorators
```

Sub-path imports are declared in `package.json`: `twenty-ui/display`, `twenty-ui/input`, `twenty-ui/layout`, `twenty-ui/navigation`, `twenty-ui/theme`, `twenty-ui/theme-constants`, etc.

### Linaria CSS-in-JS

Styling uses **`@linaria/react`** (zero-runtime CSS-in-JS via `wyw-in-js` toolchain). The import is always:

```ts
import { styled } from '@linaria/react';
```

All style values reference CSS custom properties via a `themeCssVariables` object:

```ts
// used in styled components throughout twenty-ui
import { themeCssVariables } from '@ui/theme-constants';

const StyledContainer = styled.div`
  display: flex;
  gap: ${themeCssVariables.spacing[4]};
  border-radius: ${themeCssVariables.border.radius.md};
`;
```

Dynamic props follow the standard Linaria pattern:

```ts
const StyledButton = styled.div<{ accent: 'default' | 'danger'; disabled: boolean }>`
  color: ${({ accent, disabled }) =>
    disabled ? themeCssVariables.font.color.tertiary :
    accent === 'danger' ? themeCssVariables.font.color.danger :
    themeCssVariables.font.color.secondary};
`;
```

The CSS custom property values themselves are defined in generated `.light` and `.dark` CSS files (`theme-light.css`, `theme-dark.css`). `ThemeProvider` toggles `class="light"` or `class="dark"` on `document.documentElement` to switch themes. Components that need raw numeric values read from `useContext(ThemeContext).theme` (populated via `getComputedStyle`).

Responsive breakpoints use the `MOBILE_VIEWPORT = 768` constant (not a CSS variable, because CSS variables cannot be used in media queries):

```ts
import { MOBILE_VIEWPORT } from 'twenty-ui/theme-constants';
// @media (max-width: ${MOBILE_VIEWPORT}px) { ... }
```

### CSS Import Order

`index.tsx` imports the global CSS files before rendering the React tree:

```ts
// packages/twenty-front/src/index.tsx
import 'twenty-ui/style.css';
import 'twenty-ui/theme-light.css';
import 'twenty-ui/theme-dark.css';
import './index.css';
```

Sources: [packages/twenty-front/src/index.tsx:5-8]()

---

## Lingui Internationalization

Lingui lives in `twenty-front`, not `twenty-ui`. Three usage patterns appear across the codebase:

**Pattern 1 — hook + tagged template** (most common in components):
```ts
import { useLingui } from '@lingui/react/macro';
const { t } = useLingui();
// in JSX:
<Button label={t`Save`} />
```

**Pattern 2 — `t` macro directly** (utility functions outside React):
```ts
import { t } from '@lingui/core/macro';
const errorMessage = t`Invalid cron expression`;
```

**Pattern 3 — `msg` + `i18n._()` for static descriptors** (evaluated lazily):
```ts
import { msg } from '@lingui/core/macro';
import { i18n } from '@lingui/core';
const label = msg`Default role`;
i18n._(label); // resolved at call site
```

**Locale detection order**: URL param `?locale=` → `localStorage['locale']` → `navigator.language` → English fallback. `dynamicActivate(locale)` lazy-imports the compiled catalog from `src/locales/generated/${locale}.ts`.

---

## Architecture Summary

```text
packages/twenty-front/src/
│
├── index.tsx                  Entry point — mounts <App />, imports CSS
├── app/components/App.tsx     Root component, React Router, providers
│
└── modules/
    ├── apollo/                ApolloFactory, link chain, two clients
    │     └── InMemoryCache   Caches workspace record query results
    │
    ├── metadata-store/        Jotai family state (localStorage)
    │     └── 24 entity keys  Schema, views, navigation, roles, AI agents …
    │         loaded by MinimalMetadataLoadEffect (Apollo as transport only)
    │         updated by MetadataStoreSSEEffect (SSE → draft → current)
    │
    ├── object-record/         recordStoreFamilyState (Jotai, memory)
    │     └── keyed by recordId; selectors drill to individual fields
    │
    ├── auth/                  tokenPairState (cookie), currentUser (memory)
    ├── navigation/            drawer state, last-visited atoms
    ├── command-menu/          component-instance-scoped state
    ├── workflow/              builder atoms, execution state
    ├── settings/              mostly ephemeral form state
    │
    └── ui/utilities/state/jotai/
          ├── jotaiStore.ts    Single global Jotai store, reset on logout
          └── utils/           createAtomState, createAtomFamilyState,
                               createAtomSelector, createAtomWritableFamilySelector,
                               createAtomComponentState, buildGetHelper …

packages/twenty-ui/
    ├── src/display|input|layout|navigation|…   Shared components (Linaria styled)
    └── src/theme-constants/   CSS variables, ThemeProvider, light/dark CSS
```

The central architectural insight: **Apollo `InMemoryCache` owns workspace record data; Jotai `metadataStoreState` owns schema/configuration.** These two caches are independent and require separate invalidation strategies. Apollo is used as a network transport for metadata, but the cache layer for metadata is entirely Jotai-backed and localStorage-persisted.

Sources: [packages/twenty-front/src/modules/metadata-store/states/metadataStoreState.ts:1-36](), [packages/twenty-front/src/modules/ui/utilities/state/jotai/jotaiStore.ts:1-12](), [packages/twenty-front/src/modules/apollo/services/apollo.factory.ts:41-44]()
