Google Fonts logs the IP and user agent of everyone who loads a font
from it. Any page of ours that links to `fonts.googleapis.com` hands our
users (and every self-hoster's users) to a third party for nothing in
return, since we can serve the same bytes ourselves.
After this PR there is no reference to `fonts.googleapis.com`,
`fonts.gstatic.com` or `next/font/google` left in the repo.
## What changed
**twenty-front, PDF export.** `exportBlockNoteEditorToPdf` registered
Inter by URL against `fonts.gstatic.com`, so exporting a note made the
browser fetch three TTFs from Google. The registration turned out to be
unnecessary altogether: `@blocknote/xl-pdf-exporter` already registers
an `Inter` family for its PDF schema, shipped inlined in the package as
a base64 TTF with the same 2849-codepoint coverage. Deleting our
`Font.register` means no font request leaves the browser, with 41 fewer
lines and nothing vendored.
Only weights 400 and 700 were ever used, and 700 already resolved to
blocknote's `Inter18pt-Bold` before this branch, so the custom 500/600
registrations were dead. The only rendering change is body text going
from `Inter` to `Inter18pt`, the same typeface at its 18pt optical size.
**twenty-sdk, OAuth callback page.** The local "you can close this tab"
page linked to Google Fonts, which meant running `twenty auth` phoned
Google from the developer's browser. Replaced with a system font stack;
a transient callback page did not justify a webfont round trip in the
first place.
**twenty-ui, Storybook.** `preview-head.html` loaded Inter from Google.
It now imports `@fontsource/inter` in `preview.tsx`, matching what
twenty-front's Storybook already does.
**twenty-website.** Host Grotesk, Aleo, Azeret Mono and VT323 came
through `next/font/google`. Next self-hosts those at runtime, so this
was not a visitor-facing leak, but the build still had to reach Google,
which makes builds non-hermetic and fails in an air-gapped environment.
The latin subsets are now vendored in `src/fonts/`, next to the Inter
files that were already there, and loaded with `next/font/local`. All
four are OFL 1.1; `src/fonts/README.md` records each file's upstream and
license. Total added weight is ~78 KB, and these are the exact files
Next was downloading at build time anyway.
Host Grotesk and Azeret Mono ship as single variable files, so they are
declared once over their full `wght` axis rather than as one face per
weight.
## Also removed
Both Storybooks pulled `iframeResizer.contentWindow.min.js` from
`cdnjs.cloudflare.com`. Storybook has not needed it since v7 and nothing
in either package references `iframeResizer` or `parentIFrame`, so it
was a third-party script executing in the preview iframe for no reason.
Argos does not screenshot through the manager iframe either:
`@argos-ci/storybook` hooks Vitest browser mode and calls
`server.commands.argosScreenshot`, so Playwright drives the page
directly.
## Verification
Not just typecheck. The interesting parts were tested end to end, which
caught two bugs an earlier revision of this PR had introduced.
**PDF export** — production Vite build, served over HTTP, real Chromium,
exporting through the actual `exportBlockNoteEditorToPdf`, then
extracting the PDF's text back out:
```
Latin heading Cyrillic: Привет мир Greek: Ελληνικά κείμενο
Latin-ext: Zażółć gęślą jaźń, Český Vietnamese: Tiếng Việt
PASS Latin / Cyrillic / Greek / Polish / Czech / Vietnamese
```
Embedded fonts are `Inter18pt-Regular` / `Inter18pt-Bold`, no Helvetica
fallback, zero requests off-origin.
**Website** — built it, audited the build output (12 `@font-face` rules,
all `/_next/static/media/`, weights `300 800` / `100 900` / `300` /
`400` / `400,500,600`, `display: swap` preserved), then loaded it in
Chromium: 136 requests, zero to Google. The deployed preview was checked
too: no Google references in the served HTML or across all 21 CSS
chunks, every font file returns `200 font/woff2` and parses to the
expected family, and the asset hashes match a local build byte for byte.
**Two bugs this caught**, both in earlier commits on this branch, both
now fixed:
1. Registering `@fontsource/inter`'s latin file dropped coverage from
2849 codepoints to 230, silently removing Cyrillic, Greek, Vietnamese
and extended-Latin from every export. fontsource splits Inter into seven
per-script files chosen by `unicode-range`, but `Font.register` binds
one file per weight with no equivalent.
2. Any woff2 aborts the export outright with `RangeError: Offset is
outside the bounds of the DataView`. fontkit parses woff2, but
`@react-pdf`'s subsetter chokes on the transformed `glyf` table.
Confirmed format was the only variable by running identical content
through local TTF, WOFF and WOFF2 files.
Both are moot now that the registration is gone, but they are why this
is worth a careful look rather than a rubber stamp.
## Left alone, but worth knowing about
More third-party calls exist. None are font-related and each is a
separate decision:
- `twenty-website` loads `dotlottie-player.wasm` from **unpkg.com** at
runtime on the homepage, via `@lottiefiles/dotlottie-react`. This is a
live third-party CDN request on every visit, the same class of problem
as the fonts, and looks like a small config change to self-host.
- The halftone studio loads the Draco decoder from `www.gstatic.com`
and, in exported scenes, three.js from `unpkg.com`.
- The partners marketplace fixtures hotlink logos from
`cdn.simpleicons.org` and `upload.wikimedia.org`.
- reCAPTCHA and the Front support chat are config-gated and off unless
an admin configures them, which seems right.
- `APP_REGISTRY_CDN_URL` defaults to `https://unpkg.com`.
- `twenty-front/index.html` points its `og:image` at
`raw.githubusercontent.com`. Only social crawlers fetch it, so this is
cosmetic.
## Migrate all `twenty-ui-deprecated` components into `twenty-ui`
Ports all **192 components** and **70 stories** into the new `twenty-ui`
package with full public-API parity (export diff: 0 missing / 0 extra
across all 13 modules; story titles byte-identical for the Argos
cross-package diff).
- **Styling:** Linaria → SCSS Modules, `var(--t-*)` tokens, `data-*`
state. Canonical pattern in `Button.module.scss`.
- **Behavior:** Base UI where mapped (Checkbox, Radio, Modal→Dialog,
Tooltip drops `react-tooltip`, JSON tree→Collapsible); framer kept only
where animation is the public contract.
- **Fixed an inert axe gate** in `.storybook/vitest.setup.ts` (a11y
addon annotations were never registered). Now live; 119 stories carry
`a11y: 'todo'` overrides pending a fix pass.
## Description
Promotes the next-gen UI library (formerly `twenty-new-ui`) to the name
**`twenty-ui`** (v0.1.0, publishable) and renames the old package to
**`twenty-ui-deprecated`**. Rewrites ~1,730 `twenty-ui` imports →
`twenty-ui-deprecated`, updates all configs/CI/Docker/deps, and migrates
twenty-front's `Toggle` to the new package (first consumer) as a
drop-in.
## Next steps
- Wire the `ui/v*` publish dispatch (`cd-deploy-tag.yaml` +
`.yarnrc.yml`), then tag `ui/v0.1.0` to publish.
- Continue migrating components from `twenty-ui-deprecated` →
`twenty-ui`.
## Migrate twenty-ui from Emotion to Linaria
Completes the migration of all `twenty-ui` components from Emotion
(runtime CSS-in-JS) to Linaria (zero-runtime, CSS extracted at build
time).
- Replaced `@emotion/styled` with `@linaria/react` across ~170 files
- Removed all Emotion dependencies from `twenty-ui`
- Introduced a CSS custom properties-based theme system:
`themeCssVariables` where every leaf is a `var(--t-xxx)` reference,
injected onto `document.documentElement` by
`ThemeCssVariableInjectorEffect`
- No more `theme` prop threading — styled components reference
`themeCssVariables.x.y` directly at build time
- Updated `twenty-front` consumers to remove `theme={theme}` prop
passing
**Before / After:**
```tsx
// Emotion
color: ${({ theme }) => theme.font.color.primary};
padding: ${({ theme }) => theme.spacing(4)};
// Linaria
color: ${themeCssVariables.font.color.primary};
padding: ${themeCssVariables.spacing[4]};
```
### Theme architecture
Two build-time utilities produce the theme system:
- **`buildThemeReferencingRootCssVariables`** — walks the theme object
and builds a nested mirror where every leaf is a `var(--t-xxx)` string
(evaluated at build time by wyw-in-js)
- **`prepareThemeForRootCssVariableInjection`** — walks the runtime
theme and collects flat `[--css-variable-name, value]` pairs, injected
onto `document.documentElement` by `ThemeCssVariableInjectorEffect`
Both share naming conventions (`camelToKebab`, `SPACING_VALUES`,
`formatSpacingKey`) and are unit tested.
### Spacing cleanup
Spacing scale now uses integers 0–32 (generated via loop), with `0.5`
and `1.5` as the only fractional exceptions. All other fractional
spacing usages (`0.25`, `0.75`, `1.25`, `2.5`, `3.5`) were replaced with
literal pixel values across ~20 twenty-front files.
### Framer Motion integration
Linaria doesn't support `styled(motion.div)` — wrapping a motion element
with `styled()` causes the component body to be stripped at build time.
Instead, we define the styled component first, then wrap it with
`motion.create()`:
```tsx
const StyledBarBase = styled.div`
background-color: ${themeCssVariables.font.color.primary};
height: 100%;
`;
const StyledBar = motion.create(StyledBarBase);
```
### Block interpolations
Linaria doesn't support interpolations that return multiple CSS
declarations (Linaria wraps the entire block in a single `var()`,
producing invalid CSS). These were split into individual property
interpolations:
```tsx
// Emotion — single interpolation returning multiple declarations
border-left: ${({ divider, theme }) => {
const border = `1px solid ${theme.border.color.light}`;
return divider ? `border-${divider}: ${border}` : '';
}}
// Linaria — one interpolation per property
border-left: ${({ divider }) =>
divider === 'left' ? `1px solid ${themeCssVariables.border.color.light}` : 'none'};
border-right: ${({ divider }) =>
divider === 'right' ? `1px solid ${themeCssVariables.border.color.light}` : 'none'};
```
### Dynamic styles via CSS variables
When a component needs to compute styles from multiple props with
complex branching logic (e.g. `Button` combining `variant`, `accent`,
`inverted`, `disabled`, `focus`, `position`), Linaria's prop
interpolations become unwieldy. In those cases we use a
`computeDynamicStyles` function that returns a `CSSProperties` object
injected via `style={}`, referenced from the static CSS with `var()`:
```tsx
const StyledButton = styled.button`
background: var(--btn-bg);
border-color: var(--btn-border-color);
&:hover { background: var(--btn-hover-bg); }
`;
const dynamicStyles = useMemo(() => {
const s = computeButtonDynamicStyles(variant, accent, ...);
return { '--btn-bg': s.background, '--btn-hover-bg': s.hoverBackground } as CSSProperties;
}, [variant, accent, ...]);
return <StyledButton style={dynamicStyles} />;
```
### CSS var + unit concatenation
CSS custom properties can't be concatenated with unit suffixes directly
(`var(--x)px` is invalid). Values that need units use `calc()`:
```tsx
// Broken
transition: background ${themeCssVariables.animation.duration.instant}s ease;
// Fixed
transition: background calc(${themeCssVariables.animation.duration.instant} * 1s) ease;
```
Upgraded from 8.6.15 to 9.1.17 in two steps:
- 8.6.15 -> 9.0.0
- 9.0.0 -> 9.1.17
I had to disable `storybook-addon-cookie` since it is not supported for
Storybook 9. However, I do intend to upgrade to Storybook 10 when this
is merged, so we can replace the aforementioned add-on with this fork
specifically created to support Storybook 10 and above:
https://www.npmjs.com/package/@storybook-community/storybook-addon-cookie.
Additionally, once we upgrade to Version 10 successfully, I will start
looking into integrating the official Vitest add-on.
# Introduction
closes https://github.com/twentyhq/core-team-issues/issues/591
Same than for `twenty-shared` made in
https://github.com/twentyhq/twenty/pull/11083.
## TODO
- [x] Manual migrate twenty-website twenty-ui imports
## What's next:
- Generate barrel and migration script factorization within own package
+ tests
- Refactoring using preconstruct ? TimeBox
- Lint circular dependencies
- Lint import from barrel and forbid them
### Preconstruct
We need custom rollup plugins addition, but preconstruct does not expose
its rollup configuration. It might be possible to handle this using the
babel overrides. But was a big tunnel.
We could give it a try afterwards ! ( allowing cjs interop and stuff
like that )
Stuck to vite lib app
Closed related PRs:
- https://github.com/twentyhq/twenty/pull/11294
- https://github.com/twentyhq/twenty/pull/11203
In this PR I'm optimizing a whole RecordTableCell in real conditions
with a complex RelationFieldDisplay component :
- Broke down getObjectRecordIdentifier into multiple utils
- Precompute memoized function for getting chip data per field with
useRecordChipDataGenerator()
- Refactored RelationFieldDisplay
- Use CSS modules where performance is needed instead of styled
components
- Create a CSS theme with global CSS variables to be used by CSS modules
* feat: create a separate package for twenty-ui, extract the pill component with hard-coded theme values into it, and use the component inside twenty-front to complete the setup
* feat: extract the light and the dark theme into twenty-ui and update the AppThemeProvider component inside twenty-front to consume themes from twenty-ui
* fix: create a decorator inside preview.tsx to provide a default theme to storybook development server
* fix: remove redundant type declarations and revert back the naming convention for theme declarations
* fix: introduce a default value for pill label within the story for development server
* fix: introduce the nx script into package.json for twenty-ui and resolve imports for theme type within the package
* fix: remove the pill component from the twenty-front package along with the story for it
* fix: revert the package versions to those before running the nx cli command for storybook init
* feat: update readme to include details for building the ui library and starting the storybook development server
* fix: include details about twenty-ui inside jest.config for twenty-front to complete front-jest job
* - Added preview head for font
- Added theme addon for light/dark switch
- Added ComponentDecorator
---------
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>