128d2d394d
## Summary
Lets a Twenty application add **view fields (columns) to an existing
view it does not own** — including standard views like the People index
view — without redeclaring/owning that view. This mirrors the existing,
working pattern by which an app adds a custom field to a standard object
via `defineField` + `objectUniversalIdentifier`.
The asymmetry being removed was purely in the manifest schema:
`ViewFieldManifest` only existed *nested* inside
`ViewManifest.fields[]`, so adding a view field forced declaring a
`ViewManifest` — which the sync treats as a view the app creates and
owns, and rejects when the UID is a standard view's. Validation,
persistence, the FK aggregator machinery, and uninstall cleanup were
already generic and cross-app-safe, so no engine changes were needed.
### Changes
- **twenty-shared:** new top-level `StandaloneViewFieldManifest`
(`ViewFieldManifest & { viewUniversalIdentifier }`),
`Manifest.viewFields`, and a `SyncableEntity.ViewField` member.
- **twenty-sdk:** `defineViewField` (validates `universalIdentifier` +
`viewUniversalIdentifier` + `fieldMetadataUniversalIdentifier`), CLI
manifest assembly of a top-level `viewFields` list, and `dev:add
viewField` scaffolding.
- **twenty-server:** one top-level loop over `manifest.viewFields` that
reuses the existing `fromViewFieldManifestToUniversalFlatViewField`
converter (already parameterized by `viewUniversalIdentifier`). No
validator/persistence/aggregator changes.
### Notes for maintainers
- Confirm the `Manifest.viewFields` optionality convention — implemented
as a **required** array to mirror `fields`/`views`.
- Two different apps adding a column for the same field to the same view
conflicts on the existing unique `(fieldMetadataId, viewId)` partial
index; the existing `flat-view-field-validator` duplicate check surfaces
this as a structured validation error.
- `dev:add viewField` scaffolding is included (was optional in the
plan).
## Test Plan
- [x] `twenty-shared` typecheck
- [x] `twenty-sdk` 364 unit tests + `buildManifest` assembly test
(rich-app fixture) + typecheck + prettier
- [x] `twenty-server` typecheck + `lint:diff-with-main`
- [x] **Server integration suite**
`successful-manifest-update-view-field.integration-spec.ts` (4/4):
- standalone view field attaches to the standard `allPeople` view
without recreating it (sync succeeds, no
`INVALID_VIEW_DATA`/`ENTITY_ALREADY_EXISTS`)
- uninstall removes the contributed column while the standard view + its
columns remain intact
- duplicate `(view, field)` rejected with `METADATA_VALIDATION_FAILED`
- unknown target view rejected
- [x] Sibling `successful-manifest-update-field.integration-spec.ts`
still green (no harness regression)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
87 lines
3.8 KiB
TypeScript
87 lines
3.8 KiB
TypeScript
/*
|
|
* _____ _
|
|
*|_ _|_ _____ _ __ | |_ _ _
|
|
* | | \ \ /\ / / _ \ '_ \| __| | | | Auto-generated file
|
|
* | | \ V V / __/ | | | |_| |_| | Any edits to this will be overridden
|
|
* |_| \_/\_/ \___|_| |_|\__|\__, |
|
|
* |___/
|
|
*/
|
|
|
|
export type { AgentManifest } from './agentManifestType';
|
|
export type { AppConnection } from './appConnectionType';
|
|
export type { ApplicationManifest } from './applicationType';
|
|
export type {
|
|
ApplicationVariable,
|
|
ApplicationVariables,
|
|
} from './applicationVariablesType';
|
|
export type { AssetManifest } from './assetManifestType';
|
|
export type { ConnectionProviderManifest } from './connectionProviderManifestType';
|
|
export type { ConnectionProviderType } from './connectionProviderType';
|
|
export { ASSETS_DIR } from './constants/AssetDirectory';
|
|
export { DEFAULT_API_KEY_NAME } from './constants/DefaultApiKeyName';
|
|
export { DEFAULT_API_URL_NAME } from './constants/DefaultApiUrlName';
|
|
export { DEFAULT_APP_ACCESS_TOKEN_NAME } from './constants/DefaultAppAccessTokenName';
|
|
export { GENERATED_DIR } from './constants/GeneratedDirectory';
|
|
export { NODE_ESM_CJS_BANNER } from './constants/NodeEsmCjsBanner';
|
|
export { OUTPUT_DIR } from './constants/OutputDirectory';
|
|
export { TWENTY_STANDARD_APPLICATION_NAME } from './constants/TwentyStandardApplicationName';
|
|
export { TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER } from './constants/TwentyStandardApplicationUniversalIdentifier';
|
|
export { SyncableEntity } from './enums/syncable-entities.enum';
|
|
export type {
|
|
RegularFieldManifest,
|
|
RelationFieldManifest,
|
|
FieldManifest,
|
|
} from './fieldManifestType';
|
|
export type {
|
|
CommandMenuItemManifest,
|
|
FrontComponentManifest,
|
|
} from './frontComponentManifestType';
|
|
export type { IndexFieldManifest } from './indexFieldManifestType';
|
|
export type { IndexManifest } from './indexManifestType';
|
|
export type {
|
|
LogicFunctionManifest,
|
|
CronTriggerSettings,
|
|
DatabaseEventTriggerSettings,
|
|
HttpRouteTriggerSettings,
|
|
} from './logicFunctionManifestType';
|
|
export type { Manifest } from './manifestType';
|
|
export type { NavigationMenuItemManifest } from './navigationMenuItemManifestType';
|
|
export type { OAuthConnectionProviderConfig } from './oauthConnectionProviderConfigType';
|
|
export type { OAuthProviderTokenRequestContentType } from './oauthProviderTokenRequestContentType.type';
|
|
export type { ObjectFieldManifest } from './objectFieldManifest.type';
|
|
export type { ObjectManifest } from './objectManifestType';
|
|
export type {
|
|
PageLayoutWidgetManifest,
|
|
PageLayoutTabManifest,
|
|
PageLayoutManifest,
|
|
} from './pageLayoutManifestType';
|
|
export type {
|
|
PermissionFlagPermissionType,
|
|
PermissionFlagManifest,
|
|
} from './permissionFlagManifestType';
|
|
export type { PostInstallLogicFunctionApplicationManifest } from './postInstallLogicFunctionApplicationType';
|
|
export type { PreInstallLogicFunctionApplicationManifest } from './preInstallLogicFunctionApplicationType';
|
|
export type {
|
|
ObjectPermissionManifest,
|
|
FieldPermissionManifest,
|
|
RoleManifest,
|
|
} from './roleManifestType';
|
|
export type { RunAgentInput, RunAgentResult } from './runAgentType';
|
|
export type { ServerVariables } from './server-variables.type';
|
|
export type { SkillManifest } from './skillManifestType';
|
|
export type { StoredOAuthConnectionProviderConfig } from './storedOAuthConnectionProviderConfigType';
|
|
export type { SyncableEntityOptions } from './syncableEntityOptionsType';
|
|
export type { ToolTriggerSettings } from './toolTriggerSettingsType';
|
|
export type {
|
|
ViewManifestFilterValue,
|
|
ViewFieldManifest,
|
|
StandaloneViewFieldManifest,
|
|
ViewFilterManifest,
|
|
ViewFilterGroupManifest,
|
|
ViewGroupManifest,
|
|
ViewFieldGroupManifest,
|
|
ViewSortManifest,
|
|
ViewManifest,
|
|
} from './viewManifestType';
|
|
export type { WorkflowActionTriggerSettings } from './workflowActionTriggerSettingsType';
|