fix(clipboard): preserve pasted instance links

- Share exported GUID assignments across clipboard roots

- Preserve source metadata while applying pasted instance overrides
This commit is contained in:
Danila Poyarkov 2026-08-12 20:08:06 +03:00
parent 38e98095b2
commit cf72b8d1c4
2 changed files with 42 additions and 3 deletions

View file

@ -8,7 +8,7 @@ import {
sortChildren
} from '@open-pencil/fig/node-change'
import { initCodec, getCompiledSchema, getSchemaBytes } from '@open-pencil/kiwi/fig/codec'
import type { NodeChange as KiwiNodeChange } from '@open-pencil/kiwi/fig/codec'
import type { GUID, NodeChange as KiwiNodeChange } from '@open-pencil/kiwi/fig/codec'
import { decodeBinarySchema, compileSchema, ByteBuffer } from '@open-pencil/kiwi/schema-runtime'
import type { SceneGraph, SceneNode } from '@open-pencil/scene-graph'
@ -283,7 +283,9 @@ export function importClipboardNodes(
remapComponentIds(created, graph)
populateAndApplyOverrides(graph, guidMap as Map<string, InstanceNodeChange>, created, blobs)
graph.preserveSourceMetadataDuring(() => {
populateAndApplyOverrides(graph, guidMap as Map<string, InstanceNodeChange>, created, blobs)
})
for (const figmaId of internalTopLevel) {
const ourId = created.get(figmaId)
@ -321,6 +323,8 @@ export async function buildFigmaClipboardHTML(
}
}
const nodeIdToGuid = new Map<string, GUID>()
const assignedGuidValues = new Set<string>()
const blobs: Uint8Array[] = []
for (let i = 0; i < nodes.length; i++) {
collectTextNodes(nodes[i])
@ -332,8 +336,12 @@ export async function buildFigmaClipboardHTML(
localIdCounter,
graph,
blobs,
nodeIdToGuid,
fontDigestMap,
undefined,
fontDigestMap
undefined,
undefined,
assignedGuidValues
)
)
}

View file

@ -141,6 +141,37 @@ describe('buildFigmaClipboardHTML', () => {
expect(html).toContain('figmeta')
})
it('preserves source metadata while importing instance overrides', async () => {
const source = new SceneGraph()
const sourcePage = source.getPages()[0]
const component = source.createNode('COMPONENT', sourcePage.id, { name: 'Button' })
source.createNode('TEXT', component.id, { name: 'Label', text: 'Effective label' })
const instance = source.createNode('INSTANCE', sourcePage.id, {
name: 'Button instance',
componentId: component.id
})
source.populateInstanceChildren(instance.id, component.id)
const html = await buildFigmaClipboardHTML([component, instance], source)
const parsed = await parseFigmaClipboard(expectDefined(html, 'Figma clipboard html'))
const clipboard = expectDefined(parsed, 'Figma clipboard')
const target = new SceneGraph()
const targetPage = target.getPages()[0]
importClipboardNodes(clipboard.nodes, target, targetPage.id)
const importedInstance = [...target.getAllNodes()].find(
(node) => node.type === 'INSTANCE' && node.name === 'Button instance'
)
const importedLabel = importedInstance
? [...target.getAllNodes()].find(
(node) => node.type === 'TEXT' && node.parentId === importedInstance.id
)
: undefined
expect(importedInstance).toBeDefined()
expect(importedLabel?.text).toBe('Effective label')
expect(importedLabel?.source.editedFields).toEqual([])
})
it('roundtrips: encode then decode back', async () => {
const graph = new SceneGraph()
const page = graph.getPages()[0]