From 7f2c21bb23a126906fc8a23c9fc8877b045c7daa Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Sun, 24 May 2026 22:37:03 +0300 Subject: [PATCH] refactor(core): avoid record casts for dynamic writes - Use KiwiNodeChange index signature for raw Figma field assignment - Type preserved raw NodeChange keys with keyof NodeChange - Replace scene graph property write casts with a typed setter --- .../core/src/kiwi/fig/node-change/convert.ts | 4 ++-- .../src/kiwi/fig/node-change/export-node.ts | 20 ++++++++--------- packages/core/src/scene-graph/instances.ts | 22 +++++++++++++------ 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/packages/core/src/kiwi/fig/node-change/convert.ts b/packages/core/src/kiwi/fig/node-change/convert.ts index 9cfbbc432..cbcb871b3 100644 --- a/packages/core/src/kiwi/fig/node-change/convert.ts +++ b/packages/core/src/kiwi/fig/node-change/convert.ts @@ -823,7 +823,7 @@ export const FIGMA_RAW_NODE_FIELD_KEYS = [ 'vectorData', 'fillGeometry', 'strokeGeometry' -] +] as const satisfies readonly (keyof NodeChange)[] function extractFigmaRawGeometry( nc: NodeChange, @@ -831,7 +831,7 @@ function extractFigmaRawGeometry( ): Pick { const rawNodeFields: Record = {} for (const key of FIGMA_RAW_NODE_FIELD_KEYS) { - const value = (nc as Record)[key] + const value = nc[key] if (value !== undefined) rawNodeFields[key] = preserveFigmaPayloadBlobs(value, blobs) } return { diff --git a/packages/core/src/kiwi/fig/node-change/export-node.ts b/packages/core/src/kiwi/fig/node-change/export-node.ts index 34fc82235..4f3fa25bd 100644 --- a/packages/core/src/kiwi/fig/node-change/export-node.ts +++ b/packages/core/src/kiwi/fig/node-change/export-node.ts @@ -309,9 +309,9 @@ function applyRawFigmaNodeFields( blobIndexByHex: context.blobIndexByHex, includePaintVariables: true, includeVariableMaps: true - }) as Record - for (const key of Object.keys(materialized)) { - if (RAW_FIELDS_OVERRIDE_BLOCKLIST.has(key)) continue + }) as Partial + for (const key of Object.keys(materialized) as (keyof KiwiNodeChange)[]) { + if (RAW_FIELDS_OVERRIDE_BLOCKLIST.has(String(key))) continue // For paint arrays on imported nodes, the raw NC data preserves the // original opacity/color.a split (e.g. opacity=0 for invisible strokes). // The scene model may lose this distinction for instance children whose @@ -328,7 +328,7 @@ function applyRawFigmaNodeFields( if (context.assetRefToVarGuid && context.assetRefToVarGuid.size > 0) { paints = convertColorVarAssetRefs(paints, context.assetRefToVarGuid) } - ;(nc as Record)[key] = paints + nc[key] = paints continue } // Also convert colorVar.assetRef in raw effects (e.g. shadow color variables) @@ -339,16 +339,16 @@ function applyRawFigmaNodeFields( context.assetRefToVarGuid.size > 0 ) { const converted = convertColorVarAssetRefs(materialized[key], context.assetRefToVarGuid) - ;(nc as Record)[key] = converted + nc[key] = converted continue } if (key === 'derivedTextData' && node.source.id) { - ;(nc as Record)[key] = materialized[key] + nc[key] = materialized[key] continue } // Skip any key already set on nc — explicit serialization takes priority - if (key in (nc as Record)) continue - ;(nc as Record)[key] = materialized[key] + if (key in nc) continue + nc[key] = materialized[key] } } @@ -360,7 +360,7 @@ function applyRawFigmaNodeFields( * reference resolvable regardless of whether key/version is present on the * VARIABLE NodeChange. */ -function convertColorVarAssetRefs(paints: unknown, assetRefToVarGuid: Map): unknown { +function convertColorVarAssetRefs(paints: T, assetRefToVarGuid: Map): T { if (!Array.isArray(paints)) return paints const result = paints.map((paint: Record) => { const colorVar = paint.colorVar as Record | undefined @@ -390,7 +390,7 @@ function convertColorVarAssetRefs(paints: unknown, assetRefToVarGuid: Map( + target: Partial, + key: K, + value: SceneNode[K] +): void { + target[key] = value +} + function copyProp( target: Partial | SceneNode, source: SceneNode, key: keyof SceneNode ): void { - const val = source[key] if (key === 'fills') { - ;(target as Record)[key] = copyFills(val as Fill[]) + setSceneProp(target, key, copyFills(source.fills)) } else if (key === 'strokes') { - ;(target as Record)[key] = copyStrokes(val as Stroke[]) + setSceneProp(target, key, copyStrokes(source.strokes)) } else if (key === 'effects') { - ;(target as Record)[key] = copyEffects(val as Effect[]) + setSceneProp(target, key, copyEffects(source.effects)) } else if (key === 'styleRuns') { - ;(target as Record)[key] = copyStyleRuns(val as StyleRun[]) + setSceneProp(target, key, copyStyleRuns(source.styleRuns)) } else { - ;(target as Record)[key] = Array.isArray(val) ? structuredClone(val) : val + const value = source[key] + setSceneProp(target, key, Array.isArray(value) ? structuredClone(value) : value) } }