## PR Description
In the process of migrating all the existing commands to the backend, we
stumbled across a couple of problems that made us reconsider the full
migration. This PR introduces a way for command menu items to bypass
front components and to directly reference a frontend component from
twenty front.
It:
- Introduces a `engineFrontComponentKey` field on `CommandMenuItem` as
an alternative to `frontComponentId` and `workflowVersionId`, allowing
command menu items to reference frontend components by key directly
rather than requiring a FrontComponent entity
- Updates the DB constraint to allow exactly one of `workflowVersionId`,
`frontComponentId`, or `engineFrontComponentKey`
### All standard command menu items from the frontend which use
`standardFrontComponentKey`
These are all commands that execute a GraphQL query or a mutation.
Two mains concerned have been raised that made us go with this
(temporary) architecture instead:
- If those commands are part of the standard application, they can only
alter objects from that application and not custom objects.
- We would need to implement a way to trigger optimistic rendering from
the front components, which might take some time to implement.
List:
- Create new record
- Delete (single record)
- Delete records (multiple)
- Restore record
- Restore records (multiple)
- Permanently destroy record
- Permanently destroy records (multiple)
- Add to favorites
- Remove from favorites
- Merge records
- Duplicate Dashboard
- Save Dashboard
- Save Page Layout
- Activate Workflow
- Deactivate Workflow
- Discard Draft (workflow)
- Test Workflow
- Tidy up workflow
- Duplicate Workflow
- Stop (workflow run)
- Use as draft (workflow version)
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
## Context
The goal is to add a "See records" button in all objects that would
redirect to that view (this will be done in a later PR). See screenshot
below.
<img width="665" height="312" alt="Screenshot 2026-03-10 at 15 54 36"
src="https://github.com/user-attachments/assets/6e23a75b-cff0-4d93-bce8-b5481b05c6f6"
/>
## Implementation
- If a view does not exist on an object, there is a **temporary**
fallback where the frontend creates the missing view as a custom view
when going over the object index page
- System objects are now surfaced but we don't want their records to be
editable, they will be readonly (mostly, all fields will be non-editable
except for their custom fields).
- We can't create a new record of a system object, some actions are also
hidden.
- The backend now rejects if you are trying to delete the last view of
an object
## Add standard command menu items
### Summary
This PR introduces standard command menu items, migrating hardcoded
command menu actions to the backend command menu item architecture
powered by front components. It adds a new `twenty-standard-application`
package that defines, builds, and registers front components as standard
command menu items, gated behind the `IS_COMMAND_MENU_ITEM_ENABLED`
feature flag.
### Description
- **New `twenty-standard-application` package**: Contains front
component definitions with an esbuild-based build pipeline that
generates minified `.mjs` bundles and a manifest with checksums.
- **Server-side registration**: New constants register all items with
metadata (labels, icons, positions, availability types, conditional
expressions). A `StandardFrontComponentUploadService` uploads built
components to file storage.
- **`FALLBACK` availability type**: New enum value for command menu
items that appear as fallback options (e.g., "Search Records" fallback).
- **`CommandMenuContextApi` refactor**
- **Conditional availability enhancements**: New array-based helper
functions for evaluating multi-record conditions.
- **Frontend wiring** (twenty-front):
`useCommandMenuItemFrontComponentCommands`
## Next steps
Only simple commands have been implemented for now:
- **Navigation (9)** -- `CommandLink`: go-to-companies,
go-to-dashboards, go-to-notes, go-to-opportunities, go-to-people,
go-to-runs, go-to-settings, go-to-tasks, go-to-workflows
- **Side panel (4)** -- `CommandOpenSidePanelPage`: ask-ai,
search-records, search-records-fallback, view-previous-ai-chats
We still have to implement front components for all the following
commands:
All have placeholder `execute` logic (`async () => {}`) with a `// TODO:
implement execute logic` comment:
**Record (22)**
- `add-to-favorites`, `remove-from-favorites`
- `create-new-record`, `create-new-view`
- `delete-single-record`, `delete-multiple-records`
- `destroy-single-record`, `destroy-multiple-records`
- `restore-single-record`, `restore-multiple-records`
- `export-from-record-index`, `export-from-record-show`,
`export-multiple-records`, `export-note-to-pdf`, `export-view`
- `hide-deleted-records`, `see-deleted-records`
- `import-records`, `merge-multiple-records`, `update-multiple-records`
- `navigate-to-next-record`, `navigate-to-previous-record`
**Page layout (3)** -- `cancel-record-page-layout`,
`edit-record-page-layout`, `save-record-page-layout`
**Dashboard (4)** -- `cancel-dashboard-layout`, `duplicate-dashboard`,
`edit-dashboard-layout`, `save-dashboard-layout`
**Workflow (10)** -- `activate-workflow`, `add-node-workflow`,
`deactivate-workflow`, `discard-draft-workflow`, `duplicate-workflow`,
`see-active-version-workflow`, `see-runs-workflow`,
`see-versions-workflow`, `test-workflow`, `tidy-up-workflow`
**Workflow version (4)** -- `see-runs-workflow-version`,
`see-versions-workflow-version`, `see-workflow-workflow-version`,
`use-as-draft-workflow-version`
**Workflow run (3)** -- `see-version-workflow-run`,
`see-workflow-workflow-run`, `stop-workflow-run`
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).
## Summary
- **Module reorganization**: Moved `ApplicationUpgradeService` and cron
jobs to `application-upgrade/`, `ApplicationSyncService` to
`application-manifest/`, and
`runWorkspaceMigration`/`uninstallApplication` mutations to the manifest
resolver — each module now has a single clear responsibility.
- **Explicit install flow**: Removed implicit `ApplicationEntity`
creation from `ApplicationSyncService`. The install service and dev
resolver now explicitly create the `ApplicationEntity` before syncing.
npm packages are resolved at registration time to extract manifest
metadata (universalIdentifier, name, description, etc.), eliminating the
`reconcileUniversalIdentifier` hack.
- **Better error handling**: Frontend hooks now surface actual server
error messages in snackbars instead of swallowing them. Replaced the
ugly `ConfirmationModal` for transfer ownership with a proper form
modal. Fixed `SettingsAdminTableCard` row height overflow and corrected
the `yarn-engine` asset path.
## Test plan
- [ ] Register an npm package — verify manifest metadata (name,
description, universalIdentifier) is extracted correctly
- [ ] Install a registered npm app on a workspace — verify
ApplicationEntity is created and sync succeeds
- [ ] Test `app:dev` CLI flow — verify local app registration and sync
work
- [ ] Upload a tarball — verify registration and install flow
- [ ] Transfer ownership — verify the new modal UX works
- [ ] Verify error messages appear correctly in snackbars when
operations fail
Made with [Cursor](https://cursor.com)
This PR adds the necessary tool to create a demo workspace with :
relevant custom objects and fields, mock data and a real dashboard with
graph widgets.
It is still a bit under-optimized and slow but it works.
This PR also adds an AI tool that allows to see what happens in real
time, it navigates the app and waits when necessary.
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
- Remove feature flag
- Remove legacy methods in file-upload and file-service
- Migrate AI Chat to new file management
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
## PR Description
- Uses `expr-eval` to enable front components (SDK plugins) to define
conditional availability as declarative expressions.
- Moves shared types and constants to `twenty-shared`
- Introduces a `conditionalAvailabilityExpression` field on
`CommandMenuItemEntity`, allowing command menu items to store an
`expr-eval` compatible expression string that is evaluated against a
CommandMenuContext to determine if the item should be shown.
- Creates an esbuild transform plugin
`conditional-availability-transform-plugin` in `twenty-sdk` that
converts TypeScript conditional availability expressions into
`expr-eval` compatible syntax at build time, so SDK developers can write
natural TS expressions that get transformed to evaluable strings.
- Removes deprecated `forceRegisteredActionsByKey` state and its usage.
- Creates `useCommandMenuContext` hook that builds the full
`CommandMenuContext` object from React state, which is then passed to
`useCommandMenuItemFrontComponentActions` for evaluating conditional
availability expressions.
# Introduction
We need to build and validate the flat entity operation in the following
order delete update and create
For example if not, if a created field has the same name than a deleted
one than it will fail whereas it should not
## Context
Part 1 of migrating gridPosition in favor of typed position
FE should now always send both values to the BE and use both.
Next steps:
- Update the backend to enforce and validate the new position field + DB
migrations gridPositon -> position (type: GRID)
- Cleanup frontend usage
- Cleanup backend
- Fixes self host application
- add new telemetry information
- add serverId to identify a server instance
- remove .twenty from git tracking
- tree-shake "twenty-sdk" usage in built logic functions and front
components
- fix "twenty-sdk" version usage
- fix twenty-zapier cli
---------
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Adds color support for navigation menu items.
---------
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Devessier <baptiste@devessier.fr>
Co-authored-by: Charles Bochet <charles@twenty.com>
# Introduction
Resolving each create|updated|deleted entities related entities
application through their universal foreingKey ( silently failing if not
found leaving the validator handling that )
In order to compute all the required application in the dependency flat
entity maps
## Tested
Creating a field on a custom local twenty-apps installed on the
workspace + view field
## Out of scope
- updated snapshot
- update graphql generated front
# Introduction
While preparing the twenty-standard as code migration to twenty-app
through sdk I've faced permanent field enum update as the id was
generated dynamically at each twenty standard app construction
Making them deterministic in order to avoid having this noise
Won't backfill this on existing workspace as it's not critical and that
we will rework the options in the future
# Introduction
## Centralize system field definitions
- Extract a single `PARTIAL_SYSTEM_FLAT_FIELD_METADATAS` constant as the
source of truth for all 8 system fields (`id`, `createdAt`, `updatedAt`,
`deletedAt`, `createdBy`, `updatedBy`, `position`, `searchVector`),
eliminating duplication across custom object and standard app field
builders
- Refactor `buildDefaultFlatFieldMetadatasForCustomObject` to use the
shared constant via a new `buildObjectSystemFlatFieldMetadatas` helper
## Mark system fields as `isSystem: true`
- Fields `id`, `createdAt`, `updatedAt`, `deletedAt`, `createdBy`,
`updatedBy`, `position`, `searchVector` are now properly flagged as
system fields across all standard objects and custom object creation
- Standard app field builders for all ~30 standard objects updated to
set `isSystem: true` on `createdAt`, `updatedAt`, `deletedAt`,
`createdBy`, `updatedBy`
- System-only standard objects (blocklist, calendar channels, message
threads, etc.) now also include `createdBy`, `updatedBy`, `position`,
`searchVector` field definitions that were previously missing
## Validate system fields on object creation
- New transversal validation (`crossEntityTransversalValidation`) runs
after all atomic entity validations in the build orchestrator, ensuring
all 8 system fields are present with correct `type` and `isSystem: true`
when an object is created
- New `buildUniversalFlatObjectFieldByNameAndJoinColumnMaps` utility to
resolve field names to universal identifiers for a given object
- New exception codes: `MISSING_SYSTEM_FIELD` and `INVALID_SYSTEM_FIELD`
on `ObjectMetadataExceptionCode`
## Protect system fields and objects from mutation
- Field validators now block update/delete of `isSystem` fields by
non-system callers (`FIELD_MUTATION_NOT_ALLOWED`)
- Object validators now block update/delete of `isSystem` objects by
non-system callers
- `POSITION` and `TS_VECTOR` field type validators replaced: instead of
rejecting creation outright, they now validate that the field is named
correctly (`position` / `searchVector`) and has `isSystem: true`
## Distinguish `isSystemBuild` from `isCallerTwentyStandardApp`
- New `isCallerTwentyStandardApp` utility checks whether the caller's
`applicationUniversalIdentifier` matches the twenty standard app
- Name-sync logic (`isFlatFieldMetadataNameSyncedWithLabel`,
`areFlatObjectMetadataNamesSyncedWithLabels`) refactored to use
`isCallerTwentyStandardApp` for custom suffix decisions, keeping
`isSystemBuild` for mutation permission checks
- `WorkspaceMigrationBuilderOptions` type updated to include
`applicationUniversalIdentifier`
## Adapt frontend filtering
- New `HIDDEN_SYSTEM_FIELD_NAMES` constant (`id`, `position`,
`searchVector`) and `isHiddenSystemField` utility to only hide truly
internal fields while keeping user-facing system fields (`createdAt`,
`updatedAt`, `deletedAt`, `createdBy`, `updatedBy`) visible in the UI
- ~20 frontend files updated to replace `!field.isSystem` checks with
`!isHiddenSystemField(field)` across record index, settings, data model,
charts, workflows, spreadsheet import, aggregations, and role
permissions
## Add 1.19 upgrade commands
- **`backfill-system-fields-is-system`**: Raw SQL command to set
`isSystem = true` on existing workspace fields matching system field
names, and fix `position` field type from `NUMBER` to `POSITION` for
`favorite`/`favoriteFolder` objects. Includes proper cache invalidation.
- **`add-missing-system-fields-to-standard-objects`**: Codegen'd
workspace migration to create missing `position`, `searchVector`,
`createdBy`, `updatedBy` fields on standard objects that didn't
previously have them. Runs via `WorkspaceMigrationRunnerService` in a
single transaction with idempotency check. **Known limitation**: assumes
all standard objects exist and are valid in the target workspace.
## Add `universalIdentifier` for system fields in standard object
constants
- `standard-object.constant.ts` updated to include `universalIdentifier`
for `createdBy`, `updatedBy`, `position`, and `searchVector` across all
standard objects
- `fieldManifestType.ts` updated to support the new field manifest shape
## System relation
Completely removed and backfilled all `isSystem` relation to be false
false
As we won't require an object to have any relation system fields
## Add integration tests
- New test suite `failing-sync-application-object-system-fields`
covering: missing system fields, wrong field types (`id` as TEXT,
`createdAt` as TEXT, `position` as TEXT), system field deletion
attempts, and system field update attempts
- New test utilities: `buildDefaultObjectManifest` (builds an object
manifest with all 8 system fields) and `setupApplicationForSync`
(centralizes application setup)
- Existing successful sync test updated to verify system fields are
created with correct properties
## Next step
Make the builder scope the compared entity to be the currently built app
+ nor twenty standard app
This PR adds Message folder association for message channel messages,
Currently under testing phase, not ready yet.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Issue : With IS_NAVIGATION_MENU_ITEM_ENABLED:true +
IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED:false, nav menu design is
changed after 1.18.0 release : views expansion removed, system object
displayed, position re-ordered
We prefer keeping the same "old" favorite behaviour and design state
- After 1.18.0 all workspaces have up-to-date navigation menu items
(migrated)
- IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED becomes the FF for nav menu
new design
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
## Refactor page layout widget types into shared package and expose from
SDK
### Why
Widget configuration types were defined only on the server, forcing SDK
consumer apps to import from deep internal `twenty-shared/dist` paths —
fragile and breaks on structural changes. Server DTOs also had no
compile-time guarantee they matched the canonical types.
### What changed
- **`twenty-shared`**: Migrated `ChartFilter`, `GridPosition`,
`RatioAggregateConfig` and all 20 widget configuration variants into
`twenty-shared/types`. `PageLayoutWidgetConfiguration` (base, with
`SerializedRelation`) and `PageLayoutWidgetUniversalConfiguration`
(derived via `FormatRecordSerializedRelationProperties`) are now the
single source of truth.
- **`twenty-sdk`**: Re-exported `AggregateOperations`,
`ObjectRecordGroupByDateGranularity`, `PageLayoutTabLayoutMode`, and
`PageLayoutWidgetUniversalConfiguration` so consumer apps import from
`twenty-sdk` directly.
- **`twenty-server`**: All widget DTOs now `implements` their shared
type for compile-time enforcement. Added helpers to convert nested
`fieldMetadataId` ↔ `fieldMetadataUniversalIdentifier` inside chart
filters. Removed redundant local type re-exports.
- Update migration command to handle case where workspace logo is
originated from workspace email and point to twenty-icons.com
- Update same logic for new workspaces
- Add feature-flag for all newly created workspaces
## Summary
- Replace the character-stripping approach (`removeSqlDDLInjection`)
with standard PostgreSQL `escapeIdentifier` and `escapeLiteral`
functions across all workspace schema manager services
- Add missing identifier escaping to `createForeignKey` (was the only
method in the FK manager without it)
- Add allowlist validation for index WHERE clauses and FK action types
- Harden tsvector expression builder with proper identifier quoting
## Context
The workspace schema managers build DDL dynamically from metadata (table
names, column names, enum values, etc.). The previous approach stripped
all non-alphanumeric characters — safe but lossy (silently corrupts
values with legitimate special characters). The new approach uses
PostgreSQL's standard escaping:
- **Identifiers**: double internal `"` and wrap → `"my""table"` (same
algorithm as `pg` driver's `escapeIdentifier`)
- **Literals**: double internal `'` and wrap → `'it''s a value'` (same
algorithm as `pg` driver's `escapeLiteral`)
`removeSqlDDLInjection` is kept only for name generation (e.g.,
`computePostgresEnumName`) where stripping to `[a-zA-Z0-9_]` is the
correct behavior.
## Files changed
| File | What |
|------|------|
| `remove-sql-injection.util.ts` | Added `escapeIdentifier` +
`escapeLiteral` |
| `validate-index-where-clause.util.ts` | New — allowlist for partial
index WHERE clauses |
| 5 schema manager services | Replaced strip+manual-quote with
`escapeIdentifier`/`escapeLiteral` |
| `build-sql-column-definition.util.ts` | `escapeIdentifier` for column
names, validated `generatedType` |
| `sanitize-default-value.util.ts` | `escapeLiteral` instead of
stripping |
| `serialize-default-value.util.ts` | `escapeLiteral` for values,
`escapeIdentifier` for enum casts |
| `get-ts-vector-column-expression.util.ts` | `escapeIdentifier` for
field names in expressions |
| `sanitize-default-value.util.spec.ts` | Updated tests for escape
behavior |
## Test plan
- [x] All 64 existing tests pass across 6 test suites
- [x] `lint:diff-with-main` passes
- [x] TypeScript typecheck — no new errors
- [ ] Verify workspace sync-metadata still works end-to-end
- [ ] Verify custom object/field creation works
- [ ] Verify enum field option changes work
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
## Summary
- Replace all generic `"Unknown error"` fallback messages across the
server codebase with messages that include the actual error details
- The most impactful change is in `guard-redirect.service.ts`, which
handles OAuth redirect errors — non-`AuthException` errors (e.g.,
passport state verification failures) now show `"Authentication error:
<actual message>"` instead of the opaque `"Unknown error"`
- Gmail/Google error handler services now include the error message in
the thrown exception instead of discarding it
- Other catch blocks (workflow delay resume, migration runner rollback,
code interpreter, marketplace) now use `String(error)` for non-Error
objects instead of a static fallback
Fixes the class of issues reported in
https://github.com/twentyhq/twenty/issues/17812, where a user saw
"Unknown error" during Google OAuth and had no way to diagnose the root
cause (which turned out to be a session cookie / SSL configuration
issue).
## Test plan
- [ ] Verify OAuth error flows (e.g., Google Auth with misconfigured
callback URL) now display the actual error message on the `/verify` page
instead of "Unknown error"
- [ ] Verify Gmail sync error handling still correctly classifies and
re-throws errors with descriptive messages
- [ ] Verify workflow delay resume failures include the error details in
the workflow run status
Made with [Cursor](https://cursor.com)
Co-authored-by: Cursor <cursoragent@cursor.com>
Create the necessary tooling to listen to metadata events and plug it to
the front components. Now we have a hot reload like experience when we
edit a component in an app.
## Backend
- Split `EventWithQueryIds` into `ObjectRecordEventWithQueryIds` and
`MetadataEventWithQueryIds`
- Publish metadata event batches to active SSE streams in
`MetadataEventsToDbListener`
## Frontend
- Create a metadata event dispatching pipeline: SSE metadata events are
grouped by metadata name, transformed into
`MetadataOperationBrowserEventDetail` objects, and dispatched as browser
`CustomEvents`
- Add `useListenToMetadataOperationBrowserEvent` hook for consuming
metadata operation events filtered by metadata name and operation type
- Rename `useListenToObjectRecordEventsForQuery` to
`useListenToEventsForQuery`, now accepting both
`RecordGqlOperationSignature` and `MetadataGqlOperationSignature`
- Implement `useOnFrontComponentUpdated` which subscribes to front
component metadata events and updates the Apollo cache when the
component is modified
- Add `builtComponentChecksum` to the front component query and appends
it to the component URL for browser cache invalidation
## Context
Introducing "NewFieldDefaultConfiguration" to FIELDS widget
configurations
```typescript
{
isVisible: boolean;
viewFieldGroupId: string | null;
}
```
This configuration will define where a new field should be added (which
section) and its default visibility inside FIELDS widget views.
The new field position should always be at the end (meaning the last
position for the view fields OR the last position of a viewFieldGroup)
See "New fields" on this screenshot
<img width="401" height="724" alt="Layout V1"
src="https://github.com/user-attachments/assets/4969bcaa-f244-4504-8947-778a02c24c47"
/>
## Context
- Add missing fields widget and FIELDS_WIDGET view for workflow run and
workflow version standard objects
- Fix FIELDS_WIDGET configuration fieldId universalIdentifier not being
converted to id when migration is executed.
# Introduction
In this PR we start returning a workspace migration post sync so it can
committed and provided within the tarball
## Universal aggregators utils
Created two utils
### deleteUniversalFlatEntityForeignKeyAggregators
Used when building a universal create action, a newly created actions
should not contain any aggregated foreign key so they won't be codegen
in the workspace migration but also they are overriden at uninversal to
flat transpilation anw
### resetUniversalFlatEntityForeignKeyAggregators
Used before validating a new flat entity creation, some validator will
consume the fk aggregator in order to validate integrity, but of
optimstically provided it can result to errors. To avoid caller
responsability we override them here
## create-field-action refactor
Refactored the universal and flat field create action to be following
the base actions in order to ease typing
Also it was tailored to handle unlimited amount of flat field metadata
in the same actions whereas in the reality we were always only sending
at max 2 ( for relation fields )
Note: relation field has to be provided at the same as if not optimistic
would fail to retrieve circular universal identifiers
## ObjectManifest
Now always expect a `labelIdentifierFieldMetadataUniversalIdentifier`
## Integration test
Created an integration test that creates an app, sync a first manifest
and a second implying update workspace migration action generation
## Summary
- Add default visible view fields for `timelineActivity`, `attachment`,
`noteTarget`, `taskTarget`, and `workspaceMember` objects so they
display useful columns out of the box
- Standardize morph relation field labels to "Target" with
`IconArrowUpRight` for consistency across all pivot/junction tables
- Mark deprecated fields (`fullPath`, `fileCategory`,
`linkedRecordCachedName`, `linkedRecordId`, `linkedObjectMetadataId`) as
`isSystem` to hide them from the UI column picker
- Fix morph field deduplication logic (`pickMorphGroupSurvivor`) to
prefer active, non-system fields over auto-generated system fields from
custom objects
- Migrate attachment seeds from legacy `fullPath`/`fileCategory` to the
new `FILES` field type, creating proper `FileEntity` records in
`core.file` via `fileStorageService.writeFile()`
- Restore `customDomain` in the user query fragment
<img width="825" height="754" alt="Screenshot 2026-02-15 at 15 44 27"
src="https://github.com/user-attachments/assets/9596a3dd-8d3a-43c0-925a-0adef9ee68a8"
/>
<img width="736" height="731" alt="Screenshot 2026-02-15 at 15 44 13"
src="https://github.com/user-attachments/assets/cd1a66c5-731d-43e6-bbc3-703cbeda1652"
/>
<img width="722" height="757" alt="Screenshot 2026-02-15 at 15 44 03"
src="https://github.com/user-attachments/assets/b5210546-6a40-4940-8e4f-874818a614fb"
/>
<img width="907" height="757" alt="Screenshot 2026-02-15 at 15 43 52"
src="https://github.com/user-attachments/assets/ead5b9a8-1989-4d68-9640-583da6233711"
/>
<img width="1002" height="731" alt="Screenshot 2026-02-15 at 15 43 38"
src="https://github.com/user-attachments/assets/38accb8c-f5d5-4bfc-b245-06389849810b"
/>
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Touches migration/upgrade commands that write to core metadata tables
and adjust field/view definitions, plus changes dev seeding to create
`core.file` records; mistakes could affect UI visibility or seed
integrity across workspaces.
>
> **Overview**
> Adds a new `upgrade:1-18:backfill-standard-views-and-field-metadata`
command that, per workspace, marks specific fields as `isSystem`,
normalizes morph-relation field `label`/`icon` to
`Target`/`IconArrowUpRight`, and backfills missing standard
`view`/`viewField` rows for `attachment`, `noteTarget`, `taskTarget`,
`timelineActivity`, and `workspaceMember`, followed by cache
invalidation + metadata version bump.
>
> Refactors morph-relation deduplication to pick a single survivor per
`morphId` using a new `pickMorphGroupSurvivor` rule (prefer active +
non-system, then smallest id), with new unit tests.
>
> Updates standard metadata generators and snapshots to reflect the new
system flags and default view fields, and rewrites attachment dev
seeding to populate the new `file` (FILES field) JSON and create
corresponding `core.file` entries via `FileStorageService.writeFile`
with workspace-scoped file IDs.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
b1939bbf6f8cce294f9b4cdec06b19778daa205e. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>