085c0b9b7f591c5b1737afa12e37774d35d0c7d3
11758 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
085c0b9b7f |
feat(admin-panel): add read-only Billing tab and workspace logos (#20012)
## Summary - Adds a **Billing** tab on the admin-panel workspace detail page that surfaces Stripe customer + active subscription details (status, plan, interval, current period, trial, cancellation, line items, credit balance). Tab is gated on `IS_BILLING_ENABLED` both in the backend service and in the frontend tab list — completely hidden on instances where billing is disabled. - Renders a **workspace avatar next to the name** in the admin Top Workspaces list by plumbing the workspace `logo` field through the admin DTO, statistics SQL query, and generated admin GraphQL types. - **Read-only** by design: no Stripe API calls, no mutations — data comes from the existing \`BillingCustomerEntity\` / \`BillingSubscriptionEntity\` / \`BillingPriceEntity\` tables via \`BillingSubscriptionService.getCurrentBillingSubscription\`. ### What the tab shows - **Customer** container — Stripe customer ID (with link to the Stripe dashboard, monospaced), credit balance (formatted, from \`creditBalanceMicro\`). - **Subscription** container — status tag (color-coded), plan tag, billing interval, current period range, trial range (if trialing), \`cancelAtPeriodEnd\` / \`cancelAt\` / \`canceledAt\` (only when set), Stripe subscription ID (external link). - **Line items** — one card per subscription item with product name, product key tag, seats (if quantity), credits per period (for metered), unit price (formatted with currency). ### Design choices - Styling matches the user-facing billing page (\`SubscriptionInfoContainer\` + \`Tag\` + \`H2Title\` + \`Section\`) — no new UI primitives. - Currency is rendered inline with amounts via \`Intl.NumberFormat\` (e.g. \`\$19.00\`) instead of as a separate row. - Uses the generated admin GraphQL types (\`WorkspaceBillingAdminPanelQuery\`, \`SubscriptionStatus\`, \`SubscriptionInterval\`) — no hand-typed response shapes. ## Test plan - [x] \`npx nx typecheck twenty-server\` — passes - [x] \`npx nx typecheck twenty-front\` — passes - [x] oxlint + prettier on all touched files — clean - [x] \`graphql:generate --configuration=admin\` — regenerated; new \`workspaceBillingAdminPanel\` query + \`logo\` field on \`AdminPanelTopWorkspace\` appear in \`generated-admin/graphql.ts\` - [x] Backend GraphQL schema introspection shows \`workspaceBillingAdminPanel\` query on \`/admin-panel\` - [x] Direct GraphQL call with seeded \`BillingCustomer\` + \`BillingSubscription\` + \`BillingPrice\` rows returns the expected shape (\`status: "Trialing"\`, plan \`PRO\`, items with quantity/unitAmount/includedCredits, trial period dates) - [x] With \`IS_BILLING_ENABLED=false\` (default) the Billing tab is hidden — verified in the admin panel UI - [x] Top Workspaces list renders workspace avatars next to names — verified in the admin panel UI - [ ] Smoke test the Billing tab render in a real instance that has \`IS_BILLING_ENABLED=true\` + live Stripe data (skipped locally due to dev-env auth friction after toggling billing/multi-workspace; recommend a reviewer check) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9ce9e2bc12 |
i18n - translations (#20015)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
4f938aa097 |
feat(app): infrastructure for pre-installed apps (#19973)
**PR 1 of 2.** Follow-up PR ships the Exa app, sets it as a default
pre-installed app, and removes the current `WebSearchTool` /
`WebSearchService` / `ExaDriver`. This PR adds the plumbing; no
user-visible change yet.
## Summary
- Server admins can declare a list of npm app packages to auto-install
on every new workspace and backfill onto existing workspaces via CLI.
- Server-level secrets (like Exa's API key) live on the
`ApplicationRegistration` (one row per server, encrypted) and are
injected into logic function execution env at runtime. No more
per-workspace storage of global secrets.
- A generic `POST /app/billing/charge` endpoint lets app logic functions
emit workspace usage events for metered features. Exa uses it in PR 2;
future apps (call recorder, etc.) reuse it.
- `LogicFunctionToolProvider` tool name prefix changes `logic_function_`
→ `app_`. Shorter, accurate (they come from installed apps).
## What's in this PR
**Logic function executor — server-level variables**
- `LogicFunctionExecutorService.getExecutionEnvVariables` now resolves
env vars in the order: hardcoded defaults →
`ApplicationRegistrationVariable[]` (server-level) →
`ApplicationVariable[]` (workspace-level override). The manifest
`serverVariables` schema has existed; this closes the loop.
**Config**
- `PRE_INSTALLED_APPS` — comma-separated list of npm packages. Default:
empty.
**\`PreInstalledAppsService\`** (new module)
- \`onApplicationBootstrap()\` — fetches each package's manifest from
the app registry CDN, upserts an \`ApplicationRegistration\`, and seeds
declared \`serverVariables\` from matching env vars (e.g.
\`EXA_API_KEY\` env → encrypted registration variable).
- \`installOnWorkspace(workspaceId)\` — installs all pre-installed apps
on a single workspace. Tolerates per-app failures.
**Auto-install on new workspace activation**
- \`WorkspaceService.prefillCreatedWorkspaceRecords\` invokes
\`installOnWorkspace\` after prefilling standard records. Non-blocking
on failure.
**Backfill CLI command**
- \`install-pre-installed-apps\` — iterates active and suspended
workspaces, installs pre-installed apps that aren't yet installed.
Idempotent. Run after changing \`PRE_INSTALLED_APPS\`.
**App billing endpoint**
- \`POST /app/billing/charge\`. Authenticated via \`APPLICATION_ACCESS\`
token (already injected into logic function execution env as
\`DEFAULT_APP_ACCESS_TOKEN\`). Body: \`{ creditsUsedMicro, quantity,
unit, operationType, resourceContext? }\`. Emits \`USAGE_RECORDED\` with
\`applicationId\` as \`resourceId\`. Generic — reusable by any app.
**Tool name prefix**
- \`LogicFunctionToolProvider.buildLogicFunctionToolName\` now produces
\`app_<name>\` instead of \`logic_function_<name>\`. Only affects tools
sourced from logic functions; other tool providers unchanged.
## Stats
- 16 files, +501 / −2
- 7 new files (1 command, 1 service × 2, 1 controller, 1 DTO, 2 modules)
- Typecheck: 7 pre-existing errors, zero new
- Prettier clean
## Behavior deltas
- **\`PRE_INSTALLED_APPS\` default = empty**: existing servers see no
change on merge.
- **\`ApplicationRegistrationVariable\` is now read by the executor**:
apps that were using manifest \`serverVariables\` but expecting them to
be ignored by the executor will now see them injected. No apps ship with
\`isTool: true\` logic functions today, so this is latent — first
consumer is Exa in PR 2.
- **Tool prefix**: currently no logic-function tools are named
\`logic_function_*\` in any production flow. The prefix change affects
only future tools emitted by \`LogicFunctionToolProvider\`.
## Risks
- **CDN unavailability at startup**: if the app registry CDN is down,
\`ensureRegistrationsExist\` logs warnings but doesn't block server
start. Installation on new workspaces during this window will find no
registrations and log a non-blocking error. Backfill command can retry
after CDN recovers.
- **Cold-start overhead**: \`ensureRegistrationsExist\` is called once
per process on bootstrap. Current configurable default is empty, so zero
overhead. When an admin sets \`PRE_INSTALLED_APPS\`, they accept one
HTTP call per package at boot.
- **Server-level variables flow**:
\`ApplicationRegistrationVariable.encryptedValue\` is shared by all
workspaces of a server. Appropriate for a single-tenant Exa key. Not
appropriate for per-tenant keys — those go in workspace-level
\`ApplicationVariable\` and override.
## Test plan
- [ ] \`npx nx typecheck twenty-server\` passes (verified: 7
pre-existing unrelated errors, zero new)
- [ ] Set \`PRE_INSTALLED_APPS=@twenty-apps/hello-world\` (or any real
npm-published app), \`HELLO_WORLD_API_KEY=xxx\`, restart server:
\`ApplicationRegistration\` row is upserted,
\`ApplicationRegistrationVariable\` for HELLO_WORLD_API_KEY is populated
(encrypted).
- [ ] Create a new workspace: the app is auto-installed,
\`ApplicationEntity\` row created, \`LogicFunctionEntity\` rows created.
- [ ] Existing workspace: run \`yarn nx run twenty-server:command
install-pre-installed-apps\`: apps install across all workspaces,
idempotent on re-run.
- [ ] Trigger a logic function that reads
\`process.env.HELLO_WORLD_API_KEY\`: value resolves from the
server-level \`ApplicationRegistrationVariable\`.
- [ ] Log a charge from the handler: \`POST /app/billing/charge\` with
\`Authorization: Bearer \$DEFAULT_APP_ACCESS_TOKEN\` body
\`{creditsUsedMicro: 1000, quantity: 1, unit: "INVOCATION",
operationType: "WEB_SEARCH"}\` → returns \`{success: true}\`,
\`USAGE_RECORDED\` event emitted with correct
\`resourceId=applicationId\`.
- [ ] Tool name generated by \`LogicFunctionToolProvider\` starts with
\`app_\`.
## What's NOT in this PR (PR 2 scope)
- The Exa app itself (\`packages/twenty-apps/...\` directory)
- Removing \`WebSearchTool\`, \`WebSearchService\`, \`ExaDriver\`,
\`web-search\` module
- Removing \`WEB_SEARCH_DRIVER\` config var
- Removing the current \`exa_web_search\` entry in
\`ActionToolProvider\`
- Chat preload list updated to \`app_exa_web_search\`
- Frontend \`getToolDisplayMessage\` branch for \`app_exa_web_search\`
- Setting \`PRE_INSTALLED_APPS\` default to include \`@twenty-apps/exa\`
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
ec98130def |
fix(admin-panel): inline skeleton loaders for table sections (#20014)
## Summary \`SettingsSkeletonLoader\` wraps its content in \`PageHeader\` + \`PageBody\`, which is right for **full-page replacement** (user detail, config variable detail, etc.) but renders as a large empty stub with one tiny floating bar when placed **inline inside a section that's already scaffolded**. That's what showed up in Recent Users, Top Workspaces, and the Chats tab in the admin panel — a jarring white page flash where a few row placeholders should be. This PR adds \`SettingsAdminSectionSkeletonLoader\` — a small, configurable-row-count skeleton that uses the project's standard \`SkeletonTheme\` pattern (\`theme.background.tertiary\` / \`theme.background.transparent.lighter\` + \`borderRadius: 4\`, matching \`PageContentSkeletonLoader\` and \`SettingsAdminTabSkeletonLoader\`) and renders row-height bars that match \`TableRow\` spacing. Swaps it into the three inline call sites. Full-page usages of \`SettingsSkeletonLoader\` are unchanged. ### Changed - **New**: \`packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminSectionSkeletonLoader.tsx\` - **Swapped** (3 inline loading states): \`SettingsAdminGeneral.tsx\` (Recent Users + Top Workspaces), \`SettingsAdminWorkspaceDetail.tsx\` (Chats tab) ## Test plan - [x] typecheck (\`npx nx typecheck twenty-front --skip-nx-cache\`) — clean - [x] oxlint — 0 warnings - [x] prettier — clean - [ ] Visual: navigate to admin panel, observe Recent Users / Top Workspaces / Chats tab loading — should see a tight stack of row-height placeholder bars instead of a big empty page stub 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b1838b3090 |
Retrieve ai catalog at bootstrap (#20005)
# Introduction Migrating from build injection to a runtime bootstrap injection of the ai catalog that are dynamic to the env we're deploying to |
||
|
|
fa3d0cd4a6 |
Force uuids in AI workflow tools (#20010)
- Add .uuid() Zod validation to all AI workflow tool schemas (workflowVersionId, workflowId, stepId, edge target) so the AI model is constrained to use valid UUIDs instead of human-readable strings like step-find-stale-leads - Fixes Sentry noise from Invalid UUID errors triggered when workflows created with non-UUID step IDs are later edited via GraphQL mutations that enforce UUID scalars Will fix https://twenty-v7.sentry.io/issues/7240446584/events/05c7782655a34f4a8f5fc889164026ab/?project=4507072499810304&referrer=previous-event |
||
|
|
652930e0ac |
i18n - docs translations (#20009)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
d887fdc532 |
Stop throwing for event stream does not exists (#20008)
Fixes https://twenty-v7.sentry.io/issues/7351816489/?environment=prod&environment=prod-eu&project=4507072499810304&query=is%3Aunresolved%20%21issue.type%3A%5Bperformance_consecutive_db_queries%2Cperformance_consecutive_http%2Cperformance_file_io_main_thread%2Cperformance_db_main_thread%2Cperformance_n_plus_one_db_queries%2Cperformance_n_plus_one_api_calls%2Cperformance_p95_endpoint_regression%2Cperformance_slow_db_query%2Cperformance_render_blocking_asset_span%2Cperformance_uncompressed_assets%2Cperformance_http_overhead%2Cperformance_large_http_payload%5D%20timesSeen%3A%3E10&referrer=issue-stream&sort=date - When an SSE event stream already exists in Redis during a reconnection, the server now checks if the caller is the rightful owner (via isAuthorized) and destroys the stale stream before creating a fresh one, instead of throwing an EVENT_STREAM_ALREADY_EXISTS error - Unauthorized callers still receive the error, preserving the security guard against hijacking |
||
|
|
992a7ca12f |
i18n - docs translations (#20007)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
a445f4a6fa |
feat(sdk): add definePageLayoutTab for extending existing page layouts (#20004)
## Summary
Introduces `definePageLayoutTab` so apps can attach a single tab (with
optional widgets) to an **existing** `pageLayout` referenced by
`pageLayoutUniversalIdentifier`. The parent layout can be standard, from
the same app, or from another app — mirroring how `defineField`
references an object via `objectUniversalIdentifier`.
This complements `definePageLayout`: use `definePageLayout` when you own
the entire layout, use `definePageLayoutTab` when you only want to add
to one.
```ts
import { definePageLayoutTab, PageLayoutTabLayoutMode } from 'twenty-sdk/define';
export default definePageLayoutTab({
universalIdentifier: 'b1b2b3b4-b5b6-4000-8000-000000000001',
pageLayoutUniversalIdentifier: 'STANDARD-OR-OTHER-APP-PAGE-LAYOUT-UUID',
title: 'Hello World',
position: 1000,
icon: 'IconWorld',
layoutMode: PageLayoutTabLayoutMode.CANVAS,
widgets: [/* ... */],
});
```
## Changes
- **twenty-shared**: new top-level `pageLayoutTabs:
PageLayoutTabManifest[]` on `Manifest`, optional
`pageLayoutUniversalIdentifier` on `PageLayoutTabManifest`, new
`SyncableEntity.PageLayoutTab`.
- **twenty-sdk**:
- new `definePageLayoutTab` + `PageLayoutTabConfig` exports;
- manifest extraction wiring (`TargetFunction.DefinePageLayoutTab`,
`ManifestEntityKey.PageLayoutTabs`);
- dev-mode label/state for the new entity;
- CLI scaffold (`getPageLayoutTabBaseFile`) + unit tests for `npx
twenty-cli add`.
- **twenty-server**: convert top-level `pageLayoutTabs` (and their
widgets) into universal flat entities in
`computeApplicationManifestAllUniversalFlatEntityMaps`. Cross-app FK
validation on `pageLayoutUniversalIdentifier` is already handled by the
existing `FlatPageLayoutTab` validator.
- **docs**: new `definePageLayoutTab` accordion in `apps/layout.mdx`
with usage example and guidance vs `definePageLayout`.
- **CI / rich-app fixture**: `extra-tab.page-layout-tab.ts` exercises
the new flow with a front-component widget; `expected-manifest.ts` and
`manifest.tests.ts` updated.
|
||
|
|
20f7ba82d7 |
optimize workspace export command (#20000)
- Use `COPY` instead of `INSERT` for workspace tables, 40x faster imports - Multi value statements, 2x smaller file size - Bump Batch size to 10K , remove COUNT query - Add `formatPgCopyField` utility with unit tests |
||
|
|
aa4aea0f9b |
Fix layout edition mode dark mode text color (#19992)
Summary
Fixed the dark-mode regression in the layout customization banner at
packages/twenty-front/src/modules/layout-customization/components/LayoutCustomizationBar.tsx:
- The banner's background is themeCssVariables.color.blue
(theme-independent), so its text color must be theme-independent too.
- Replaced color: ${themeCssVariables.font.color.inverted} (which
resolves to near-black in dark mode) with color:
${GRAY_SCALE_LIGHT.gray1} (always white), matching the convention used
in AnimatedButton.tsx for text on colored backgrounds.
## Before
<img width="1508" height="185" alt="Screenshot 2026-04-22 at 19 50 03"
src="https://github.com/user-attachments/assets/b0a95fc2-05d5-4207-9d72-64a83daf94ae"
/>
## After
<img width="1511" height="190" alt="Screenshot 2026-04-22 at 19 49 39"
src="https://github.com/user-attachments/assets/9ce33353-412f-4db2-9322-a6f293f6f1b4"
/>
|
||
|
|
77f5b9d7c8 |
chore(deps): bump @ai-sdk/google from 3.0.31 to 3.0.64 (#19998)
Bumps [@ai-sdk/google](https://github.com/vercel/ai) from 3.0.31 to 3.0.64. <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/vercel/ai/commit/e2f82795704d7badbd90f9f4a798d8bf2d40b2df"><code>e2f8279</code></a> Version Packages (<a href="https://redirect.github.com/vercel/ai/issues/14527">#14527</a>)</li> <li><a href="https://github.com/vercel/ai/commit/71c52e0c053571dd6893be6fa56d275ec2d9e216"><code>71c52e0</code></a> Backport: chore(provider/google): update available models (<a href="https://redirect.github.com/vercel/ai/issues/14524">#14524</a>)</li> <li><a href="https://github.com/vercel/ai/commit/e662efda9bb35838d5c2a03a6320197d1192db4c"><code>e662efd</code></a> Version Packages (<a href="https://redirect.github.com/vercel/ai/issues/14510">#14510</a>)</li> <li><a href="https://github.com/vercel/ai/commit/83434a97d6856c34d440e22038ac083cd5134017"><code>83434a9</code></a> Backport: feat (provider/gateway): add provider routing sort options (<a href="https://redirect.github.com/vercel/ai/issues/14508">#14508</a>)</li> <li><a href="https://github.com/vercel/ai/commit/3aa3a68cfec59cae4dccb1ad1446d30fbe93f17c"><code>3aa3a68</code></a> Version Packages (<a href="https://redirect.github.com/vercel/ai/issues/14507">#14507</a>)</li> <li><a href="https://github.com/vercel/ai/commit/a27a631939dedc310b13fd48de144760cc2a3145"><code>a27a631</code></a> Backport: feat (provider/gateway): make model list resilient to unknown model...</li> <li><a href="https://github.com/vercel/ai/commit/a2d619a76be80c4fdd60ce047d21fdf95879e08f"><code>a2d619a</code></a> Version Packages (<a href="https://redirect.github.com/vercel/ai/issues/14502">#14502</a>)</li> <li><a href="https://github.com/vercel/ai/commit/b937f3e7fa0c943c6ce8a379b57dd5bb7abf07b4"><code>b937f3e</code></a> Backport: fix(xai): support encrypted reasoning round-trip for ZDR (<a href="https://redirect.github.com/vercel/ai/issues/14497">#14497</a>)</li> <li><a href="https://github.com/vercel/ai/commit/b0e5ab3a75052fbc0469066b19196ec419e49c20"><code>b0e5ab3</code></a> Version Packages (<a href="https://redirect.github.com/vercel/ai/issues/14494">#14494</a>)</li> <li><a href="https://github.com/vercel/ai/commit/8c4abafaaa28f82f031a498ff335c9f394a33d48"><code>8c4abaf</code></a> Backport: chore(provider/gateway): update gateway model settings files v6 (<a href="https://redirect.github.com/vercel/ai/issues/1">#1</a>...</li> <li>Additional commits viewable in <a href="https://github.com/vercel/ai/compare/@ai-sdk/google@3.0.31...@ai-sdk/google@3.0.64">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
80e8f6d516 |
chore(deps): bump @blocknote/server-util from 0.47.1 to 0.47.3 (#19997)
Bumps [@blocknote/server-util](https://github.com/TypeCellOS/BlockNote/tree/HEAD/packages/server-util) from 0.47.1 to 0.47.3. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/TypeCellOS/BlockNote/releases"><code>@blocknote/server-util</code>'s releases</a>.</em></p> <blockquote> <h2>v0.47.3</h2> <h2>0.47.3 (2026-03-25)</h2> <h3>🩹 Fixes</h3> <ul> <li><strong>core:</strong> preserve whitespace edge cases but collapse html formatting newlines (BLO-1065) (<a href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2551">#2551</a>, <a href="https://redirect.github.com/TypeCellOS/BlockNote/issues/2230">#2230</a>)</li> </ul> <h3>❤️ Thank You</h3> <ul> <li>Yousef</li> </ul> <h2>v0.47.2</h2> <h2>0.47.2 (2026-03-20)</h2> <h3>🩹 Fixes</h3> <ul> <li>use <code><details></code> & <code><summary></code> for toggle block HTML export (<a href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2524">#2524</a>)</li> <li>remove <code>@hocuspocus/provider</code> peer dependency by inlining tiptap comment types BLO-1064 (<a href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2564">#2564</a>)</li> <li><strong>core:</strong> slash menu fails in custom blocks after space BLO-1036 (<a href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2553">#2553</a>)</li> <li><strong>i18n:</strong> fix typo in russian translation (<a href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2560">#2560</a>)</li> </ul> <h3>❤️ Thank You</h3> <ul> <li>Drone</li> <li>Yousef</li> </ul> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/TypeCellOS/BlockNote/blob/main/CHANGELOG.md"><code>@blocknote/server-util</code>'s changelog</a>.</em></p> <blockquote> <h2>0.47.3 (2026-03-25)</h2> <h3>🩹 Fixes</h3> <ul> <li><strong>core:</strong> preserve whitespace edge cases but collapse html formatting newlines (BLO-1065) (<a href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2551">#2551</a>, <a href="https://redirect.github.com/TypeCellOS/BlockNote/issues/2230">#2230</a>)</li> </ul> <h3>❤️ Thank You</h3> <ul> <li>Yousef</li> </ul> <h2>0.47.2 (2026-03-20)</h2> <h3>🩹 Fixes</h3> <ul> <li>use <!-- raw HTML omitted -->/<!-- raw HTML omitted --> for toggle block HTML export (<a href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2524">#2524</a>)</li> <li>remove <code>@hocuspocus/provider</code> peer dependency by inlining tiptap comment types BLO-1064 (<a href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2564">#2564</a>)</li> <li><strong>core:</strong> slash menu fails in custom blocks after space BLO-1036 (<a href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2553">#2553</a>)</li> <li><strong>i18n:</strong> fix typo in russian translation (<a href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2560">#2560</a>)</li> </ul> <h3>❤️ Thank You</h3> <ul> <li>Claude Opus 4.6</li> <li>Drone</li> <li>Yousef</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/TypeCellOS/BlockNote/commit/cd92dc21be49397b658fef4e308e01ce8f5c04ad"><code>cd92dc2</code></a> chore(release): publish 0.47.3</li> <li><a href="https://github.com/TypeCellOS/BlockNote/commit/b63b4096daa575f821980ef897fd90f4c76d9e42"><code>b63b409</code></a> chore(release): publish 0.47.2</li> <li><a href="https://github.com/TypeCellOS/BlockNote/commit/d76fd68e016da698f3896f9d349a935a26a52d5f"><code>d76fd68</code></a> test: get snapshots working again (<a href="https://github.com/TypeCellOS/BlockNote/tree/HEAD/packages/server-util/issues/2554">#2554</a>)</li> <li>See full diff in <a href="https://github.com/TypeCellOS/BlockNote/commits/v0.47.3/packages/server-util">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Abdullah <125115953+mabdullahabaid@users.noreply.github.com> |
||
|
|
39831338f7 |
i18n - translations (#19988)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
876214bc1d |
scaffold record page layout + fields view when adding an object (#19977)
## Summary
Extends `yarn twenty add` → **Object** so it scaffolds a complete record
page out
of the box:
- A **record-page-fields view** (`<name>-record-page-fields.ts`,
FIELDS_WIDGET)
pre-populated with the `name` field plus the auto-generated default
fields (`createdAt`,
`updatedAt`, `createdBy`, `updatedBy`) — the default-field entries are
emitted as
`generateDefaultFieldUniversalIdentifier({ objectUniversalIdentifier,
fieldName: '...' })`
calls rather than pre-computed UUIDs, so the generated file
double-serves as
documentation for the public util.
- A **record page layout** (`<name>-record-page-layout.ts`) with a Home
tab whose Fields
widget points at the new view (via `viewUniversalIdentifier`), plus a
Timeline tab.
- The companion prompt now covers all three artefacts (was view + nav
menu item).
Fix: Server-side, renames `viewId` → `viewUniversalIdentifier` on the
universal-flat FIELDS
widget configuration so it is consistent with other universal-flat
references. The DB-side
DTO keeps `viewId` (now typed as `SerializedRelation`), and the
conversion utils map
between the two.
<img width="337" height="349" alt="Screenshot 2026-04-22 at 15 40 22"
src="https://github.com/user-attachments/assets/59e36540-1761-46b0-808d-648c68604268"
/>
|
||
|
|
0d996a5629 |
Resend app improvements (#19986)
## Summary Major overhaul of the `twenty-for-twenty` Resend app to make sync more reliable, observable, and feature-complete. ### SDK upgrade - Bumps `twenty-sdk` to `2.0.0` and `twenty-client-sdk` to `1.23.0-canary.1` - Pins React back to `^18.2.0` to match the SDK ### Sync engine rewrite - Splits the single `sync-resend-data` job into 4 staggered cron-driven logic functions: **Emails**, **Contacts**, **Broadcasts (+ segments + dependencies)**, **Templates** — each running every 5 minutes on a different minute offset with per-slot timeouts - Adds a new `ResendSyncCursor` object + `with-sync-cursor` orchestration so each step persists its progress, last run timestamp, and last run status - Introduces an `INITIAL_SYNC_MODE` app variable + `resend-initial-sync-mode-monitor` that flips to intermediate sync once every cursor is empty (intermediate sync only refetches the last 7 days of emails) - Stops auto-creating People from Resend contacts; instead backfills `personId` on Resend contacts/emails by matching existing People by email - Renames `on-*-deleted` handlers to `on-*-destroyed` and removes from Resend on destroy (not soft delete) - Adds rate-limit retry, paginated `for-each-page`, typed-client, and existing-IDs lookup helpers ### New objects & fields - New `ResendTopic` object with relation to `ResendBroadcast` (+ navigation menu item, view, page layout) - New `ResendSyncCursor` object (step / cursor / last run at / last run status) - Adds `html` and `text` fields on `ResendBroadcast`; removes raw `htmlBody`/`textBody`/`tags` from `ResendEmail` ### New UI - **Sync Status standalone page** (`ResendSyncStatus` front component + nav item) showing live cursor / last run state per step - **Person Resend Email Stats** front component: deliverability rate + per-status breakdown with progress bars - **Email Broadcast HTML viewer** front component renders an individual email against its parent broadcast's HTML; new dedicated **Broadcast HTML viewer** - Adds Resend Broadcast record page layout (Home / Preview / Timeline / Tasks / Notes / Files tabs) ### Tests - ~25 new unit / integration test files covering sync utilities, cursor lifecycle, webhook handler, email-stats computation, sync-status page resolution, and rate-limit retry - Replaces legacy `fetch-all-paginated` tests with `for-each-page` tests |
||
|
|
3ebeb3a3e8 |
feat(community): add github-connector example app (#19961)
## Summary Adds a new community app at `packages/twenty-apps/community/github-connector` that demonstrates a complete, production-style GitHub integration built on the Twenty SDK. It is extracted (and decoupled) from the internal `twenty-eng` workspace so external developers can use it as a reference for their own connectors. What it ships: - **Six synced objects**: `pullRequest`, `pullRequestReview`, `pullRequestReviewEvent`, `issue`, `projectItem`, `engineer` - **Logic functions** for periodic backfills (PRs, reviews, issues, project items, contributors) and a single signed-webhook route trigger (`POST /github/webhook`) that performs idempotent upserts for `pull_request`, `pull_request_review`, `issues`, and `projects_v2_item` events - **Views, navigation menu items and a GitHub folder** so the data is discoverable in the UI out of the box - **Configurable repos / project numbers** via `GITHUB_REPOS` and `GITHUB_PROJECT_NUMBERS` application variables — no hardcoded org ## Authentication Two interchangeable modes (PAT preferred for quick setup, GitHub App recommended for production): 1. **Personal Access Token** — set `GITHUB_TOKEN`. Used as-is for both REST and GraphQL. 2. **GitHub App** — set `GITHUB_APP_ID`, `GITHUB_APP_PRIVATE_KEY`, `GITHUB_APP_INSTALLATION_ID`. Issues a signed JWT, exchanges it for a short-lived installation token, and caches the token until expiry. Webhook signature verification (`X-Hub-Signature-256`) is enforced when `GITHUB_WEBHOOK_SECRET` is set. ## Notes - Built on `twenty-sdk@2.0.0` / `twenty-client-sdk@2.0.0` - Decoupled from internal modules (`quality/bug`, `discord`, `release`, `code-build`, `project-management`) — `mustBeQa` is inlined and a local `github` nav folder replaces shared ones - `npx twenty typecheck`, `yarn lint`, and `npx twenty build` all run cleanly - Includes a comprehensive README with setup, env vars, webhook configuration, and the auth resolution flow |
||
|
|
f30ef2432f |
i18n - translations (#19987)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
921a0f01c8 |
Forbid permissions update cross app role retarget (#19982)
closes https://github.com/twentyhq/twenty/issues/19807 |
||
|
|
0696290af4 |
fix(page-layout): hide deactivated fields from FIELDS widget and layout editor (#19984)
## Context - The FIELDS widget resolved every viewField against objectMetadataItem.fields, which includes deactivated field metadata — so fields deactivated after being added to a view kept rendering on records. - Same leak existed in the layout editor: deactivated fields appeared as toggleable hidden viewFields, and newly-deactivated object fields were auto-proposed via the "missing fields" flow. ## Fix - pre-filter objectMetadataItem.fields to field.isActive at each entry point (useFieldsWidgetGroups, useFieldsWidgetEditorGroupsData, useFieldsWidgetHiddenFields) and inside buildDefaultFieldsWidgetGroups for the no-view fallback. |
||
|
|
32e0425a65 |
Docs - Update getting started (#19976)
- Add product tour video - Add new graphic design assets --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Etienne <etiennejouan@users.noreply.github.com> |
||
|
|
f34ba6ac12 |
i18n - docs translations (#19983)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
f0a625c3f8 |
Cleanup application and app registration test util (#19981)
## Introduction Centralizing integ test app cleanup Role will be deleted by the app uninstall if exists |
||
|
|
b010599000 |
fix(server): preserve kanban/calendar fields in view manifest sync (#19946)
## Summary The `fromViewManifestToUniversalFlatView` converter hardcoded five view fields to `null` instead of reading them from the manifest: - `mainGroupByFieldMetadataUniversalIdentifier` - `kanbanAggregateOperation` - `kanbanAggregateOperationFieldMetadataUniversalIdentifier` - `calendarLayout` - `calendarFieldMetadataUniversalIdentifier` As a result **any** Kanban view in an app manifest is rejected by `validateFlatViewCreation` with `"Kanban view must have a main group by field"`, and any Calendar view would trip the `view.entity.ts` check constraint requiring `calendarLayout` + `calendarFieldMetadataId` to be non-null. Discovered while trying to install [`twenty-crm-meeting-baas`](https://github.com/Meeting-BaaS/twenty-crm-meeting-baas) which ships a Kanban view. ## Changes - **Server converter**: read all five fields from the manifest (with `?? null` fallback). - **`ViewManifest` type** (`twenty-shared`): add the five fields so SDK users can set them type-safely. - **Move `ViewCalendarLayout`** from `twenty-server` to `twenty-shared` so the manifest type can reference it. Seven import sites updated; the front-end imports via generated GraphQL types and is unaffected. - **Unit tests**: extend `from-view-manifest-to-universal-flat-view.util.spec.ts` with preservation + null-default cases for both Kanban and Calendar (5 tests total). - **Regression coverage**: add a Kanban view (`post-cards-by-status.view.ts`) to the `rich-app` fixture grouped by the existing `status` SELECT field. The existing `applications-install-delete-reinstall` e2e test now exercises the Kanban path end-to-end — a future regression here would fail CI. Note: `expected-manifest.ts` and the `views.length` assertion in `manifest.tests.ts` were updated to reflect the new fixture view. ## Test plan - [x] `nx test twenty-server -- from-view-manifest-to-universal-flat-view` → 5/5 pass - [x] `nx typecheck twenty-shared` / `twenty-sdk` / `twenty-server` → no new errors (one pre-existing unrelated error in `admin-panel.module-factory.ts`) - [x] `nx lint twenty-shared` / `twenty-sdk` → clean - [x] Manual install of the Meeting BaaS app on a dev workspace succeeds with the Kanban view after this fix - [ ] CI: SDK e2e `applications-install-delete-reinstall` passes against the new fixture view - [ ] CI: integration test `calendar-field-deactivation-deletes-views` still passes after the enum move 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> |
||
|
|
c2cf3eac50 |
feat(sdk): confirm authentication method on remote add (#19947)
## Summary `yarn twenty remote add` only prints `✓ Default remote set to X.` after authenticating. When using the OAuth path, the browser flow happens silently — there's no line that says _"you authenticated"_ — so users (including me this morning while installing a Twenty app) are left wondering whether auth actually completed and which method was used. This PR adds explicit confirmation of the auth step: **New remote via OAuth** ``` ✓ Remote "myremote" added (https://app.twenty.com) via OAuth. ✓ Default remote set to "myremote". ``` **New remote via API key** ``` ✓ Remote "myremote" added (https://app.twenty.com) via API key. ✓ Default remote set to "myremote". ``` **Re-authenticating an existing remote** ``` ✓ Re-authenticated "myremote" via OAuth. ✓ Default remote set to "myremote". ``` ## Implementation - `authenticate()` now returns the method actually used (`'OAuth' | 'API key'`) instead of `void`. This correctly surfaces OAuth → API-key fallback: if OAuth fails and the user drops into the API-key prompt, the success line reflects that. - New-remote and re-auth paths print distinct messages so the user can tell which path they took. - No new API calls — method name comes from which branch of `authenticate()` succeeded. ## Test plan - [x] `nx typecheck twenty-sdk` — clean - [x] `nx lint twenty-sdk` — clean - [ ] Manual smoke test: `yarn twenty remote add --as test --api-url ...` via OAuth, API key, and re-auth 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
789f8aba5d |
i18n - translations (#19975)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
68d509e98d |
Update settings application illustrations and app metadata previews (#19964)
## Summary - Refresh the settings application visuals with new light/dark PNG covers for the data model card - Replace the custom and standard application carousel assets with the new provided illustrations - Align app chips, type tags, and application detail previews with the updated icon and description treatment - Keep the data model cover container and overlay button behavior intact while swapping the underlying imagery ## Testing - Not run (not requested) - Existing frontend typecheck and formatting checks were exercised during implementation --------- Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
0c929e7903 |
refactor(tool-provider): rename web_search to exa_web_search, drop XOR toggle (#19969)
## Summary
- Today `WEB_SEARCH_PREFER_NATIVE` forces a **mutual exclusion**: either
the custom Exa tool preloads as `web_search` or the SDK-native
`web_search` binds. Same name, different backends.
- This PR lets them **coexist**. Custom Exa becomes `exa_web_search`;
native keeps `web_search`. The model picks based on tool descriptions.
- `WEB_SEARCH_PREFER_NATIVE` and `shouldUseNativeSearch()` are deleted.
Exa enablement follows `WEB_SEARCH_DRIVER` (existing). Native enablement
follows the agent's `modelConfiguration.webSearch.enabled` (existing).
## Key changes
**Config / service**
- Deleted `WEB_SEARCH_PREFER_NATIVE` (config-variables.ts)
- Deleted `WebSearchService.shouldUseNativeSearch()`
- `WebSearchService.isEnabled()` unchanged — still gates Exa
availability
**Custom tool rename**
- `ActionToolProvider.toolMap`: `'web_search'` → `'exa_web_search'`
- Descriptor name matches
- `WebSearchTool.description` rewritten to position Exa as
structured/entity-aware, complementary to native
**Native tool binder**
- `NativeToolBinder.bind()` drops the `shouldUseNativeSearch` gate.
Per-agent `modelConfiguration.webSearch.enabled` (inside
`getNativeModelTools`) stays authoritative.
**Chat**
- Preload list now always includes `exa_web_search` —
`ActionToolProvider` silently skips the descriptor when Exa is disabled,
so `getToolsByName` degrades gracefully
- Native tools always attempted; returns empty ToolSet when the model
doesn't support them
- `directTools = { ...preloadedTools, ...nativeSearchTools }` — both
present when both enabled
- `billNativeWebSearchUsage` called unconditionally (the function
already short-circuits on count ≤ 0)
**Workflow agent**
- Same unconditional billing pattern
- `WebSearchService` dependency removed
**System prompt**
- Dropped the special-cased `web_search` branch. Preloaded tools list
uniformly now.
**Frontend**
- `exa_web_search` reuses the same "Searching the web for X" display as
native
- Test coverage added
## Billing isolation (verified)
- `countNativeWebSearchCallsFromSteps` counts `toolName ===
'web_search'` only. After the rename, only native calls match. Exa calls
(`exa_web_search`) are billed separately via
`WebSearchService.emitUsageEvent` inside `search()`.
- No double-billing path.
## Behavior deltas (intended)
| Scenario | Before | After |
|---|---|---|
| Anthropic model + Exa enabled + PREFER_NATIVE=true | native only |
**both** |
| Anthropic + Exa enabled + PREFER_NATIVE=false | Exa only (as
`web_search`) | **both** |
| Non-native model + Exa enabled | Exa as `web_search` | Exa as
`exa_web_search` |
| Any model + Exa disabled + native supported | native only | native
only |
| Workflow agent with `webSearch.enabled=true` + Anthropic + Exa enabled
| native only | **both** |
## Known regression (accepted)
Customers who set `WEB_SEARCH_PREFER_NATIVE=false` to force Exa-only
will now **also** see native `web_search` if the model supports it.
There's no chat-level kill switch after this PR. Per discussion, this is
accepted — future model-level capability gating (in the model JSON) will
be the right place for that control.
## Stats
- 10 files, +63 / −73 (net deletion)
- Typecheck clean (server: 7 pre-existing unrelated, front: 13
pre-existing unrelated — zero new either side)
- Prettier clean
## Test plan
- [ ] `npx nx typecheck twenty-server` and `npx nx typecheck
twenty-front` pass
- [ ] With Anthropic + Exa enabled: chat shows both `web_search` and
`exa_web_search` in preloaded list; model can call either
- [ ] With Anthropic + Exa disabled: chat shows only native `web_search`
- [ ] With non-native model + Exa enabled: chat shows only
`exa_web_search`
- [ ] Workflow agent with `modelConfiguration.webSearch.enabled=true` +
Exa enabled: both available
- [ ] Billing: native calls billed via `billNativeWebSearchUsage`; Exa
calls billed via `WebSearchService.emitUsageEvent`; no double-billing
- [ ] Frontend: `exa_web_search` renders "Searching the web for X" the
same as `web_search`
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
f018f17133 |
i18n - docs translations (#19970)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
a486ead39d |
fix: restore Try Twenty button text visibility on docs navbar (#19968)
# [Docs]: Fix blank Try Twenty button text in dark mode ## 🐛 Problem The "Try Twenty" CTA button in the docs navbar appears blank/invisible when users enable dark mode, making it impossible to click through to the sign-up page. **Issue:** #19965 ## Root Cause CSS specificity conflict in `packages/twenty-docs/custom.css`: - The dark mode CSS rule targets only the `<a>` element - Mintlify renders button text in a nested `<span>` with class `text-white` (white color) - The `text-white` Tailwind utility directly applies `color: rgb(255 255 255)` to the span - This overrides the inherited dark color from the parent `<a>` element - **Result:** White text on white background = invisible button ## Solution Update the CSS selector in `packages/twenty-docs/custom.css` to target both the `<a>` element AND the nested `<span>`: **Before:** ```css :is(.dark, [data-theme="dark"]) #topbar-cta-button a { background-color: #ffffff !important; color: #141414 !important; } |
||
|
|
44309a6fd9 |
refactor(tool-provider): rename NativeModelToolProvider to NativeToolBinderService (#19966)
**Stacked on top of #19962.** ## Summary - `NativeModelToolProvider` lived under `providers/` and had the `*-tool.provider.ts` suffix, but it never implemented `ToolProvider`, wasn't in `TOOL_PROVIDERS`, had no descriptors, and wasn't executed by `ToolExecutorService`. The shape misled readers. - It's actually a **parallel concept**: a binder that produces SDK-native tool objects (Anthropic `webSearch`, OpenAI `webSearch`, etc.) which the AI SDK passes straight to the model. Opaque, not serializable, never in the catalog, never dispatched by the executor. - This PR renames + moves it to reflect that. ## Renames | Before | After | |---|---| | `NativeModelToolProvider` (class) | `NativeToolBinderService` | | `NativeToolProvider` (interface) | `NativeToolBinder` | | `generateTools(context)` (method) | `bind(context)` | | `providers/native-model-tool.provider.ts` | `native/native-tool-binder.service.ts` | | `interfaces/native-tool-provider.interface.ts` | `native/native-tool-binder.interface.ts` | ## What doesn't change - `ToolCategory.NATIVE_MODEL` enum stays (still used by `getToolsByCategories`). - `isAvailable()` signature unchanged. - `WebSearchService.shouldUseNativeSearch()` toggle untouched — that's product-level and belongs to a separate PR that handles the Exa coexistence story. - No behavior change. Pure rename + move. ## Why this matters for the broader architecture This rename makes the native/binder concept **visible in the type system and directory structure**. That's what later enables coexisting native + custom tools (e.g., `web_search` native alongside `exa_web_search` custom) without the current naming collision, because native tools are no longer masquerading as a registry provider. ## Stats - 5 files, +30 / −28. - Blast radius: 4 files modified, 1 file renamed (git tracks as rename). - Typecheck clean (7 pre-existing unrelated errors, zero new). - Prettier clean. ## Test plan - [ ] `npx nx typecheck twenty-server` passes - [ ] AI chat: native `web_search` still works end-to-end when enabled - [ ] Workflow AI agent: `ToolCategory.NATIVE_MODEL` still works (goes through `bind()` now) - [ ] MCP: unaffected (doesn't use native tools) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
2a5d5b36db |
refactor(tool-provider): kill execute_tool's dual dispatch (#19962)
**Stacked on top of #19960.**
## Summary
- `execute_tool` used to check `directTools[toolName]` first, falling
back to the registry. Same tool name, different wrapping: preloaded went
through `wrapToolsWithOutputSerialization`, fallback didn't. Silent
divergence — a model calling a CRUD tool via
\`learn_tools\`/\`execute_tool\` got raw output, while calling it as a
preloaded direct tool got compacted output.
- Now: `execute_tool` always routes through
`toolRegistry.resolveAndExecute`. One path, no fast-path.
- Output serialization (`compactToolOutput`) moves into the registry,
gated by a new `serializeOutput` flag on `hydrateToolSet` /
`resolveAndExecute` / `getToolsByName` / `getToolsByCategories` /
`ToolRetrievalOptions`. Chat passes `true`, MCP and workflow pass
`false`.
## Key changes
**Registry (`tool-registry.service.ts`)**
- `hydrateToolSet` options gain `serializeOutput?: boolean`; when true
the execute closure wraps dispatch result with `compactToolOutput`.
- `resolveAndExecute` signature: replaces unused \`_options:
ToolExecutionOptions\` with `{ serializeOutput?: boolean }`.
- `getToolsByName` and `getToolsByCategories` thread `serializeOutput`
through to `hydrateToolSet`.
**Meta-tool (`execute-tool.tool.ts`)**
- API changes from positional `(toolRegistry, context, directTools?,
excludeTools?)` to `(toolRegistry, context, options?: { excludeTools?,
serializeOutput? })`.
- `directTools` fallback removed. All invocations go to the registry.
**Chat (`chat-execution.service.ts`)**
- Passes `serializeOutput: true` to `getToolsByName` — preloaded tools
get compacted output from the hydrator, no external wrap needed.
- Drops the external `wrapToolsWithOutputSerialization(preloadedTools)`
call.
- `createExecuteToolTool` call now passes `{ serializeOutput: true }`.
Direct-tool and `execute_tool` paths produce identical output shape.
**MCP (`mcp-protocol.service.ts`)**
- `createExecuteToolTool` call updated to new options shape with `{
excludeTools: MCP_EXCLUDED_TOOLS }`. No `serializeOutput` flag → raw
output as today.
**Deletes**
- `output-serialization/wrap-tools-with-output-serialization.util.ts` —
sole caller removed.
## Behavior changes
- **Chat, `execute_tool` fallback path**: now produces compacted output
(matches direct path). Net effect: fewer tokens for CRUD results reached
via discovery. Intended improvement.
- **Chat, `execute_tool({toolName: 'web_search'})` edge**: today
silently hits the native tool via `directTools`; now returns \"tool not
found, use get_tool_catalog\". Self-correcting, rare — native tools are
always directly available to the model.
- **MCP**: no change. No `serializeOutput` flag → identical raw output.
- **Workflow agent**: no change. Doesn't use `execute_tool`.
## Test plan
- [ ] `npx nx typecheck twenty-server` passes (verified: 7 pre-existing
unrelated errors, zero new)
- [ ] \`npx jest
packages/twenty-server/src/engine/api/mcp/services/__tests__/mcp-protocol.service.spec.ts\`
passes in CI
- [ ] AI chat: call a preloaded tool (e.g. \`search_help_center\`)
directly → compacted output
- [ ] AI chat: call a non-preloaded CRUD tool via
\`learn_tools\`/\`execute_tool\` → compacted output (this is the
behavior change)
- [ ] AI chat: native \`web_search\` still works when model calls it
directly
- [ ] MCP: \`tools/call\` on a registry tool → raw output (nulls
preserved)
- [ ] Workflow AI agent: tool dispatch unchanged
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
66a68d8e1c |
i18n - docs translations (#19967)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
b77c44fd20 |
refactor(tool-provider): dedupe descriptor/generator paths (#19960)
## Summary - Every tool provider used to implement `generateDescriptors()` **and** register a category generator at `onModuleInit()` that re-ran the same factories at execute time. `ToolExecutorService` carried two registries (`staticToolHandlers`, `categoryGenerators`) to route between them. - Providers now own execution of their own tools via a new `executeStaticTool()` method. `ToolExecutorService` drops both maps and delegates by `descriptor.category`. Each factory-backed provider has a single `buildToolSet()` used by both descriptor generation and execution. - Extracts `resolveObjectIcon` shared util (was duplicated verbatim in workflow + dashboard providers), and deletes the orphaned `ToolGeneratorModule` whose consumers were removed in the earlier AI chat simplification refactor. No behavior change. Same factories run, same permission checks, same tools execute. Net diff: 18 files, +311 / −480. ## Key changes - `ToolProvider` interface gains `executeStaticTool(name, args, context)`. - `ToolExecutorService` loses its `staticToolHandlers` and `categoryGenerators` maps, injects `TOOL_PROVIDERS`, and does `providers.find(p => p.category === descriptor.category).executeStaticTool(...)` for `kind: 'static'` descriptors. - `ActionToolProvider` drops the register-handler loop in its constructor; `executeStaticTool` looks up in the existing `toolMap`. - `View`, `Metadata`, `Workflow`, `Dashboard`, `ViewField` providers each have a single `buildToolSet(context)` private method used by both `generateDescriptors` and `executeStaticTool`. No more `onModuleInit`, no `ToolExecutorService` dependency. - `DatabaseToolProvider` and `LogicFunctionToolProvider` implement `executeStaticTool` with an invariant-violation throw — they only emit `database_crud` / `logic_function` kinds, so the static-tool path is unreachable for them. - Deletes `tool-generator/` (dead code — zero consumers). ## Dependency graph before/after **Before:** provider → `ToolExecutorService` (for `register*` calls) **After:** `ToolExecutorService` → `TOOL_PROVIDERS` → providers. Cleaner, no cycle. ## Test plan - [ ] `npx nx typecheck twenty-server` passes (verified: same 7 pre-existing unrelated errors) - [ ] `npx nx lint twenty-server` passes - [ ] AI chat: trigger a tool call that hits `execute_tool` fallback (e.g. a view/metadata tool not in the preloaded set) — verify it still executes - [ ] AI chat: trigger a preloaded action tool (e.g. `search_help_center`) — verify it still executes - [ ] MCP: `tools/list` and `tools/call` for both preloaded and catalog-discovered tools - [ ] Workflow AI agent: run a workflow with AI agent step that calls DATABASE_CRUD tools - [ ] Verify the `web_search` / `code_interpreter` tools (if enabled) still dispatch correctly --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
3d164640c8 |
Remove Product Hunt banner section (#19959)
Removed Product Hunt banner from README. |
||
|
|
e184732af4 |
i18n - docs translations (#19958)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
710e3e2dfe |
i18n - docs translations (#19957)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
40b1cdaba6 |
i18n - docs translations (#19956)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
800d0f28ff |
i18n - docs translations (#19955)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
11fd3bd6ee |
i18n - docs translations (#19954)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
b77c65946f |
i18n - docs translations (#19952)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
2a6fed5f5f |
i18n - docs translations (#19948)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
92c8965dd3 |
i18n - docs translations (#19944)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
f19b3bfd38 |
Fix left/right arrow keys not working in dropdown search inputs (#19759)
Closes #12847 Implements a two-mode focus management pattern for `SelectableList` components with search inputs, resolving the conflict between text input cursor movement and grid/list navigation. ### How it works **Input mode** (search input focused): - Left/right arrow keys move the text cursor normally (`enableOnFormTags: false` on ArrowLeft/ArrowRight hotkeys) - Up/down arrow keys blur the input and transfer focus to the grid, entering grid mode **Grid mode** (search input blurred): - All arrow keys navigate the selectable list grid - Pressing up arrow from the top row clears the grid selection and refocuses the search input, returning to input mode - Typing any printable character refocuses the search input (wildcard hotkey with `enableOnFormTags: false`) ### Demo https://github.com/user-attachments/assets/825ad603-a5f8-4863-8269-3ecf35965847 https://github.com/user-attachments/assets/9d07346d-18a0-40fa-8874-21040c11f03d |
||
|
|
0aaffe3212 |
i18n - docs translations (#19943)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
66857ca77b |
Remove cross version upgrade placeholder (#19940)
Moving to somewhere else to leverage docker cache |
||
|
|
76582dc04a |
Export generateDefaultFieldUniversalIdentifier from SDK (#19937)
## Context
- Moved generateDefaultFieldUniversalIdentifier from the internal
cli/utilities/build/manifest/utils/ folder to sdk/define/objects/ and
re-exported it from
the public twenty-sdk/define entry point, so app authors can compute the
deterministic UID
of a default field (id, name, createdAt, …) from their own code.
- Narrowed the signature from { objectConfig: ObjectConfig; fieldName }
to {
objectUniversalIdentifier: string; fieldName: string } — the only thing
the helper needs,
and what app code has on hand.
- Updated the two internal callers and the spec to the new import path
and signature.
```typescript
import {
defineView,
generateDefaultFieldUniversalIdentifier,
} from "twenty-sdk/define";
export default defineView({
universalIdentifier: "70f10d44-144a-4da8-8c6f-3ec2422138c0",
name: "all-thing",
objectUniversalIdentifier: "c782b61c-70fd-4c88-9cd6-4e61ab8d7591",
icon: "IconList",
position: 0,
fields: [
{
universalIdentifier: "75a90bc4-d901-4df4-85e0-af29db5e0104",
fieldMetadataUniversalIdentifier: generateDefaultFieldUniversalIdentifier(
{
objectUniversalIdentifier: "c782b61c-70fd-4c88-9cd6-4e61ab8d7591",
fieldName: "createdAt",
},
),
position: 0,
isVisible: true,
size: 200,
},
{
universalIdentifier: "75a90bc5-d901-4df4-85e0-af29db5e0105",
fieldMetadataUniversalIdentifier: generateDefaultFieldUniversalIdentifier(
{
objectUniversalIdentifier: "c782b61c-70fd-4c88-9cd6-4e61ab8d7591",
fieldName: "createdBy",
},
),
position: 0,
isVisible: true,
size: 200,
},
],
});
```
|
||
|
|
a2099d22b5 |
Optimize website images (#19933)
AVIF has broad support in 2026 we should leverage it for smaller image size <img width="1496" height="847" alt="Screenshot 2026-04-21 at 5 49 58 PM" src="https://github.com/user-attachments/assets/8366a2c7-f72b-4cff-891e-78a372d9a84b" /> ``` /halftone/materials/glass/environment.jpg 41KB 31KB 23% /illustrations/generated/handshake.png 61KB 49KB 19% /illustrations/generated/home-background-bridge.png 19KB 13KB 27% /illustrations/generated/mic.png 47KB 32KB 32% /illustrations/generated/milestone.jpg 101KB 89KB 12% /illustrations/generated/partner-meeting.webp 78KB 37KB 52% /images/case-studies/header-pattern.png 250KB 165KB 33% /images/home/hero/background.webp 101KB 73KB 26% /images/home/hero/foreground.webp 60KB 37KB 38% /images/home/hero/sales-dashboard/distribution.webp 11KB 7KB 29% /images/home/hero/sales-dashboard/revenue.webp 15KB 9KB 37% /images/home/hero/sales-dashboard/visits.webp 14KB 11KB 21% /images/home/hero/twenty-demo-logo.webp 0KB 0KB -99% /images/home/logo-bar/bayer.webp 52KB 37KB 29% /images/home/logo-bar/french-republic.webp 6KB 6KB 5% /images/home/logo-bar/nic.webp 4KB 4KB -3% /images/home/logo-bar/pwc.webp 3KB 2KB 10% /images/home/logo-bar/shiawase-home.webp 7KB 8KB -2% /images/home/logo-bar/windmill-logo.png 2KB 3KB -17% /images/home/logo-bar/windmill-original.webp 2KB 2KB 8% /images/home/problem/monolith-problem.webp 23KB 19KB 13% /images/home/stepper/background-shape.webp 1KB 0KB 34% /images/home/stepper/background.webp 78KB 36KB 53% /images/home/stepper/download-worker.webp 21KB 19KB 6% /images/home/stepper/gears.jpg 24KB 22KB 6% /images/home/three-cards-feature/familiar-interface-gradient.webp 7KB 8KB -11% /images/home/three-cards-feature/familiar-interface.webp 62KB 45KB 27% /images/home/three-cards-feature/fast-path-background-noise.webp 49KB 16KB 66% /images/home/three-cards-feature/fast-path-gradient.webp 7KB 8KB -7% /images/home/three-cards-feature/fast-path.webp 52KB 41KB 21% /images/home/three-cards-feature/live-data-gradient.webp 3KB 2KB 35% /images/home/three-cards-feature/live-data.webp 46KB 36KB 20% /images/partner/hero/hero.webp 123KB 97KB 20% /images/partner/hero/partners-hero.webp 42KB 32KB 23% /images/partner/testimonials/amrendra-singh.webp 6KB 5KB 7% /images/partner/testimonials/benjamin-reynolds.webp 111KB 80KB 27% /images/partner/testimonials/bertrams.jpeg 12KB 10KB 9% /images/partner/testimonials/joseph-chiang.jpg 13KB 11KB 13% /images/partner/testimonials/mike-babiy.png 24KB 19KB 20% /images/partner/testimonials/olivier-reinaud.jpg 11KB 9KB 20% /images/pricing/engagement-band/overlay.webp 72KB 56KB 22% /images/pricing/plans/organization-icon.png 4KB 3KB 10% /images/pricing/plans/pro-icon.png 2KB 3KB -7% /images/pricing/salesforce/help-icon.webp 0KB 0KB -45% /images/product/demo/background.webp 84KB 70KB 16% /images/product/demo/kanban.webp 63KB 57KB 9% /images/product/feature/contacts.webp 44KB 32KB 28% /images/product/feature/dashboards.webp 24KB 21KB 14% /images/product/feature/data.webp 23KB 20KB 13% /images/product/feature/emails.webp 21KB 18KB 12% /images/product/feature/files.webp 14KB 12KB 14% /images/product/feature/mask.webp 148KB 82KB 44% /images/product/feature/pipeline.webp 30KB 26KB 11% /images/product/feature/tasks.webp 37KB 33KB 9% /images/product/stepper/background-shape.webp 1KB 1KB 25% /images/product/stepper/background.webp 61KB 52KB 13% /images/product/stepper/step-one.webp 7KB 7KB 0% /images/product/stepper/step-three.webp 17KB 14KB 13% /images/product/stepper/step-two.webp 7KB 7KB 3% /images/product/tabs/background-shape.webp 7KB 1KB 83% /images/product/tabs/background.webp 180KB 171KB 5% /images/product/tabs/deals.webp 93KB 89KB 4% /images/product/tabs/history.webp 66KB 59KB 10% /images/product/tabs/tasks.webp 90KB 85KB 5% /images/product/tabs/workflow.webp 60KB 55KB 7% /images/releases/0.10/0.10-currency.webp 40KB 28KB 29% /images/releases/0.10/0.10-datetime.webp 34KB 18KB 45% /images/releases/0.10/0.10-json.webp 35KB 25KB 26% /images/releases/0.10/0.10-multi-select.webp 46KB 36KB 22% /images/releases/0.10/0.10-remote.webp 45KB 27KB 39% /images/releases/0.11/0.11-calendar.webp 69KB 56KB 18% /images/releases/0.11/0.11-speed.webp 30KB 21KB 29% /images/releases/0.12/0.12-database-diagram.webp 35KB 25KB 29% /images/releases/0.12/0.12-link-field.webp 50KB 35KB 30% /images/releases/0.12/0.12-loader.webp 57KB 35KB 38% /images/releases/0.12/0.12-notifications.webp 78KB 64KB 17% /images/releases/0.2.3_relations.webp 55KB 39KB 28% /images/releases/0.2.3_webhooks.webp 30KB 25KB 16% /images/releases/0.20/0.20-blocklist.webp 35KB 27KB 22% /images/releases/0.20/0.20-onboarding.webp 68KB 53KB 21% /images/releases/0.20/0.20-timeline.webp 81KB 55KB 32% /images/releases/0.21/0.21-advanced-email-settings.webp 42KB 30KB 27% /images/releases/0.21/0.21-many-many.webp 60KB 46KB 22% /images/releases/0.22/0.22-kanban-improvements.webp 50KB 34KB 31% /images/releases/0.22/0.22-mass-deletion.webp 32KB 20KB 37% /images/releases/0.22/0.22-navbar.webp 35KB 23KB 34% /images/releases/0.23/0.23-created-by.webp 78KB 60KB 23% /images/releases/0.23/0.23-filter-webhooks.webp 54KB 39KB 28% /images/releases/0.23/0.23-notes-tasks.webp 83KB 62KB 25% /images/releases/0.24/0.24-soft-delete.webp 70KB 47KB 31% /images/releases/0.3.0_rating.webp 50KB 46KB 8% /images/releases/0.3.1_contributors.webp 40KB 32KB 20% /images/releases/0.3.2_new_layout.webp 52KB 37KB 28% /images/releases/0.3.3_emails.webp 48KB 38KB 21% /images/releases/0.3.3_kanban.webp 44KB 35KB 19% /images/releases/0.3.3_sign_up.webp 24KB 17KB 25% /images/releases/0.30/0.30-array-field.webp 90KB 68KB 24% /images/releases/0.30/0.30-emails.webp 44KB 30KB 31% /images/releases/0.30/0.30-new-settings.webp 31KB 19KB 37% /images/releases/0.31/0.31-advanced-settings.webp 44KB 30KB 31% /images/releases/0.31/0.31-search.webp 18KB 12KB 31% /images/releases/0.32/0.32-improved-cmdk.webp 50KB 33KB 33% /images/releases/0.32/0.32-webhooks.webp 41KB 30KB 26% /images/releases/0.33/0.33-multiselect-filter.webp 53KB 36KB 31% /images/releases/0.33/0.33-percentage-number.webp 33KB 20KB 38% /images/releases/0.34/0.34-subdomains.webp 57KB 41KB 28% /images/releases/0.35/0.35-Favorites.webp 61KB 41KB 32% /images/releases/0.4/0.4-address-field-type.webp 23KB 15KB 32% /images/releases/0.4/0.4-expand-relation-card.webp 28KB 18KB 34% /images/releases/0.4/0.4-multi-workspace.webp 48KB 35KB 25% /images/releases/0.40/0.40-aggregates.webp 72KB 57KB 21% /images/releases/0.40/0.40-group-by.webp 47KB 32KB 31% /images/releases/0.41/0.41-labs.webp 55KB 40KB 27% /images/releases/0.42/0.42-document-viewer.webp 86KB 72KB 15% /images/releases/0.42/0.42-microsoft.webp 79KB 64KB 18% /images/releases/0.42/0.42-translation.webp 66KB 53KB 19% /images/releases/0.43.0/email-privacy.webp 47KB 33KB 27% /images/releases/0.43.0/search-upgrade.webp 44KB 30KB 31% /images/releases/0.44/0.44-admin-panel.webp 57KB 42KB 25% /images/releases/0.44/0.44-side-panel.webp 67KB 54KB 20% /images/releases/0.50/0.50-advanced-filters.webp 55KB 36KB 33% /images/releases/0.50/0.50-permissions.webp 51KB 37KB 25% /images/releases/0.51.0/0.51-options-menu.webp 59KB 40KB 31% /images/releases/0.52.0/0.52-custom-date-format.webp 37KB 23KB 36% /images/releases/0.52.0/0.52-filtered-views-records.webp 46KB 31KB 31% /images/releases/1.00/1.00-import-update.webp 63KB 39KB 37% /images/releases/1.00/1.00-performance-improvement.webp 52KB 35KB 31% /images/releases/1.00/1.00-permissions.webp 72KB 52KB 28% /images/releases/1.00/1.00-subfield-filtering.webp 73KB 49KB 33% /images/releases/1.00/1.00-workflow.webp 52KB 36KB 29% /images/releases/1.1/1.1-multi-manual-trigger.webp 51KB 37KB 26% /images/releases/1.10/1.10.0-calendar.webp 54KB 38KB 28% /images/releases/1.10/1.10.0-dashboards.webp 33KB 20KB 37% /images/releases/1.11/1.11.0-morph-relations.webp 53KB 40KB 24% /images/releases/1.11/1.11.0-unlisted-views.webp 50KB 34KB 32% /images/releases/1.12/1.12.0-folder-sync.webp 38KB 25KB 32% /images/releases/1.12/1.12.0-side-panel.webp 45KB 34KB 22% /images/releases/1.13/1.13.0-stop-workflow-button.png 39KB 26KB 34% /images/releases/1.14/1.14.0-resize-navbar-and-side-panel.png 58KB 40KB 30% /images/releases/1.15/1.15.0-updated-by-official.webp 36KB 23KB 35% /images/releases/1.15/1.15.0-updated-by.png 36KB 27KB 24% /images/releases/1.15/1.15.0-updated-by.webp 36KB 23KB 35% /images/releases/1.16/1.16.0-files-in-records.webp 43KB 29KB 32% /images/releases/1.16/1.16.0-flexible-relations.webp 42KB 30KB 28% /images/releases/1.17/1.17.0-ai-chat.webp 83KB 67KB 18% /images/releases/1.18/1.18.0-live-updates.webp 56KB 37KB 33% /images/releases/1.18/1.18.0-sidebar-items.webp 36KB 27KB 26% /images/releases/1.19/1.19.0-invite-roles.webp 40KB 26KB 34% /images/releases/1.2/1.2-any-fields.webp 47KB 30KB 35% /images/releases/1.2/1.2-import-relations.webp 42KB 31KB 26% /images/releases/1.20/1.20.0-easier-field-editing.webp 37KB 26KB 27% /images/releases/1.20/1.20.0-field-widgets.webp 38KB 29KB 24% /images/releases/1.21/1.21.0-email-replies.webp 50KB 38KB 25% /images/releases/1.21/1.21.0-maintenance-mode.webp 55KB 39KB 28% /images/releases/1.22/1.22.0-rich-text-layouts.webp 54KB 41KB 24% /images/releases/1.23/1.23.0-easier-layouts.webp 46KB 30KB 34% /images/releases/1.3/1.3-IMAP.webp 83KB 59KB 29% /images/releases/1.3/1.3-merge.webp 64KB 44KB 30% /images/releases/1.4/1.4-field-permissions.webp 50KB 37KB 24% /images/releases/1.4/1.4-two-factor-auth.webp 67KB 50KB 25% /images/releases/1.4/1.4-workflow-filters.webp 38KB 26KB 31% /images/releases/1.5/1.5-workflow-branches.webp 31KB 21KB 31% /images/releases/1.6/1.6-workflows-improvements.webp 45KB 31KB 29% /images/releases/1.7/1.7-impersonating.webp 79KB 60KB 23% /images/releases/1.7/1.7-upsert.webp 51KB 31KB 38% /images/releases/1.8/1.8-bulk-select.webp 49KB 34KB 29% /images/releases/1.8/1.8-search-limit.webp 34KB 23KB 31% /images/releases/1.8/1.8-workflow-iterator.webp 38KB 23KB 39% /images/releases/labs/translation.webp 65KB 46KB 28% /images/shared/companies/logos/a16z.png 0KB 0KB -48% /images/shared/companies/logos/accel.png 0KB 0KB -129% /images/shared/companies/logos/airbnb.png 0KB 0KB -52% /images/shared/companies/logos/airtable.png 0KB 1KB -42% /images/shared/companies/logos/anthropic.png 0KB 0KB -45% /images/shared/companies/logos/apple-1977.png 0KB 1KB -54% /images/shared/companies/logos/apple.png 0KB 0KB -89% /images/shared/companies/logos/calendar.png 0KB 0KB -48% /images/shared/companies/logos/claude.png 40KB 31KB 21% /images/shared/companies/logos/cursor.png 0KB 0KB -58% /images/shared/companies/logos/docusign.png 0KB 0KB -48% /images/shared/companies/logos/figma.png 0KB 0KB -46% /images/shared/companies/logos/founders-fund.png 0KB 0KB -61% /images/shared/companies/logos/github.png 0KB 0KB -37% /images/shared/companies/logos/gmail.png 0KB 1KB -60% /images/shared/companies/logos/google.png 0KB 1KB -36% /images/shared/companies/logos/hubspot.png 0KB 0KB -2% /images/shared/companies/logos/kleiner-perkins.png 0KB 0KB -105% /images/shared/companies/logos/lemlist.png 0KB 0KB -64% /images/shared/companies/logos/linkedin.png 0KB 0KB -66% /images/shared/companies/logos/mailchimp.png 0KB 0KB -39% /images/shared/companies/logos/meet.png 0KB 0KB -51% /images/shared/companies/logos/metabase.png 1KB 1KB -32% /images/shared/companies/logos/microsoft.png 0KB 0KB 4% /images/shared/companies/logos/notion.png 0KB 1KB -19% /images/shared/companies/logos/okta.png 0KB 0KB -35% /images/shared/companies/logos/openai.png 0KB 1KB -13% /images/shared/companies/logos/outlook.png 0KB 0KB -26% /images/shared/companies/logos/outreach.png 0KB 0KB -53% /images/shared/companies/logos/postgresql.png 1KB 1KB -2% /images/shared/companies/logos/qonto.png 0KB 0KB -61% /images/shared/companies/logos/salesforce.png 0KB 0KB -39% /images/shared/companies/logos/segment.png 0KB 1KB -49% /images/shared/companies/logos/sequoia.png 0KB 0KB -59% /images/shared/companies/logos/slack.png 0KB 0KB -40% /images/shared/companies/logos/stripe.png 0KB 0KB -51% /images/shared/companies/logos/tally.png 0KB 0KB -53% /images/shared/companies/logos/twenty.png 0KB 0KB -56% /images/shared/companies/logos/whatsapp.png 0KB 0KB -64% /images/shared/companies/logos/zapier.png 0KB 0KB -27% /images/shared/engagement-band/halftone-on-gray.png 7KB 17KB -121% /images/shared/engagement-band/halftone-on-white.png 2KB 2KB -2% /images/shared/light-noise.webp 0KB 0KB -225% /images/shared/menu/developers-preview.png 65KB 33KB 49% /images/shared/menu/partners-preview.png 52KB 24KB 53% /images/shared/menu/user-guide-preview.png 58KB 50KB 14% /images/shared/people/avatars/alexandre-prot.jpg 0KB 0KB -32% /images/shared/people/avatars/anonymous-anna.jpg 0KB 0KB -81% /images/shared/people/avatars/anonymous-fabrice.jpg 0KB 0KB -28% /images/shared/people/avatars/anonymous-felix.jpg 0KB 0KB -40% /images/shared/people/avatars/anonymous-indira.jpg 0KB 0KB -44% /images/shared/people/avatars/anonymous-laura.jpg 0KB 0KB -52% /images/shared/people/avatars/anonymous-mike.jpg 0KB 0KB -78% /images/shared/people/avatars/anonymous-thomas.jpg 0KB 0KB -44% /images/shared/people/avatars/ben-chestnut.jpg 0KB 0KB -74% /images/shared/people/avatars/brian-chesky.jpg 0KB 0KB -52% /images/shared/people/avatars/chris-wanstrath.jpg 0KB 0KB -46% /images/shared/people/avatars/craig-federighi.jpg 0KB 0KB -63% /images/shared/people/avatars/dario-amodei.jpg 15KB 14KB 4% /images/shared/people/avatars/dylan-field.jpg 0KB 0KB -62% /images/shared/people/avatars/eddy-cue.jpg 0KB 0KB -54% /images/shared/people/avatars/ivan-zhao.jpg 0KB 0KB -69% /images/shared/people/avatars/jeff-williams.jpg 0KB 0KB -59% /images/shared/people/avatars/joe-gebbia.jpg 0KB 0KB -52% /images/shared/people/avatars/katherine-adams.jpg 0KB 0KB -64% /images/shared/people/avatars/patrick-collison.jpg 0KB 0KB -20% /images/shared/people/avatars/peter-reinhardt.jpg 0KB 0KB -87% /images/shared/people/avatars/peter-thiel.jpg 0KB 0KB -57% /images/shared/people/avatars/phil-schiller.jpg 0KB 0KB -52% /images/shared/people/avatars/ping-li.jpg 13KB 12KB 10% /images/shared/people/avatars/ray-damm.jpg 0KB 0KB -41% /images/shared/people/avatars/reid-hoffman.jpg 0KB 0KB -60% /images/shared/people/avatars/roelof-botha.jpg 0KB 0KB -41% /images/shared/people/avatars/ryan-roslansky.jpg 0KB 0KB -46% /images/shared/people/avatars/steve-anavi.jpg 0KB 0KB -32% /images/shared/people/avatars/stewart-butterfield.jpg 0KB 0KB -36% /images/shared/people/avatars/sundar-pichai.jpg 0KB 0KB -41% /images/shared/people/avatars/thomas-dohmke.jpg 15KB 14KB 4% /images/shared/people/avatars/tim-cook.jpg 0KB 0KB -68% /images/why-twenty/hero/background.webp 10KB 9KB 11% === TOTALS === Files: 249 Total WebP: 8.2 MB Total AVIF: 6.1 MB Total saved: 2.1 MB (26%) ``` |
||
|
|
a7b10b281c |
Optimize 3d models (#19935)
Remove unused textures and UV data from 3D models Also simplified meshes where possible and re-compressed Verified that no visual regression was seen but needs to be double checked in case I missed something Total model size: 5.1MB -> 1.5MB |