d51d982a5ffdb389ca03d2b8b28091059be78fbd
9578
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d51d982a5f |
Fix: Database query on opportunity table (#20017)
## Automated fix for [bug 30463](https://sonarly.com/issue/30463?type=bug) **Severity:** `critical` ### Summary When createMany is called with upsert:true and records lack pre-assigned IDs, findExistingRecords() executes a SELECT with no WHERE clause, scanning the entire table. This caused an 11-second transaction on the opportunity table (2.9s query + 7.7s JS processing). ### Root Cause 1. WHY was the transaction 11 seconds? Because a SELECT on the opportunity table took 2.9s and its result processing took 7.7s. 2. WHY did the SELECT take 2.9s? Because it was a full table scan with NO WHERE clause, fetching every record (including soft-deleted). 3. WHY was there no WHERE clause? Because findExistingRecords() in common-create-many-query-runner.service.ts calls buildWhereConditions() which returned an empty array, so no .orWhere() was applied to the query builder before .getMany() was called. 4. WHY did buildWhereConditions return empty? Because the input records had no pre-assigned IDs and the opportunity object has no other unique fields, so there were no conflicting field values to build conditions from. 5. WHY wasn't the empty-conditions case guarded? The findExistingRecords() method was written without an early-return check for empty whereConditions — it always executes the query regardless. **Introduced by:** etiennejouan on 2025-10-15 in commit [`4ae2999`](https://github.com/twentyhq/twenty/commit/4ae299973bcea3470252c4c68540d33a4df59cc7) > Common Api - createOne/Many (#15083) ### Suggested Fix Added an early return in findExistingRecords() when buildWhereConditions() returns an empty array. When no WHERE conditions exist (because input records lack values for any unique/conflicting field), the method now returns an empty array immediately instead of executing a SELECT with no WHERE clause that scans the entire table. This prevents the O(n) full table scan + O(n) JS result processing that caused the 11-second transaction. The downstream categorizeRecords() correctly handles empty existingRecords by classifying all input records as recordsToInsert. --- *Generated by [Sonarly](https://sonarly.com)* Co-authored-by: Sonarly Claude Code <claude-code@sonarly.com> |
||
|
|
dbc350fafe |
i18n - translations (#20016)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
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"
/>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |
||
|
|
3eabdf302e |
Allow closing navbar folder while viewing an active child item (#19936)
### Before https://github.com/user-attachments/assets/f8c5c075-b086-4a4c-9514-c7cbbf9b7c56 ### After https://github.com/user-attachments/assets/37427428-046c-4815-a57e-bc5c4fcc7f68 |