Introduces a dedicated, stable identifier for the relation-traversal filter
target on `ViewFilterEntity` so saved views survive renames of the field
they reference, and removes the shortcut that previously cast a target
field's NAME into the closed `CompositeFieldSubFieldName` slot.
Schema
- New column `core.viewFilter.relationTargetFieldMetadataId` (uuid,
nullable, FK to `core.fieldMetadata.id` with ON DELETE SET NULL so
filters degrade gracefully if their target field is deleted, rather
than vanishing).
- Partial index on the column where not-null.
- Instance-fast migration (2.5.0, ts 1798000005000) adds the column,
the FK, and the index; registered in `INSTANCE_COMMANDS`.
- Field metadata configuration wired through:
`FLAT_VIEW_FILTER_EDITABLE_PROPERTIES`,
`all-entity-properties-configuration-by-metadata-name`,
`all-many-to-one-metadata-foreign-key`,
`all-many-to-one-metadata-relations` (universal foreign key
`relationTargetFieldMetadataUniversalIdentifier`).
API
- `ViewFilterDTO`, `CreateViewFilterInput`, `UpdateViewFilterInputUpdates`
and `UpsertViewWidgetViewFilterInput` all carry the new optional UUID.
- View → flat converters (`fromViewFilterEntityToFlatViewFilter`,
`fromCreateViewFilterInputToFlatViewFilterToCreate`,
`fromViewFilterManifestToUniversalFlatViewFilter`,
`createStandardViewFilterFlatMetadata`,
widget upsert, create-action handler) thread the new column
through both the foreign-key and universal-identifier sides.
Shared
- `RecordFilter.relationTargetFieldMetadataId` added on both the
frontend and twenty-shared types.
- `turnRecordFilterIntoRecordGqlOperationFilter` now branches on
`relationTargetFieldMetadataId` BEFORE the emptiness shortcut and
synthesizes the inner filter against the target field, wrapping it
under the relation field's name (`{ company: { name: { ... } } }`).
Falls through to the existing relation-by-record path if the target
field isn't present in the provided `fieldMetadataItems`.
Frontend
- New dedicated state
`relationTargetFieldMetadataIdUsedInDropdownComponentState` (the
existing `subFieldNameUsedInDropdownComponentState` stays narrowly
typed for composite sub-fields).
- `useSelectFieldUsedInAdvancedFilterDropdown` accepts an optional
`relationTargetFieldMetadataItem`. When present it stores the target
field's id on `RecordFilter.relationTargetFieldMetadataId` and uses
the TARGET field's type (so operand picker / value input behave as if
filtering it directly) and a "Relation → Target" label.
- `ObjectFilterDropdownInnerSelectOperandDropdown` resolves the
effective field via `useGetFieldMetadataItemByIdOrThrow` when the
relation-target state is set, so operand options match the target's
filterable type.
- `AdvancedFilterFieldSelectMenu` routes MANY_TO_ONE clicks into the
same sub-menu flow as composite fields, with a `'RELATION'` sentinel
on `objectFilterDropdownSubMenuFieldType`.
- `AdvancedFilterSubFieldSelectMenu` renders the target object's
filterable fields via `useFilterableFieldMetadataItems`; the ghost
`-1` selectable id that the composite branch used for the "Any X
field" item is no longer included in the relation list (fixes the
keyboard-navigation issue cubic-dev-ai flagged).
- `ObjectFilterDropdownFilterSelectMenuItem` shows the sub-menu
chevron for MANY_TO_ONE relations via a new
`isManyToOneRelationField` util.
- `mapRecordFilterToViewFilter` / `mapViewFiltersToFilters`,
`areViewFiltersEqual`, the save mutation inputs, and the GraphQL
fragment all thread the new field through the round-trip so saved
views actually persist relation traversal.
- Role-permissions sub-field menu narrows the `'RELATION'` sentinel
back out (it never deals with relations).
Out of scope
- Normal filter dropdown (`ViewBarFilterDropdownFieldSelectMenu`) —
composite drill-down doesn't exist there today, so adding relation
drill-down would mean introducing the entire sub-menu pattern on the
normal surface. Tracking as follow-up; advanced filter is the only
surface that currently builds nested filter shapes.
- REST DSL, ONE_TO_MANY reverse traversal, aggregates — unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>