Compare commits

...
Author SHA1 Message Date
Devessier dd174a6099 fix: save groups before widget config 2026-03-06 19:04:46 +01:00
Devessier a06b35c70d feat: initial draft 2026-03-06 18:53:19 +01:00
06451407ce i18n - docs translations (#18464)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-06 15:44:31 +01:00
59029a0035 [Fix] : Dragged element is considered to be part of a dropdown in dashboard tab list (#18414)
Fixes #15327

The issue occurred because the drag clone's visual state was previously
tied strictly to hovering over the `VISIBLE_TABS` boundaries. When a tab
was dragged outside this area (such as the last tab naturally crossing
into the `MORE_BUTTON` hover zone), the drag clone incorrectly fell back
to the dropdown menu item style.

We fixed this by making the `isHoveringTabList` logic more robust.
Instead of enforcing the tab style only within the `VISIBLE_TABS`
boundary, the dropdown style is now strictly restricted to the
`OVERFLOW_TABS` boundary.

With this change:
- Visible tabs successfully maintain their appearance when dragged
anywhere outside the dropdown.
- Dropdown tabs correctly transition to the normal tab style when
dragged out of the dropdown area, improving UX.



https://github.com/user-attachments/assets/9474e4c1-26a8-46e3-b9ee-4c6dbd8a4ea6

---------

Co-authored-by: Arun kumar <arunkumar@Aruns-MacBook-Air.local>
Co-authored-by: ehconitin <nitinkoche03@gmail.com>
Co-authored-by: nitin <142569587+ehconitin@users.noreply.github.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-03-06 14:35:32 +00:00
cac4999e9f fix: handle Escape in date/datetime pickers and remove ValidationStep any (#18107)
## Summary
- **DatePicker / DateTimePicker:** Call onEscape when user presses
Escape (fixes FIXME).
- **FormDateFieldInput:** Revert input/picker on Escape; handle Escape
in text input.
- **FormDateTimeFieldInput:** Remove FIXME.
- **ValidationStep:** Replace any with typed callback.

---------

Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr>
2026-03-06 14:19:23 +00:00
martmullandGitHub 403db7ad3f Add default viewField when creating object (#18441)
as title
2026-03-06 14:59:31 +01:00
Charles BochetandGitHub ef499b6d47 Re-enable disabled lint rules and right-size CI runners (#18461)
## Summary

- Re-enable one lint rule that was temporarily disabled during the
ESLint-to-Oxlint migration:
- **`twenty/sort-css-properties-alphabetically`** in twenty-front — 578
violations auto-fixed across 390 files
- Document why **`typescript/consistent-type-imports`** cannot be
auto-fixed in twenty-server: NestJS relies on `emitDecoratorMetadata`
for DI, so converting constructor parameter imports to `import type`
erases them at compile time and breaks dependency injection at runtime
- Right-size CI runners, reducing 8-core usage from 18 jobs to 3:

| Change | Jobs | Rationale |
|--------|------|-----------|
| **Keep 8-core** | `ci-merge-queue/e2e-test`,
`ci-front/front-sb-build`, `ci-front/front-build` | Heavy builds needing
max CPU + memory (10GB NODE_OPTIONS, full Storybook webpack bundling) |
| **8-core → 4-core** | `ci-server` (build, lint-typecheck, validation,
test, integration-test), `ci-front/front-sb-test`,
`ci-zapier/server-setup`, `ci-sdk/sdk-e2e-test` | Already sharded into
10-12 parallel instances, I/O-bound (DB/Redis), or moderate single
builds |
| **8-core → 2-core** | `ci-emails/emails-test` | Trivially lightweight
(build + curl health check) |
| **Removed** | `ci-front/front-chromatic-deployment` | Dead code —
permanently disabled with `if: false` |

- Fix merge queue CI issues:
- **Concurrency**: Use `merge_group.base_ref` instead of unique merge
group ref so new queue entries cancel previous runs
- **Required status checks**: Add `merge_group` trigger to all 6
required CI workflows (front, server, shared, website, docker-compose,
sdk) with `changed-files-check` auto-skipped for merge_group events —
status check jobs auto-pass without re-running full CI
- **Build caching**: Add Nx build cache restore/save to E2E test job
with fallback to `main` branch cache for faster frontend and server
builds

## Test plan

- [ ] CI passes on this PR (verifies lint rule auto-fix works)
- [ ] Verify 4-core runner jobs complete within their 30-minute timeouts
- [ ] Verify merge queue status checks auto-pass (ci-front-status-check,
ci-server-status-check, etc.)
- [ ] Verify merge queue E2E concurrency cancels previous runs when a
new PR enters the queue
2026-03-06 13:33:02 +00:00
Abdullah.andGitHub 9f9a6a45dd fix: enforce the user to pass in property with id suffix for morph relations in Rest API (#18335)
REST API allowed users to pass in targetOpportunity, targetPerson,
targetCompany etc when trying to create a noteTarget or a taskTarget.
The request went through, we got back a 201, the record was created, but
the relationship was never established since the FK was empty in the
database.

This PR enforces users to send in the property with the `Id` suffix for
consistency. So, the user sends in targetOpportunityId, targetPersonId,
targetCompanyId etc.

<p align="center">
<img width="854" height="480" alt="image"
src="https://github.com/user-attachments/assets/ed50a623-68d4-4266-baf1-e94a657c3fd4"
/>
</p>

If the users try to send without the "Id" suffix, they get an error
explaining what to do.

<p align="center">
<img width="854" height="480" alt="image"
src="https://github.com/user-attachments/assets/8bab399b-7a04-4b6a-86b5-6f0e0b1ecd5d"
/>
</p>

Additionally, the documentation itself contains the correct property
names.

<p align="center">
<img width="854" height="480" alt="image"
src="https://github.com/user-attachments/assets/83e51cd6-8ef7-4a4d-8696-ab37cc4a9dd6"
/>
</p>

Finally, the filters also enforce this "Id" suffix convention in the GET
request.

<p align="center">
<img width="854" height="480" alt="image"
src="https://github.com/user-attachments/assets/168a2f09-1242-40fa-bd84-1f7d9c60357c"
/>
</p>

Edit: Updated error messages after the screenshots were taken to make
them a little generic. Secondly, this PR also fixes the issue of morph
relation ids and objects not appearing in the response (when depth is
1).
2026-03-06 14:21:19 +01:00
Thomas TrompetteandGitHub 1f1da901ea Bug fixes batch (#18457)
Fixes https://github.com/twentyhq/twenty/issues/18181

Fixes https://github.com/twentyhq/twenty/issues/16842
Iterators remain running, which prevent the stopping state to eventually
become stopped

Fixes https://github.com/twentyhq/twenty/issues/18186
2026-03-06 14:20:54 +01:00
f65aafe96b i18n - translations (#18462)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-06 14:13:03 +01:00
Baptiste DevessierandGitHub a79b816117 Allow users to set where new fields must be created in a record page layout (#18420)
## Demo


https://github.com/user-attachments/assets/eaf89d0c-96e0-4e49-ac58-290c8e7403ff
2026-03-06 14:04:57 +01:00
Charles BochetandGitHub d37ed7e07c Optimize merge queue to only run E2E and integrate prettier into lint (#18459)
## Summary

- **Merge queue optimization**: Created a dedicated
`ci-merge-queue.yaml` workflow that only runs Playwright E2E tests on
`ubuntu-latest-8-cores`. Removed `merge_group` trigger from all 7
existing CI workflows (front, server, shared, website, sdk, zapier,
docker-compose). The merge queue goes from ~30+ parallel jobs to a
single focused E2E job.
- **Label-based merge queue simulation**: Added `run-merge-queue` label
support so developers can trigger the exact merge queue E2E pipeline on
any open PR before it enters the queue.
- **Prettier in lint**: Chained `prettier --check` into `lint` and
`prettier --write` into `lint --configuration=fix` across `nx.json`
defaults, `twenty-front`, and `twenty-server`. Prettier formatting
errors are now caught by `lint` and fixed by `lint:fix` /
`lint:diff-with-main --configuration=fix`.

## After merge (manual repo settings)

Update GitHub branch protection required status checks:
1. Remove old per-workflow merge queue checks (`ci-front-status-check`,
`ci-e2e-status-check`, `ci-server-status-check`, etc.)
2. Add `ci-merge-queue-status-check` as the required check for the merge
queue
2026-03-06 13:20:57 +01:00
WeikoandGitHub d825ac06dd fix server production build (#18458)
## Context
- fuse.js was imported in navigate-app-tool.ts but only declared in the
root package.json (has been like this for months but for the first time
being used in the server)
- This works locally due to yarn hoisting, but breaks in Docker
production builds

```bash
Error: Cannot find module 'fuse.js'
Require stack:
- /app/packages/twenty-server/dist/engine/core-modules/tool/tools/navigate-tool/navigate-app-tool.js
- /app/packages/twenty-server/dist/engine/core-modules/tool-provider/providers/action-tool.provider.js
- /app/packages/twenty-server/dist/engine/core-modules/tool-provider/tool-provider.module.js
- /app/packages/twenty-server/dist/engine/metadata-modules/ai/ai-agent-execution/ai-agent-execution.module.js
- /app/packages/twenty-server/dist/engine/metadata-modules/ai/ai-agent-monitor/ai-agent-monitor.module.js
- /app/packages/twenty-server/dist/engine/metadata-modules/metadata-engine.module.js
- /app/packages/twenty-server/dist/engine/api/graphql/core-graphql-api.module.js
- /app/packages/twenty-server/dist/app.module.js
- /app/packages/twenty-server/dist/main.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1456:15)
    at defaultResolveImpl (node:internal/modules/cjs/loader:1066:19)
    at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1071:22)
    at Module._load (node:internal/modules/cjs/loader:1242:25)
    at wrapModuleLoad (node:internal/modules/cjs/loader:255:19)
    at Module.require (node:internal/modules/cjs/loader:1556:12)
    at require (node:internal/modules/helpers:152:16)
    at Object.<anonymous> (/app/packages/twenty-server/dist/engine/core-modules/tool/tools/navigate-tool/navigate-app-tool.js:13:54)
    at Module._compile (node:internal/modules/cjs/loader:1812:14)
    at Object..js (node:internal/modules/cjs/loader:1943:10) {
  code: 'MODULE_NOT_FOUND',
  ```

Fix: Adding the dependency in twenty-server package.json to make it available in production builds
2026-03-06 11:44:18 +01:00
635 changed files with 4049 additions and 2762 deletions
+5
View File
@@ -19,7 +19,12 @@ runs:
shell: bash
run: git fetch origin main --depth=1
- name: Get last successful commit
if: env.NX_BASE == ''
uses: nrwl/nx-set-shas@v4
- name: Fallback to origin/main if no base found
if: env.NX_BASE == ''
shell: bash
run: echo "NX_BASE=$(git rev-parse origin/main)" >> $GITHUB_ENV
- name: Run affected command
shell: bash
env:
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 10
runs-on: ubuntu-latest-8-cores
runs-on: ubuntu-latest
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
+3 -158
View File
@@ -1,8 +1,7 @@
name: CI Front and E2E
name: CI Front
on:
pull_request:
merge_group:
permissions:
@@ -19,6 +18,7 @@ env:
jobs:
changed-files-check:
if: github.event_name != 'merge_group'
uses: ./.github/workflows/changed-files.yaml
with:
files: |
@@ -29,15 +29,6 @@ jobs:
packages/twenty-shared/**
packages/twenty-sdk/**
!packages/twenty-sdk/package.json
changed-files-check-e2e:
uses: ./.github/workflows/changed-files.yaml
with:
files: |
packages/**
!packages/create-twenty-app/package.json
!packages/twenty-sdk/package.json
playwright.config.ts
.github/workflows/ci-front.yaml
front-sb-build:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
@@ -74,7 +65,7 @@ jobs:
key: ${{ env.STORYBOOK_BUILD_CACHE_KEY_FOR_SAVE_ACTION }}
front-sb-test:
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
runs-on: ubuntu-latest-4-cores
needs: front-sb-build
strategy:
fail-fast: false
@@ -160,32 +151,6 @@ jobs:
# npx nyc merge coverage-artifacts ${{ env.PATH_TO_COVERAGE }}/coverage-storybook.json
# - name: Checking coverage
# run: npx nx storybook:coverage twenty-front --checkCoverage=true --configuration=${{ matrix.storybook_scope }}
front-chromatic-deployment:
timeout-minutes: 30
if: false
needs: front-sb-build
runs-on: ubuntu-latest-8-cores
env:
REACT_APP_SERVER_BASE_URL: http://127.0.0.1:3000
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Restore storybook build cache
uses: ./.github/actions/restore-cache
with:
key: ${{ env.STORYBOOK_BUILD_CACHE_KEY_FOR_RESTORE_ACTION }}
- name: Front / Write .env
run: |
cd packages/twenty-front
touch .env
echo "" >> .env
echo "REACT_APP_SERVER_BASE_URL=$REACT_APP_SERVER_BASE_URL" >> .env
- name: Publish to Chromatic
run: npx nx run twenty-front:chromatic:ci
front-task:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
@@ -257,117 +222,6 @@ jobs:
# name: frontend-build
# path: packages/twenty-front/build
# retention-days: 1
e2e-test:
runs-on: ubuntu-latest
needs: [changed-files-check-e2e, front-build]
if: |
always() &&
needs.changed-files-check-e2e.outputs.any_changed == 'true' &&
(needs.front-build.result == 'success' || needs.front-build.result == 'skipped') &&
(github.event_name == 'push' || github.event_name == 'merge_group' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'run-e2e')))
timeout-minutes: 30
env:
NODE_OPTIONS: "--max-old-space-size=10240"
services:
postgres:
image: twentycrm/twenty-postgres-spilo
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: "true"
SPILO_PROVIDER: "local"
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 10
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Check system resources
run: |
echo "Available memory:"
free -h
echo "Available disk space:"
df -h
echo "CPU info:"
lscpu
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build twenty-shared
run: npx nx build twenty-shared
- name: Install Playwright Browsers
run: npx nx setup twenty-e2e-testing
- name: Setup environment files
run: |
cp packages/twenty-front/.env.example packages/twenty-front/.env
npx nx reset:env:e2e-testing-server twenty-server
# - name: Download frontend build artifact
# if: needs.front-build.result == 'success'
# uses: actions/download-artifact@v4
# with:
# name: frontend-build
# path: packages/twenty-front/build
# - name: Build frontend (if not available from front-build)
# if: needs.front-build.result == 'skipped'
# run: NODE_ENV=production NODE_OPTIONS="--max-old-space-size=10240" npx nx build twenty-front
- name: Build frontend
run: NODE_ENV=production NODE_OPTIONS="--max-old-space-size=10240" npx nx build twenty-front
- name: Build server
run: npx nx build twenty-server
- name: Create and setup database
run: |
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "default";'
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
npx nx run twenty-server:database:reset
- name: Start server
run: |
npx nx start twenty-server &
echo "Waiting for server to be ready..."
timeout 60 bash -c 'until curl -s http://localhost:3000/health; do sleep 2; done'
- name: Start frontend
run: |
npm_config_yes=true npx serve -s packages/twenty-front/build -l 3001 &
echo "Waiting for frontend to be ready..."
timeout 60 bash -c 'until curl -s http://localhost:3001; do sleep 2; done'
- name: Start worker
run: |
npx nx run twenty-server:worker &
echo "Worker started"
- name: Run Playwright tests
run: npx nx test twenty-e2e-testing
# - uses: actions/upload-artifact@v4
# if: always()
# with:
# name: playwright-report
# path: packages/twenty-e2e-testing/run_results/
# retention-days: 30
ci-front-status-check:
if: always() && !cancelled()
timeout-minutes: 5
@@ -385,12 +239,3 @@ jobs:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
ci-e2e-status-check:
if: always() && !cancelled()
timeout-minutes: 5
runs-on: ubuntu-latest
needs: [changed-files-check-e2e, e2e-test]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
+140
View File
@@ -0,0 +1,140 @@
name: CI Merge Queue
on:
merge_group:
pull_request:
types: [labeled, synchronize, opened, reopened]
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'merge_group' && github.event.merge_group.base_ref || github.ref }}
cancel-in-progress: true
jobs:
e2e-test:
if: >
github.event_name == 'merge_group' ||
(github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'run-merge-queue'))
runs-on: ubuntu-latest-8-cores
timeout-minutes: 30
env:
NODE_OPTIONS: "--max-old-space-size=10240"
services:
postgres:
image: twentycrm/twenty-postgres-spilo
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: "true"
SPILO_PROVIDER: "local"
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 10
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Restore Nx build cache
uses: actions/cache/restore@v4
with:
key: v4-e2e-build-${{ github.ref_name }}-${{ github.sha }}
restore-keys: |
v4-e2e-build-${{ github.ref_name }}-
v4-e2e-build-main-
path: |
.nx
node_modules/.cache
packages/*/node_modules/.cache
- name: Build twenty-shared
run: npx nx build twenty-shared
- name: Install Playwright Browsers
run: npx nx setup twenty-e2e-testing
- name: Setup environment files
run: |
cp packages/twenty-front/.env.example packages/twenty-front/.env
npx nx reset:env:e2e-testing-server twenty-server
- name: Build frontend
run: NODE_ENV=production npx nx build twenty-front
- name: Build server
run: npx nx build twenty-server
- name: Save Nx build cache
if: always()
uses: actions/cache/save@v4
with:
key: v4-e2e-build-${{ github.ref_name }}-${{ github.sha }}
path: |
.nx
node_modules/.cache
packages/*/node_modules/.cache
- name: Create and setup database
run: |
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "default";'
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
npx nx run twenty-server:database:reset
- name: Start server
run: |
npx nx start twenty-server &
echo "Waiting for server to be ready..."
timeout 60 bash -c 'until curl -s http://localhost:3000/health; do sleep 2; done'
- name: Start frontend
run: |
npm_config_yes=true npx serve -s packages/twenty-front/build -l 3001 &
echo "Waiting for frontend to be ready..."
timeout 60 bash -c 'until curl -s http://localhost:3001; do sleep 2; done'
- name: Start worker
run: |
npx nx run twenty-server:worker &
echo "Worker started"
- name: Run Playwright tests
run: npx nx test twenty-e2e-testing
- name: Upload Playwright results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-results
path: |
packages/twenty-e2e-testing/run_results/
packages/twenty-e2e-testing/test-results/
retention-days: 7
ci-merge-queue-status-check:
if: always() && !cancelled()
timeout-minutes: 5
runs-on: ubuntu-latest
needs: [e2e-test]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
+4 -3
View File
@@ -1,9 +1,8 @@
name: CI SDK
on:
merge_group:
pull_request:
merge_group:
permissions:
contents: read
@@ -14,10 +13,12 @@ concurrency:
jobs:
changed-files-check:
if: github.event_name != 'merge_group'
uses: ./.github/workflows/changed-files.yaml
with:
files: |
packages/twenty-sdk/**
packages/twenty-server/**
!packages/twenty-sdk/package.json
sdk-test:
needs: changed-files-check
@@ -50,7 +51,7 @@ jobs:
tasks: ${{ matrix.task }}
sdk-e2e-test:
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
runs-on: ubuntu-latest-4-cores
needs: [changed-files-check, sdk-test]
if: needs.changed-files-check.outputs.any_changed == 'true'
services:
+6 -6
View File
@@ -2,7 +2,6 @@ name: CI Server
on:
pull_request:
merge_group:
permissions:
@@ -17,6 +16,7 @@ env:
jobs:
changed-files-check:
if: github.event_name != 'merge_group'
uses: ./.github/workflows/changed-files.yaml
with:
files: |
@@ -32,7 +32,7 @@ jobs:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
runs-on: ubuntu-latest-4-cores
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
@@ -60,7 +60,7 @@ jobs:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
runs-on: ubuntu-latest-4-cores
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
@@ -79,7 +79,7 @@ jobs:
server-validation:
needs: server-build
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
runs-on: ubuntu-latest-4-cores
services:
postgres:
image: twentycrm/twenty-postgres-spilo
@@ -179,7 +179,7 @@ jobs:
server-test:
needs: server-build
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
runs-on: ubuntu-latest-4-cores
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
@@ -201,7 +201,7 @@ jobs:
server-integration-test:
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
runs-on: ubuntu-latest-4-cores
needs: server-build
strategy:
fail-fast: false
+2 -2
View File
@@ -1,9 +1,8 @@
name: CI Shared
on:
merge_group:
pull_request:
merge_group:
permissions:
contents: read
@@ -14,6 +13,7 @@ concurrency:
jobs:
changed-files-check:
if: github.event_name != 'merge_group'
uses: ./.github/workflows/changed-files.yaml
with:
files: |
@@ -4,9 +4,8 @@ permissions:
contents: read
on:
merge_group:
pull_request:
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -14,6 +13,7 @@ concurrency:
jobs:
changed-files-check:
if: github.event_name != 'merge_group'
uses: ./.github/workflows/changed-files.yaml
with:
files: |
+2 -2
View File
@@ -4,9 +4,8 @@ permissions:
contents: read
on:
merge_group:
pull_request:
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -14,6 +13,7 @@ concurrency:
jobs:
changed-files-check:
if: github.event_name != 'merge_group'
uses: ./.github/workflows/changed-files.yaml
with:
files: |
+1 -3
View File
@@ -3,8 +3,6 @@ name: CI Zapier
on:
pull_request:
merge_group:
permissions:
contents: read
@@ -28,7 +26,7 @@ jobs:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
runs-on: ubuntu-latest-4-cores
services:
postgres:
image: twentycrm/twenty-postgres-spilo
+4 -4
View File
@@ -44,12 +44,12 @@
"cache": true,
"options": {
"cwd": "{projectRoot}",
"command": "npx oxlint -c .oxlintrc.json ."
"command": "npx oxlint -c .oxlintrc.json . && (prettier . --check --cache --cache-location ../../.cache/prettier/{projectRoot} --cache-strategy metadata || (echo 'ERROR: Prettier formatting check failed! Fix with: npx nx lint --configuration=fix' && false))"
},
"configurations": {
"ci": {},
"fix": {
"command": "npx oxlint --fix -c .oxlintrc.json ."
"command": "npx oxlint --fix -c .oxlintrc.json . && prettier . --write --cache --cache-location ../../.cache/prettier/{projectRoot} --cache-strategy metadata"
}
},
"dependsOn": ["^build", "twenty-oxlint-rules:build"]
@@ -58,12 +58,12 @@
"executor": "nx:run-commands",
"cache": false,
"options": {
"command": "FILES=$(git diff --name-only --diff-filter=d main -- {projectRoot}/ | grep -E '{args.pattern}'); [ -z \"$FILES\" ] && echo 'No changed files.' || npx oxlint -c {projectRoot}/.oxlintrc.json $FILES",
"command": "FILES=$(git diff --name-only --diff-filter=d main -- {projectRoot}/ | grep -E '{args.pattern}'); [ -z \"$FILES\" ] && echo 'No changed files.' || (npx oxlint -c {projectRoot}/.oxlintrc.json $FILES && (prettier --check $FILES || (echo 'ERROR: Prettier formatting check failed! Fix with: npx nx lint:diff-with-main --configuration=fix' && false)))",
"pattern": "\\.(ts|tsx|js|jsx)$"
},
"configurations": {
"fix": {
"command": "FILES=$(git diff --name-only --diff-filter=d main -- {projectRoot}/ | grep -E '{args.pattern}'); [ -z \"$FILES\" ] && echo 'No changed files.' || npx oxlint --fix -c {projectRoot}/.oxlintrc.json $FILES"
"command": "FILES=$(git diff --name-only --diff-filter=d main -- {projectRoot}/ | grep -E '{args.pattern}'); [ -z \"$FILES\" ] && echo 'No changed files.' || (npx oxlint --fix -c {projectRoot}/.oxlintrc.json $FILES && prettier --write $FILES)"
}
}
},
+22 -13
View File
@@ -17,22 +17,31 @@
"import/no-duplicates": "error",
"typescript/no-redeclare": "error",
"typescript/ban-ts-comment": "error",
"typescript/consistent-type-imports": ["error", {
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}],
"typescript/consistent-type-imports": [
"error",
{
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}
],
"typescript/explicit-function-return-type": "off",
"typescript/explicit-module-boundary-types": "off",
"typescript/no-empty-object-type": ["error", {
"allowInterfaces": "with-single-extends"
}],
"typescript/no-empty-object-type": [
"error",
{
"allowInterfaces": "with-single-extends"
}
],
"typescript/no-empty-function": "off",
"typescript/no-explicit-any": "off",
"typescript/no-unused-vars": ["warn", {
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}]
"typescript/no-unused-vars": [
"warn",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}
]
}
}
@@ -0,0 +1 @@
dist
+12 -19
View File
@@ -19,9 +19,11 @@ Create Twenty App is the official scaffolding CLI for building apps on top of [T
- Strong TypeScript support and typed client generation
## Documentation
See Twenty application documentation https://docs.twenty.com/developers/extend/capabilities/apps
## Prerequisites
- Node.js 24+ (recommended) and Yarn 4
- A Twenty workspace and an API key (create one at https://app.twenty.com/settings/api-webhooks)
@@ -64,11 +66,10 @@ yarn twenty app:uninstall
Control which example files are included when creating a new app:
| Flag | Behavior |
|------|----------|
| `-e, --exhaustive` | **(default)** Creates all example files without prompting |
| `-m, --minimal` | Creates only core files (`application-config.ts` and `default-role.ts`) |
| `-i, --interactive` | Prompts you to select which examples to include |
| Flag | Behavior |
| ------------------ | ----------------------------------------------------------------------- |
| `-e, --exhaustive` | **(default)** Creates all example files |
| `-m, --minimal` | Creates only core files (`application-config.ts` and `default-role.ts`) |
```bash
# Default: all examples included
@@ -76,24 +77,12 @@ npx create-twenty-app@latest my-app
# Minimal: only core files
npx create-twenty-app@latest my-app -m
# Interactive: choose which examples to include
npx create-twenty-app@latest my-app -i
```
In interactive mode, you can pick from:
- **Example object** — a custom CRM object definition (`objects/example-object.ts`)
- **Example field** — a custom field on the example object (`fields/example-field.ts`)
- **Example logic function** — a server-side handler with HTTP trigger (`logic-functions/hello-world.ts`)
- **Example front component** — a React UI component (`front-components/hello-world.tsx`)
- **Example view** — a saved view for the example object (`views/example-view.ts`)
- **Example navigation menu item** — a sidebar link (`navigation-menu-items/example-navigation-menu-item.ts`)
- **Example skill** — an AI agent skill definition (`skills/example-skill.ts`)
- **Integration test** — a vitest integration test verifying app installation (`__tests__/app-install.integration-test.ts`)
## What gets scaffolded
**Core files (always created):**
- `application-config.ts` — Application metadata configuration
- `roles/default-role.ts` — Default role for logic functions
- `logic-functions/pre-install.ts` — Pre-install logic function (runs before app installation)
@@ -102,6 +91,7 @@ In interactive mode, you can pick from:
- A prewired `twenty` script that delegates to the `twenty` CLI from twenty-sdk
**Example files (controlled by scaffolding mode):**
- `objects/example-object.ts` — Example custom object with a text field
- `fields/example-field.ts` — Example standalone field extending the example object
- `logic-functions/hello-world.ts` — Example logic function with HTTP trigger
@@ -112,14 +102,15 @@ In interactive mode, you can pick from:
- `__tests__/app-install.integration-test.ts` — Integration test that builds, installs, and verifies the app (includes `vitest.config.ts`, `tsconfig.spec.json`, and a setup file)
## Next steps
- Run `yarn twenty help` to see all available commands.
- Use `yarn twenty auth:login` to authenticate with your Twenty workspace.
- Explore the generated project and add your first entity with `yarn twenty entity:add` (logic functions, front components, objects, roles, views, navigation menu items, skills).
- Use `yarn twenty app:dev` while you iterate — it watches, builds, and syncs changes to your workspace in real time.
- Two typed API clients are autogenerated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`).
## Publish your application
Applications are currently stored in `twenty/packages/twenty-apps`.
You can share your application with all Twenty users:
@@ -144,9 +135,11 @@ git push
Our team reviews contributions for quality, security, and reusability before merging.
## Troubleshooting
- Auth prompts not appearing: run `yarn twenty auth:login` again and verify the API key permissions.
- Types not generated: ensure `yarn twenty app:dev` is running — it autogenerates the typed client.
## Contributing
- See our [GitHub](https://github.com/twentyhq/twenty)
- Join our [Discord](https://discord.gg/cx5n4Jzs57)
+3 -16
View File
@@ -18,10 +18,6 @@ const program = new Command(packageJson.name)
'-m, --minimal',
'Create only core entities (application-config and default-role)',
)
.option(
'-i, --interactive',
'Interactively choose which entity examples to include',
)
.helpOption('-h, --help', 'Display this help message.')
.action(
async (
@@ -29,19 +25,14 @@ const program = new Command(packageJson.name)
options?: {
exhaustive?: boolean;
minimal?: boolean;
interactive?: boolean;
},
) => {
const modeFlags = [
options?.exhaustive,
options?.minimal,
options?.interactive,
].filter(Boolean);
const modeFlags = [options?.exhaustive, options?.minimal].filter(Boolean);
if (modeFlags.length > 1) {
console.error(
chalk.red(
'Error: --exhaustive, --minimal, and --interactive are mutually exclusive.',
'Error: --exhaustive and --minimal are mutually exclusive.',
),
);
process.exit(1);
@@ -56,11 +47,7 @@ const program = new Command(packageJson.name)
process.exit(1);
}
const mode: ScaffoldingMode = options?.minimal
? 'minimal'
: options?.interactive
? 'interactive'
: 'exhaustive';
const mode: ScaffoldingMode = options?.minimal ? 'minimal' : 'exhaustive';
await new CreateAppCommand().execute(directory, mode);
},
@@ -8,9 +8,12 @@
"rules": {
"no-unused-vars": "off",
"typescript/no-unused-vars": ["warn", {
"argsIgnorePattern": "^_"
}],
"typescript/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"typescript/no-explicit-any": "off"
}
}
@@ -4,6 +4,7 @@
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/cli/__tests__/apps/rich-app
## UUID requirement
- All generated UUIDs must be valid UUID v4.
## Common Pitfalls
@@ -27,5 +27,11 @@
"~/*": ["./*"]
}
},
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/*.integration-test.ts"]
"exclude": [
"node_modules",
"dist",
"**/*.test.ts",
"**/*.spec.ts",
"**/*.integration-test.ts"
]
}
@@ -24,7 +24,7 @@ export class CreateAppCommand {
const { appName, appDisplayName, appDirectory, appDescription } =
await this.getAppInfos(directory);
const exampleOptions = await this.resolveExampleOptions(mode);
const exampleOptions = this.resolveExampleOptions(mode);
await this.validateDirectory(appDirectory);
@@ -103,9 +103,7 @@ export class CreateAppCommand {
return { appName, appDisplayName, appDirectory, appDescription };
}
private async resolveExampleOptions(
mode: ScaffoldingMode,
): Promise<ExampleOptions> {
private resolveExampleOptions(mode: ScaffoldingMode): ExampleOptions {
if (mode === 'minimal') {
return {
includeExampleObject: false,
@@ -119,94 +117,15 @@ export class CreateAppCommand {
};
}
if (mode === 'exhaustive') {
return {
includeExampleObject: true,
includeExampleField: true,
includeExampleLogicFunction: true,
includeExampleFrontComponent: true,
includeExampleView: true,
includeExampleNavigationMenuItem: true,
includeExampleSkill: true,
includeExampleIntegrationTest: true,
};
}
const { selectedExamples } = await inquirer.prompt([
{
type: 'checkbox',
name: 'selectedExamples',
message: 'Select which example files to include:',
choices: [
{
name: 'Example object (custom object definition)',
value: 'object',
checked: true,
},
{
name: 'Example field (custom field on the example object)',
value: 'field',
checked: true,
},
{
name: 'Example logic function (server-side handler)',
value: 'logicFunction',
checked: true,
},
{
name: 'Example front component (React UI component)',
value: 'frontComponent',
checked: true,
},
{
name: 'Example view (saved view for the example object)',
value: 'view',
checked: true,
},
{
name: 'Example navigation menu item (sidebar link)',
value: 'navigationMenuItem',
checked: true,
},
{
name: 'Example skill (AI agent skill definition)',
value: 'skill',
checked: true,
},
{
name: 'Integration test (vitest test verifying app installation)',
value: 'integrationTest',
checked: true,
},
],
},
]);
const includeField = selectedExamples.includes('field');
const includeView = selectedExamples.includes('view');
const includeExampleIntegrationTest =
selectedExamples.includes('integrationTest');
const includeObject =
selectedExamples.includes('object') || includeField || includeView;
if ((includeField || includeView) && !selectedExamples.includes('object')) {
console.log(
chalk.yellow(
'Note: Example object auto-included because example field/view depends on it.',
),
);
}
return {
includeExampleObject: includeObject,
includeExampleField: includeField,
includeExampleLogicFunction: selectedExamples.includes('logicFunction'),
includeExampleFrontComponent: selectedExamples.includes('frontComponent'),
includeExampleView: includeView,
includeExampleNavigationMenuItem:
selectedExamples.includes('navigationMenuItem'),
includeExampleSkill: selectedExamples.includes('skill'),
includeExampleIntegrationTest,
includeExampleObject: true,
includeExampleField: true,
includeExampleLogicFunction: true,
includeExampleFrontComponent: true,
includeExampleView: true,
includeExampleNavigationMenuItem: true,
includeExampleSkill: true,
includeExampleIntegrationTest: true,
};
}
@@ -1,4 +1,4 @@
export type ScaffoldingMode = 'exhaustive' | 'minimal' | 'interactive';
export type ScaffoldingMode = 'exhaustive' | 'minimal';
export type ExampleOptions = {
includeExampleObject: boolean;
@@ -673,15 +673,24 @@ describe('copyBaseApplicationProject', () => {
const content = await fs.readFile(viewPath, 'utf8');
expect(content).toContain("import { defineView } from 'twenty-sdk'");
expect(content).toContain(
"import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object'",
"import { defineView, ViewKey } from 'twenty-sdk'",
);
expect(content).toContain(
"import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, NAME_FIELD_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object'",
);
expect(content).toContain('export default defineView({');
expect(content).toContain(
'objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER',
);
expect(content).toContain("name: 'example-view'");
expect(content).toContain("name: 'All example items'");
expect(content).toContain('fields: [');
expect(content).toContain(
'fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER',
);
expect(content).toContain('isVisible: true');
expect(content).toContain('key: ViewKey.INDEX');
expect(content).toContain('size: 200');
});
});
@@ -712,6 +721,7 @@ describe('copyBaseApplicationProject', () => {
expect(content).toContain('export default defineNavigationMenuItem({');
expect(content).toContain("name: 'example-navigation-menu-item'");
expect(content).toContain("icon: 'IconList'");
expect(content).toContain("color: 'blue'");
expect(content).toContain('position: 0');
});
});
@@ -431,16 +431,29 @@ const createExampleView = async ({
fileName: string;
}) => {
const universalIdentifier = v4();
const viewFieldUniversalIdentifier = v4();
const content = `import { defineView } from 'twenty-sdk';
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object';
const content = `import { defineView, ViewKey } from 'twenty-sdk';
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, NAME_FIELD_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object';
export const EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER = '${universalIdentifier}';
export default defineView({
universalIdentifier: '${universalIdentifier}',
name: 'example-view',
universalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
name: 'All example items',
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
icon: 'IconList',
key: ViewKey.INDEX,
position: 0,
fields: [
{
universalIdentifier: '${viewFieldUniversalIdentifier}',
fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
position: 0,
isVisible: true,
size: 200,
},
],
});
`;
@@ -460,18 +473,15 @@ const createExampleNavigationMenuItem = async ({
const universalIdentifier = v4();
const content = `import { defineNavigationMenuItem } from 'twenty-sdk';
import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from 'src/views/example-view';
export default defineNavigationMenuItem({
universalIdentifier: '${universalIdentifier}',
name: 'example-navigation-menu-item',
icon: 'IconList',
color: 'blue',
position: 0,
// Link to a view:
// viewUniversalIdentifier: '...',
// Or link to an object:
// targetObjectUniversalIdentifier: '...',
// Or link to an external URL:
// link: 'https://example.com',
viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
});
`;
+1 -3
View File
@@ -24,7 +24,5 @@
"vite.config.ts",
"jest.config.mjs"
],
"exclude": [
"src/constants/base-application/vitest.config.ts"
]
"exclude": ["src/constants/base-application/vitest.config.ts"]
}
@@ -35,7 +35,7 @@ cd my-twenty-app
yarn twenty app:dev
```
The scaffolder supports three modes for controlling which example files are included:
The scaffolder supports two modes for controlling which example files are included:
```bash filename="Terminal"
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill)
@@ -43,9 +43,6 @@ npx create-twenty-app@latest my-app
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
# Interactive: select which examples to include
npx create-twenty-app@latest my-app --interactive
```
From here you can:
@@ -121,7 +118,7 @@ my-twenty-app/
└── example-skill.ts # Example AI agent skill definition
```
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`). With `--interactive`, you choose which example files to include.
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`).
At a high level:
@@ -36,7 +36,7 @@ cd my-twenty-app
yarn twenty app:dev
```
يدعم المُنشئ ثلاثة أوضاع للتحكم في ملفات الأمثلة التي سيتم تضمينها:
يدعم المُنشئ وضعين للتحكم في ملفات الأمثلة التي سيتم تضمينها:
```bash filename="Terminal"
# الافتراضي (شامل): جميع الأمثلة (كائن، حقل، دالة منطقية، مكوّن الواجهة الأمامية، عرض، عنصر قائمة التنقل، مهارة)
@@ -44,9 +44,6 @@ npx create-twenty-app@latest my-app
# الأدنى: الملفات الأساسية فقط (application-config.ts و default-role.ts)
npx create-twenty-app@latest my-app --minimal
# التفاعلي: اختر الأمثلة التي تريد تضمينها
npx create-twenty-app@latest my-app --interactive
```
من هنا يمكنك:
@@ -122,7 +119,7 @@ my-twenty-app/
└── example-skill.ts # تعريف مهارة لوكيل الذكاء الاصطناعي — مثال},{
```
مع `--minimal`، سيتم إنشاء الملفات الأساسية فقط (`application-config.ts`، `roles/default-role.ts`، `logic-functions/pre-install.ts`، و`logic-functions/post-install.ts`). مع `--interactive`، تختار ملفات الأمثلة التي تريد تضمينها.
مع `--minimal`، سيتم إنشاء الملفات الأساسية فقط (`application-config.ts`، `roles/default-role.ts`، `logic-functions/pre-install.ts`، و`logic-functions/post-install.ts`).
بشكل عام:
@@ -36,7 +36,7 @@ cd my-twenty-app
yarn twenty app:dev
```
Nástroj pro generování kostry podporuje tři režimy pro řízení toho, které ukázkové soubory jsou zahrnuty:
Nástroj pro generování kostry podporuje dva režimy pro řízení toho, které ukázkové soubory jsou zahrnuty:
```bash filename="Terminal"
# Výchozí (úplný): všechny příklady (objekt, pole, logická funkce, front-endová komponenta, zobrazení, položka navigační nabídky, dovednost)
@@ -44,9 +44,6 @@ npx create-twenty-app@latest my-app
# Minimální: pouze základní soubory (application-config.ts a default-role.ts)
npx create-twenty-app@latest my-app --minimal
# Interaktivní: vyberte, které příklady zahrnout
npx create-twenty-app@latest my-app --interactive
```
Odtud můžete:
@@ -122,7 +119,7 @@ my-twenty-app/
└── example-skill.ts # Ukázková definice dovednosti agenta AI
```
S volbou `--minimal` se vytvoří pouze základní soubory (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` a `logic-functions/post-install.ts`). S volbou `--interactive` si vyberete, které ukázkové soubory chcete zahrnout.
S volbou `--minimal` se vytvoří pouze základní soubory (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` a `logic-functions/post-install.ts`).
V kostce:
@@ -36,7 +36,7 @@ cd my-twenty-app
yarn twenty app:dev
```
Das Scaffolding-Tool unterstützt drei Modi, um zu steuern, welche Beispieldateien enthalten sind:
Das Scaffolding-Tool unterstützt zwei Modi, um zu steuern, welche Beispieldateien enthalten sind:
```bash filename="Terminal"
# Standard (umfassend): alle Beispiele (Objekt, Feld, Logikfunktion, Frontend-Komponente, View, Navigationsmenüeintrag, Skill)
@@ -44,9 +44,6 @@ npx create-twenty-app@latest my-app
# Minimal: nur Kerndateien (application-config.ts und default-role.ts)
npx create-twenty-app@latest my-app --minimal
# Interaktiv: wähle aus, welche Beispiele enthalten sein sollen
npx create-twenty-app@latest my-app --interactive
```
Von hier aus können Sie:
@@ -122,7 +119,7 @@ my-twenty-app/
└── example-skill.ts # Example AI agent skill definition
```
Mit `--minimal` werden nur die Kerndateien erstellt (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` und `logic-functions/post-install.ts`). Mit `--interactive` wählst du aus, welche Beispieldateien enthalten sein sollen.
Mit `--minimal` werden nur die Kerndateien erstellt (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` und `logic-functions/post-install.ts`).
Auf hoher Ebene:
@@ -36,17 +36,14 @@ cd my-twenty-app
yarn twenty app:dev
```
Lo strumento di scaffolding supporta tre modalità per controllare quali file di esempio vengono inclusi:
Lo strumento di scaffolding supporta due modalità per controllare quali file di esempio vengono inclusi:
```bash filename="Terminal"
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill)
# Predefinita (esaustiva): tutti gli esempi (oggetto, campo, funzione logica, componente front-end, vista, voce del menu di navigazione, skill)
npx create-twenty-app@latest my-app
# Minimal: only core files (application-config.ts and default-role.ts)
# Minimale: solo i file principali (application-config.ts e default-role.ts)
npx create-twenty-app@latest my-app --minimal
# Interactive: select which examples to include
npx create-twenty-app@latest my-app --interactive
```
Da qui puoi:
@@ -122,7 +119,7 @@ my-twenty-app/
└── example-skill.ts # Example AI agent skill definition
```
Con `--minimal`, vengono creati solo i file principali (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` e `logic-functions/post-install.ts`). Con `--interactive`, scegli quali file di esempio includere.
Con `--minimal`, vengono creati solo i file principali (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` e `logic-functions/post-install.ts`).
A livello generale:
@@ -36,17 +36,14 @@ cd my-twenty-app
yarn twenty app:dev
```
O gerador de estrutura oferece suporte a três modos para controlar quais arquivos de exemplo são incluídos:
The scaffolder supports two modes for controlling which example files are included:
```bash filename="Terminal"
# Padrão (exhaustivo): todos os exemplos (objeto, campo, função de lógica, componente de front-end, visualização, item do menu de navegação, habilidade)
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill)
npx create-twenty-app@latest my-app
# Mínimo: apenas arquivos principais (application-config.ts e default-role.ts)
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
# Interativo: selecione quais exemplos incluir
npx create-twenty-app@latest my-app --interactive
```
A partir daqui você pode:
@@ -122,7 +119,7 @@ my-twenty-app/
└── example-skill.ts # Exemplo de definição de habilidade de agente de IA
```
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`). Com `--interactive`, você escolhe quais arquivos de exemplo incluir.
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`).
Em alto nível:
@@ -36,7 +36,7 @@ cd my-twenty-app
yarn twenty app:dev
```
Generatorul de schelet acceptă trei moduri pentru a controla ce fișiere de exemplu sunt incluse:
Generatorul de schelet acceptă două moduri pentru a controla ce fișiere de exemplu sunt incluse:
```bash filename="Terminal"
# Implicit (exhaustiv): toate exemplele (obiect, câmp, funcție logică, componentă de interfață, vizualizare, element de meniu de navigare, abilitate)
@@ -44,9 +44,6 @@ npx create-twenty-app@latest my-app
# Minimal: doar fișierele de bază (application-config.ts și default-role.ts)
npx create-twenty-app@latest my-app --minimal
# Interactiv: selectezi ce exemple să incluzi
npx create-twenty-app@latest my-app --interactive
```
De aici puteți:
@@ -122,7 +119,7 @@ my-twenty-app/
└── example-skill.ts # Example AI agent skill definition
```
Cu `--minimal`, sunt create doar fișierele de bază (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` și `logic-functions/post-install.ts`). Cu `--interactive`, alegi ce fișiere de exemplu să incluzi.
Cu `--minimal`, sunt create doar fișierele de bază (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` și `logic-functions/post-install.ts`).
Pe scurt:
@@ -36,17 +36,14 @@ cd my-twenty-app
yarn twenty app:dev
```
Генератор каркаса поддерживает три режима для управления тем, какие примерные файлы включаются:
The scaffolder supports two modes for controlling which example files are included:
```bash filename="Terminal"
# По умолчанию (полный набор): все примеры (объект, поле, логическая функция, фронтенд-компонент, представление, пункт меню навигации, навык)
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill)
npx create-twenty-app@latest my-app
# Минимальный: только основные файлы (application-config.ts и default-role.ts)
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
# Интерактивный: выбрать, какие примеры включить
npx create-twenty-app@latest my-app --interactive
```
Отсюда вы можете:
@@ -122,7 +119,7 @@ my-twenty-app/
└── example-skill.ts # Example AI agent skill definition
```
С `--minimal` создаются только основные файлы (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` и `logic-functions/post-install.ts`). С `--interactive` вы выбираете, какие примерные файлы включить.
С `--minimal` создаются только основные файлы (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` и `logic-functions/post-install.ts`).
В общих чертах:
@@ -36,17 +36,14 @@ cd my-twenty-app
yarn twenty app:dev
```
İskelet oluşturucu, hangi örnek dosyaların dahil edileceğini kontrol etmek için üç modu destekler:
The scaffolder supports two modes for controlling which example files are included:
```bash filename="Terminal"
# Varsayılan (kapsamlı): tüm örnekler (nesne, alan, mantık fonksiyonu, ön bileşen, görünüm, gezinme menüsü öğesi, yetenek)
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill)
npx create-twenty-app@latest my-app
# Minimal: yalnızca çekirdek dosyalar (application-config.ts ve default-role.ts)
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
# Etkileşimli: dahil edilecek örnekleri seçin
npx create-twenty-app@latest my-app --interactive
```
Buradan şunları yapabilirsiniz:
@@ -122,7 +119,7 @@ my-twenty-app/
└── example-skill.ts # Örnek yapay zekâ ajanı yetenek tanımı
```
`--minimal` ile yalnızca çekirdek dosyalar oluşturulur (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` ve `logic-functions/post-install.ts`). `--interactive` ile hangi örnek dosyaların dahil edileceğini siz seçersiniz.
`--minimal` ile yalnızca çekirdek dosyalar oluşturulur (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` ve `logic-functions/post-install.ts`).
Genel hatlarıyla:
@@ -36,17 +36,14 @@ cd my-twenty-app
yarn twenty app:dev
```
The scaffolder supports three modes for controlling which example files are included:
The scaffolder supports two modes for controlling which example files are included:
```bash filename="Terminal"
# 默认(完整):所有示例(对象、字段、逻辑函数、前端组件、视图、导航菜单项、技能)
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill)
npx create-twenty-app@latest my-app
# 最小化:仅核心文件(application-config.ts default-role.ts
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
# 交互式:选择要包含的示例
npx create-twenty-app@latest my-app --interactive
```
从这里您可以:
@@ -122,7 +119,7 @@ my-twenty-app/
└── example-skill.ts # Example AI agent skill definition
```
使用 `--minimal` 时,只会创建核心文件(`application-config.ts`、`roles/default-role.ts`、`logic-functions/pre-install.ts` 和 `logic-functions/post-install.ts`)。 With `--interactive`, you choose which example files to include.
使用 `--minimal` 时,只会创建核心文件(`application-config.ts`、`roles/default-role.ts`、`logic-functions/pre-install.ts` 和 `logic-functions/post-install.ts`)。
总体来说:
@@ -87,7 +87,7 @@ export class LoginPage {
async clickLoginWithEmailIfVisible() {
try {
await this.loginWithEmailButton.click();
await this.loginWithEmailButton.click({ timeout: 3000 });
} catch {
// Button not found - email field might already be visible (SSO-only or different auth flow)
}
@@ -106,7 +106,7 @@ export class LoginPage {
}
async typeEmail(email: string) {
await this.emailField.fill(email, { timeout: 10000 });
await this.emailField.fill(email, { timeout: 30000 });
}
async typePassword(email: string) {
@@ -9,18 +9,18 @@ const test = base.extend<{ loginPage: LoginPage }>({
});
const loginAndSelectWorkspace = async (loginPage: LoginPage, page: any) => {
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await loginPage.clickLoginWithEmailIfVisible();
await loginPage.typeEmail(process.env.DEFAULT_LOGIN!);
await loginPage.clickContinueButton();
await loginPage.typePassword(process.env.DEFAULT_PASSWORD!);
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await loginPage.clickSignInButton();
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
const workspaceButton = page.getByText('Apple', { exact: true });
await workspaceButton.waitFor({ state: 'visible', timeout: 15000 }).catch(
await workspaceButton.waitFor({ state: 'visible', timeout: 30000 }).catch(
() => {
// Single workspace mode — no workspace selection
},
@@ -34,7 +34,7 @@ const loginAndSelectWorkspace = async (loginPage: LoginPage, page: any) => {
() =>
!window.location.href.includes('verify') &&
!window.location.href.includes('welcome'),
{ timeout: 15000 },
{ timeout: 30000 },
);
};
@@ -82,13 +82,13 @@ test.describe('Return-to-path after login', () => {
page,
loginPage,
}) => {
const targetPath =
'/authorize?clientId=test-client-id&redirectUrl=https%3A%2F%2Fexample.com%2Fcallback';
const targetPath = '/settings/accounts';
const targetPathWithParams = `${targetPath}?tab=emails&filter=unread`;
await test.step(
'Navigate to path with query params while logged out',
async () => {
await page.goto(targetPath);
await page.goto(targetPathWithParams);
await page.waitForURL('**/welcome');
await page.waitForLoadState('domcontentloaded');
},
@@ -101,14 +101,15 @@ test.describe('Return-to-path after login', () => {
await test.step(
'Verify redirected to original path with query params',
async () => {
await page.waitForURL('**/authorize**', { timeout: 15000 });
await page.waitForURL(`**${targetPath}**`, {
timeout: 30000,
waitUntil: 'commit',
});
const url = new URL(page.url());
expect(url.pathname).toBe('/authorize');
expect(url.searchParams.get('clientId')).toBe('test-client-id');
expect(url.searchParams.get('redirectUrl')).toBe(
'https://example.com/callback',
);
expect(url.pathname).toBe(targetPath);
expect(url.searchParams.get('tab')).toBe('emails');
expect(url.searchParams.get('filter')).toBe('unread');
},
);
});
@@ -34,7 +34,7 @@ test('Sign up with invite link via email', async ({
});
await test.step('Create new account', async () => {
await loginPage.clickLoginWithEmail();
await loginPage.clickLoginWithEmailIfVisible();
await loginPage.typeEmail(email);
await loginPage.clickContinueButton();
await loginPage.typePassword(process.env.DEFAULT_PASSWORD);
@@ -34,10 +34,7 @@ test('Create workflow', async ({ page }) => {
const recordName = page.getByTestId('top-bar-title').getByPlaceholder('Name');
await expect(recordName).toBeVisible();
await recordName.click();
const nameInput = page.getByTestId('top-bar-title').getByRole('textbox');
await nameInput.fill(NEW_WORKFLOW_NAME);
await recordName.fill(NEW_WORKFLOW_NAME);
const workflowDiagramContainer = page.locator('.react-flow__renderer');
await workflowDiagramContainer.click();
+38 -25
View File
@@ -5,12 +5,13 @@
"categories": {
"correctness": "off"
},
"ignorePatterns": [
"node_modules"
],
"ignorePatterns": ["node_modules"],
"rules": {
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"no-console": ["warn", { "allow": ["group", "groupCollapsed", "groupEnd"] }],
"no-console": [
"warn",
{ "allow": ["group", "groupCollapsed", "groupEnd"] }
],
"no-control-regex": "off",
"no-debugger": "error",
"no-duplicate-imports": "error",
@@ -34,31 +35,43 @@
"typescript/no-redeclare": "error",
"typescript/ban-ts-comment": "error",
"typescript/consistent-type-imports": ["error", {
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}],
"typescript/consistent-type-imports": [
"error",
{
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}
],
"typescript/explicit-function-return-type": "off",
"typescript/explicit-module-boundary-types": "off",
"typescript/no-empty-object-type": ["error", {
"allowInterfaces": "with-single-extends"
}],
"typescript/no-empty-object-type": [
"error",
{
"allowInterfaces": "with-single-extends"
}
],
"typescript/no-empty-function": "off",
"typescript/no-explicit-any": "off",
"typescript/no-unused-vars": ["warn", {
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}],
"typescript/no-unused-vars": [
"warn",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}
],
"twenty/enforce-module-boundaries": ["error", {
"depConstraints": [
{
"sourceTag": "scope:backend",
"onlyDependOnLibsWithTags": ["scope:shared", "scope:backend"]
}
]
}]
"twenty/enforce-module-boundaries": [
"error",
{
"depConstraints": [
{
"sourceTag": "scope:backend",
"onlyDependOnLibsWithTags": ["scope:shared", "scope:backend"]
}
]
}
]
}
}
+1
View File
@@ -0,0 +1 @@
src/locales/generated
+6 -6
View File
@@ -5,12 +5,12 @@
"plugins": [
[
"@lingui/swc-plugin",
{
"runtimeModules": {
"i18n": ["@lingui/core", "i18n"],
"trans": ["@lingui/react", "Trans"]
},
"stripNonEssentialFields": false
{
"runtimeModules": {
"i18n": ["@lingui/core", "i18n"],
"trans": ["@lingui/react", "Trans"]
},
"stripNonEssentialFields": false
}
]
]
+1 -5
View File
@@ -13,9 +13,5 @@
"src/*": ["./src/*"]
}
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"vite.config.ts"
]
"include": ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts"]
}
+1 -6
View File
@@ -7,10 +7,5 @@
"types": ["node", "@nx/react/typings/image.d.ts", "vite/client"]
},
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"],
"exclude": [
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx"
]
"exclude": ["**/*.spec.ts", "**/*.test.ts", "**/*.spec.tsx", "**/*.test.tsx"]
}
+1 -1
View File
@@ -97,7 +97,7 @@
"twenty/effect-components": "error",
"twenty/no-hardcoded-colors": "error",
"twenty/matching-state-variable": "error",
"twenty/sort-css-properties-alphabetically": "off",
"twenty/sort-css-properties-alphabetically": "error",
"twenty/styled-components-prefixed-with-styled": "error",
"twenty/no-state-useref": "error",
"twenty/component-props-naming": "error",
+1
View File
@@ -1,3 +1,4 @@
src/generated
src/generated-metadata
src/locales/generated
src/testing/mock-data/generated
+4 -4
View File
@@ -77,11 +77,11 @@
],
"options": {
"cwd": "{projectRoot}",
"command": "npx oxlint --type-aware -c .oxlintrc.json src/"
"command": "npx oxlint --type-aware -c .oxlintrc.json src/ && (prettier src/ --check --cache --cache-location ../../.cache/prettier/{projectRoot} --cache-strategy metadata || (echo 'ERROR: Prettier formatting check failed! Fix with: npx nx lint twenty-front --configuration=fix' && false))"
},
"configurations": {
"fix": {
"command": "npx oxlint --type-aware --fix -c .oxlintrc.json src/"
"command": "npx oxlint --type-aware --fix -c .oxlintrc.json src/ && prettier src/ --write --cache --cache-location ../../.cache/prettier/{projectRoot} --cache-strategy metadata"
}
}
},
@@ -89,11 +89,11 @@
"executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
"command": "FILES=$(git diff --name-only --diff-filter=d main...HEAD -- src/ | grep -E '\\.(ts|tsx)$'); [ -z \"$FILES\" ] && echo 'No changed files.' || npx oxlint --type-aware -c .oxlintrc.json $FILES"
"command": "FILES=$(git diff --name-only --relative --diff-filter=d main...HEAD -- src/ | grep -E '\\.(ts|tsx)$'); [ -z \"$FILES\" ] && echo 'No changed files.' || (npx oxlint --type-aware -c .oxlintrc.json $FILES && (prettier --check $FILES || (echo 'ERROR: Prettier formatting check failed! Fix with: npx nx lint:diff-with-main twenty-front --configuration=fix' && false)))"
},
"configurations": {
"fix": {
"command": "FILES=$(git diff --name-only --diff-filter=d main...HEAD -- src/ | grep -E '\\.(ts|tsx)$'); [ -z \"$FILES\" ] && echo 'No changed files.' || npx oxlint --type-aware --fix -c .oxlintrc.json $FILES"
"command": "FILES=$(git diff --name-only --relative --diff-filter=d main...HEAD -- src/ | grep -E '\\.(ts|tsx)$'); [ -z \"$FILES\" ] && echo 'No changed files.' || (npx oxlint --type-aware --fix -c .oxlintrc.json $FILES && prettier --write $FILES)"
}
}
},
File diff suppressed because one or more lines are too long
@@ -35,12 +35,12 @@ const StyledSkeletonContainer = styled.div`
`;
const StyledSkeletonTitleContainer = styled.div`
align-items: flex-start;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
gap: 10px;
height: 32px;
justify-content: center;
max-width: 196px;
min-width: 196px;
@@ -10,8 +10,8 @@ const StyledSkeletonContainer = styled.div`
display: flex;
flex-direction: column;
gap: 6px;
min-width: 196px;
max-width: 196px;
min-width: 196px;
`;
export const MainNavigationDrawerItemsSkeletonLoader = ({
@@ -44,11 +44,11 @@ const StyledRightPanelContainer = styled.div`
`;
const StyledRightPanelFlexContainer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
height: 32px;
justify-content: flex-end;
margin-bottom: 12px;
`;
@@ -7,8 +7,8 @@ import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledSidePanelContainer = styled.div`
display: flex;
flex-direction: column;
width: 100%;
padding: ${themeCssVariables.spacing[4]};
width: 100%;
`;
const StyledSkeletonLoader = () => {
@@ -16,9 +16,9 @@ const StyledContainer = styled.div`
gap: 12px;
height: 100dvh;
min-width: ${NAVIGATION_DRAWER_CONSTRAINTS.default}px;
width: 100%;
padding: 12px 8px 12px 8px;
overflow: hidden;
padding: 12px 8px 12px 8px;
width: 100%;
@media (max-width: ${MOBILE_VIEWPORT}px) {
width: 100%;
@@ -4157,6 +4157,12 @@ msgstr "Verstek landkode"
msgid "Default palette"
msgstr "Verstekpalet"
#. js-lingui-id: U/uGBk
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "Default position/visibility for fields created in the future"
msgstr ""
#. js-lingui-id: v41VX6
#: src/pages/settings/members/SettingsWorkspaceMembers.tsx
#: src/modules/workspace/components/WorkspaceInviteTeam.tsx
@@ -8951,6 +8957,12 @@ msgstr "Nuwe E-posdomein"
msgid "New Field"
msgstr "Nuwe Veld"
#. js-lingui-id: DNUCGO
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "New fields"
msgstr ""
#. js-lingui-id: 96G6Re
#: src/modules/side-panel/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemMainMenu.tsx
@@ -4157,6 +4157,12 @@ msgstr "رمز البلد الافتراضي"
msgid "Default palette"
msgstr "لوحة الألوان الافتراضية"
#. js-lingui-id: U/uGBk
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "Default position/visibility for fields created in the future"
msgstr ""
#. js-lingui-id: v41VX6
#: src/pages/settings/members/SettingsWorkspaceMembers.tsx
#: src/modules/workspace/components/WorkspaceInviteTeam.tsx
@@ -8951,6 +8957,12 @@ msgstr "نطاق بريد إلكتروني جديد"
msgid "New Field"
msgstr "حقل جديد"
#. js-lingui-id: DNUCGO
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "New fields"
msgstr ""
#. js-lingui-id: 96G6Re
#: src/modules/side-panel/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemMainMenu.tsx
@@ -4157,6 +4157,12 @@ msgstr "Codi de país predeterminat"
msgid "Default palette"
msgstr "Paleta predeterminada"
#. js-lingui-id: U/uGBk
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "Default position/visibility for fields created in the future"
msgstr ""
#. js-lingui-id: v41VX6
#: src/pages/settings/members/SettingsWorkspaceMembers.tsx
#: src/modules/workspace/components/WorkspaceInviteTeam.tsx
@@ -8951,6 +8957,12 @@ msgstr "Nou domini de correu"
msgid "New Field"
msgstr "Nou camp"
#. js-lingui-id: DNUCGO
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "New fields"
msgstr ""
#. js-lingui-id: 96G6Re
#: src/modules/side-panel/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemMainMenu.tsx
@@ -4157,6 +4157,12 @@ msgstr "Výchozí číselný kód země"
msgid "Default palette"
msgstr "Výchozí paleta"
#. js-lingui-id: U/uGBk
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "Default position/visibility for fields created in the future"
msgstr ""
#. js-lingui-id: v41VX6
#: src/pages/settings/members/SettingsWorkspaceMembers.tsx
#: src/modules/workspace/components/WorkspaceInviteTeam.tsx
@@ -8951,6 +8957,12 @@ msgstr "Nová e-mailová doména"
msgid "New Field"
msgstr "Nové pole"
#. js-lingui-id: DNUCGO
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "New fields"
msgstr ""
#. js-lingui-id: 96G6Re
#: src/modules/side-panel/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemMainMenu.tsx
@@ -4157,6 +4157,12 @@ msgstr "Standardlandekode"
msgid "Default palette"
msgstr "Standardfarvepalet"
#. js-lingui-id: U/uGBk
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "Default position/visibility for fields created in the future"
msgstr ""
#. js-lingui-id: v41VX6
#: src/pages/settings/members/SettingsWorkspaceMembers.tsx
#: src/modules/workspace/components/WorkspaceInviteTeam.tsx
@@ -8951,6 +8957,12 @@ msgstr "Nyt e-mail-domæne"
msgid "New Field"
msgstr "Nyt felt"
#. js-lingui-id: DNUCGO
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "New fields"
msgstr ""
#. js-lingui-id: 96G6Re
#: src/modules/side-panel/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemMainMenu.tsx
@@ -4157,6 +4157,12 @@ msgstr "Standard-Ländercode"
msgid "Default palette"
msgstr "Standardpalette"
#. js-lingui-id: U/uGBk
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "Default position/visibility for fields created in the future"
msgstr ""
#. js-lingui-id: v41VX6
#: src/pages/settings/members/SettingsWorkspaceMembers.tsx
#: src/modules/workspace/components/WorkspaceInviteTeam.tsx
@@ -8951,6 +8957,12 @@ msgstr "Neue E-Mail-Domäne"
msgid "New Field"
msgstr "Neues Feld"
#. js-lingui-id: DNUCGO
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "New fields"
msgstr ""
#. js-lingui-id: 96G6Re
#: src/modules/side-panel/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemMainMenu.tsx
@@ -4157,6 +4157,12 @@ msgstr "Προεπιλεγμένος Κωδικός Χώρας"
msgid "Default palette"
msgstr "Προεπιλεγμένη παλέτα"
#. js-lingui-id: U/uGBk
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "Default position/visibility for fields created in the future"
msgstr ""
#. js-lingui-id: v41VX6
#: src/pages/settings/members/SettingsWorkspaceMembers.tsx
#: src/modules/workspace/components/WorkspaceInviteTeam.tsx
@@ -8951,6 +8957,12 @@ msgstr "Νέα Περιοχή Email"
msgid "New Field"
msgstr "Νέο Πεδίο"
#. js-lingui-id: DNUCGO
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "New fields"
msgstr ""
#. js-lingui-id: 96G6Re
#: src/modules/side-panel/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemMainMenu.tsx
+12
View File
@@ -4152,6 +4152,12 @@ msgstr "Default Country Code"
msgid "Default palette"
msgstr "Default palette"
#. js-lingui-id: U/uGBk
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "Default position/visibility for fields created in the future"
msgstr "Default position/visibility for fields created in the future"
#. js-lingui-id: v41VX6
#: src/pages/settings/members/SettingsWorkspaceMembers.tsx
#: src/modules/workspace/components/WorkspaceInviteTeam.tsx
@@ -8946,6 +8952,12 @@ msgstr "New Emailing Domain"
msgid "New Field"
msgstr "New Field"
#. js-lingui-id: DNUCGO
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "New fields"
msgstr "New fields"
#. js-lingui-id: 96G6Re
#: src/modules/side-panel/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemMainMenu.tsx
@@ -4157,6 +4157,12 @@ msgstr "Código de país predeterminado"
msgid "Default palette"
msgstr "Paleta predeterminada"
#. js-lingui-id: U/uGBk
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "Default position/visibility for fields created in the future"
msgstr ""
#. js-lingui-id: v41VX6
#: src/pages/settings/members/SettingsWorkspaceMembers.tsx
#: src/modules/workspace/components/WorkspaceInviteTeam.tsx
@@ -8951,6 +8957,12 @@ msgstr "Nuevo dominio de correo electrónico"
msgid "New Field"
msgstr "Nuevo Campo"
#. js-lingui-id: DNUCGO
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "New fields"
msgstr ""
#. js-lingui-id: 96G6Re
#: src/modules/side-panel/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemMainMenu.tsx
@@ -4157,6 +4157,12 @@ msgstr "Oletusmaakoodi"
msgid "Default palette"
msgstr "Oletuspaletti"
#. js-lingui-id: U/uGBk
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "Default position/visibility for fields created in the future"
msgstr ""
#. js-lingui-id: v41VX6
#: src/pages/settings/members/SettingsWorkspaceMembers.tsx
#: src/modules/workspace/components/WorkspaceInviteTeam.tsx
@@ -8951,6 +8957,12 @@ msgstr "Uusi sähköpostitoimialue"
msgid "New Field"
msgstr "Uusi kenttä"
#. js-lingui-id: DNUCGO
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "New fields"
msgstr ""
#. js-lingui-id: 96G6Re
#: src/modules/side-panel/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemMainMenu.tsx
@@ -4157,6 +4157,12 @@ msgstr "Code du pays par défaut"
msgid "Default palette"
msgstr "Palette par défaut"
#. js-lingui-id: U/uGBk
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "Default position/visibility for fields created in the future"
msgstr ""
#. js-lingui-id: v41VX6
#: src/pages/settings/members/SettingsWorkspaceMembers.tsx
#: src/modules/workspace/components/WorkspaceInviteTeam.tsx
@@ -8951,6 +8957,12 @@ msgstr "Nouveau domaine d'envoi d'e-mail"
msgid "New Field"
msgstr "Nouveau champ"
#. js-lingui-id: DNUCGO
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "New fields"
msgstr ""
#. js-lingui-id: 96G6Re
#: src/modules/side-panel/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemMainMenu.tsx
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -4157,6 +4157,12 @@ msgstr "קוד מדינה ברירת מחדל"
msgid "Default palette"
msgstr "פלטת ברירת המחדל"
#. js-lingui-id: U/uGBk
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "Default position/visibility for fields created in the future"
msgstr ""
#. js-lingui-id: v41VX6
#: src/pages/settings/members/SettingsWorkspaceMembers.tsx
#: src/modules/workspace/components/WorkspaceInviteTeam.tsx
@@ -8951,6 +8957,12 @@ msgstr "<span dir=\"rtl\">דומיין חדש לשליחת מיילים</span>"
msgid "New Field"
msgstr "שדה חדש"
#. js-lingui-id: DNUCGO
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "New fields"
msgstr ""
#. js-lingui-id: 96G6Re
#: src/modules/side-panel/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemMainMenu.tsx
@@ -4157,6 +4157,12 @@ msgstr "Alapértelmezett országkód"
msgid "Default palette"
msgstr "Alapértelmezett paletta"
#. js-lingui-id: U/uGBk
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "Default position/visibility for fields created in the future"
msgstr ""
#. js-lingui-id: v41VX6
#: src/pages/settings/members/SettingsWorkspaceMembers.tsx
#: src/modules/workspace/components/WorkspaceInviteTeam.tsx
@@ -8951,6 +8957,12 @@ msgstr "Új Emailküldési Tartomány"
msgid "New Field"
msgstr "Új mező"
#. js-lingui-id: DNUCGO
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "New fields"
msgstr ""
#. js-lingui-id: 96G6Re
#: src/modules/side-panel/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemMainMenu.tsx
@@ -4157,6 +4157,12 @@ msgstr "Prefisso internazionale predefinito"
msgid "Default palette"
msgstr "Tavolozza predefinita"
#. js-lingui-id: U/uGBk
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "Default position/visibility for fields created in the future"
msgstr ""
#. js-lingui-id: v41VX6
#: src/pages/settings/members/SettingsWorkspaceMembers.tsx
#: src/modules/workspace/components/WorkspaceInviteTeam.tsx
@@ -8951,6 +8957,12 @@ msgstr "Nuovo dominio di email"
msgid "New Field"
msgstr "Nuovo Campo"
#. js-lingui-id: DNUCGO
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "New fields"
msgstr ""
#. js-lingui-id: 96G6Re
#: src/modules/side-panel/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemMainMenu.tsx
@@ -4157,6 +4157,12 @@ msgstr "デフォルトの国コード"
msgid "Default palette"
msgstr "デフォルトのパレット"
#. js-lingui-id: U/uGBk
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "Default position/visibility for fields created in the future"
msgstr ""
#. js-lingui-id: v41VX6
#: src/pages/settings/members/SettingsWorkspaceMembers.tsx
#: src/modules/workspace/components/WorkspaceInviteTeam.tsx
@@ -8951,6 +8957,12 @@ msgstr "新しいメール送信ドメイン"
msgid "New Field"
msgstr "新規フィールド"
#. js-lingui-id: DNUCGO
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "New fields"
msgstr ""
#. js-lingui-id: 96G6Re
#: src/modules/side-panel/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemMainMenu.tsx
@@ -4157,6 +4157,12 @@ msgstr "기본 국가 코드"
msgid "Default palette"
msgstr "기본 팔레트"
#. js-lingui-id: U/uGBk
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "Default position/visibility for fields created in the future"
msgstr ""
#. js-lingui-id: v41VX6
#: src/pages/settings/members/SettingsWorkspaceMembers.tsx
#: src/modules/workspace/components/WorkspaceInviteTeam.tsx
@@ -8951,6 +8957,12 @@ msgstr "새 이메일 도메인"
msgid "New Field"
msgstr "새 필드"
#. js-lingui-id: DNUCGO
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "New fields"
msgstr ""
#. js-lingui-id: 96G6Re
#: src/modules/side-panel/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemMainMenu.tsx
@@ -4157,6 +4157,12 @@ msgstr "Standaard landcode"
msgid "Default palette"
msgstr "Standaardpalet"
#. js-lingui-id: U/uGBk
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "Default position/visibility for fields created in the future"
msgstr ""
#. js-lingui-id: v41VX6
#: src/pages/settings/members/SettingsWorkspaceMembers.tsx
#: src/modules/workspace/components/WorkspaceInviteTeam.tsx
@@ -8951,6 +8957,12 @@ msgstr "Nieuw e-maildomein"
msgid "New Field"
msgstr "Nieuw veld"
#. js-lingui-id: DNUCGO
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "New fields"
msgstr ""
#. js-lingui-id: 96G6Re
#: src/modules/side-panel/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemMainMenu.tsx
@@ -4157,6 +4157,12 @@ msgstr "Standardlandkode"
msgid "Default palette"
msgstr "Standardpalett"
#. js-lingui-id: U/uGBk
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "Default position/visibility for fields created in the future"
msgstr ""
#. js-lingui-id: v41VX6
#: src/pages/settings/members/SettingsWorkspaceMembers.tsx
#: src/modules/workspace/components/WorkspaceInviteTeam.tsx
@@ -8951,6 +8957,12 @@ msgstr "Nytt e-postdomene"
msgid "New Field"
msgstr "Nytt felt"
#. js-lingui-id: DNUCGO
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor.tsx
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor.tsx
msgid "New fields"
msgstr ""
#. js-lingui-id: 96G6Re
#: src/modules/side-panel/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemMainMenu.tsx

Some files were not shown because too many files have changed in this diff Show More