feat(app-dev): sync error hints, flatEntity labels, dev-mode summary UI, and docs (#21252)

Split out of #21240 — all remaining app-dev improvements. Stacked on
#21251 (review/merge that first).

- Actionable recovery hints on failed syncs; unified diff renderer;
`--dry-run` guard.
- Return `flatEntity` on update/delete sync actions and unify the diff
label.
- Summarize the dev-mode entity list unless `--verbose`.
- Docs: syncing & recovery guide + dry-run + open-an-issue prompt.
- Live execution mode for synced logic functions; clearer manifest
warnings.

<img width="1018" height="700" alt="image"
src="https://github.com/user-attachments/assets/5e9ce19e-0f1d-4f99-8524-4e118bde932b"
/>
This commit is contained in:
martmull
2026-06-08 17:43:28 +02:00
committed by GitHub
parent 13e8e26d1c
commit 77d1e8ced6
34 changed files with 716 additions and 292 deletions
@@ -0,0 +1,11 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type MetadataUniversalFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type';
import { type MetadataUniversalFlatEntityPropertiesToCompare } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/metadata-universal-flat-entity-properties-to-compare.type';
export type UniversalFlatEntityDiff<T extends AllMetadataName> = {
[K in MetadataUniversalFlatEntityPropertiesToCompare<T>]?: {
before: MetadataUniversalFlatEntity<T>[K];
after: MetadataUniversalFlatEntity<T>[K];
};
};
@@ -18,6 +18,7 @@ import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-e
import { WorkspaceMigrationBuilderAdditionalCacheDataMaps } from 'src/engine/workspace-manager/workspace-migration/types/workspace-migration-builder-additional-cache-data-maps.type';
import { AllUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/all-universal-flat-entity-maps.type';
import { MetadataUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/metadata-universal-flat-entity-maps.type';
import { UniversalFlatEntityDiff } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-diff.type';
import { UniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-maps.type';
import { addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/add-universal-flat-entity-to-universal-flat-entity-and-related-entity-maps-through-mutation-or-throw.util';
import { deleteUniversalFlatEntityForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/delete-universal-flat-entity-foreign-key-aggregators.util';
@@ -164,7 +165,11 @@ export abstract class WorkspaceEntityMigrationBuilderService<
actionsResult.delete.push(
...(Array.isArray(validationResult.action)
? validationResult.action
: [validationResult.action]),
: [validationResult.action]
).map((action) => ({
...action,
flatEntity: universalFlatEntityToDelete,
})),
);
}
@@ -221,6 +226,17 @@ export abstract class WorkspaceEntityMigrationBuilderService<
...flatEntityUpdate.update,
};
const diff = Object.fromEntries(
Object.entries(flatEntityUpdate.update).map(([key, after]) => [
key,
{
before:
existingFlatEntity[key as keyof MetadataUniversalFlatEntity<T>],
after,
},
]),
) as UniversalFlatEntityDiff<T>;
replaceUniversalFlatEntityInUniversalFlatEntityMapsThroughMutationOrThrow(
{
universalFlatEntity: updatedFlatEntity,
@@ -232,7 +248,12 @@ export abstract class WorkspaceEntityMigrationBuilderService<
actionsResult.update.push(
...(Array.isArray(validationResult.action)
? validationResult.action
: [validationResult.action]),
: [validationResult.action]
).map((action) => ({
...action,
flatEntity: updatedFlatEntity,
diff,
})),
);
}
@@ -1,5 +1,6 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type MetadataUniversalFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type';
import { type WORKSPACE_MIGRATION_ACTION_TYPE } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/constants/workspace-migration-action-type.constant';
export type BaseUniversalDeleteWorkspaceMigrationAction<
@@ -8,4 +9,5 @@ export type BaseUniversalDeleteWorkspaceMigrationAction<
universalIdentifier: string;
type: typeof WORKSPACE_MIGRATION_ACTION_TYPE.delete;
metadataName: T;
flatEntity?: MetadataUniversalFlatEntity<T>;
};
@@ -1,5 +1,7 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type MetadataUniversalFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type';
import { type UniversalFlatEntityDiff } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-diff.type';
import { type UniversalFlatEntityUpdate } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-update.type';
import { type WORKSPACE_MIGRATION_ACTION_TYPE } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/constants/workspace-migration-action-type.constant';
@@ -10,4 +12,6 @@ export type BaseUniversalUpdateWorkspaceMigrationAction<
metadataName: T;
universalIdentifier: string;
update: UniversalFlatEntityUpdate<T>;
flatEntity?: MetadataUniversalFlatEntity<T>;
diff?: UniversalFlatEntityDiff<T>;
};