fix(fig): preserve marker-based instance text edits on export (#648)

This commit is contained in:
Danila Poyarkov 2026-09-10 19:16:03 +03:00 committed by GitHub
parent 6e74ad2a51
commit 9d4fe4e421
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 51 additions and 2 deletions

View file

@ -46,6 +46,7 @@
### Fixed
- Preserve edited instance text, including cleared labels, when saving and reopening `.fig` files.
- Honor `.pen` frame layout defaults and sizing and padding shorthands so imported auto-layout frames keep their computed dimensions and child positions. (#564)
- Avoid macOS Keychain prompts during credential status checks and pause repeated credential access after failures until explicitly retried from Settings.

View file

@ -401,12 +401,15 @@ function serializeTextOverrides(
): KiwiSymbolOverridePayload[] {
const result: KiwiSymbolOverridePayload[] = []
forEachInstanceOverride(instance.instanceOverrides, (nodeId, field, value) => {
if (field !== 'text' || typeof value !== 'string' || !nodeId) return
if (field !== 'text' || !nodeId) return
const target = context.graph.getNode(nodeId)
if (!target || !isDescendantOf(context, nodeId, instance.id)) return
const targetGuid = resolveOverrideTargetGuid(context, target, localIdCounter)
if (targetGuid)
result.push({ guidPath: { guids: [targetGuid] }, textData: { characters: value } })
result.push({
guidPath: { guids: [targetGuid] },
textData: { characters: typeof value === 'string' ? value : target.text }
})
})
return result
}

View file

@ -0,0 +1,45 @@
import { expect, test } from 'bun:test'
import { FigmaAPI } from '@open-pencil/core'
import { exportFigFile, parseFigFile } from '@open-pencil/core/io'
import { initCodec } from '@open-pencil/core/kiwi'
import { SceneGraph, getInstanceOverride } from '@open-pencil/scene-graph'
for (const text of ['User edit', '']) {
test(`preserves API instance text edit ${JSON.stringify(text)} across save/reload`, async () => {
await initCodec()
const graph = new SceneGraph()
const api = new FigmaAPI(graph)
const component = api.createComponent()
const label = api.createText()
label.characters = 'Default'
component.appendChild(label)
const edited = component.createInstance()
edited.name = 'Edited'
const inherited = component.createInstance()
inherited.name = 'Inherited'
edited.children[0].characters = text
const raw = graph.getNode(edited.id)
if (!raw) throw new Error('Missing instance')
expect(
getInstanceOverride(raw.instanceOverrides, edited.id, edited.children[0].id, 'text')
).toBe(true)
label.characters = 'Component edit'
graph.syncInstances(component.id)
expect(edited.children[0].characters).toBe(text)
expect(inherited.children[0].characters).toBe('Component edit')
const bytes = await exportFigFile(graph)
const restored = await parseFigFile(bytes.buffer as ArrayBuffer)
for (const [name, expected] of [
['Edited', text],
['Inherited', 'Component edit']
]) {
const instance = [...restored.getAllNodes()].find(
(node) => node.type === 'INSTANCE' && node.name === name
)
if (!instance) throw new Error(`Missing ${name}`)
expect(restored.getChildren(instance.id)[0]?.text).toBe(expected)
}
})
}