28adcbffb9cacea4e4ab4674b87cff8bdcd945b7
288 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2ef7b7824e |
Upgrade call-recorder, people-data-labs, last-contact and partners apps to twenty-sdk 2.23.0-alpha.1 (#23098)
## What Upgrades the two breaking-change-prone apps to `twenty-sdk` / `twenty-client-sdk` `2.23.0-alpha.1`, and adds the server-side hook that lets the 2.23 upgrade install them: - **people-data-labs** - **partners** Follows up on #22882 (System side effect relations), which re-derived the system relation field universal identifiers name-free and shipped `getSystemRelationFieldUniversalIdentifier` in the SDK. ## How - **people-data-labs**: bump the SDK to `2.23.0-alpha.1`. The enriched views temporarily hardcoded the new system relation identifiers with a TODO because the SDK still embedded the old values; now that the name-free identifiers ship in `2.23`, derive them from `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.{company,person}.fields.{noteTargets,taskTargets,attachments,timelineActivities}.universalIdentifier` (identical to the previously pinned values, verified). Engine already pinned `twenty >=2.23.0`; app stays `1.0.7` (manifest unchanged). - **partners**: bump the SDK to `2.23.0-alpha.1`. The partner role references `opportunity.fields.{taskTargets,noteTargets,attachments,timelineActivities}` universal identifiers, which the SDK now resolves to the `2.23` name-free values. Pin `engines.twenty >=2.23.0` and bump the app to `1.3.1`. - **server**: add an opt-in `skipWorkspaceCompatibilityCheck` to the install/upgrade path. The `upgrade-people-data-labs-application` 2.23 command runs mid-upgrade, before the workspace is marked as having completed 2.23, so the workspace-compatibility check would otherwise reject installing `1.0.7` (`engines >=2.23.0`). The server is already on 2.23, so the command passes the flag to install `1.0.7` and close the desync window. Version-progression (downgrade/same-version) checks still run. - **call-recorder** and **last-contact** are intentionally left unchanged (reverted): they don't define custom objects and don't reference the system relation identifiers, so they aren't breaking-change-prone and need no SDK bump. ## Breaking change constraints - **people-data-labs** and **partners** reference system relation identifiers that only exist on a `2.23` server, so both pin `engines.twenty >=2.23.0`. Their `dockerhub-latest` integration leg is red by design until a >=2.23 server image is published (same accepted state as #22882); the `local` leg is green. ## Validation - Regenerated the app lockfiles against the published `2.23.0-alpha.1`. - `people-data-labs` typechecks cleanly against the real `2.23` SDK types. - CI: people-data-labs and partners green on `local`, red on `dockerhub-latest` by design; server/SDK/all other checks green. - Rebased onto latest `main`. |
||
|
|
a0e8d48656 |
Reduce call-recorder recovery crons to daily to relieve production (#23099)
## Context Call Recorder is installed on 700+ workspaces and its two recovery crons run every 15 minutes with the same pattern in every workspace, so all executions land on the same minute boundaries and impact production. The `callRecording.updated` event trigger (#23014) now covers the fast path within seconds; these crons are only backstops for crashed creations and missed webhooks. ## What changed Pattern updates only, no logic changes: - `process-pending-call-recording-requests`: `*/15 * * * *` -> `0 3 * * *` - `reconcile-stale-bot-state`: `*/15 * * * *` -> `30 3 * * *` The daily times are staggered half an hour apart from the existing daily crons (04:00 upcoming-events sweep, 04:30 orphaned-bots cleanup) so the four daily jobs never coincide. ## Notes - Recovery latency for rows missed by the event trigger becomes up to 24h instead of 15min, which is acceptable for backstops (the 7-day convergence lookback is unaffected). - Cron patterns live in installed manifests, so existing installations pick this up on app upgrade only. - The daily herd across workspaces at 03:00/03:30 remains synchronized until generic cron spreading lands server-side (#23088 covers only the `*/5` and `*/15` patterns). --------- Co-authored-by: martmull <martin@twenty.com> |
||
|
|
1be5a0e54a |
System side effect relations (#22882)
Closes twentyhq/core-team-issues#2667 ## What Default relations to the standard relation objects (`timelineActivities`, `attachments`, `noteTargets`, `taskTargets`) are now fully owned by the **metadata side-effect engine**. Neither the API transpilers nor the SDK manifest builder provision them anymore: any object creation, rename or deletion — regardless of the caller — goes through the same engine handlers. ## Why - Provisioning was duplicated across the API path and the SDK manifest builder, with diverging behavior. - Universal identifiers of relation fields were derived from object **names**, so renaming an object mutated them and forced lossy delete+create cycles on manifest sync. ## How ### Engine-owned lifecycle (side-effect handlers) - `objectSystemRelationsOnCreate`: provisions the 8 forward/reverse relation fields (+ join column indexes) when an object is created. - `objectSystemRelationsOnUpdate`: renames the reverse morph fields (`target<ObjectName>`) when their host object is renamed — a lossless `fieldMetadata.update`. - `objectSystemSideEffectsOnDelete`: cascades deletion of engine-owned fields/indexes when the object is deleted. - The API transpilers and the SDK `buildManifest` no longer inject these fields; `isSystemSideEffect: true` marks engine-owned entities, guarded by a granular property allowlist (only `isActive` is user-editable) and excluded from manifest deletion inference. ### Name-free deterministic universal identifiers New `getSystemRelationFieldUniversalIdentifier({ applicationUniversalIdentifier, objectUniversalIdentifier, relationTargetObjectUniversalIdentifier })` in `twenty-shared`, exported from `twenty-sdk/define`. The identifier is keyed on the two **object** identifiers instead of field names (direction encoded by argument order), so object renames never mutate relation field identifiers. It cannot collide with the name-based `getFieldUniversalIdentifier` derivation (field names cannot contain `:`). ### twenty-standard re-owned All 48 forward/reverse system relation field declarations in `STANDARD_OBJECTS` now pin the derived name-free identifiers (computed inline via the shared util) and carry `isSystemSideEffect: true`, with labels/icons declared explicitly (translated via `msg`). `twenty-standard` is projected as if the engine had generated these fields itself. ### 2.23 upgrade commands - `reconcile-system-relation-field-universal-identifier`: structurally matches existing default relation fields per workspace and backfills the derived universal identifiers, `isSystemSideEffect` flags, and standard labels/icons. - `upgrade-people-data-labs-application`: upgrades installed PDL apps to `1.0.7` right after the backfill to close the desync window (its views reference the re-derived identifiers). ### Misc - `people-data-labs` `1.0.7`: views temporarily pin the new derived identifiers (TODO: import from the next released `twenty-sdk`). - `UpgradeStatusModule` split out of `UpgradeModule` so the application module cluster can consume upgrade status/migration services without importing the versioned command bundles (fixes a require cycle that crashed boot). - Docs: `system-fields.mdx` documents the system relation fields and their resolver; `sync-and-recovery.mdx` plan example no longer shows auto-injected relations. ## Known red CI `people-data-labs (dockerhub-latest)` fails by design until the 2.23 server image is published: the app pins the new identifiers which only exist on a 2.23 server. The `local` leg (server built from this branch) is green. ## System fields are no longer manifest-authorable (accepted regression) The manifest converter no longer derives `isSystem` / `isSystemSideEffect` from field names. Reserved-system-named manifest fields (`id`, `createdAt`, `updatedAt`, `deletedAt`, `createdBy`, `updatedBy`, `position`, `searchVector`) are now skipped at conversion time when they carry the exact derived universal identifier (keeps manifests built with older SDKs installable), and rejected with `INVALID_INPUT` when they pin any other identifier. System fields are therefore fully engine-canonical: nothing a manifest carries can produce a system-flagged entity anymore. **Accepted regression**: a manifest can no longer influence system field properties at all. Previously a (legacy) re-declaration could shape them at creation — which actually produced broken system fields, e.g. a nullable, non-unique `id` — and could still toggle the allowlisted `isActive` / `universalSettings` afterwards. We consider this acceptable for now: per-app granularity over system fields will be reintroduced later through the **override framework**, which will also settle update semantics by forbidding direct updates over `isSystemSideEffect: true` entities and expressing divergence as overrides. `isSystemSideEffect`-only entities (the default relation fields provisioned by this PR) still have no engine-level update guard (see Follow-up below); that part is unchanged and also lands with the overrides refactor. ## Follow-up `isSystemSideEffect` field update/delete guards intentionally live at the API layer (`sanitize-raw-update-field-input.ts`, `from-delete-field-input-...util.ts`) rather than in the engine-level `FlatFieldMetadataValidatorService`. Moving them into the validator requires threading operation-origin (direct field mutation vs engine cascade) through the migration matrix, otherwise legitimate object rename/delete cascades (which carry `isSystemBuild=false`) would be rejected. Tracked in twentyhq/core-team-issues#2671. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22882?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
1b5e974629 |
feat(call-recorder): add copy-to-clipboard buttons for transcript, summary, and video link (#23052)
## Context Closes twentyhq/core-team-issues#2692. Adds copy-to-clipboard actions to the call recorder app so users can quickly share a call's transcript, summary, and video. ## What changed - **Copy transcript** button in the *Recording and Transcript* widget header. Copies the transcript as plain text with resolved speaker display names and timestamps (mirroring what is shown on screen). - **Copy video download link** button in the same header. Copies the signed video file URL. - **Copy summary** button in the *Summary* widget header. Copies the summary markdown. Each button is powered by a new reusable `CopyToClipboardButton` component that writes to the clipboard, briefly swaps to a check icon for feedback, and surfaces a success/error snackbar. Buttons are disabled when there is nothing to copy (no transcript / video / summary, or while loading). A `buildTranscriptPlainText` utility turns parsed transcript entries into shareable text, with participant display names preferred over raw diarized speaker labels. ## Screenshots The *Recording and Transcript* header now shows a copy-transcript and a copy-video-link button, and the *Summary* header shows a copy-summary button. | Light | Dark | | --- | --- | | <img width="426" src="https://raw.githubusercontent.com/twentyhq/twenty/claude/issue-2692-screenshots/.github/pr-screenshots/2692/call-recorder-copy-buttons-light.png" /> | <img width="426" src="https://raw.githubusercontent.com/twentyhq/twenty/claude/issue-2692-screenshots/.github/pr-screenshots/2692/call-recorder-copy-buttons-dark.png" /> | ## Tests - New unit tests for `buildTranscriptPlainText` (speaker/timestamp formatting, missing timestamps, participant name resolution). - Full app unit suite passes (491 tests), plus typecheck and lint. |
||
|
|
5bf3472eb9 |
chore(twenty-exa): bump to 0.2.0, add marketplace metadata and Twenty version floor (#23063)
## What Prepares the Exa app (`@twentyhq/twenty-exa`) for a fresh npm release. - Bump `version` `0.1.0` → `0.2.0` - Add `engines.twenty: ">=2.19.0"` so older servers don't install an incompatible build - Add marketplace metadata in `defineApplication()`: `category: 'Search'`, `websiteUrl`, `termsUrl`, `emailSupport`, `issueReportUrl` (matching the values used by the other `@twentyhq/*` apps) ## Why The version currently published on npm is the **unscoped** `twenty-exa@0.1.0`, which predates several SDK breaking changes. The in-repo source has since migrated to `twenty-sdk@~2.16` and `exa-js` v2: - `chargeCredits` now imported from `twenty-sdk/billing` (was a local util) - logic function uses `toolTriggerSettings.inputSchema` (was `isTool` + `toolInputSchema`) - schema type imported from `twenty-sdk/logic-function` (was `twenty-shared/logic-function`) - `category` enum updated to the exa-js v2 union (removed `github`/`tweet`/`linkedin profile`, added `people`) So the published build is effectively broken on current servers. This PR readies a `0.2.0` release under the standard scoped name `@twentyhq/twenty-exa`. The app's `universalIdentifier` is unchanged (`2b7f4a2e-9c4b-4a11-b63c-2e5e7d3f5a9a`), so Twenty treats this as the **same app** and upgrades existing installs in place — the name change (unscoped → scoped) is only an npm-registry concern. ## Changes - `packages/twenty-apps/public/twenty-exa/package.json` - `packages/twenty-apps/public/twenty-exa/src/application.config.ts` ## Testing - `yarn typecheck` — pass - `yarn lint` — pass (0 errors) - `yarn twenty dev:build` — builds a valid `@twentyhq/twenty-exa@0.2.0` tarball ## Follow-up (not in this PR — npm/ops, needs auth) - Publish `@twentyhq/twenty-exa@0.2.0` to npm (`yarn twenty app:publish`) - Deprecate + de-keyword the old unscoped `twenty-exa` so only one package feeds the shared `universalIdentifier` on catalog sync <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23063?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
e6c6cccafa |
v1.3.0 — Partner workspace self-service (glowup app) (#22929)
## Glowup — app · v1.3.0 (Release ② of the brief + glowup rollout) Partner **workspace self-service**: partners manage their own profile, links, services, and case studies from inside the CRM (new objects + record-page views + a "My Profile" self-service front-component). Evolved superset of the closed #22470 (v1.3.0). App-only — **0 website files**. Version **1.3.0** (prod is currently 1.2.10). SDK **2.19.0**. Supersedes **#22470** (closed). ### Verified locally Provisioned a throwaway workspace, synced the schema, seeded, and exercised the full surface end-to-end: marketplace + public profiles render live; **partner self-service pages** (My Profile / My Case Studies / links / services) load and save when acting as a partner user; both intake forms (partner application + client brief) submit successfully. `oxlint` 0/0, typecheck clean. ### Notes - Committed `APPLICATION_UNIVERSAL_IDENTIFIER` is the **canonical** prod id `e662fc1f-02c1-41ff-b8ba-c95a447b3965` (local bundle rewrites it to a throwaway that stays uncommitted). - New views reference app-owned fields only — no hardcoded system-field ids. ### Remaining before merge - CI lint / typecheck / tests (green locally). - Refresh the partners-doc (new objects/views change the app surface). --- ## 🚦 Release order — do not break ``` ① BRIEF WEB — #22291 ✅ MERGED (website deploy pending prod CLIENT_BRIEF_* env vars) │ ▼ ② GLOWUP APP — THIS PR (rk-partner-profile-page v1.3.0 → main) ⟵ replaces #22470 merge → DEPLOY TO PROD (verify canonical id first, yarn twenty deploy && install -r partner-twenty-com) → set new app variables on prod → refresh partners-doc │ ⟵⟵ GATE for ③ ⟵⟵ ▼ ③ GLOWUP WEB — rk-glowup-web-stacked (reopen ONE PR, base main; was #22471 / #22402) ONLY after ② is LIVE on prod (the site reads the new links / services / case-study objects) ``` - ② gates only ③. After ② deploys, reconcile **#22637** (partners-traffic-web) with ③ — both touch `partners-marketplace/*`. |
||
|
|
fa720358d9 |
Ignore call-recorder bots for unsupported meeting platforms (#23050)
## What The call-recorder scheduled a Recall bot for any calendar event that had a conference link, even when the link pointed to a platform Recall cannot join (e.g. ro.am, Daily, Whereby, or a plain dial-in). Those requests could never produce a recording. This adds a supported-platform check to the recording policy so unsupported links are ignored, with a dedicated reason, and documents the supported platforms in the app README. ## Changes - Add `SUPPORTED_MEETING_PLATFORM_URL_PATTERNS` constant (Zoom, Google Meet, Microsoft Teams, Webex, GoTo Meeting), extracted from the existing link-extraction patterns so extraction and validation share one source of truth. - Add `isSupportedMeetingPlatformUrl` util. - `resolveCallRecorderPolicyResult` now returns `UNSUPPORTED_MEETING_PLATFORM` (bot not required) when the resolved conference link is not a supported platform. - Document supported platforms and the ignore behavior in the call-recorder README. ## Tests - New unit tests for `isSupportedMeetingPlatformUrl`. - New policy test for the unsupported-platform case; updated existing policy tests to use real supported URLs. - All call-recorder unit tests pass; typecheck and lint clean. Closes twentyhq/core-team-issues#2705 --- _Generated by [Claude Code](https://claude.ai/code/session_01MWPkbdUg4QMdj4FM5mtNww)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23050?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
c90057178c |
Bump app version (#23049)
as title <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23049?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
89609c520c |
Reduce call-recorder Recall API load and harden bot scheduling recovery (#23014)
## Context We receive Recall rate limit alerts on `/api/v1/bot`. Recall's List Bots endpoint allows only 60 requests/min per Recall workspace (vs 300/min for Retrieve and 120/min for Create), and that budget is shared by every Twenty workspace on the instance since `RECALL_API_KEY` is a server-level variable. The call-recorder recovery crons fanned out one list call per stuck recording, fired at the same wall-clock minute for every workspace, and never resolved dead rows, so the pending set only grew. This PR reworks the bot-scheduling recovery mechanism so that crash-recovery work is rare, cheap, and mostly event-driven. One commit per change: ## Changes 1. **Fail never-scheduled recordings once their meeting ends** (`bot_never_scheduled` failure reason). Previously these rows stayed `REQUESTED+SCHEDULED` forever and were re-fetched by every recovery run. Rows with an unresolved creation attempt keep their recovery chance until the 7-day convergence lookback passes (a bot may have recorded before the id write-back was lost), then fail as `bot_schedule_outcome_unknown`. 2. **Batch bot lookups into one list call per run.** The pending-bot sweep and the failed-cancellation retry each issue at most one workspace-wide `GET /api/v1/bot/` (filtered by `twentyWorkspaceId` + active statuses) and match bots to recordings in memory via `twentyCallRecordingId` metadata, instead of one list call per stuck row. Truncated lists count as failed lookups so an incomplete map never authorizes a duplicate creation. 3. **Record a `botScheduleAttemptedAt` marker before POSTing a bot.** Recovery can now distinguish rows that never reached Recall (re-schedule directly, zero Recall reads) from rows whose creation outcome is unknown (only these join the lookup). 4. **Store the bot-creation `Idempotency-Key` on the row and recover by re-sending.** When a stuck row's stored key still hashes from the current scheduling inputs, recovery re-sends the creation: Recall either returns the existing bot or creates the intended one, all on the Create budget (120/min) without touching the List budget (60/min). Drifted inputs still fall back to the lookup. Re-sends preserve the first attempt's timestamp and are only trusted within a 12-hour window, so repeated unknown outcomes age into the lookup path rather than risking a twin bot after Recall's key retention expires. 5. **Resume pending rows on `callRecording.updated` events and slow the cron.** A new database-event trigger resumes scheduling within seconds when a row transitions back to pending (bot vanished at Recall, canceled request re-requested, failed row reset by reconciliation), with queue retries. It skips creations (the inserting run schedules inline), skips its own progress writes, uses slim-payload diffs to skip cheaply, and defers ambiguous rows to the cron so event bursts cannot fan out list calls. The pending-requests cron becomes a backstop and drops from every 5 minutes to every 15. Follow-up commits harden edge cases raised in review (status revalidation before POST, per-row cancellation recovery window, future-timestamp guard, attempt-state cleanup when a bot is confirmed gone at Recall) and add a lifecycle integration test. ## Notes - Two new app fields on `callRecording`: `botScheduleAttemptedAt` (DATE_TIME) and `botScheduleIdempotencyKey` (TEXT), both nullable and not UI-editable. - A tight race between the event trigger and the cron converges on one bot via the deterministic idempotency key. - Not addressed here (needs a server-side change): per-workspace jitter when dispatching logic-function cron triggers, so identical patterns don't fire for every workspace on the same minute. ## Test - New `call-recorder-lifecycle.integration-test.ts` on the app's integration harness: the global setup installs the app on a live test server, all reads and writes go through the real API into the test database, and only externals are mocked — the Recall API (a fetch interceptor that replays the same bot for a repeated `Idempotency-Key`, like the real API) and the trigger transports (webhook payloads invoke the webhook logic function handler; cron and database-event triggers run their flows). Thirteen scenarios assert the resulting CallRecording rows in the DB: scheduling from calendar reconciliation (events attached to a seeded `SHARE_EVERYTHING` calendar channel, since unassociated events are invisible), webhook status progression with artifact-import route calls, transcript completion, out-of-order delivery protection, fatal failure, unknown bots, cancellation with retried Recall delete, and every crash recovery path. Verified locally against a live server: 15 integration tests pass (including the existing schema contract test). - `yarn test:unit`: 488 tests pass. `yarn typecheck` and `yarn lint` clean. --------- Co-authored-by: martmull <martin@twenty.com> |
||
|
|
4e2f9e3416 |
Fix join at in the past (#23000)
as title, we floor the bot join at date 1second in the future <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23000?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
19f0e3cad5 |
Reduce Recall bot lifecycle reconciliation traffic (#22908)
## Summary - split pending Call Recording request maintenance from stale recording convergence - recover Recall bots by workspace and Call Recording metadata before creating a replacement - retry failed cancellations, including the canceled-plus-botless write-back race - move the broad orphaned-bot list sweep from every five minutes to a dedicated daily job - filter Recall bot lists by workspace metadata at the provider boundary ## Why This is stack 1/3 extracted from #22739. Healthy installed workspaces currently list Recall bots every five minutes even when no local state has diverged. This layer removes that unconditional list sweep while keeping pending request recovery at five-minute latency. The cancellation recovery also closes a crash window where Recall accepted a bot creation but the local bot ID write-back failed before the user canceled the request. The maintenance job now rediscovers and cancels that bot before it can join. ## Stack 1. **Recall bot lifecycle reconciliation** — this PR 2. Divergence-scoped recording synchronization — #22909 3. Artifact import offloading — #22910 ## Validation - `npm run typecheck` - `npm run lint` - `npm run test:unit` — 71 files, 454 tests --------- Co-authored-by: Claude <martmull@hotmail.fr> |
||
|
|
79f3a5243a |
Add callAppRoute to RestApiClient (#22863)
Adds a `callAppRoute` method to `RestApiClient` in `twenty-client-sdk/rest`. It calls one of the app's own HTTP routes using the injected `TWENTY_FUNCTIONS_URL`, resolved internally the same way the client already resolves `TWENTY_API_URL`, so app code no longer reads env vars or knows how function routes are hosted. Both app runtimes already go through `RestApiClient` for route calls (logic functions and front components), so both get this in one place; front components keep the existing 401 token-refresh flow. Pairs with #22825, which makes the injected `TWENTY_FUNCTIONS_URL` callable in every topology (app custom domain -> workspace isolated functions domain -> `SERVER_URL/s`). Once this ships in an SDK release, Call Recorder's own-route plumbing (logic-function and front-component utils) drops its URL resolution and calls `client.callAppRoute(path, body)`. --------- Co-authored-by: martmull <martmull@hotmail.fr> Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> |
||
|
|
cdb2590355 |
Migrate call-recorder tests off own-code vi.mock (#22902)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22902?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
2201917f33 |
Harden call recorder Recall API boundary (#22832)
Part 2/5 of splitting #22739. Stacked on #22831. - `RecallBotSnapshot`: Recall bot payloads are parsed once at the API boundary (`parseRecallBotSnapshot`); `getRecallBot`/`listScheduledRecallBots` return typed snapshots, flows never touch raw provider records - Retry policy: honors `Retry-After` (seconds or HTTP-date, capped at 60s), treats 409 and 507 (ad-hoc pool exhausted) as retryable with tailored delays, adds equal jitter to the linear backoff, and returns instead of sleeping past 10s in-process so invocations never sleep into their timeout - `listScheduledRecallBots` accepts a server-side `metadata__` filter and reports `truncated` instead of failing beyond 10 pages - Extracts `cancelOrEjectRecallBot` into the recall-api layer - Replaces the `CALL_RECORDER_MAX_MEDIA_FILE_SIZE_MB` server variable with a fixed 500 MB constant: uploads stream since #22652, so the cap no longer guards function memory and does not need to be operator-tunable Next: billing charge verification, divergence-scoped sync crons, webhook artifact continuation. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22832?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
f13bde1e03 |
Align call recorder vocabulary with core dialect (#22831)
Part 1/5 of splitting #22739 into a reviewable stack. Mechanical renames only, no behavior change: - `ingestion` -> `import` across data/domain/flows (`completeCallRecordingIngestion` -> `completeCallRecordingImport`, `ingestCallRecordingMedia` -> `importCallRecordingMedia`, `reconcileCallRecordingTranscriptArtifact` -> `importCallRecordingTranscript`, ...) - `reapOrphanedCallRecorders` -> `cleanupOrphanedRecallBots` - `ensureCallRecorder` -> `scheduleRecallBotForCallRecording`, `healCallRecordingsMissingBot` -> `scheduleRecallBotsForPendingCallRecordings` - `extractRecallBotConvergence` -> `extractRecallBotSyncState` - formatting drift in touched files Next in the stack: Recall API hardening, billing charge verification, divergence-scoped sync crons, webhook artifact continuation. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22831?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
bc1ccf526f |
chore(twenty-partners): upgrade to twenty-sdk 2.21 (#22869)
## What Upgrades the `twenty-partners` internal app to consume twenty-sdk 2.21. - Bump `twenty-sdk` and `twenty-client-sdk` from `2.19.0-alpha.1` to `2.21.0` (`package.json` + `yarn.lock`). - Replace the removed `generateDefaultFieldUniversalIdentifier` helper with `getFieldUniversalIdentifier` in `partner-applications.view.ts`, renaming the `fieldName` argument to `name`. ## Why `generateDefaultFieldUniversalIdentifier` was removed from the SDK and replaced by `getFieldUniversalIdentifier`. Both compute the same deterministic uuid-v5 (`fieldMetadata:objectUID:name` under the app universal identifier), so the resolved `createdAt` field identifier is unchanged; this is the only code change required to build against 2.21. ## Verified - Install resolves to 2.21.0; runtime check confirms `getFieldUniversalIdentifier` is exported and `generateDefaultFieldUniversalIdentifier` is gone. - Type check: no real errors. - `oxlint`: 0 warnings, 0 errors. --- _Generated by [Claude Code](https://claude.ai/code/session_01RUQ2yyfZcyKWqG57HbUYxx)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22869?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
a6af730353 |
chore: upgrade call-recorder, last-contact, people-data-labs to twenty-sdk 2.20 (#22852)
## What Upgrades three public apps to `twenty-sdk` 2.20. For each app, bumped `twenty-sdk` and `twenty-client-sdk` to `2.20.0`, raised the `engines.twenty` floor to `>=2.20.0`, and regenerated `yarn.lock`: - **call-recorder**: `2.19.0` -> `2.20.0` - **last-contact** (`@twentyhq/last-contact`): `2.19.0-alpha.1` -> `2.20.0` - **people-data-labs**: `2.19.0-alpha.1` -> `2.20.0` ## Verification - Lockfile diffs are version/checksum-only; the SDK's transitive dependency set is unchanged between 2.19 and 2.20, so no new packages were introduced. - `yarn typecheck` passes cleanly for all three apps against 2.20, confirming no breaking API changes to adapt to. --- _Generated by [Claude Code](https://claude.ai/code/session_01YWiC3qAbBcE1kvBMWvaxba)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22852?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
7f8a1da27a |
Fix Cloudflare rate limiting during last-contact backfill (#22811)
## Context
Upgrading `twenty-last-contact` on a production workspace failed with a
Cloudflare error 1015 ("You are being rate limited"). The
`backfill-last-contact` post-install function runs on every version
upgrade and fired 20 concurrent update mutations per batch with no pause
between batches, on top of paginated full-collection reads. On a
workspace with real email/calendar history that burst trips Cloudflare's
rate limit, and since the client SDK throws on any non-2xx response, a
single 429 killed the whole install/upgrade hook mid-backfill.
## Changes
- New `executeWithRetry` util: retries rate-limit (429 / Cloudflare
1015) and transient gateway/network errors (502/503/504, timeouts,
connection resets) with exponential backoff and jitter, capped at 5
attempts. Honors a `retry_after` hint when present in the response body.
Non-retryable errors still throw immediately.
- All backfill queries and mutations are wrapped with it.
- Update batch concurrency reduced from 20 to 10 to keep bursts under
the rate limit in the first place.
- Bumped app version to 1.1.1 with a changelog entry.
## Test
- Added unit tests for `executeWithRetry` (success passthrough,
retry-then-succeed, non-retryable passthrough, retry exhaustion,
`retry_after` handling).
- `yarn test:unit` (28 passed), `yarn typecheck`, `yarn lint` all green
in the app package.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01AtnkEfbpFhLp5qCJbSmZpE)_
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22811?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
|
||
|
|
ace16add6c |
fix(twenty-partners): align createdAt view field with 2.19 deterministic ids and bump to 1.2.10 (#22782)
## What - Bump `twenty-partners` from `1.2.0` to `1.2.10`. - Fix the red integration tests by pointing the "Partner Applications" view `createdAt` column at the real field metadata id. ## Why the integration tests were red The `twenty-partners` CI job spins up `twentycrm/twenty-app-dev:latest` and runs the integration suite. The suite's global setup does a dev sync of the app, which failed: ``` Dev sync failed: viewField: INVALID_VIEW_DATA: Field metadata not found (universalIdentifier: 835c9a7e-72ec-46c5-8d90-39a02998f561) ``` The `partner-applications.view.ts` `createdAt` column referenced `PARTNER_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER = 421cbcea-...`, an invented id. `createdAt` is a reserved system field auto-created on the custom `partner` object, and since 2.19 its universal identifier is derived deterministically by the server from the application id, the object id and the field name. The invented id matched nothing, so the sync rejected the dangling view field and the app never registered, failing every integration test. ## Fix Set `PARTNER_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER` to the deterministically derived value `746e2944-28d0-545e-9832-a46516e1d9a0` (application id `e662fc1f-...` + partner object id `39101b39-...` + field name `createdAt`). This matches the same derivation the server uses for standard object system fields, verified against the `twenty-shared` opportunity `createdAt` snapshot. |
||
|
|
9e20e2222a |
Fix front-component serving on Safari, kill stale presigned caching, and cache built bundles client-side (#22672)
## Context Built front-component bundles are served via `GET /rest/front-components/:id/:cacheKey`. On S3-backed storage (Twenty Cloud) the endpoint used to 302-redirect the worker's authenticated fetch to a presigned S3 URL. That redirect caused two bugs, and fixing it removed the caching the redirect was accidentally providing — so this PR also adds a proper client-side cache. Closes twentyhq/core-team-issues#2653. ### Bug 1 — Safari 403 (Authorization header forwarded across redirect) The renderer worker fetches the bundle with `Authorization: Bearer`. The controller answered with a 302 to a presigned S3 URL. Per the Fetch spec, browsers must strip `Authorization` on a cross-origin redirect. Chrome/Firefox do, but Safari/WebKit forwards it, so S3 receives both a query-string signature and an `Authorization` header and rejects with `InvalidArgument: Only one auth mechanism allowed`. Result: front components never load in Safari on S3-backed storage. ### Bug 2 — 302 cached publicly (browser-independent) The redirect branch set no `Cache-Control`, so a CDN could cache it far beyond the presigned URL's TTL (`STORAGE_S3_PRESIGNED_URL_EXPIRES_IN`, 900s). Consequences: any client re-served the cached 302 after 15 min hits an expired signature (403, also affects Chrome), and the cached redirect containing a live presigned URL is served to unauthenticated requests (short-lived auth bypass). ### Regression this introduces — warm-load caching lost Marking the handoff `no-store` (Bug 2 fix) is correct, but it means the built bundle is no longer cached anywhere on the S3 path. The browser HTTP cache cannot compensate: the presigned URL that actually returns the bytes carries a fresh `X-Amz-Date`/`X-Amz-Signature` on every request, so each download is a brand-new cache key and never hits. Net effect without mitigation: every worker mount re-downloads the full bundle. ## What changed - **Front components return a 200 JSON body instead of a 302.** The controller now responds `200 { url }` with `Cache-Control: private, no-store`. The worker parses the JSON and issues a separate header-less `fetch(url)` to S3. No redirect means the `Authorization` header is never forwarded, making it browser-independent, and the handoff carrying the presigned URL is never cached. The stream path (local storage) is unchanged. - **Client-side bundle cache in the renderer (restores warm loads).** `fetchComponentSource` wraps the fetch chain in a `CacheStorage` layer keyed by the **content-addressed** `/front-components/:id/:checksum.js` URL. A hit returns the stored bundle and skips **both** the `no-store` handoff to Twenty and the S3 download — restoring cross-session warm loads without ever persisting a presigned credential. Because `CacheStorage` is writable by any same-origin code (including the untrusted component code this cache feeds), cached content is verified against the sha-256 checksum embedded in the URL on every read, and evicted on mismatch. Caching degrades to a plain fetch where `CacheStorage` or WebCrypto is unavailable. - **sha-256 checksums for built front components.** The SDK build and workspace prefill now fingerprint built front-component bundles with sha-256 (WebCrypto has no md5), enabling the integrity check above. Other file folders keep md5. Legacy md5-fingerprinted URLs (32-hex) simply bypass the cache — already-synced components keep working and start benefiting from caching on their next build/sync. - **WebKit e2e coverage.** Added a `webkit` project to the postcard example's Playwright config mirroring `chrome` (shared setup + storageState), plus iframe/worker diagnostics logging so front-component failures surface in the test log. `TZ` is pinned to `Europe/Paris` because WebKit on Linux ignores Playwright's `timezoneId` emulation and rejects the runner's legacy `CET` alias, which crashed the record page before the component could render. ### Why we hand off to S3 instead of streaming through Twenty On S3-backed storage we deliberately **do not** proxy/stream the bundle bytes through the API. The controller returns the presigned URL and the worker fetches the content directly from S3, for two reasons: - **Server CPU/bandwidth.** Streaming every bundle on every cold load would put the API server on the hot path for all front-component content. Handing off to S3 keeps that load off the server. - **Domain isolation.** Front-component content is fetched from the object-storage domain (e.g. `s3.domain.com`), a different origin than the API and the front app. Serving untrusted/app-authored bundle content from a separate domain than `twenty.com` keeps it off the app's origin. The stream path is kept only as the local-storage fallback (no S3/presign available), where these concerns don't apply. ## Examples ### The JSON handoff (S3 path) ```http GET /rest/front-components/d3b07384-.../a1b2c3d4.js HTTP/1.1 Host: twenty.com Authorization: Bearer <worker-token> ``` ```http HTTP/1.1 200 OK Content-Type: application/json Cache-Control: private, no-store {"url":"https://s3.domain.com/bucket/.../checkout-widget.mjs?X-Amz-Date=20260709T091500Z&X-Amz-Expires=900&...&X-Amz-Signature=AAAA1111..."} ``` The worker then fetches that presigned URL **without** headers (the Safari fix) and gets the bundle bytes. ### Why the browser HTTP cache can't reuse it | | Load 1 (09:15) | Load 2 (09:30) | Same key? | |---|---|---|---| | Twenty handoff URL | `.../a1b2c3d4.js` | `.../a1b2c3d4.js` | ✅ but response is `no-store` | | Presigned `X-Amz-Signature` | `AAAA1111...` | `ZZZZ9999...` | ❌ | | Effective S3 URL (the HTTP cache key) | `...&X-Amz-Signature=AAAA1111...` | `...&X-Amz-Signature=ZZZZ9999...` | ❌ new key → miss | ### What the CacheStorage layer stores ``` key = https://twenty.com/rest/front-components/d3b07384-.../a1b2c3d4.js (stable, chosen by us) value = <bundle JS bytes> (NOT the presigned URL) ``` Keying by the stable logical URL (not the volatile URL the bytes arrived from) is the one thing the native HTTP cache can't express. The presigned URL is used once and discarded. ### Invalidation No TTL and no explicit delete — invalidation is by key change. A rebuild changes the checksum → changes the URL → guaranteed miss on the new key. The old entry is orphaned and reclaimed by normal browser eviction (quota/LRU; Safari ITP after 7 idle days). Global invalidation lever: bump the cache name suffix (`front-component-source-v1`). ## Deploy note — front/server release window Old frontend bundles (already-open tabs) hitting the new server receive the JSON handoff where they expect raw JS and fail to render until the tab is reloaded. The other direction is safe: the new worker against an old server follows the 302 transparently (the content-type check falls through to `response.text()`). Accepted as a short deploy-window trade-off. ## Follow-ups (not in this PR) - The client-side cache is a bridge for the `no-store` presigned handoff. If built components are later served from a stable, non-signed, public-by-URL path (they are already content-addressed by checksum, so `immutable` is safe), the browser + CDN cache natively and this custom layer can be removed. - `GET /file/:fileFolder/:id` presigned 302s still carry no `Cache-Control`. An explicit policy there (bounded `private, max-age` below the presigned TTL) was prototyped in this PR and deliberately dropped to keep the scope on front components — the file path authenticates via a query-param token (part of any cache key), so its exposure differs and deserves its own PR. ## Non-goals Per the issue, file serving keeps its query-param token + 302 model. Native browser loads (`<img>`, downloads) cannot do a two-step fetch and already work on Safari. The public-asset redirect is left untouched since its caching is intentional. ## Test plan - Renderer: `fetchComponentSource.spec.ts` covers cache miss + write, verified cache hit (no network), poisoned-entry eviction, checksum-mismatch (never cached), non-fingerprinted and legacy-md5 URL bypass, and the no-`CacheStorage` / no-WebCrypto fallbacks. `fetchComponentSourceFromNetwork.spec.ts` covers the direct JS response, the JSON handoff follow-through (header-less presigned fetch), and error mapping. - e2e: the postcard front-component spec now runs on both Chromium and WebKit against prod-parity storage (S3 + Lambda). - `oxlint` + `oxfmt` clean; typecheck passes on changed packages. ### Reproduction proof — Safari was always broken (e2e probe) We ran the prod-parity postcard e2e suite (S3 storage + Lambda) with WebKit against **`main` without this fix**, via a throwaway probe PR: twentyhq/twenty#22717. Result — [ci-privileged run 29015624468](https://github.com/twentyhq/ci-privileged/actions/runs/29015624468): ``` 1 failed [webkit] › card-front-component.spec.ts:61 › renders the postcard name and status badge in the record preview 2 passed (1.4m) ``` `[webkit]` times out waiting for `getByTestId('postcard-card')` to become visible (*element(s) not found*) while the Chromium run of the same spec passes. This confirms the front component **never rendered in Safari** on S3-backed storage prior to this PR — it is a genuine, browser-specific bug, not a flake. The fix in this PR is expected to turn that same `[webkit]` assertion green. Note: running the WebKit tests in CI requires the WebKit browser binary and its system dependencies in the e2e job (now installed via `npx playwright install --with-deps chromium webkit`). |
||
|
|
23cae2040a |
Improve application asset management (#22564)
App manifests could point the logo and screenshots at either external
URLs or public folder paths, and that was handled inconsistently across
install, sync and the marketplace.
This makes assets always bundled files:
- Manifests now use `logo` and `galleryImages` (a `string[]` of public
folder paths) instead of `logoUrl` and `screenshots`. The old fields
still work but are deprecated. Gallery order comes from the array index.
Normalization (deprecated-field migration, and warning about + ignoring
external URLs) happens in `defineApplication`, so the warnings surface
at define time.
- Logo is stored as a File record (`logoFileId`).
- The registration gallery is configured via a `settings` jsonb column
on `applicationRegistration` (`{ galleryImages: string[] }`) — populated
from the manifest, read by the marketplace detail (falling back to the
legacy `screenshots` column, then the manifest). No dedicated gallery
table.
- The marketplace detail DTO and front now use `galleryImages`.
Verified against a local Postgres: the fast instance commands run with
no pending-migration diff, the schema is correct, and the server boots.
Typecheck, lint, codegen and the application unit tests pass.
Not included yet: rehosting assets into storage for npm catalog and
tarball registrations, versioned cache busting on the serving route, and
a backfill for existing installs.
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22564?utm_source=github"
rel="nofollow noreferrer noopener" target="_blank">``<img alt="Review
in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg">``</a>
|
||
|
|
d8082d86ca |
Add Last contact on Companies and Opportunities to last-contact app (#22720)
## After Example with google company, 2 contacts, 2 opportunities: <img width="1512" height="332" alt="image" src="https://github.com/user-attachments/assets/9dac3ac1-bbbb-41a4-b6d6-5f2180e33c13"/> <img width="1512" height="313" alt="image" src="https://github.com/user-attachments/assets/e6e326db-942f-45c2-a696-05fa4c32619b"/> <img width="1309" height="313" alt="image" src="https://github.com/user-attachments/assets/64ed84ab-f7f0-47e5-b48c-42572c7d2534"/> ## What Extends the `twenty-last-contact` app so **Last contact** is also surfaced on **Companies** and **Opportunities**, not just People. Feedback: Companies and Opportunities already show emails and meetings from their related Person records on their timeline, so they should expose the most recent touch as fields too. Terminology and mechanism intentionally mirror what the app already does on People (no email-specific fields). ## Changes - **New fields**, identical in name/label/semantics to the People headline columns: - `Company.lastContactAt` and `Opportunity.lastContactAt` (datetime, "Last contact") - `Company.lastContactItemMessage` / `lastContactItemCalendarEvent` and the same pair on Opportunity (morph relation, "Last contact item"), with inverse `lastContactForCompanies` / `lastContactForOpportunities` relations on Message and Calendar event - All app fields are read-only in the UI (`isUIEditable: false`) - **View fields** on the All Companies and All Opportunities views (Last contact + Last contact item, visible). - **Live updates**: the new `updateRelatedLastContact` util propagates a person's interaction to their company and their point-of-contact opportunities, guarded so an older interaction never overwrites a newer one. It is called from both the email handler (`on-email-interaction`) and the shared calendar path (`updatePersonLastContactFromCalendar`, used by `on-calendar-interaction` and `on-calendar-event-started`), so emails and meetings both count. - **Backfill** (`backfill-last-contact`): aggregates each person's last contact up to their company, and each opportunity's from its point of contact. Related-record scope: a company's last contact comes from its people; an opportunity's from its point of contact. ## Not included (deliberately) The directional fields (`lastInboundAt`, `lastOutboundAt`, `lastContactBy`) and the `lastEmail`/`lastMeeting` shortcuts are not mirrored: aggregated across many people they get semantically fuzzy, and they would double the write amplification on every synced email for little added signal. ## Tests - Unit tests for `updateRelatedLastContact` (email and meeting propagation, recency guard, no-company case). - Integration tests: a related person's email sets company + opportunity last contact; a later meeting supersedes an email; an older interaction does not overwrite a newer one. - Existing unit + integration tests updated and passing; typecheck and lint clean. |
||
|
|
78a0f9ea77 | feat(call-recorder): type application and server variables, make summary prompt rich text (#22685) | ||
|
|
545de99476 |
[Twenty Fireflies] Add calendar event summary and transcript UI components (#22667)
## Summary Add comprehensive UI components and hooks for displaying call recording summaries and transcripts on calendar event pages. This includes markdown parsing for summaries, diarized transcript rendering, and data fetching hooks integrated with the Twenty SDK. ## Key Changes ### New Hooks - `useCalendarEventSummary`: Fetches and manages summary markdown for a calendar event's call recordings - `useCalendarEventTranscript`: Fetches and manages transcript data for a calendar event's call recordings ### Summary Components - `CalendarEventSummary`: Top-level component that displays summary for selected calendar event - `CalendarEventSummaryContent`: Container with header and content frame - `CalendarEventSummaryBody`: Handles loading, error, and empty states - `SummaryMarkdown`: Renders parsed markdown with support for headings, lists, and paragraphs - `SummaryInlineSegments`: Renders inline text with bold formatting support ### Transcript Components - `CalendarEventTranscript`: Top-level component that displays transcript for selected calendar event - `CalendarEventTranscriptContent`: Container with header and scrollable content frame - `CalendarEventTranscriptBody`: Handles loading, error, and empty states - `TranscriptEntryList`: Renders list of transcript entries - `TranscriptEntryListItem`: Individual transcript entry with speaker avatar, timestamp, and text - `TranscriptErrorBox`: Styled error state display ### Utilities - `parseSummaryMarkdownBlocks`: Parses markdown into structured blocks (headings, lists, paragraphs) - `parseSummaryInlineSegments`: Parses inline markdown for bold text formatting - `parseTranscriptEntries`: Parses diarized transcript format into structured entries with speaker info and timestamps - `formatSecondsAsClockTimestamp`: Formats seconds into HH:MM:SS or MM:SS format - `asRecord`: Type guard utility for converting values to records ### Page Layout Configuration - `calendar-event-summary-tab.ts`: Defines Summary tab for calendar event page layout - `calendar-event-transcript-tab.ts`: Defines Transcript tab for calendar event page layout - Front component definitions for both summary and transcript ### Types - `TranscriptEntry` and `TranscriptWord`: Structured transcript data types - `SummaryMarkdownBlock`: Block-level markdown structure - `SummaryInlineSegment`: Inline text segment with formatting ## Implementation Details - Uses `CoreApiClient` from Twenty SDK to query call recordings filtered by calendar event ID - Implements proper cleanup with cancellation tokens to prevent state updates on unmounted components - Supports both loading and error states with user-friendly messaging - Markdown parser handles headings (h1-h6), bullet lists, and paragraphs with inline bold formatting - Transcript parser validates diarized format and gracefully handles malformed entries - Styled with emotion and theme constants from twenty-ui for consistent design - Integrates with `useSelectedRecordIds` hook to track selected calendar event https://github.com/user-attachments/assets/0bd63590-bb1e-4f89-8b96-e3fbb659473e <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22667?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
d746909184 |
feat(sdk): validate graph page-layout widgets at build time (#22559)
When an app defines a graph widget (aggregate, pie, bar or line chart), the built manifest can carry the wrong key and the server rejects it at sync time with a confusing "aggregate field is required" error. The SDK type already requires `aggregateFieldMetadataUniversalIdentifier` and renames the raw `aggregateFieldMetadataId` at compile time. But the manifest build runs esbuild with no type checking, so a wrong or missing key slips through and only fails later on the server. This adds a build-time check that mirrors the server validator, with a hint pointing at the right key when the raw one was used. It is non-breaking since correctly authored apps already use the universal key. Tests: unit tests on the validator, plus a real graph widget added to the rich-app fixture so the integration and e2e suites cover the happy path. |
||
|
|
640e6b8b33 |
fix(call-recorder): stream Recall media to storage to fix OOM (#22652)
## Summary Fixes Call Recorder media ingestion OOMs by streaming Recall media into Twenty direct uploads instead of buffering the full file in memory. ## Changes - Opens the Recall media download stream and uses its `Content-Length` as the direct upload size. - Creates a Twenty direct upload target, streams the media body to it with Node `http`/`https` backpressure, then completes the upload. - Cleans up download/upload streams on target creation, upload, and storage response failures. - Keeps the media size cap for now while making it no longer required for memory safety. - Bumps `twenty-client-sdk` and `twenty-sdk` to `2.19.0`. ## Tests - `yarn test:unit src/logic-functions/flows/__tests__/ingest-call-recording-media.test.ts src/logic-functions/flows/__tests__/put-media-download-body-to-upload-target.test.ts` - `yarn typecheck` |
||
|
|
525ed74b2f |
Fireflies: port app to the CallRecording standard object (#22642)
## Context The Fireflies app predates the core `CallRecording` standard object: it wrote transcripts and summaries as markdown into two rich-text field extensions on `CalendarEvent`. That model has no per-call record, silently drops orphan calls, and diverges from how the Call Recorder app stores recordings. This is the first step of moving Fireflies onto the CallRecording architecture; media ingestion, retry markers, and a dedicated tab/front component iterate on top of this. ## What this PR does Replaces the CalendarEvent field extensions with upserts into the core `CallRecording` object: - **One CallRecording per Fireflies call** via a deterministic UUID derived from the Fireflies meeting id — the `meeting.transcribed` webhook, the `meeting.summarized` webhook, and manual **Sync Fireflies Call** runs all converge on the same row regardless of order, with a create-race fallback to update - **Transcript as diarized JSON** (participant + sentence-level relative timestamps) instead of markdown, matching the entry shape the CallRecording `transcript` field holds for other recording apps; fetchers now request `date` and sentence `end_time` - **Summary stays rich text** in `CallRecording.summary`, composed from the Fireflies overview / action items / topics / keywords - **Call metadata filled** — title, `startedAt`/`endedAt` (from `date` + `duration`), `externalRecordingId`; transcript sync marks the row `COMPLETED`, a summary-first sync creates it as `PROCESSING` - **Orphan calls are kept**: when no CalendarEvent matches by `eventExternalId` / `iCalUid`, the CallRecording is created without a calendar event link instead of being dropped - **Role**: now reads/writes `callRecording`; `calendarEvent` drops to read-only; the two schema field extensions and the markdown transcript formatter are removed ### Housekeeping - App version bumped to **0.2.0** so installed workspaces pick up the upgrade - `twenty-sdk` / `twenty-client-sdk` aligned on `^2.18.0` (matches call-recorder) - Gallery screenshots depicting the removed CalendarEvent fields dropped from the marketplace config - README: "Upgrading from 0.1.x" section documenting the field removal and the backfill path - Integration test guarding the mirrored status constants against the server's CallRecording select options ## Upgrade note Upgrading removes the two app-owned CalendarEvent fields and their stored content (app upgrades infer deletions from the manifest diff). That data is a cache of Fireflies content and is re-derivable: any call still in Fireflies can be re-ingested as a CallRecording via **Sync Fireflies Call**. A follow-up PR adds a post-install/upgrade sweep (same pattern as the Call Recorder sweep in #22552) that pages through Fireflies history and replays each call through the same sync flow, so history backfills automatically on install and upgrade — the deterministic ids make re-sweeping idempotent. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22642?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
435073e9c5 |
Display featured applications in marketplace (#22635)
## After <img width="1060" height="589" alt="image" src="https://github.com/user-attachments/assets/74dfadcf-8698-4404-81c6-b309cc4cbf79" /> <img width="732" alt="image" src="https://github.com/user-attachments/assets/0e1a3644-04bc-4208-aa77-3842d9db9cc8" /> <img width="797" alt="image" src="https://github.com/user-attachments/assets/0456ecce-607a-4705-8a89-c77029bfb6ac" /> - Remove IS_MARKETPLACE_SETTING_TAB_VISIBLE feature flag - add vetted toggle in admin app tab - added people data labs, last contact and call recorder to default vetted applications <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22635?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: prastoin <paul@twenty.com> |
||
|
|
18c10f4632 |
Call recorder: sweep upcoming calendar events for recording bots on install (#22552)
## Context The Call Recorder schedules Recall bots reactively — a database event trigger reconciles a calendar event when it is created or updated. That misses meetings that already existed before the app was installed, and meetings created far ahead that are never edited as they approach. Neither gets a bot, though recording is on by default. ## What this PR does Moves to the rolling near-term window Recall recommends for [your own calendar integration](https://docs.recall.ai/docs/creating-and-scheduling-bots#scheduling-bots-with-your-own-calendar-integration) — "a daily sync of the next 7 days". Bots are scheduled only for meetings starting within a **7-day horizon**, kept complete by three mechanisms: - **Horizon (policy).** `resolveCallRecorderPolicyResult` caps scheduling at 7 days from now (`EVENT_BEYOND_SCHEDULING_HORIZON`), measured from `startsAt` (the bot's join time). The existing reactive trigger inherits this — far-future creates no longer schedule, and a meeting moved out of the window has its bot canceled. - **Daily sweep (cron).** New `sweep-upcoming-calendar-events` reconciles the 7-day window each day, so a meeting that ages into it without being edited still gets a bot. - **Fresh-install seed (post-install).** The app's single post-install hook (`start-post-install-backfills`) runs the sweep once on a fresh install so a new workspace is covered right away instead of waiting for the first cron; on an upgrade it relies on the cron and backfills missing summaries instead. The sweep runs through the authenticated `reconcile-upcoming-calendar-events` route, which batches ids through the existing reconciliation flow and re-invokes itself near the 900s timeout. Deterministic recording ids keep it idempotent. App self-calls go through a shared `postToOwnRoute` util targeting the server-injected `TWENTY_FUNCTIONS_URL`; a failed kickoff throws so the async hook retries instead of going silently green. Also: fallback titles for call recordings whose calendar event is visibility-restricted; app version → 1.0.7. ## Deferred - Far-future bots already scheduled by the previous no-cap behavior aren't proactively canceled — they fire naturally, or cancel if their event is edited out of the window. - Recall rejects an in-place `join_at` update under 10 min out; today that logs a warning rather than delete-and-recreate. ## Test plan - `yarn test:unit`: 407 tests / 65 files pass — new coverage for the horizon (including a meeting that starts in-window but ends beyond it), the 7-day query filter, the cron handler, the post-install hook's fresh-install vs upgrade branches, and the batch/continuation flow. - `yarn typecheck`, `yarn lint`, and `yarn twenty dev:build` (manifest build) pass. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22552?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
6966af735b |
chore(apps): patch bump public apps moved to sdk 2.19.0-alpha.1 (#22623)
## Summary Bumps the `version` field (patch) of the public apps that were moved to `twenty-sdk@2.19.0-alpha.1` in #22601. That PR intentionally left `version` untouched ("they'll be bumped at publish time") — this is that follow-up bump. | App | Before | After | |---|---|---| | `@twentyhq/call-recorder` | 1.0.6 | 1.0.7 | | `@twentyhq/people-data-labs` | 1.0.3 | 1.0.4 | | `@twentyhq/last-contact` | 1.0.1 | 1.0.2 | ## Notes - `twenty-partners`, `postcard` and `self-hosting` are intentionally **not** bumped. |
||
|
|
fe442fe5fe |
chore(apps): bump sdk to 2.19.0-alpha.1 and require twenty server >=2.19.0 (#22601)
## Summary
- Bumps `twenty-sdk` / `twenty-client-sdk` to the exact `2.19.0-alpha.1`
prerelease for the apps under `packages/twenty-apps` that actually
target a mutated standard identifier, and refreshes their lockfiles.
- Declares `"engines": { "twenty": ">=2.19.0" }` in those apps so
pre-2.19 servers refuse to install or upgrade to the rebuilt packages.
Only apps that reference a standard object's **system-field** universal
identifier, define a **relation into** a standard object, or call the
field-UID derivation helper need 2.19 (the identifiers those touch
changed from hardcoded UUIDs to deterministic hashes). Apps that only
define their own custom objects, or add plain scalar fields to a
standard object via its stable object-level id, were left on their prior
SDK pins. Currently bumped: `postcard`, `self-hosting`,
`twenty-partners`, `call-recorder`, `people-data-labs`,
`twenty-last-contact`.
## Context
Follow-up to #22565 (deterministic system field universal identifiers)
and #22599 (SDK prerelease bump).
Packages built with SDK ≤ 2.18 carry legacy system field identifiers and
are rejected by servers running `main`. Rebuilding with the 2.19 SDK
fixes that — but a rebuilt package must not be *upgraded into* by a 2.18
server, since 2.18 has no deterministic-identifier validation and would
diff the changed system field identifiers as a destructive delete +
create (the 2.19 backfill has not run there yet).
The `engines.twenty` constraint closes that gap: `doInstallApplication`
validates it via `validateServerCompatibility` before any mutation, and
this check has shipped since ~2.10, so every 2.18 server enforces it.
Resulting matrix:
- 2.18 fresh install of a rebuilt app: works, converges as a no-op once
the 2.19 backfill runs
- 2.18 upgrade of an existing install: rejected with
`SERVER_VERSION_INCOMPATIBLE` before any mutation
- 2.19 (post-backfill) install/upgrade: syncs cleanly
## Expected CI failures
**The `CI Twenty Apps` integration-test jobs are expected to fail on
this PR** (e.g. `people-data-labs`, `twenty-partners`). This is a
server-version mismatch, not an app bug — lint, typecheck and unit tests
all pass:
- The integration step spawns a real Twenty server from Docker Hub
`twentycrm/twenty-app-dev:latest` and runs `twenty dev` to sync each
app's metadata into it.
- `latest` currently resolves to **v2.18.5** — no `2.19` image is
published to Docker Hub yet.
- These apps now reference 2.19's **deterministic system-field universal
identifiers** (e.g. `company.createdBy`, `opportunity.createdAt`). A
2.18 server still carries the legacy identifiers, so the sync rejects
every 2.19-derived reference with `INVALID_VIEW_DATA` /
`FIELD_METADATA_NOT_FOUND` ("Field metadata not found").
- The failure surfaces as low-level field errors rather than a clean
`SERVER_VERSION_INCOMPATIBLE` because the `engines.twenty` gate
(`validateServerCompatibility`) only runs on the `app:install` / publish
paths — **not** on the `twenty dev` dev-sync path the integration tests
use.
These jobs will go green automatically once `twenty-app-dev:2.19` is
published to Docker Hub (or once CI pins the spawn action's
`twenty-version` to a 2.19 tag).
## Intentionally not included
- App `version` fields are untouched; they'll be bumped at publish time.
## Test plan
- [x] Refresh each bumped app's `yarn.lock` (`2.19.0-alpha.1` is now on
npm)
- [ ] Rebuild one app manifest and verify default field identifiers
match `getFieldUniversalIdentifier`
- [ ] Verify a 2.18 server rejects an upgrade to a rebuilt package with
`SERVER_VERSION_INCOMPATIBLE`
- [ ] Re-run `CI Twenty Apps` integration jobs once a
`twenty-app-dev:2.19` image is available
Made with [Cursor](https://cursor.com)
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22601?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-light.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
|
||
|
|
cabe5545ae |
Use SDK calendarEventRecordPageFields identifiers in call-recorder (#22618)
Replaces the hardcoded calendarEventRecordPageFields view/group identifiers in the call-recorder preference view-field with `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.calendarEvent.views.calendarEventRecordPageFields`, resolving the TODO. The published `twenty-sdk@2.18.0` (already pinned by the app) ships these identifiers with values matching the previously hardcoded ones. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22618?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
07a921f8ca |
Add Document Generator SDK app + step-by-step tutorial (#22522)
## What & why
This adds a **guided tutorial** that teaches the Twenty SDK by building
one real, useful app end to end — plus the finished app itself, ready
for the marketplace.
The app, **Document Generator**, turns reusable templates into
personalized documents using CRM data: write a template once with
`{{placeholders}}`, then generate a filled-in document for any Person or
Company from the command menu, an AI agent, or a workflow.
## Two parts
**1. The app — `packages/twenty-apps/public/document-generator`**
Each capability maps to one tutorial chapter:
- **Data:** `documentTemplate` + `document` objects, fields, and a
bidirectional relation
- **Logic:** a single `generate-document` handler exposed as an **AI
tool**, a **workflow action**, and an **HTTP POST route**; plus a public
**HTML view route**
- **UI:** two views + sidebar navigation, a **command-menu item** (on
Person selection) that opens a **React front component**
- **AI:** an agent + skill; a default application role; marketplace
metadata + logo
- **Tests:** unit tests for the template renderer + an install
integration test
**2. The tutorial —
`packages/twenty-docs/.../apps/tutorials/document-generator/`**
A six-chapter series under **Developers › Apps › Tutorial** (Overview →
Data model → Generating documents → HTTP routes → Building the UI → AI
agent → Publishing). Minimal prose, paste-ready code, inline links to
the matching reference pages, and real screenshots. Registers a new
"Tutorial" nav group and regenerates `docs.json` + the navigation
template.
## Verification
Validated against a running Twenty instance (`twenty-app-dev` on
`:2020`):
- `twenty dev --once` installs cleanly (28 metadata objects created)
- Generated a real document from a Person — placeholders resolved (name,
job title, `company.name`, email), zero missing tokens
- Command menu → front component → generate flow works in the UI
- Public HTML view route renders the document
- App gates green: `yarn lint` (0/0), `yarn typecheck`, `yarn test:unit`
(7/7)
All screenshots in the tutorial are captured from this run.
## Notes
- Left out per-app CI workflows (`.github/workflows`) to keep scope
tight — happy to add them if wanted.
https://claude.ai/code/session_012FoC76zPbPmpgtN7MXMPwy
---
_Generated by [Claude
Code](https://claude.ai/code/session_012FoC76zPbPmpgtN7MXMPwy)_
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22522?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
---------
Co-authored-by: github-actions <github-actions@twenty.com>
|
||
|
|
1d3f6176b2 |
Use record pickers for People Data Labs enrichment workflow inputs (#22596)
Follow-up to #21494, which added record-typed logic function workflow inputs but deferred the People Data Labs migration until the SDK release. twenty-sdk 2.16.0 (published) now includes the `record`/`records` input schema support, so this types the enrichment inputs accordingly: `records` on enrich-people/enrich-companies and `recordId` on enrich-person/enrich-company render as record pickers bound to Person/Company. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22596?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
6c40c7b91a |
Deterministic system field universal identifier (#22565)
# Introduction Close twentyhq/core-team-issues#2641 Auto-provisioned field metadata used to get its `universalIdentifier` from three unrelated sources: random `v4()` on the server when creating custom objects, hardcoded values in `STANDARD_OBJECTS`, and an ad-hoc `v5` derivation in the SDK manifest build. This PR unifies all of them behind the shared `getFieldUniversalIdentifier` derivation: ``` universalIdentifier = f(applicationUniversalIdentifier, objectUniversalIdentifier, fieldName) ``` ## Ownership model The rollout is built on an explicit split of who owns a field's universal identifier: - **The 8 system fields** (`id`, `createdAt`, `updatedAt`, `deletedAt`, `createdBy`, `updatedBy`, `position`, `searchVector`) are **server-owned**. Their universal identifiers are always the deterministic derivation, on **every** application (standard, workspace-custom, installed). Clients cannot provide custom values: a temporary check in `validateObjectMetadataSystemFieldsIntegrity` rejects any non-derived system field identifier at migration build time. This check stands in until system fields are generated exclusively server side by the metadata side-effect engine and stripped from client inputs — at which point it becomes structurally impossible to send one. - **`name` is a default field, not a system field**: it is auto-provisioned when absent (server side for custom objects, SDK side for application objects) but authors can define their own. It is only derived where it is guaranteed to be auto-provisioned. In particular, standard objects keep their **historical hardcoded** `name` identifiers: the standard app authors its `name` fields like any installed app would, and moving those identifiers would break every installed application referencing them (e.g. views on `opportunity.name`). - **User-created and author-provided fields** keep random / explicit identifiers, untouched. ## Server - `validateObjectMetadataSystemFieldsIntegrity` now validates, on top of the existing type/`isSystem` checks, that each system field's `universalIdentifier` equals the deterministic derivation. Runs for every object creation going through the migration orchestrator: app sync, custom object creation, standard provisioning - `build-default-flat-field-metadatas-for-custom-object.util.ts` derives the system field identifiers (and the auto-provisioned `name`) with `getFieldUniversalIdentifier` instead of `v4()` - `build-default-relation-flat-field-metadatas-for-custom-object.util.ts` derives both the forward and the reverse default relation field identifiers deterministically - `generateMorphOrRelationFlatFieldMetadataPair` accepts optional `sourceFieldUniversalIdentifier` / `targetFieldUniversalIdentifier` so callers can inject deterministic values; user-created relations still default to `v4()` ## twenty-shared - `STANDARD_OBJECTS` system field identifiers (the 8) are now computed at module load via `buildStandardObjectSystemFields`; `name` and every other identifier keep their hardcoded values - New snapshot test pinning **every** universal identifier of `STANDARD_OBJECTS`: any identifier change now requires an explicit snapshot update and should ship with a coordinated backfill ## SDK (breaking, pre-GA) - `generateDefaultFieldUniversalIdentifier` delegates to `getFieldUniversalIdentifier` and now requires `applicationUniversalIdentifier` - Reverse default relation field identifiers are derived from the field's real coordinates (standard object UID + actual field name, e.g. `targetRocket` on `attachment`) instead of the legacy custom-object UID + synthetic `${fieldName}Inverse` hash input. Field *names* are unchanged - The manifest build threads the application universal identifier through default field injection (two-pass over object configs) - `twenty dev:add` now resolves the application universal identifier upfront and refuses to scaffold anything until `defineApplication` declares one — no more `fill-later` placeholder for the app UID in generated files ## Upgrade A 2.19 **workspace command** backfills existing `fieldMetadata.universalIdentifier` rows to the deterministic derivation. Coverage follows the ownership model: - **The 8 system fields**: taken over for **every application**, whatever value they currently hold. This is both safe and required now that sync rejects non-derived values — leaving a row unconverged would make its application unsyncable - **`name`**: workspace-custom app → always taken over (server-generated, no author to clobber); installed applications → only rows still carrying the legacy SDK derivation are recomputed, author-provided identifiers are never touched; standard app → never touched (hardcoded in `STANDARD_OBJECTS`) - **Default relation fields**: workspace-custom app → forward fields on custom objects and reverse fields on the standard relation objects; installed applications → legacy-derivation probe only All identifiers of a workspace are updated inside a single transaction, then the command flushes the field-metadata-related workspace caches and bumps the metadata version. Stored `applicationRegistration.manifest` snapshots are intentionally **not** rewritten: installs and upgrades always sync from the `manifest.json` inside the resolved package (npm/tarball), the stored column is only used for display/marketplace purposes. ## Breaking behavior for old packages (fail closed) Packages built with an older SDK carry legacy system field identifiers in their tarball `manifest.json`. Installing or upgrading such a package now fails with an explicit `INVALID_SYSTEM_FIELD` validation error ("universal identifier is not deterministic") instead of silently mismatching against the backfilled rows and triggering a destructive delete+create. The remediation is to rebuild the package with the new SDK; the backfill has already converged the installed rows, so the rebuilt manifest syncs cleanly. ## Test plan - [x] `twenty-sdk` unit tests (526 tests) and typecheck - [x] `twenty-shared` unit tests (1635 tests) including the `STANDARD_OBJECTS` snapshot; `name` identifiers verified byte-for-byte identical to `main` - [x] Lint and typecheck clean on all touched packages - [x] Integration: create a custom object and verify system + default relation field identifiers match the deterministic derivation (`create-one-object-metadata-deterministic-field-universal-identifiers`, 13 assertions passing) - [x] Integration: `failing-sync-application-object-system-fields` extended with a non-derived system field identifier case; all identifiers in the spec pinned deterministically so snapshots embedding expected/actual values are stable across runs (verified with a double run) - [x] Integration: all application sync suites pass with the derived system field identifiers now required by the `buildDefaultObjectManifest` test helper (9 suites, 20 tests) - [x] Full test-database reset: standard app provisioning and seeded workspaces pass the new validation - [x] SDK manifest build verified on the postcard example app: all auto-generated default field identifiers match the derivation - [ ] Run `upgrade:2-19:backfill-deterministic-field-universal-identifiers` (dry-run then real) on a seeded workspace and verify identifier convergence with a rebuilt app manifest |
||
|
|
fe8ec0463a | Call recorder: parse conference links from calendar event text (#22555) | ||
|
|
74b3a4216e |
chore(apps): remove the twenty-for-twenty app (consolidated into twenty-eng) (#22549)
Removes the internal `twenty-for-twenty` app from this public repo. It has been consolidated into the private `twenty-eng` monorepo, which now also owns the Resend module (moved there in a companion PR). - Removes `packages/twenty-apps/internal/twenty-for-twenty/**` (177 files). - No build wiring referenced it (no nx project, not in `nx.json`/root workspaces); the only mention elsewhere is a naming-convention comment in `twenty-linear`. ## ⚠️ Sequencing - This removes **source only** — it does **not** uninstall the app currently deployed on the workspace. Merge only **after** the twenty-eng app has taken over the Resend objects there, so the live integration isn't left orphaned. - Supersedes the migration-plan doc PR (#22546), which added a doc into this now-removed directory; that doc now lives in the twenty-eng app. #22546 can be closed. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22549?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
6b2405c2e9 |
feat(twenty-for-twenty): bump to SDK 2.18 with local UI components (#22542)
## What & why Bumps the **Twenty for Twenty** internal app to the current SDK line and serves it on the matching `twenty-app-dev` image. - `twenty-sdk` / `twenty-client-sdk`: `^2.14.0` → **`^2.18.0`** (npm latest) - Runs against `twentycrm/twenty-app-dev:v2.18.5` (same 2.18 line) - App version `0.1.0` → **`0.2.0`** ### The `twenty-sdk/ui` break twenty-sdk 2.18 **removed the `twenty-sdk/ui` subpath** (#22326, "Remove twenty-ui reexport from the SDK"). The intended replacement — `twenty-ui@1.0.0-alpha.1` subpaths — requires **React 19 + a monaco-editor peer** that this React-18 app can't adopt, so the app no longer builds against 2.18 as-is. Instead of migrating to twenty-ui, this replaces the four `twenty-sdk/ui` consumers with **self-contained local components** under `src/ui/`, imported via a new `@ui` alias: - `Callout`, `H2Title`, `Status` - Tabler-style inline-SVG icons (`IconAlertCircle`, `IconInfoCircle`, `IconMail`, `IconRefresh`, `IconHelp`) - a `ThemeColor` type They mirror the twenty-ui components 1:1 using the `--t-*` theme CSS variables the front-component host injects (the same inline-style pattern the app already used for theme tokens) — no new runtime deps, no React 19 requirement. ## Test plan - `twenty dev:build` ✓, `yarn typecheck` ✓, `yarn lint` ✓ - Synced into a local `twenty-app-dev:v2.18.5` container (`twenty dev --once`) — objects/fields/views/app install applied cleanly - Verified rendering live: Sync Status page (`H2Title` headings + `Status` "Not synced" pills) and all three `Callout` variants (error/info/neutral) with correct colors + icons <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22542?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
badaae17cd |
Call recorder: skeleton loading state + port front components to twenty-ui (#22473)
## What this does
Three changes to the call-recorder app's recording/transcript front
component:
### 1. Fix loading flicker with a skeleton loader (`4dc3a167`)
The widget's loading state rendered an empty `<video controls>` element
at 16:9, which was torn down and replaced once the recording query
resolved — so every open flashed a video player, even for calendar
events with no video at all.
- New `RecordingSkeletonLoader`: three transcript-shaped pulsing rows
(avatar circle + speaker bar + text bars) held at the same 240px
min-height as the empty state, so nothing jumps when content arrives.
Visuals follow the twenty-front skeleton standard
(react-loading-skeleton theming: `background.tertiary` base,
`background.transparent.lighter` highlight sweep, 4px radius), rebuilt
with emotion since the sandboxed bundle can't import the host's global
skeleton stylesheet.
- `RecordingVideoPlayer` now only renders with a real URL (`src:
string`).
### 2. Port front components to twenty-ui (`cea0d6ee`)
`twenty-ui@1.0.0-alpha.1` is now published and consumable by apps
(CSS-injection build support + the renderer's style bridge), so the
"remove once twenty-ui can be imported safely" duplications are
resolved:
- Deleted `recording-theme-css-variables.ts` → all components use
`themeCssVariables` from `twenty-ui/theme-constants` (only non-1:1
rename: `accent.primary` → `accent.accent9`).
- Deleted `TranscriptSpeakerAvatar` / `TranscriptSpeakerChip` → replaced
with twenty-ui `Avatar` (`size="md"`, `type="rounded"`) and `Chip`
(`ChipVariant.Transparent`, `isBold`, non-clickable) in
`TranscriptEntryListItem`.
- Added `import 'twenty-ui/style.css'` to the front-component entry,
plus a root `css.d.ts` declaration. The import is load-bearing: the SDK
inlines the CSS into the component bundle and the renderer's style
bridge injects it into the host document — the host's own twenty-ui CSS
can't be relied on, since scoped class hashes are content-derived and
drift between builds.
- Bumped `twenty-sdk` / `twenty-client-sdk` to `^2.18.0` (needed for the
CSS-injection build support) and added `twenty-ui@^1.0.0-alpha.1`.
### 3. Fix manifest build + typecheck after the port (`baa6eb2f`)
- `yarn twenty dev --once` failed at "Building manifest..." — the
manifest build loads front-component modules with `twenty-ui`
proxy-mocked, so the named `themeCssVariables` import resolves to
`undefined` and any static `${themeCssVariables.x}` interpolation throws
at module scope. All static interpolations are now lazy (`${() =>
themeCssVariables.x}`), deferring the access to render time in the
browser.
- Added `css.d.ts` to `tsconfig.spec.json`'s `include` — its
`["src/**"]` include replaced the parent config's default file set, so
the `*.css` module declaration was never loaded and `yarn typecheck`
failed with TS2882.
## Notes for review / testing
- Worth exercising manually: the skeleton during load, speaker
chips/avatars rendering via twenty-ui, and hovering a long (overflowing)
speaker name — Chip's tooltip uses a react-dom portal, which is the one
path not previously exercised inside the front-component sandbox.
- App version intentionally left at `1.0.4`.
https://claude.ai/code/session_01WuHQFWiRocn82ejxsnmz28
---
_Generated by [Claude
Code](https://claude.ai/code/session_01WuHQFWiRocn82ejxsnmz28)_
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22473?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
|
||
|
|
cbbb11b774 |
Add configurable call recorder summaries (#22405)
<img width="2560" height="1319" alt="CleanShot 2026-07-02 at 16 58 03" src="https://github.com/user-attachments/assets/d968bff7-4b57-4816-be9c-02e5af32ae3d" /> <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22405?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
25fe66565c |
feat(applications): add type and options to application variables (#22157)
## Before <img width="1452" height="709" alt="image" src="https://github.com/user-attachments/assets/cd384ffa-cbe6-49d5-a807-ca8d580f55a9" /> <img width="1074" height="452" alt="image" src="https://github.com/user-attachments/assets/720d38db-3495-4032-8831-17d24ec6a7e7" /> ## After <img width="1421" height="865" alt="image" src="https://github.com/user-attachments/assets/2275c996-c895-4800-8324-2aa2ddfddd43" /> <img width="1348" height="870" alt="image" src="https://github.com/user-attachments/assets/3e1a891d-6db0-4cbd-870a-2a5bbde4929d" /> ## Summary Adds typed application variables with optional select **options**. This is the other half of #22059, split out from the custom-settings-tab removal. ## Changes - **Shared types**: `ApplicationVariable` / `ServerVariables` gain an optional `type` (a `FieldMetadataType` subset — `TEXT`, `BOOLEAN`, `NUMBER`, `DATE`, `SELECT`, `MULTI_SELECT`, `RAW_JSON`, `RICH_TEXT`, `ARRAY`, …) and select `options`. New `serializeApplicationVariableValue` / `deserializeApplicationVariableValue` helpers convert typed values to/from the encrypted string storage. - **Server**: `type`/`options` columns on `applicationVariable` and `applicationRegistrationVariable` (entities + DTOs), a fast `2-17` instance command, manifest processing via the serialization helpers, and a `QueryDeepPartialEntity` cast where the manifest JSON column is persisted. - **Frontend**: a polymorphic `SettingsApplicationVariableInput` that renders the native `Form*` field component for each type (boolean, number, date/date-time, select, multi-select, array, raw JSON, rich text, text); fragment/query updates to fetch `type`/`options`. - **SDK**: `defineApplication` validates that `SELECT`/`MULTI_SELECT` variables declare non-empty `options` at build time (since `options` is kept structurally optional for TypeORM/SDK compatibility). Variables default to `TEXT` when no type is given, so existing manifests are unaffected. ## Notes The generated GraphQL artifacts (`type`/`options` on the variable types) are regenerated by codegen; that change accompanies this PR. https://claude.ai/code/session_013Z7UB35V2mvUozh55QHG23 --- _Generated by [Claude Code](https://claude.ai/code/session_013Z7UB35V2mvUozh55QHG23)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22157?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
bc1a4cb3fb |
feat(call-recorder): cap media file size during ingestion to avoid OOM (#22463)
Media ingestion buffered whole Recall files in memory; long recordings OOM'd the logic function. - Caps the size of ingested media files, configurable via the `CALL_RECORDER_MAX_MEDIA_FILE_SIZE_MB` app variable (default 80 MB). - Downloads stream chunk by chunk and stop at the cap; a Content-Length above the cap skips the download without reading the body. - A skipped file is recorded as `video_file_too_large` / `audio_file_too_large` in `callRecorderFailureReason`; the completion gate treats a marked file as resolved, so the recording still completes and bills with its remaining artifacts. - A real failure reason always wins over the size markers when the recording fails. Deferred: the cap is a stopgap until core supports streaming uploads (TODO in code). <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22463?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
f7f224aa7a |
Fix lint (#22416)
as title <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22416?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
05ce08ddba |
Add twenty-app keyword (#22415)
as title, bump version <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22415?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
efd600c12b |
Update app name (#22410)
as title <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22410?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
868ae4cbfd |
feat(last-contact): design for Last contact by + Last contact item (#22308)
To test, go to https://twenty-applications.twenty.com/settings/applications/6aa2ca76-fdbe-456d-89ab-c622452ef055 ## After <img width="1512" height="697" alt="image" src="https://github.com/user-attachments/assets/5b17f94e-6e0e-400a-8291-a39ad796e423" /> ## What Adds the design spec for the next version of the `twenty-last-contact` public app, extending it from a single `lastContactAt` date column to the three-column experience in the app's cover image on the All People view: 1. **Last contact by** — the team member who last interacted with the person (`ACTOR` field). 2. **Last contact** — the existing `lastContactAt` field, unchanged. 3. **Last contact item** — the email or meeting that was the last contact, as a clickable record (`MORPH_RELATION` → message | calendarEvent). All three columns always describe the same single most-recent interaction (atomic "newer wins" update). This PR contains the **design doc only** — `docs/superpowers/specs/2026-06-29-last-contact-by-and-item-design.md`. Implementation follows. ## Why The app today only answers *when* you last talked to someone. These fields also answer *who* on your team and *through which* email/meeting, matching the product vision in the cover. ## Key design decisions - **`lastContactBy` is an ACTOR**, with the team member resolved from the interaction's participants (`messageParticipant` / `calendarEventParticipant` both carry `workspaceMemberId` + `workspaceMember`). - **No provider (Gmail/Outlook) logo.** That data lived on `connectedAccount`, which v2.7 (`drop-connected-account-standard-object`) removed from the app-queryable workspace schema. Confirmed acceptable; the actor still shows the member + an email/calendar source. - **`lastContactItem` is a MORPH_RELATION** following the SDK pattern used by `attachment` / `noteTarget` / `taskTarget` (shared `morphId`, one field per target, reverse relation on each target object). ## Reviewer notes - **Load-bearing open risk** documented in the spec: how to *write* a morph relation through the app's GraphQL API — no app in the repo writes morph yet. The plan starts with a spike on this; if morph writes aren't supported from an app, the fallback is two nullable `RELATION` fields (`lastContactMessage` / `lastContactCalendarEvent`). - No code/behavior change yet — safe to merge or hold as the design of record. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22308?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
e0ebe0e029 |
v1.2.0 — Client brief intake (marketplace B2) (#22290)
## Summary **Version:** 1.2.0 (`packages/twenty-apps/internal/twenty-partners/package.json`) Adds marketplace B2 client brief intake to the partners app: - `POST /s/client-briefs` logic function — validates payload, creates an unlisted Opportunity (`isListed = false`) with `— client brief` name suffix - **Client briefs** ops view for review before listing - Unit + integration tests Merge this **before** the website PR (`rk-client-brief-web`). ## Test plan - [ ] `yarn twenty dev --once` syncs schema - [ ] `yarn test` passes in `twenty-partners` - [ ] Smoke: `POST /s/client-briefs` with `x-application-secret` creates Opportunity with `isListed = false` - [ ] Client briefs view visible in nav <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22290?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
0dc6272da5 |
Remove twenty-ui reexport from the SDK and use twenty-ui directly (#22326)
## What & why Removes the `twenty-sdk/ui` reexport. Apps now use Twenty UI by installing [`twenty-ui@1.0.0-alpha.1`](https://www.npmjs.com/package/twenty-ui/v/1.0.0-alpha.1) from npm and importing its subpaths directly. The reexport re-exported types that didn't resolve, forcing typecheck workarounds. ## Changes - **twenty-sdk**: delete `src/ui/index.ts`, drop the `./ui` export, remove it from the browser vite build, and rewire the CLI manifest-mock to `twenty-ui` (`.css` falls through to the empty-CSS loader). `twenty-ui` stays a devDependency for the CLI fixture tests. - **Renderer + create-twenty-app template**: import from `twenty-ui` subpaths; the template pins `twenty-ui@1.0.0-alpha.1`. - **Docs**: new "Using Twenty UI components" section (install + subpath imports + `useTheme()` for theme tokens), codex references, and the cross-doc-contract validator. The `twenty-for-twenty` / `twenty-slack` example apps are intentionally left on `twenty-sdk/ui`: they consume the published SDK (which still ships `./ui`), and `twenty-ui@1.0.0-alpha.1` requires react 19 + a `monaco-editor` peer the react-18 apps can't satisfy. They migrate once the SDK is republished. |
||
|
|
4045f9852e |
feat(call-recorder): use workspace logo for Recall bot image (#22302)
workspace logo - <img width="433" height="465" alt="CleanShot 2026-06-29 at 20 43 11" src="https://github.com/user-attachments/assets/946782a5-7bab-461b-a613-ce056fec8bbc" /> how bot appears - <img width="2560" height="1315" alt="CleanShot 2026-06-29 at 20 47 01" src="https://github.com/user-attachments/assets/91daa29c-83ef-4ac1-9d7c-ffa8bcf08135" /> <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22302?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
996d3a7921 |
bump call recorder (#22318)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22318?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |