## Remove logic function layer
Package.json and yarn.lock are now on the application entity, so the
logic function layer is no longer used except as a legacy source for the
1.17 backfill. This PR removes all layer usage outside of that migration
and keeps only the entity for backfill.
### Summary
- **Kept:** `LogicFunctionLayerEntity` and its table, only used by the
1.17 backfill command to read legacy layer data and backfill application
package files.
- **Removed:** All other layer logic: CRUD, cache, resolvers, services,
DTOs, and frontend types. Logic functions now depend only on the
application for package/dependency context.
### Why Dependencies instead of Source for package files
Package.json and yarn.lock are the application’s dependency set and are
stored under the application in **FileFolder.Dependencies**. The build
service and drivers now read them only from Dependencies; nothing is
written to Source for these files.
This PR migrates `noteTarget` and `taskTarget` to morph relations behind
separate feature flags, following the Attachment/TimelineActivity
pattern.
It introduces the `IS_NOTE_TARGET_MIGRATED` and
`IS_TASK_TARGET_MIGRATED` flags, updates standard field metadata and
indexes to use morph relations, and adds two **1.17 workspace
migrations** that:
- rename `noteTarget.*Id` / `taskTarget.*Id` columns to `target*Id`
- convert the corresponding field metadata to `MORPH_RELATION` with a
shared `morphId`
On the frontend, note/task target read and write paths switch to
`target*Id` when the respective flag is enabled. Deleted targets are
filtered on reload to prevent reappearing relations.
Replace manual HTTP batch implementation with
`@jrmdayn/googleapis-batcher` library (we already use this for fetching
message list)
Initial real testing works fine but do not merge yet needs more
extensive real test runs
# Backfill application package files for custom and standard apps
- Backfill `package.json` / `yarn.lock` (and related fields) for
existing workspaces; new standard/custom apps get default dependency
files.
- Default package files under
`application/constants/default-package-files/`; util with hardcoded
checksums (comment on how to regenerate).
- New **FileFolder.Dependencies** for app dependency files;
**writeFile_v2** accepts optional `queryRunner` for transactional
writes.
Usages:
- **Upgrade command** `upgrade:1-17:backfill-application-package-files`:
standard/custom apps → default files; other apps → from logic function
layer.
- **Workspace creation**: create workspace with
`workspaceCustomApplicationId` first, then create application (enables
same-transaction insert). Migration makes workspace/application/file FKs
deferrable.
- dev seeder
PR #17492 fixed `markAsMessagesListFetchOngoing` to set
`syncStageStartedAt`, but also changed `isSyncStale` to return `false`
for null values. This prevented the stale recovery job from recovering
channels that were already stuck before the fix was deployed.
Changing `isSyncStale` to return `true` for null/undefined allows the
stale job to properly reset stuck channels back to PENDING state.
# Introduction
In preparation of the workspace agnostic builder, we're migrating
`FlatEntityMaps` to be universal identifier oriented and based
As in the builder context there're won't be any ids at all
Please also note that the FlatEntity is a UniversalFlatEntity superset
From
```ts
import { type SyncableFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
export type FlatEntityMaps<T extends SyncableFlatEntity> = {
byId: Partial<Record<string, T>>;
idByUniversalIdentifier: Partial<Record<string, string>>;
universalIdentifiersByApplicationId: Partial<Record<string, string[]>>;
};
```
To
```ts
export type FlatEntityMaps<
T extends SyncableFlatEntity | UniversalSyncableFlatEntity,
> = {
byUniversalIdentifier: Partial<Record<string, T>>;
universalIdentifierById: Partial<Record<string, string>>;
universalIdentifiersByApplicationId: Partial<Record<string, string[]>>; // this might make more sense to be migrated to universalIdentifiersByApplicationUniversalIdentifier but it's the main topic of this PR
};
```
## Low level maps tools
Had to refactor find | create | delete | replace | find-many | get-sub
tools ( through mutations and or throw equivalent )
## Root Cause
The `DateTimePicker` component lazy-loads `react-datepicker` using
React's `lazy()` with a `Suspense` fallback that shows skeleton loaders:
```typescript
const ReactDatePicker = lazy<ComponentType<DatePickerPropsType>>(() =>
import('react-datepicker').then(...)
);
// In render:
<Suspense fallback={<SkeletonLoader />}>
<ReactDatePicker ... />
</Suspense>
```
On slower CI runners (GitHub Actions vs depot.dev), the lazy load takes
longer, causing tests to timeout while still showing skeletons instead
of the actual date picker content.
## Fix
Increased `findByText` timeout from the default 1000ms to 10000ms for
tests that wait for the date picker to load:
- `DateTimeFieldInput.stories.tsx` - 4 stories fixed
- `InternalDatePicker.stories.tsx` - 2 stories fixed
## Why This Wasn't Flaky Before
depot.dev runners have faster I/O and more consistent performance, so
the lazy load completed quickly. GitHub Actions runners have more
variable performance, causing the load to sometimes exceed the 1000ms
default timeout.
## Summary
Follow-up to #17656 - the `RelationToOneFieldDisplay` performance test
is still failing on GitHub Actions runners.
**Failure:** 0.313ms actual vs 0.3ms threshold
**Fix:** Increased threshold from 0.3ms → 0.4ms to provide more headroom
for runner variability.
- fixedOverflowWidgets fixes the suggestions being overflow outside the
editor
- on focus, stop event propagation to avoid catching spaces as document
level events
## Summary
Fixes the dark mode error chip background color issue by changing
`background.danger` from `red12` to `red3` in the dark theme constants.
Fixes#17657
Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Thomas des Francs <Bonapara@users.noreply.github.com>
## Summary
After migrating from depot.dev runners to GitHub Actions runners, three
performance tests are failing due to slower single-threaded performance
on the new infrastructure (even with 16 cores).
## Problem
The performance tests use React's `<Profiler>` API to measure component
render times. depot.dev runners have better single-core performance and
lower memory latency compared to GitHub Actions runners, even larger
ones.
Failed tests:
| Test | Threshold | Actual | Overage |
|------|-----------|--------|---------|
| `DateTimeFieldDisplay` | 0.2ms | 0.281ms | +41% |
| `ChipFieldDisplay` | 0.2ms | 0.222ms | +11% |
| `RelationToOneFieldDisplay` | 0.22ms | 0.237ms | +8% |
## Solution
Increased thresholds to account for GitHub Actions runner performance:
- `DateTimeFieldDisplay`: 0.2ms → 0.35ms
- `ChipFieldDisplay`: 0.2ms → 0.3ms
- `RelationToOneFieldDisplay`: 0.22ms → 0.3ms
## Future Considerations
For a more robust long-term solution, we could implement baseline
comparison that:
1. Saves performance results on main branch builds
2. Compares PR results against baseline with tolerance (±25%)
3. Self-calibrates to actual runner performance
This would catch real regressions without being sensitive to
infrastructure differences.
# Introduction
Following https://github.com/twentyhq/twenty/pull/17632 and
https://github.com/twentyhq/twenty/pull/17572
This PR deprecates the agent, skill, field metadata and role
`standardId` in favor of the `universalIdentifier` usage
## Note
- Removed previous standard ids declaration modules
- Twenty-sdk now re-exports the `STANDARD_OBJECTS` universalIdentifier
hashmap constant
- deleted some sync-metadata deadcode too ( mainly types )
Fixes https://github.com/twentyhq/twenty/issues/17544
**Problem**
When users create custom objects with short acronym names like "O&J",
the system generates an object name oJ. When creating relation fields,
the morph field name was built using string concatenation:
const morphFieldName = `target${capitalize("oJ")}`; // → "targetOJ"
This produced "targetOJ", which failed validation because the camelCase
check performed in `validateFlatFieldMetadataName` (camelCase(name) ===
name) returns "targetOj" for "targetOJ". The issue comes from
consecutive camelCase() operations.
**Solution**
Actually, the `camelCase(name) === name` check is questionnable.
What we want to check is that a name is in camelCase format, not that it
corresponds to the camelCase version of a given string, while that's we
are doing here. lodash does not provide camelCase validator, only
camelCase convertor, so we used it as a way to validate the format of
the name.
We may feel like `camelCase(name) === name` checks whether a name is
camel-cased, but in addition to that it is also checking for a camel
case "idempotency" we don't necessarily have and do not need: for
instance if an object's name is "iOS" (which could be inferred from a
label "I O S"), it won't pass the check: camelCase("iOS") is "ios" and
"ios" !== "iOS".
The existing check with
`STARTS_WITH_LOWER_CASE_AND_CONTAINS_ONLY_CAPS_AND_LOWER_LETTERS_AND_NUMBER_STRING_REGEX`
acts as a camel case validator, so we don't need that camelCase() check.
## Summary
- **Increase `--max-turns` from 50 to 200** — Claude was hitting the
turn limit before getting to `gh pr create`, resulting in branches with
no PR
- **Add common bash commands to `--allowedTools`** — `rm`, `find`,
`grep`, `cat`, `ls`, `head`, `tail`, `wc`, `sort`, `uniq`, `mkdir`,
`cp`, `mv`, `touch`, `chmod`, `echo`, `curl`, `cd`, `pwd`, `diff`,
`xargs`, `awk`, `cut`, `tee`, `tr` — denied commands were wasting turns
on retries
## Context
Both [run
21594509983](https://github.com/twentyhq/twenty/actions/runs/21594509983)
and [run
21595364188](https://github.com/twentyhq/twenty/actions/runs/21595364188)
failed with `error_max_turns` after exhausting 50 turns. Permission
denials on `rm` and `find` contributed to wasted turns.
## Test plan
- [ ] Tag `@claude` on an issue with a code change request — verify it
completes and creates a PR
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Replace all `depot-ubuntu-24.04-8` runner references with the
equivalent GitHub-hosted `ubuntu-latest-8-cores` runner
- Updated across 4 workflow files: `ci-front.yaml`, `ci-server.yaml`,
`ci-emails.yaml`, `ci-sdk.yaml`
- Also updated cache key names in `ci-front.yaml` that referenced the
depot runner name
## Test plan
- [ ] Verify CI workflows run successfully on the new GitHub-hosted
larger runners
- [ ] Confirm cache keys work correctly with the updated naming
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
# Introduction
In this PR we're deprecating the object metadata standard id and
replacing it to the universalIdentifier usage
As we've totally removed its insertion for both new field and object in
https://github.com/twentyhq/twenty/pull/17572
## Note
- Removed upgrade commands before `1.17`
## Summary
- **Set `cancel-in-progress: false`** — Claude's own comments (e.g.
error reports) were triggering new workflow runs via `issue_comment`,
which cancelled the in-progress Claude run before the new run's `if`
condition could skip it. Disabling cancel-in-progress prevents this.
- **Filter out Bot users in `if` conditions** — Added
`github.event.comment.user.type != 'Bot'` so Claude's own comments don't
start job evaluation at all.
- **Add back `additional_permissions: actions: read`** — Lets Claude
read CI failure logs to help debug broken builds.
- **Remove `discussion_comment` trigger and job** — `claude-code-action`
doesn't support this event type yet (throws `Unsupported event type:
discussion_comment`). Listed as planned in their security.md.
## Context
Claude runs were consistently failing with `The operation was canceled`
because:
1. Claude posts an error/status comment back to the issue
2. That comment triggers a new `issue_comment` workflow run on the same
issue number
3. The concurrency group cancels the in-progress run before the new run
evaluates its `if` and skips
## Test plan
- [ ] Tag `@claude` on an issue — should run to completion without being
cancelled
- [ ] Verify Claude's own reply comments don't trigger new workflow runs
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- `allowed_tools` is not a valid input for `claude-code-action@v1` — it
was silently ignored (visible as a warning in CI logs)
- This caused Claude to lack file write permissions, preventing it from
making actual code changes
- Move the tools list into `claude_args` as `--allowedTools` where it's
actually parsed
## Test plan
- [ ] Trigger `@claude` on a cross-repo issue requesting a code change —
verify it can edit files and create a PR
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
# Introduction
Important note: This PR officially deprecates the `standardId`, about to
drop col and entity property after this has been merged
Important note2: Haven't updated the optimistic tool to also update the
universal identifier aggregators only the ids one, they should not be
consumed in the runner context -> need to improve typing or either the
optimistic tooling
In this PR we're introducing all the devxp allowing future metadata
incremental universal migration -> this has an impact on all existing
metadata actions handler ( explaining its size )
This PR also introduce workspace agnostic create update actions runner
for both field and object metadata in order to battle test the described
above devxp
Noting that these two metadata are the most complex to handle
Notes:
- A workspace migration is now highly bind to a
`applicationUniversalIdentifier`. Though we don't strictly validate
application scope for the moment
## Next
Migrate both object and field builder to universal comparison
## Universal Actions vs Flat Actions Architecture
### Concept
The migration system uses a two-phase action model:
1. **Universal Actions** - Actions defined using `universalIdentifier`
(stable, portable identifiers like `standardId` + `applicationId`)
2. **Flat Actions** - Actions defined using database `entityId` (UUIDs
specific to a workspace)
### Why This Separation?
- **Universal actions are portable**: They can be serialized, stored,
and replayed across different workspaces
- **Flat actions are executable**: They contain the actual database IDs
needed to perform operations
- **Decoupling**: The builder produces universal actions; the runner
transpiles them to flat actions at execution time
### Transpiler Pattern
Each action handler must implement
`transpileUniversalActionToFlatAction()`:
```typescript
@Injectable()
export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'create',
'fieldMetadata',
) {
override async transpileUniversalActionToFlatAction(
context: WorkspaceMigrationActionRunnerArgs<UniversalCreateFieldAction>,
): Promise<FlatCreateFieldAction> {
// Resolve universal identifiers to database IDs
const flatObjectMetadata = findFlatEntityByUniversalIdentifierOrThrow({
flatEntityMaps: allFlatEntityMaps.flatObjectMetadataMaps,
universalIdentifier: action.objectMetadataUniversalIdentifier,
});
return {
type: action.type,
metadataName: action.metadataName,
objectMetadataId: flatObjectMetadata.id, // Resolved ID
flatFieldMetadatas: /* ... transpiled entities ... */,
};
}
}
```
### Action Handler Base Class
`BaseWorkspaceMigrationRunnerActionHandlerService<TActionType,
TMetadataName>` provides:
- **`transpileUniversalActionToFlatAction()`** - Abstract method each
handler must implement
- **`transpileUniversalDeleteActionToFlatDeleteAction()`** - Shared
helper for delete actions
## FlatEntityMaps custom properties
Introduced a `TWithCustomMapsProperties` generic parameter to control
whether custom indexing structures are included:
- **`false` (default)**: Returns `FlatEntityMaps<MetadataFlatEntity<T>>`
- used in builder/runner contexts
- **`true`**: Returns the full maps type with custom properties (e.g.,
`byUserWorkspaceIdAndFolderId`) - used in cache contexts
## Create Field Actions Refactor
Refactored create-field actions to support relation field pairs
bundling.
**Problem:** Relation fields (e.g., `Attachment.targetTask` ↔
`Task.attachments`) couldn't resolve each other's IDs during
transpilation because they were in separate actions with independent
`fieldIdByUniversalIdentifier` maps.
**Solution:**
- Removed `objectMetadataUniversalIdentifier` from
`UniversalCreateFieldAction` and `objectMetadataId` from
`FlatCreateFieldAction` - each field now carries its own
- Runner groups fields by object internally and processes each table
separately
- Split aggregator into two focused utilities:
- `aggregateNonRelationFieldsIntoObjectActions` - merges non-relation
fields into object actions
- `aggregateRelationFieldPairs` - bundles relation pairs with shared
`fieldIdByUniversalIdentifier`