Commit Graph

393 Commits

Author SHA1 Message Date
github-actions[bot] 9da5383289 i18n - website translations (#22683)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22683?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-09 09:12:46 +02:00
martmull 16e5d2b9d0 feat(website): add public apps marketplace with one-click install (#22611)
<img width="1335" height="570" alt="image"
src="https://github.com/user-attachments/assets/85bbe656-7525-4800-9ae6-bc59445ccf48"
/>
<img width="1512" height="723" alt="image"
src="https://github.com/user-attachments/assets/6494c539-a3e0-4e95-9df5-ee7559c098ab"
/>

<img width="1512" height="813" alt="image"
src="https://github.com/user-attachments/assets/4cb6adea-d35b-470a-9bfd-d47b2a514e8c"
/>
<img width="1512" height="739" alt="image"
src="https://github.com/user-attachments/assets/757ea3e0-bff9-4482-9a71-3e75949fd7e7"
/>
<img width="1468" height="790" alt="image"
src="https://github.com/user-attachments/assets/37365c56-47ae-4f08-a40f-32e5600a6c0f"
/>


## What

Adds a public `/apps` marketplace on twenty-website listing the vetted,
Twenty-built apps. Each app card and detail page has a one-click Install
button that deep-links to the in-app available-application page
(`app.twenty.com/settings/applications/available/:universalIdentifier`),
where authentication and permission consent are handled before
installing.

- New apps directory (`/apps`) and per-app detail pages
(`/apps/[slug]`), with a category filter
- Routing/sitemap entry for `/apps` and an Apps link in the Resources
menu
- The catalog is fetched from the Twenty GraphQL API (public
`publicMarketplaceApps` / `publicMarketplaceAppDetail` queries),
defaulting to `api.twenty.com`, and degrades to an empty state on
failure. No app info is duplicated in the website; logos and screenshots
come from the catalog's CDN URLs.

## Dependency

This is the website half of the split. It consumes the public queries
added in the server PR #22647, which should merge first.

---------

Signed-off-by: Parship Chowdhury <parshipchowdhury@gmail.com>
Co-authored-by: martmull <martin@twenty.com>
Co-authored-by: Paul Rastoin <45004772+prastoin@users.noreply.github.com>
Co-authored-by: nitin <142569587+ehconitin@users.noreply.github.com>
Co-authored-by: Aressand <97886962+Aressand@users.noreply.github.com>
Co-authored-by: Brahm Lower <bplower@gmail.com>
Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr>
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
Co-authored-by: Parship Chowdhury <parshipchowdhury@gmail.com>
Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Pratik Mahajan <Pratik@mahajan.xyz>
Co-authored-by: neo773 <neo773@protonmail.com>
Co-authored-by: Deepak kumar maharana <100968930+deep231w@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: Marie <51697796+ijreilly@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: neo773 <62795688+neo773@users.noreply.github.com>
2026-07-08 19:12:59 +00:00
Marie 8a4bcd1445 (Billing for self hosts) Tie enterprise key to server (#22464)
# Enterprise key: bind to a server, free dev instances, self-serve
transfer, shorter license

## Summary

Enterprise keys were being reused across multiple instances (e.g. one
prod + one dev, or several environments), which broke seat accounting
and made licensing ambiguous. This PR ties each enterprise key to a
**single server**, while giving customers a legitimate, self-serve way
to run a **free development instance** and to **move their key** when
they replace a server.

## Product behavior

### 1. Enterprise key is bound to one server
- The first server to validate an enterprise key **claims** it
(claim-on-first-use). From then on, that key is bound to that one server
(until unbound - see 3.).
- Any other instance that presents the **same key from a different
server is hard-rejected**: it does not receive a license, so enterprise
features stay off there.
- Each instance has a stable server identifier. If one isn't set, the
instance generates and persists one automatically on first validation
(in keyValuePair table), so existing customers generally don't need to
do anything (unless they have disabled config variables in db then they
should add it to .env).

### 2. Free development instance
- Every enterprise subscription gets **one free, non-billable
development instance** in addition to its production instance.
- An instance registers as development by declaring its instance type as
`development` (done by default when validating the enterprise key, then
can be toggled from UI or by updating value in keyValuePair table).
- The free dev slot is only granted while there is an **active
production instance** on the same subscription (so it's a perk for
paying customers, not a way to run for free).
- Only **one** dev instance can be active at a time per subscription,
and it is **not counted as a billable seat**.

### 3. Self-serve unbind / rebind (transfer)
- Admins can **release** the binding from the enterprise settings, which
frees the key so it can be **claimed by a new server**.
- This is the intended path when **sunsetting an instance and standing
up a new one** (migration, re-hosting, disaster recovery): release on
the old/dead box, then the new box claims it on its next validation.
- To prevent abuse, releases are **rate-limited (10 per rolling 30
days)**; hitting the limit shows a clear message.

### 4. Automatic release of dead servers
- If a bound server stops checking in for **14 days**, its binding is
considered stale and is **auto-released**, so a replacement can claim
the key without any manual step. This covers the case where the old
server is already gone and can't release itself.

### 5. Shorter license validity (30 → 7 days)
- The license (validity token) now expires after **7 days** instead of
30. The daily background refresh keeps healthy instances licensed
transparently.
- This limits the value of copying a license from one instance to
another, since a copied license now stops working within a week.

### 6. License issuance is rate-limited
- Issuing a new license is capped at **twice per 24h, independently for
production and for development**. This tolerates the normal daily
refresh (including small drift between runs) while blocking bursts of
license minting for cloned instances.
- Hitting this limit never revokes an existing, still-valid license —
the current one keeps working until it expires; the manual "refresh"
button just reports that the daily limit was reached.

## What changes for existing self-hosted customers

**If you run a single production instance with one enterprise key:**
nothing to do. On the next validation your instance reports its server
identifier, claims the binding, and keeps working.

**If you reuse one key across several instances (e.g. prod + dev, or
multiple environments):** only the **first** instance to validate keeps
its license. The others will **lose enterprise features**. To migrate:
- Keep your production instance as-is (it claims the binding).
- For a secondary/testing box, mark it as a **development instance**
(set the instance type to `development`) to use the free dev slot — no
extra cost.
- If you genuinely need multiple production instances, you'll need
**separate subscriptions/keys** for each.

**If you're replacing a server (decommissioning + rebuilding):**
- **Release** the binding from enterprise settings on the old instance,
then start the new one — it will claim the key automatically.
- If the old server is already gone, just wait for the **14-day
auto-release**, or contact support.

**Legacy instances that can't persist a server identifier
automatically:** set the server identifier explicitly in your
environment configuration (the instance logs a message telling you to do
so).

**Offline instances:** because licenses now last 7 days, an instance
that can't reach our licensing endpoint for more than a week will lose
enterprise features until it can check in again.

> A migration email will be sent to affected customers separately.

## Technical implementation (brief)

- Binding state lives in the **subscription's billing metadata** (bound
server id + last-seen timestamps for prod and dev, release timestamps,
and license-issuance timestamps). No new database is introduced on the
licensing side; the billing provider's subscription metadata is the
source of truth.
<img width="976" height="413" alt="metadata_3"
src="https://github.com/user-attachments/assets/ccc64822-e177-4223-a65a-4a4602aedf0e"
/>

- On each validation, a pure **binding resolver** takes the reported
server id + instance type + current metadata and returns `allowed` (with
the metadata to persist and whether the seat is billable) or `rejected`.
It handles claim-on-first-use, staleness/auto-release, the
dev-requires-active-prod rule, and the single-dev-slot rule.
- **Rate limits** (release + license issuance) use a shared
sliding-window helper stored as pruned timestamp lists in the same
metadata, so the metadata self-cleans and never grows unbounded. License
issuance uses **separate windows per instance type**.
- The self-hosted instance **generates and persists a server
identifier** if none is configured, and sends it (plus instance type) as
instance metadata on validation.
- A rejected binding returns a specific error code; the instance
**revokes its stored license** on that code. A license-issuance
rate-limit instead **throws a typed exception that surfaces to the
manual refresh** while leaving the existing license untouched; the daily
refresh job swallows it.
- License lifetime is a configurable duration (defaulted from 30 to **7
days**), clamped to the subscription's cancellation date when sooner.
2026-07-06 18:07:03 +02:00
Félix Malfait ed2b2f8911 feat: publish MCP & API discovery documents (well-known standards) (#22589)
## What & why

Makes Twenty's **MCP server** and **REST/GraphQL APIs**
auto-discoverable by catalogs (e.g. integrations.sh) and AI agents,
using vendor-neutral open standards rather than a proprietary manifest.

The tricky part is that Twenty is **multi-tenant and the REST OpenAPI is
generated per workspace** (it reflects each workspace's custom objects,
and with no token even the base schema is empty). So there is no single
public URL that describes the full API contract. This PR solves that
with two complementary layers.

## 1. Static standards on `twenty.com` (`twenty-website`)

The brand-level catalog entry, using `{your-workspace-url}` placeholders
since `twenty.com` is not a workspace host:

- `public/.well-known/mcp/server-card.json` — MCP Server Card (SEP-2127)
- `src/app/.well-known/api-catalog/route.ts` — RFC 9727 linkset (route
handler so the `application/linkset+json` content type survives the
global `nosniff` header)
- `public/llms.txt` — LLM-readable overview

## 2. Dynamic per-host serving from `twenty-server`

A new `well-known` core module serves the same documents built from the
**request host**, so every workspace subdomain, custom domain, and
self-hosted instance advertises its own **real, connectable** endpoints
(`https://{that-host}/mcp`, its live `/rest/open-api/core`, etc.) — no
placeholder:

- `GET /.well-known/mcp/server-card.json`
- `GET /.well-known/api-catalog`

Both are public + CORS + cached. The api-catalog's `service-desc` points
at each host's **live** per-workspace OpenAPI — the honest answer to
"it's generated per workspace" (real endpoint, real custom objects,
still token-gated). The `version` comes from `APP_VERSION`.

The two layers are complementary: the static one serves
catalog/marketing discovery at the brand domain; the dynamic one serves
connecting clients the real endpoints — which is where the MCP spec
expects the server card to live (same origin as `/mcp`).

## Refactor

Extracted the request→base-URL logic that `OAuthDiscoveryController` had
as a private method into a shared
`src/utils/get-request-base-url.util.ts`, now used by both it and the
new controller.

## Notes

- Docs URLs are sourced from the shared `DOCUMENTATION_BASE_URL`
(server) and the `SITE_URLS` registry (website) rather than hardcoded.
- MCP endpoint, transport (`streamable-http`), and protocol version
(`2025-06-18`) are read from the existing MCP constants.
- OAuth resource metadata (`/.well-known/oauth-protected-resource`)
already existed and is unchanged.

## Testing

- `twenty-server` unit tests for the builders and controller (host
derivation, version fallback, linkset shape) — passing.
- `nx typecheck twenty-server` — passing.
- `oxlint` + `oxfmt` clean on both packages; website `check-conventions`
OK.

https://claude.ai/code/session_01F6g7kefcfpjXSZjH6cwqhi

---
_Generated by [Claude
Code](https://claude.ai/code/session_01F6g7kefcfpjXSZjH6cwqhi)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22589?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-06 17:53:05 +02:00
Abdullah. 049a98a36a fix(website): restore footer CTA buttons on the light card inside the dark stage (#22447)
## What

The footer's **Talk to us** / **Get started** CTAs regressed: filled
rendered white-on-white (only the label showed) and outlined vanished
entirely. Resolves
[this](https://github.com/twentyhq/core-team-issues/issues/2634) issue.

## Why

#22241 marked the footer root as a dark menu-surface
(`data-scheme="dark"`) so the sticky menu adapts over the dark footer
stage. But the footer's content sits on a **white Card inside that
root**, and the button's dark override is a *descendant* selector
(`[data-scheme='dark'] &`) — so it leaked into the card. Filled → white
fill + black label (invisible fill on white); outlined → white stroke +
white label (fully invisible). The card's text was fine because it uses
the light default semantic vars; only the buttons key off the raw
attribute.

## Fix

- Mark the white `Card` as `data-scheme="light"` — it *is* a light
surface. The root keeps `data-menu-surface`/`data-scheme="dark"`, so
**menu adaptation is unchanged**.
- Add a button override scoped to `[data-scheme='dark']
[data-scheme='light'] &` — a light surface *nested inside* a dark one.
It's higher specificity than the dark rule and matches **only** this
footer case, so a dark card nested in a *light* section (e.g.
`HelpedCard`) is never affected. No other button changes.

Result: filled = black fill + white label, outlined = black stroke +
black label — matching the design.

## Testing

- `nx typecheck twenty-website` ✓ · `nx lint twenty-website` ✓
(check-conventions + oxlint + oxfmt)
- Reviewable on the PR preview (footer CTAs, plus menu/FAQ/hero/signoff
buttons unaffected).
2026-07-02 16:31:59 +05:00
Abdullah. 63d092a31b fix(website): center the User Guide nav preview image (#22445)
## What

The **User Guide** item in the Resources dropdown rendered its preview
image off-center (anchored top-left with a gap below the halftone).

## Fix

Set `imagePosition: 'center'` on the User Guide preview so the halftone
book is centered and fills to the bottom of the frame, matching the
others.

## Testing

- `nx lint twenty-website` ✓ (check-conventions + oxlint + oxfmt)
- `nx typecheck twenty-website` ✓
2026-07-02 14:47:48 +05:00
github-actions[bot] 8b6bd34a17 i18n - website translations (#22436)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22436?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-02 08:53:53 +00:00
Abdullah. 3474d75b56 feat(website): add Product to menu + footer nav, move Why into Resources dropdown (#22429)
## What

Restores the **Product** link to the site nav (removed in #21794),
reversing that commit's nav-structure change:

- **Menu** — Product is the first top-level item (in place of Why);
**Why** moves back into the **Resources** dropdown with its `why.webp`
preview (`IconBulb`, "Why teams choose Twenty").
- **Footer** — Product added to the **Sitemap** group (after Home).

The `/product` and `/why-twenty` routes already exist and are in the
sitemap; only the nav data changed. The rest of #21794 (dropdown frame
height, preview assets, current-page highlight) is untouched.

## Notes

- New `msg` strings (`Product`, and Why's restored strings) are left to
CI / the i18n bot to extract + translate — no catalog changes here.

## Testing

- `nx lint twenty-website` ✓ (check-conventions + oxlint + oxfmt)
- `nx typecheck twenty-website` ✓
2026-07-02 10:46:36 +02:00
Abdullah. 1b06532cb1 fix(website): tighten product-feature spotlight height and align bento spacing (#22428)
## What

Design polish on the product page's `ProductFeature` bento, from
designer review:

- **Spotlight height** — the first (spotlight) card's visual was
`min-height: 420px` on desktop vs the grid cards' `340px` (80px taller),
so it towered over the rest. Now **340px**, matching the grid cards.
- **Spacing consistency** — the spotlight visual used a uniform `margin`
(bottom margin included), unlike the other cards' `CardVisualFrame` (`…
0` bottom). Removed it so the visual→content gap is consistent across
every card.
- **Gap** — bumped the visual→content gap to `spacing(6)` (**24px**) on
desktop for all cards.

## Testing

- `nx lint twenty-website` ✓ (oxlint + oxfmt + check-conventions)
- `nx typecheck twenty-website` ✓
2026-07-02 10:45:45 +02:00
github-actions[bot] 0569c490fd i18n - website translations (#22264)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22264?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
2026-06-28 16:06:10 +00:00
github-actions[bot] f8a9a2b249 i18n - website translations (#22263)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22263?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
2026-06-28 13:31:10 +00:00
Abdullah. 012af11d77 feat(website): ship all documentation locales (multi-locale site) (#22257)
## What

The marketing site now serves every language the **documentation** ships
— 14 locales (`en, fr, ar, cs, de, es, it, ja, ko, pt, ro, ru, tr, zh`),
up from 3 (`en, es, fr`).

## How

- **Single source of truth.** `WEBSITE_LOCALE_LIST` derives directly
from `DOCUMENTATION_SUPPORTED_LANGUAGES` (`twenty-shared/constants`).
Add a documentation language → it flows to the website automatically.
- **Off `APP_LOCALES` entirely.** The website locale type is now
`DocumentationSupportedLanguage` (short codes), so a locale **is** its
URL segment — no short↔full mapping, and no `pt-BR`/`zh-CN` ambiguity to
resolve.
- **Removed the indirection this exposed** (it only existed because
`AppLocale` was a superset of the deployed set):
- `locale-to-url-segment` / `locale-by-url-segment` (locale == segment)
- `get-locale-messages` pass-through → callers read `MESSAGES_BY_LOCALE`
directly
- the `messages-by-locale` runtime guard → a total
`Record<DocumentationSupportedLanguage, Messages>` (a missing catalog is
now a **compile** error, not a runtime throw)
- `isWebsiteLocale` → a `string → DocumentationSupportedLanguage` type
guard
  - the vestigial language-code `split('-')` in `locale-display-name`

## Catalogs

- Renamed `es-ES → es`, `fr-FR → fr`; added 11 new locales (untranslated
for now → **English fallback**).
- `crowdin-website.yml` switched to `%two_letters_code%`.
- Regenerating catalogs also synced `en.po` with current source
(`Boolean` / `Date & Time` / removed `Fields widget` from the
already-merged #22249).
- `ci-website` is unchanged — no `lingui:compile` step added; catalogs
stay committed.

## Testing

- `typecheck` · `lint` (check-conventions + oxlint + oxfmt) · 347/347
tests — all green. PR CI runs exactly lint + typecheck + test.

## Follow-up (out of repo)

Enable the 11 languages on **Crowdin project 4** so `website-i18n-pull`
backfills real translations. Until then, the new locales render with
English fallback (correct behavior).
2026-06-28 14:39:59 +05:00
Abdullah. 77c84815ef feat(website): rework the product-stepper Layout visual (#22249)
## What

Reworks the **"Layout" step** visual of the product-page stepper (the
floating record-layout-editor scene) to match the Figma references and
read as a premium, layered composition. Data is unchanged — this is
purely the look.

## Figma alignment

- **Fields editor** — the inline-edit field row now keeps the field icon
+ a bordered input + blue **Done** + the eye / ⋮ (instead of a floating
box); **"New fields"** renders as a uniform field row; section headers
get an overflow menu; field types read `Boolean` / `Date & Time`.
Dropped the redundant "Fields widget" header label.
- **"General" widget** — header chevron, the real `anonymousFelix`
avatar on Account Owner (reusing the shared asset the other
product-feature visuals use), a **Revenue** row, and a money-bag icon
for the currency field.

## Premium composition

- The nav, record, and Fields editor read as one elevated **z-stack**
(back → front): the nav is pushed right so its edge tucks under the
record (lowest z-index), the record sits above it, and the editor stays
in front — each casting a progressively stronger shadow so the depth
ordering is unmistakable.
- The **"New record / Enrich / Edit actions"** bar is centered over the
nav–record seam (`translateX(-50%)`) to tie the layered stack together
at the top.

## Testing

- typecheck · oxfmt · oxlint · check-conventions — green.
- Verified visually with headless Playwright on `/product` (Layout
step).


<img width="697" height="734" alt="image"
src="https://github.com/user-attachments/assets/58ae7ffb-13d1-4310-bbdc-864589f8bc43"
/>
2026-06-27 17:41:34 +02:00
github-actions[bot] 4840233a1c i18n - website translations (#22244)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22244?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
2026-06-27 14:06:24 +02:00
Abdullah. 03f3789d13 feat(website): menu adapts to the section beneath it (#22241)
## What

The sticky menu now adopts the color scheme of whatever section sits
beneath it as you scroll, across every page. Background, logo, and
buttons ease between schemes for a smooth handoff.

## How

- **Declarative contract** — sections opt in with a `data-menu-surface`
attribute (`SectionShell`, the footer, and the product hero's mobile
sections); `useActiveSurfaceScheme` tracks which surface sits under the
menu's bottom edge (`MENU_HEIGHT_PX`) and reports its `data-scheme`.
- **One provider** — `MenuStyleProvider` lives in the `(site)` layout so
every page adapts; the Menu resolves its scheme as `override ??
activeScheme ?? prop`. SSR seeds the prop to each page's first section,
so there's no mount flash.
- **Hero handoff** — the product hero keeps the menu via a per-frame
override only while its track still covers the nav band
(`controlsMenu`); it releases to the observer *before* the track clears
the bar, so the menu stays opaque on exit and never flashes the halftone
backdrop rising behind it. The menu's `backdrop-filter` was removed (a
no-op over the opaque menu, and a GPU compositing artifact).

## Also in this PR

- **Menu folder reorg** to the `product-feature` convention:
`components/`, `effect-components/`, `data/`, and `types/` (one domain
type per file).
- **`MENU_HEIGHT_PX` token** replacing the literal `64` that was
duplicated across four files (menu row, the hero's scroll model and
component, the observer).
- **`findActiveSurfaceScheme`** extracted as a pure, unit-tested
function (inclusive top / exclusive bottom, first-match, no-match,
null-scheme).
- **Mobile AI-section fix** — the mobile hero sections now declare the
surface contract, so the menu adapts dark over the AI block on mobile
(it previously stayed light). Their color is driven from `data-scheme`
(single source) rather than a parallel prop.

## Testing

- Jest — 16 tests (scroll model + surface-selection util).
- Headless Playwright (mobile 390px) — menu `light` over the intro →
`dark` over the AI section; section colors unchanged (`#fff` /
`rgb(20,20,20)`).
- Desktop unaffected — the mobile sections are `display:none` (zero
rect), so the observer skips them and the hero override path is
untouched.
- Gates green — typecheck, oxlint, oxfmt, check-conventions.
2026-06-27 12:58:50 +05:00
github-actions[bot] bf7a766935 i18n - website translations (#22234)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22234?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-06-26 22:29:54 +02:00
github-actions[bot] ad1ac4f3c5 i18n - website translations (#22225)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22225?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-06-26 22:17:08 +02:00
Abdullah. d58ec64e10 feat(website): swap product Demo for a register Signoff; make signoff headings width-driven (#22232)
## Summary

Reworks the product page's closing section, and in doing so makes every
signoff closer width-driven.

### 1. Replace the Demo section with a register Signoff (`6a5f0c34bf`)

The product page ended on a static `AppPreview` mockup ("Try it live").
Swap it for the shared `Signoff` closer — a focused register CTA, the
same component the customers / why-twenty / partners pages use — so the
page ends by driving sign-ups instead of re-showing the product.

- New `ProductSignoff`: heading "Start moving faster today.", a
supporting line, and the **Get started + Talk to us** pair (matching
`CustomersCatalogSignoff`). Uses `SITE_URLS.appWelcome` per the
site-urls rule rather than inlining the URL.
- Removes the now-unused `product-demo` section and its 192KB background
asset.

### 2. Make Signoff headings width-driven (`0365c0d2ea`)

The signoff headings forced their two-line break with a literal `\n` in
the translated string — against the site's typography principle (fluid
type + `text-wrap: balance`, no `<br>`).

- `Signoff` now carries one default **`615px`** heading measure; all
five closers drop `\n`.
- The width was **measured, not guessed**: each heading was rendered
headlessly in the real production fonts (Aleo 300 body, Host Grotesk 300
accent — each heading mixes both) at the desktop size with `text-wrap:
balance`, then I found the common window where every heading breaks
identically: whyTwenty `[565–820]`, customers `[425–800]`, partner
`[380–730]`, product `[380–660]` — `615` sits in all of them. Each was
then verified to reproduce its exact current break at 615px.
- **Affects 4 other pages** (why-twenty, customers, customers/[slug],
partners) — all verified to render identically, and headings now reflow
on narrow viewports instead of being pinned by `\n`.
- Drops the one-off `headingMaxWidth` prop (now unused).

## Test plan

- oxfmt, oxlint, check-conventions, typecheck — all green.
- Headless measurement confirms all five signoff headings reproduce
their current desktop break at 615px (no visual regression).
- Visually confirmed the product closer and the four sibling signoffs.
2026-06-26 18:45:39 +02:00
Abdullah. fea2b8736f feat(website): rebuild dashboard visual faithful to twenty-front (#22218)
Rebuilds the product-feature **DashboardVisual** to mirror
twenty-front's dashboard widgets, with colors traced to twenty-front's
actual source rather than eyeballed.

## Widgets
- **Bar — "Deals by month"**: single-series `blue8` (twenty-front's
`GRAPH_DEFAULT_COLOR`), dashed `4 4` gridlines, value labels, nice
rounded Y-ticks. Replaces the old stacked bar (stacked bars aren't used
in twenty-front).
- **Donut — "Deals by stage"**: the real opportunity pipeline
(New/Screening/Meeting/Proposal/Customer), each segment colored by that
stage option's own color from the metadata
(`red/purple/sky/turquoise/yellow`), with a center total and a paginated
horizontal legend.
- **KPIs**: big-number cards (Revenue YTD / Avg deal size / Win rate).

## Responsive — `mediaUp('md')`
The dashboard is the full-width spotlight tile, whose frame is short
below md and grows to 420px at md+. So the layout keys off md: below it
collapses to **2-up KPIs + a full-width bar** (donut and the 3rd KPI
hidden, smaller breadcrumb), and the spotlight frame's mobile min-height
is bumped so the bar has room; at md+ the full 3-KPI + side-by-side
layout returns. The donut caps at its size and shrinks with its
container.

## Notes
- Follows the `product-feature` conventions (per-visual folder,
one-export-per-file, split types).
- `Tiles.tsx`: one-line spotlight mobile min-height bump (only the
dashboard uses the spotlight tile).
- typecheck + lint + build all green.

<img width="1148" height="638" alt="image"
src="https://github.com/user-attachments/assets/1eb8f120-88c2-45d1-adf9-ec00abe11006"
/>
2026-06-26 20:57:21 +05:00
Félix Malfait 7caf2a14c5 feat(website): show partner location instead of served regions in marketplace (#22224)
## Context

On the partners marketplace, the eyebrow shown below each partner's name
(on both the list card and the profile header) displayed the served
regions (e.g. `APAC`, `EUROPE`). The partner's actual location was
buried in the "Where & how" facts list. The location is more useful at a
glance, so this swaps the two.

## Changes

**List card (`PartnerCard`)**
- The eyebrow below the name now shows the partner's city and country
instead of the first served region.

**Profile page**
- `PartnerProfileHeader`: the eyebrow near the title now shows the real
location (city, country).
- `PartnerFactsList`: the served regions move into the "Where & how"
section as a `Regions` row, taking the place of the now-redundant "Based
in" row.

The `Regions` chip rows on the card are unchanged.

https://claude.ai/code/session_01FQRRfvPpmNsjAAnZeDc5sy

---
_Generated by [Claude
Code](https://claude.ai/code/session_01FQRRfvPpmNsjAAnZeDc5sy)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22224?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-06-26 16:55:49 +02:00
Abdullah. 1b4bddba6f fix(website): polish product hero AI transition (panel reveal timing + title wrapping) (#22223)
## Summary

Two desktop/tablet polish refinements to the product page hero's hero→AI
transition (`product-hero`).

### 1. Delay the Ask-AI panel reveal to after the wipe (`b638d600`)

The Ask-AI side panel used to reveal *during* the dark "wipe up" (keyed
to the morph `0.45 → 0.70`), so it competed with the rising black edge
and piled onto the heaviest motion phase — rough on weaker laptops.

It's now keyed to raw scroll progress over `[0.60, 0.70]` — the
previously-idle hold after the wipe settles at `0.55` — so it slides in
on its own beat once the eye has followed the black up. `morphProgress`
is pinned at `1` across the whole post-wipe stretch, so leaving the
morph clock was the only way to time the panel *after* the wipe. The
conversation playback moves with it, so the chat doesn't stream while
the panel is still `width: 0`.

### 2. Keep the AI heading to three lines below desktop (`8ae69a4b`)

The AI heading is the page's longest line. Below `md`, only the mobile
copy renders, where the measure was frozen at `360px` while the heading
font scales fluidly toward `md` — so on tablet widths the title crammed
onto four lines, recovering only at `921px` when the `672px` measure
kicks in.

Adds an `sm`-breakpoint measure step (`560px`) scoped to the AI heading;
the intro heading and the desktop measuring path keep their existing
`360/672` steps.

## Test plan

- `product-hero-scroll-model` jest suite updated and green (11/11),
including a new post-morph panel-timing test.
- Lint (`check-conventions` + `oxlint` + `oxfmt`) and typecheck green.
- Visually confirmed: the panel reveals after the wipe settles, and the
AI title holds three lines across tablet widths.
2026-06-26 19:43:45 +05:00
github-actions[bot] e60d790990 i18n - website translations (#22203)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22203?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-06-26 10:25:37 +02:00
Abdullah. 85f64abb28 Frameless ProductStepper with twenty-front-faithful scenes (#22197)
## What

Reworks the website's **ProductStepper** (the scroll-driven *Data model
/ Automation / Layout* section) to be frameless and to render all three
scenes faithfully to twenty-front — real icons, labels, colors,
structure, and connectors.

## Changes

**Frameless + scaling**
- Removed the shared white card/header; the three scenes now sit
directly on the dark dot-grid stage.
- Unified every scene on one `StageFit` primitive (fixed design box →
scaled to fit), pixel-identical at full width and uniform on smaller
screens.

**Data model scene**
- Real object schema (3 Standard + 2 Custom objects, real relation
fields); replaced an invented "Investors" object with the real
**Employment History** custom object (`IconBriefcase`, Company + Person
relations) from the server seed.
- Clean spanning-tree connections; removed the Standard/Custom badge;
fixed card sizing + edge centering.

**Workflow scene**
- Real action labels + a logical flow: *Record is Created → Filter →
Search Records → AI Agent → (Update Record · Send Email · Create
Record)*. Dropped the iterator (a loop construct shown without a loop
body).
- Per-action icon colors matching twenty-front (trigger blue, flow
green, record gray, send-email red, AI agent pink) on a gray tile.
- Rebuilt the node to twenty-front's real anatomy and the connectors
(source circle → `getBezierPath` → arrow marker) verbatim.

**Layout scene**
- Real workspace sidebar: real Tabler object icons, exact labels, the
true default sidebar (6 objects + Workflows folder),
`getIconTileColorShades` tile colors; dropped invented entries.
- Record overview + Fields editor with correct field-type labels (Links,
True/False, Date and Time); legibility + spacing tuning.

**Icons** — replaced every hand-drawn approximation with real
`@tabler/icons-react` / twenty-front object icons.

**Misc** — smoother step-to-step transitions (translate + easing
tokens).

## Testing
Marketing visual; `lint` / `typecheck` / `build` green, and each scene
reviewed visually against twenty-front.




https://github.com/user-attachments/assets/997e1b95-55c0-401a-93a4-c70545577057
2026-06-26 10:17:44 +02:00
github-actions[bot] 1284099940 i18n - website translations (#22162)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22162?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-06-25 13:19:57 +02:00
github-actions[bot] a87bc4bed6 i18n - website translations (#22160)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22160?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-06-25 12:31:53 +02:00
Abdullah. 5a657129f0 Split website stepper into home-stepper and product-stepper sections (#22149)
Reorganizes the flat `sections/stepper/` (30 files — two distinct
steppers plus shared code) into two product-feature-style sections, and
moves the shared code to the shared layers.

- **`sections/home-stepper/`** — the home-page stepper. Renamed
`Stepper` → `HomeStepper` (and the home components → `HomeStepperLottie`
/ `HomeStepperSteps` / `HomeStepperVisualFrame`) for symmetry with
`ProductStepper`. Shell at the root + `components/`/`data/`/`utils/` +
barrel.
- **`sections/product-stepper/`** — the product-page stepper, same
structure.

The 3 files both steppers shared can't live in a shared *section* —
`check-conventions` forbids a section importing another section. So they
moved to the shared layers:
- `StepperProgressRail`, `StepperSwipeDeck` → `ui/`
- `useBreakpointStepSync` → `platform/motion`

Both consumer pages repointed (`@/sections/home-stepper`,
`@/sections/product-stepper`); the row-gap allowlist in
`check-conventions.mjs` updated to the new paths; explanatory comments
stripped across the moved files (CSS-in-template comments and `'use
client'` kept).

Pure reorganization — no behavior change. typecheck + lint + build all
green.
2026-06-25 12:24:22 +02:00
github-actions[bot] 904d3d6bff i18n - website translations (#22158)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22158?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-06-25 12:20:32 +02:00
Abdullah. 551a792291 Rework website TasksVisual to match twenty-front tasks tab (#22143)
- Rework `TasksVisual` to match twenty-front's tasks tab: per-group
bordered cards with row dividers, `TODO`/`DONE` headers, the `+ Add
task` button, and twenty-front's checkbox, row, and target-chip styling.
- Enable the checkbox to toggle a task between `TODO` and `DONE`.

Marketing data kept intuitive (single assignee, full dates, action-led
titles).

<img width="859" height="608" alt="image"
src="https://github.com/user-attachments/assets/085c225f-b121-44e6-aaec-553c02fd2367"
/>
2026-06-25 12:20:18 +02:00
Abdullah. d6556221d9 Tidy website ImportVisual markup and CSS (#22148)
Markup/CSS tidy-up of `ImportVisual` — no visual change.

- Collapse the redundant `Grid` wrapper into `Root` (now
`flex-direction: column`) and drop the no-op `justify-content: center`
(the child was already full-width).
- Hoist the duplicated `font-size: previewFontSize(md)` to the mapping
`Row` (the `sm` example keeps its override).

<img width="574" height="604" alt="image"
src="https://github.com/user-attachments/assets/dcc72567-5c13-4d54-884a-f3120cc5a345"
/>
2026-06-25 15:12:08 +05:00
Abdullah. 491b9e954a Rework website EmailsVisual to match twenty-front emails tab (#22146)
- Rework `EmailsVisual` to match twenty-front's emails tab: bordered
thread card with dividers, `Inbox` header sizing, and twenty-front's
row/sender/date colors and hard-clipped sender names (kept `+ Compose`).
- Replace the "not shared" row with four fully-populated shared threads
(subjects/bodies sourced from twenty-server's dev seed), two
participants each, full `MMM D, YYYY` dates.

<img width="575" height="607" alt="image"
src="https://github.com/user-attachments/assets/68d1fd6d-e030-4f3c-b775-687f7e1ab173"
/>
2026-06-25 15:02:44 +05:00
Abdullah. 20f8321dca Rework website FilesVisual to match twenty-front files tab (#22147)
- Rework `FilesVisual` to match twenty-front's files tab: bordered file
card with dividers, `All` header sizing, and twenty-front's row
background and date color (kept the bordered `+ Add file`).
- Four files, one per category (pdf/xlsx/pptx/png), with full `MMM D,
YYYY` dates. File-icon colors already matched `useFileIconColors`.
2026-06-25 10:43:51 +02:00
Rashad Karanouh 500f441807 fix(website): force-dynamic partner profiles to stop OpenNext 404 cache (#22120)
## Summary

Partner profile pages (`/partners/profile/[slug]`) returned **404 on
every slug** on OpenNext/Cloudflare while `/partners/list` showed live
partners from the same API.

PR #21963 fixed the list with `export const dynamic = 'force-dynamic'`
but only added `dynamicParams = true` on profiles. That is not
sufficient on OpenNext — the Worker kept serving **cached prerendered
404s** even when `TWENTY_PARTNERS_API_KEY` was present at runtime.

This PR mirrors the list page: **`force-dynamic` on the profile route**,
plus a small lint guard so both marketplace routes stay dynamic.

## Root cause

Partner data is fetched server-side from
`https://partners.twenty.com/s/partners` using
`TWENTY_PARTNERS_API_KEY`. That key is a **Wrangler runtime secret**
(not in `dev.env` / `prod.env`, not available during CI build — by repo
convention).

| Route | Before | Behavior |
|-------|--------|----------|
| `/partners/list` | `force-dynamic` (#21963) | Fetches at request time
on Worker → works |
| `/partners/profile/[slug]` | static + `dynamicParams = true` | Build
prewarm often empty; OpenNext served cached 404 |

## Fix

- Add `export const dynamic = 'force-dynamic'` to
`profile/[slug]/page.tsx` (keep `dynamicParams = true`).
- Add `scripts/check-partners-marketplace-routes.mjs` — fails lint if
list or profile drop `force-dynamic`.
- Wire guard into `project.json` `lint` target (runs before existing
`check-conventions.mjs`).

**No infra changes.** We intentionally did not add a GitHub Actions
secret for the API key — that would contradict the documented pattern
(`wrangler secret put` only).

## Verification

- [x] `node scripts/check-partners-marketplace-routes.mjs` → OK
- [x] `npx jest src/partners-marketplace` → 36/36 pass
- [x] Deployed to **dev** (`deploy-website`, env `dev`, ref
`rk-partner-profile-404`)
- [x] `curl -sI
https://twenty-main.com/partners/profile/atlasprods-technologies-llp` →
**HTTP 200**
- [x] Browser: list → profile link loads

## Test plan

- [ ] CI lint + tests green
- [ ] After merge: deploy prod when ready (`environment: prod`, confirm
`website`)
- [ ] Spot-check `https://twenty.com/partners/profile/<slug>` → 200

## Out of scope

- Build-time `generateStaticParams` prewarm (would need a separate infra
discussion; not required once profiles are `force-dynamic`)
- Per-slug `/s/partner-by-slug` endpoint (optional perf follow-up)

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22120?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-06-24 18:48:03 +02:00
github-actions[bot] 16c9782c96 i18n - website translations (#22001)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22001?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
2026-06-23 13:17:15 +02:00
Abdullah. 6a4dc37c6d Organize twenty-website product-feature into folder structure (#21999)
Applies the PipelineVisual folder structure to every other file in
`product-feature`.

- Each feature visual (Import, Tasks, Files, Emails, Dashboard,
Contacts) becomes an `XVisual/` folder: shell + `index.ts` barrel, with
`components/`, `data/`, `types/` (one export per file), and `utils/` as
applicable. `BarChart`/`DonutChart` move into `DashboardVisual`
(exclusive to it); `RecordTabHeader` stays shared.
- The section's non-visual files get the same treatment: `components/`
(Tiles, TileVisual, TileContent, ScrollEntrance, RecordTabHeader),
`data/`, `types/`, `utils/`. `ProductFeature.tsx` stays the section
shell.
- Drop dead code: unused `WindowChrome` and the now-orphaned
`product-feature-scene` token.

No behavior change — `index.ts` barrels keep all import paths stable.
Typecheck, check-conventions, oxlint, and unit tests pass locally.
2026-06-23 13:01:00 +02:00
Abdullah. c98620e14c Rework website PipelineVisual to match twenty-front kanban (#21989)
Reworked the product page's `PipelineVisual` to match twenty-front's
kanban — board header, card layout/fields, hover states, and font
weights.

Also loaded and pinned Inter (twenty-front's product font) for the
app-preview mockups, which previously fell back to the system font.

<img width="879" height="625" alt="image"
src="https://github.com/user-attachments/assets/396f0a90-114b-47f9-92a6-2e74d732a91e"
/>
2026-06-23 12:34:56 +05:00
Rashad Karanouh 5ce91e711c fix(website): render partner marketplace dynamically to stop profile 404s (#21963)
Fixes #21962

## Root cause

Partner data is materialized **at build time** from the live partners
API, and a build-time fetch failure is silently swallowed
(`fetch-live-marketplace-partners.ts` → `catch → return []`). One root
cause surfaces in two places:

- **All profile links 404 (the reported issue).**
`profile/[slug]/page.tsx` enumerates slugs in `generateStaticParams()` —
a build-time fetch — under the `[locale]` layout's inherited
`dynamicParams = false`. If that build-time fetch fails or returns
empty, **zero slugs are generated**, and because `generateStaticParams`
never re-runs at runtime and `dynamicParams=false` disables on-demand
generation, **every** `/partners/profile/[slug]` 404s until the next
deploy — even though the marketplace returns 20 partners client-side.
- **`/partners/list` intermittently renders empty.** The list page is
statically prerendered; the same build-time failure bakes an empty
marketplace and freezes it in the OpenNext/R2 cache.

This only reproduces on deployed builds: local dev renders on demand,
the env vars are present, and the partners API is reachable.

## Fix

Two route-segment config changes, no data-layer rewrite:

| File | Change | Effect |
|---|---|---|
| `(site)/partners/profile/[slug]/page.tsx` | `export const
dynamicParams = true` | Any slug renders on-demand at runtime where the
API is reachable. `generateStaticParams` becomes best-effort prewarm
instead of a 404 trap. Genuinely missing slugs still `notFound()`. |
| `(site)/partners/list/page.tsx` | `export const dynamic =
'force-dynamic'` | List is fetched at runtime, never baked empty at
build. The explicit `next: { revalidate: 300 }` on `/s/partners`
survives `force-dynamic` (`patch-fetch.js` only forces no-store when
there is *no* explicit fetch config), so responses stay cached and are
served stale on transient blips. |

## Verification

- `oxlint` + `oxfmt --check`: clean on both files.
- `jest src/partners-marketplace`: 36/36 pass.
- End-to-end behavior (static-vs-dynamic rendering) is a build/deploy
concern with no meaningful unit test — needs a deploy to confirm against
the live marketplace.

## Note / follow-up (out of scope)

Edge case left deliberately: if a real partner's *first-ever* request
lands during an API outage, its on-demand `notFound()` could cache for
~300s. Closing that means making the slug lookup distinguish "fetch
failed" from "not found" — a larger change than this fix.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21963?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-06-22 14:54:21 +00:00
Abdullah. 602acf7a16 fix(twenty-website): fill feature-card visual frame on wide viewports (#21876)
## Problem
On the home feature cards, the visual frame is capped at `max-width:
411px` (the scene's design width) and centered. Below ~411px-wide cards
this is invisible, but once a card grows past 411px (wider viewports)
the dark scene stops filling and the **card's light background shows on
both sides** of the visual.

At 1200px everything looks correct because the cards are narrower than
411px and the cap is never engaged; the issue only appears as the
viewport widens.

## Fix
`FeatureCard.tsx`, one file:
- **Remove the `max-width: 411px` cap** (and the now-dead `margin: 0
auto`) from `CardImageFrame` so the frame fills the card width at every
breakpoint. `useScaleToFit` then scales the 411×508 scene up to match —
it's a CSS transform on DOM, so it stays crisp; no raster upscaling.
- **Even out the card gutter** — `CardImage` padding `8px → 16px` (top +
sides) so the visual's inset matches the content's 16px inset instead of
stepping in. Bottom stays `0` (the content block's 16px provides the
bottom gutter).

The visual scenes themselves are untouched — this is purely the
frame/container.

## Before

<img width="1477" height="681" alt="image"
src="https://github.com/user-attachments/assets/731f1ef7-e761-468e-b7aa-a5a06f8ac790"
/>

## After

<img width="1473" height="705" alt="image"
src="https://github.com/user-attachments/assets/99d036a6-d3ad-4682-99c8-f283b5b95171"
/>
2026-06-21 15:05:51 +02:00
Abdullah. a99d380175 feat(website): spotlight visual on top and uniform tile background (#21906)
Two tweaks to the product-feature tiles section:

- **Spotlight visual moved to the top** — the spotlight tile now renders
its visual above its content, matching the layout of the regular grid
cells.
- **Uniform tile background** — every tile now uses the same neutral
gray background; removed the per-tile `TILE_MUTED` alternating toggle.
2026-06-21 15:05:35 +02:00
github-actions[bot] a98e3a6df9 i18n - website translations (#21866)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21866?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
2026-06-19 17:26:11 +02:00
github-actions[bot] 9695d77252 i18n - website translations (#21864)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21864?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
2026-06-19 15:22:32 +00:00
github-actions[bot] f61ce4ce68 i18n - website translations (#21827)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21827?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-06-19 10:40:27 +02:00
Abdullah. 569d887d1e [Website] Cut over to the rebuilt site (#21825)
Renaming the package so any further PRs directed to the website are
targeted to the reworked code instead of diverging. Once merged, I will
start preparing this for deployment to dev to test before releasing to
prod. Any improvements will also be applied to this package.

I avoided making significant changes to API routes so nothing breaks,
but will test it thoroughly today to confirm. That said, everything is
ported - double checked.

Big diff PR, impossible to review, but last one! No more rebuilds.
2026-06-19 10:22:46 +02:00
Marie 9de1b6330c feat(website - self hosts billing): add internal endpoint to reissue enterprise keys (#21660)
## Summary

Adds an internal support endpoint to regenerate a customer's enterprise
key
when they've lost the one issued at activation. The key payload is
deterministic
from the Stripe subscription, so this re-emits an equivalent valid key
without
any new state.

`GET /api/enterprise/reissue/<subscriptionId>/<secret>`

- Guarded by a shared secret (`ENTERPRISE_ADMIN_API_SECRET`), compared
in
  constant time and fail-closed when unset.
- Looks up the subscription in Stripe (for the licensee) and signs the
key with
  `signEnterpriseKey()`, reading `ENTERPRISE_JWT_PRIVATE_KEY` from the
  environment — the private key is never accepted from the request.
- No subscription-status gate: the key alone grants nothing. Feature
access
still requires a validity token, which `/api/enterprise/validate` only
issues
  after re-checking the subscription is active.

## Notes / follow-ups

- The admin secret travels in the URL path, so it can land in
server/proxy/CDN
access logs — rotate `ENTERPRISE_ADMIN_API_SECRET` if logs are ever
exposed.
- No audit logging yet; worth adding (who reissued which subscription,
when).

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21660?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 20:42:07 +02:00
github-actions[bot] 0332d8c706 i18n - website translations (#21795)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21795?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-06-18 16:49:03 +02:00
Rashad Karanouh 7afc991bd6 Partner application form: required fields, skills rework, fail-fast validation (#21710)
## Summary (twenty-website — partner application form)

- **Required fields**: website URL, city, hourly rate, minimum project
are now required (client step-gate + server zod) with `*` markers. The
final step validates before POSTing, so empty required fields fail fast
client-side instead of round-tripping.
- **Technical skills reworked to *complement* "What you cover"** (the
service categories) rather than duplicate them — now a small shown set +
a larger searchable-only pool of tools / technologies / industries.
Field hint clarifies the intent.
- **No competitor CRMs** in suggestions (Salesforce/HubSpot/Attio
removed); a guard test fails if one ever reappears. Migrations surface
as a generic "CRM migration".
- `Form.TagInput` gains an optional `searchPool` prop (autocomplete-only
entries, not rendered as chips).

Companion to the app-side PR #21709.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21710?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-06-18 14:29:35 +00:00
Charles Bochet 7c0136b97b feat(deps): migrate frontend to React 19 (#21531)
## What

Migrates the frontend stack from **React 18.3 → 19.2**. The website,
sdk, companion and emails packages were already on React 19; this brings
the remaining holdouts (`twenty-front`, `twenty-ui`,
`twenty-ui-deprecated`, `twenty-front-component-renderer`) and
`twenty-server`'s email rendering onto 19, and pins a single React
version repo-wide.

## Why

React 18.x is now the legacy line. Staying current keeps us on the
patched/maintained branch and unblocks downstream library majors
(react-router 7, mantine 9, etc.) that require React 19 peers.

## Dependency bumps (required by React 19 peers / removed APIs)

| Package | From | To | Reason |
|---|---|---|---|
| react / react-dom | 18.3.1 | 19.2.3 | core |
| @hello-pangea/dnd | 16 | 18 | peer `^18 \|\| ^19` |
| react-datepicker | 6 | 9 | v<7 used removed `findDOMNode`; drops
`@types/react-datepicker` |
| react-data-grid | beta.13 | beta.59 | peer `^19.2`; new render API |
| graphiql (+ @graphiql/react, plugin-explorer) | 3 / 0.23 / 1 | 5 /
0.37 / 5.1 | peer `^18 \|\| ^19` |
| react-helmet-async | 1.3 | **@dr.pogodin/react-helmet** 3.2 | upstream
caps peer at `^18`; drop-in React 19 fork |

A `resolutions` pin enforces a single React (19.2.3) + `@types/react`
(19.2.14) across the monorepo to avoid duplicate copies / type-identity
splits. Versions are the aged lockfile patches (clears the
`npmMinimalAgeGate`).

## Code changes

- **Global `JSX` shim** (`react-jsx-global.d.ts` per package): React 19
moved the `JSX` namespace under `React.JSX`; several deps' published
types (notably `@linaria/react`'s `styled.d.ts`, which types every
`styled.x` via `keyof JSX.IntrinsicElements`) still reference the global
namespace. Without the shim, every styled component degrades to `any`
props.
- **Ref nullability**: `useRef<T>(null)` now returns `RefObject<T |
null>`; widened consumer prop/hook ref types accordingly (incl. the
shared `useListenClickOutside`).
- **react-datepicker v9**: `onChange`/`onSelect` accept `Date | null`,
`calendarStartDay` typing, `ReactDatePickerProps`→`DatePickerProps`,
relaxed the dynamic `selectsMultiple` discriminated union.
- **react-data-grid beta.59**: `formatter`→`renderCell`,
`editor`→`renderEditCell`, `headerRenderer`→`renderHeaderCell`,
`components`→`renderers`, `onRowClick`→`onCellClick`, object-shaped
`useRowSelection`, Set-based selection.
- **dnd style cast**: `@radix-ui/react-popper` augments `CSSProperties`
with a `--radix-*` index signature that dnd's closed `DraggingStyle`
doesn't satisfy → cast at the spread.

## Status / testing

-  `typecheck` green: twenty-front, twenty-ui, twenty-ui-deprecated,
twenty-front-component-renderer, twenty-server
-  build / lint / unit tests / storybook+argos / runtime smoke-test in
progress

Draft until local + CI verification completes. Notable behavior to QA
manually: spreadsheet import (data-grid), date pickers, drag-and-drop
boards/lists, GraphQL playground, page titles/favicon.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21531?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-06-14 15:42:22 +02:00
github-actions[bot] 796f763bb7 i18n - website translations (#21453)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-06-11 15:35:27 +02:00
github-actions[bot] 28ab160b48 i18n - website translations (#21439)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-06-11 11:02:43 +02:00
Abdullah. c05c34aaec [Website] Convert remaining images to WebP and compress some current ones. (#21404)
As title. Also deleted some unused assets that are no longer required.
2026-06-11 08:47:44 +00:00
github-actions[bot] 7002c7bcb2 i18n - website translations (#21384)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-06-10 06:52:15 +02:00