diff --git a/backend/src/db/api/assets.ts b/backend/src/db/api/assets.ts index a5ebe51..613671f 100644 --- a/backend/src/db/api/assets.ts +++ b/backend/src/db/api/assets.ts @@ -9,6 +9,12 @@ import type { DbRelationFilterConfig, } from '../../types/index.ts'; +function mapProjectId(data: AssetData): string | null | undefined { + if (data.projectId !== undefined) return data.projectId; + if (data.project !== undefined) return data.project; + return undefined; +} + class AssetsDBApi extends GenericDBApi { declare static findBy: AssetsDbApi['findBy']; @@ -105,7 +111,7 @@ class AssetsDBApi extends GenericDBApi { embed_provider: data.embed_provider || null, checksum: data.checksum || null, is_public: data.is_public || false, - projectId: data.projectId || data.project || null, + projectId: mapProjectId(data), }; } } diff --git a/backend/src/db/api/project_element_defaults.ts b/backend/src/db/api/project_element_defaults.ts index d4de173..1614f16 100644 --- a/backend/src/db/api/project_element_defaults.ts +++ b/backend/src/db/api/project_element_defaults.ts @@ -46,6 +46,14 @@ function isElementSettingsJson(value: unknown): value is ElementSettingsJson { return Boolean(value) && typeof value === 'object' && !Array.isArray(value); } +function mapProjectId( + data: ProjectElementDefaultsData, +): string | null | undefined { + if (data.projectId !== undefined) return data.projectId; + if (data.project !== undefined) return data.project; + return undefined; +} + function isGlobalElementDefaultRecord( value: EntityRecord, ): value is GlobalElementDefaultRecord { @@ -206,7 +214,7 @@ class Project_element_defaultsDBApi extends GenericDBApi { settings_json: stringifySettings(data.settings_json), source_element_id: data.source_element_id ?? null, snapshot_version: data.snapshot_version ?? 1, - projectId: data.projectId || data.project || null, + projectId: mapProjectId(data), }; } diff --git a/backend/src/types/assets.ts b/backend/src/types/assets.ts index fc72db3..cd87a4d 100644 --- a/backend/src/types/assets.ts +++ b/backend/src/types/assets.ts @@ -69,7 +69,7 @@ export interface AssetFieldMapping { embed_provider: string | null; checksum: string | null; is_public: boolean; - projectId: string | null; + projectId: string | null | undefined; } export interface AssetVariantData { diff --git a/backend/src/types/project-element-defaults.ts b/backend/src/types/project-element-defaults.ts index 9413b3e..de0d789 100644 --- a/backend/src/types/project-element-defaults.ts +++ b/backend/src/types/project-element-defaults.ts @@ -46,7 +46,7 @@ export interface ProjectElementDefaultsFieldMapping { settings_json: string | null; source_element_id: string | null; snapshot_version: number; - projectId: string | null; + projectId: string | null | undefined; } export interface ProjectElementDefaultsListFilter { diff --git a/backend/tests/update-contracts.test.ts b/backend/tests/update-contracts.test.ts index 2795065..ff6baa9 100644 --- a/backend/tests/update-contracts.test.ts +++ b/backend/tests/update-contracts.test.ts @@ -2,7 +2,9 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import db from '../src/db/models/index.ts'; +import AssetsDBApi from '../src/db/api/assets.ts'; import GenericDBApi from '../src/db/api/base.api.ts'; +import ProjectElementDefaultsDBApi from '../src/db/api/project_element_defaults.ts'; import { createEntityService } from '../src/factories/service.factory.ts'; import type { CurrentUser, @@ -377,6 +379,48 @@ void test('GenericDBApi.partialUpdate rejects positional signature', async () => ); }); +void test('ProjectElementDefaultsDBApi mapping preserves project relation when omitted', () => { + const mapped = ProjectElementDefaultsDBApi.getFieldMapping({ + element_type: 'navigation_next', + name: 'Navigation Forward Button', + sort_order: 1, + settings_json: { opacity: '0.5' }, + }); + + assert.equal(mapped.projectId, undefined); +}); + +void test('ProjectElementDefaultsDBApi mapping keeps explicit project relation', () => { + const mapped = ProjectElementDefaultsDBApi.getFieldMapping({ + element_type: 'navigation_next', + projectId: 'project-1', + settings_json: { opacity: '0.5' }, + }); + + assert.equal(mapped.projectId, 'project-1'); +}); + +void test('AssetsDBApi mapping preserves project relation when omitted', () => { + const mapped = AssetsDBApi.getFieldMapping({ + name: 'Lobby image', + asset_type: 'image', + storage_key: 'assets/lobby.jpg', + }); + + assert.equal(mapped.projectId, undefined); +}); + +void test('AssetsDBApi mapping keeps explicit project relation', () => { + const mapped = AssetsDBApi.getFieldMapping({ + name: 'Lobby image', + asset_type: 'image', + projectId: 'project-1', + storage_key: 'assets/lobby.jpg', + }); + + assert.equal(mapped.projectId, 'project-1'); +}); + void test('createEntityService update uses object signature and manages own transaction', async () => { const calls: UpdateContractCalls = {}; const transaction: TestManagedTransaction = { diff --git a/frontend/src/lib/elementStyles.ts b/frontend/src/lib/elementStyles.ts index 1d18ee0..ef3b86a 100644 --- a/frontend/src/lib/elementStyles.ts +++ b/frontend/src/lib/elementStyles.ts @@ -240,6 +240,7 @@ export const ELEMENT_STYLE_PROPS = [ 'fontWeight', 'border', 'borderRadius', + 'opacity', 'boxShadow', 'display', 'position', @@ -251,11 +252,6 @@ export const ELEMENT_STYLE_PROPS = [ 'color', ] as const; -/** - * Properties that need numeric conversion - */ -const NUMERIC_PROPS = ['opacity', 'zIndex'] as const; - /** * Get trimmed CSS value from unknown input. * Returns empty string for null/undefined, but preserves '0' for explicit zero values. @@ -343,9 +339,9 @@ export function buildElementStyle( } /** - * All style property names including numeric ones. + * All style property names. * Used for form state management in element-type-defaults admin. */ -export const ALL_STYLE_PROPS = [...ELEMENT_STYLE_PROPS, 'opacity'] as const; +export const ALL_STYLE_PROPS = ELEMENT_STYLE_PROPS; export type StylePropName = (typeof ALL_STYLE_PROPS)[number];