feat(figma): serve node metadata over the bridge and keep the node to convert when a subtree splits - #57
Merged
owjs3901 merged 1 commit intoSep 14, 2026
Conversation
… convert when a subtree splits An attached plugin could not finish a node-scoped export. get_metadata was excluded from the bridge for its response shape, yet find_metadata reduces both the plugin JSON and the official XML to one MetadataDocument, so metadata.js emits that document and the same decoder reads it. A read with no node stays remote: the top-level page list is a different contract. Separately, a root with children and more than LARGE_SUBTREE_THRESHOLD descendants was split into child snapshots that never included the root, so node-scoped projection failed with DEVUP_FIGMA_NODE_NOT_FOUND whenever the fast envelope fell back. The node to convert is now always a snapshot target.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
An attached Devup Bridge plugin still could not finish a node-scoped export. Two independent things were in the way.
1.
get_metadatawas the only read left on the metered pathEvery other read in a tsx export already has a script, so once the bridge is attached the export spends exactly one metered call — and on a View seat whose allowance is spent, that one call is the difference between a screen and a refusal.
The exclusion (
upstream.rs) was justified by response shape, not by data. Butfind_metadatadeserialises aMetadataDocumentin its first branch and reduces the official XML to that same type in the next, so a plugin emitting the canonical camelCase document is read by the same decoder rather than a parallel one.scripts/metadata.jsemits exactly that document —{ fileKey, version, rootId, nodes[{ id, type, name, childrenIds, descendantCount }] }— and nothing more. No geometry: the decoder does not read it and the snapshot already carries it.A read with no node id stays remote. That answers with the top-level page list, which is a different contract and not worth imitating.
2. A split subtree never snapshotted the node it was asked to convert
Independent of transport. In
accept_metadata, a root with children and more thanLARGE_SUBTREE_THRESHOLDdescendants is split into child snapshots — and in node scope the root itself is then never a target, so projection fails withDEVUP_FIGMA_NODE_NOT_FOUNDon exactly the screen the caller named.The path is rarely reached because the fast envelope usually serves node scope. It is reached whenever the fast envelope falls back, and then it cannot succeed at all.
Evidence
Measured on a 1920-wide screen whose fast envelope fell back (
fallbackReason: textEnvelope):Boundary
The descendant count a plugin reports is not the count the official XML reports — official metadata collapses instances nested inside an instance, the Plugin API walk does not. The split decision can therefore differ between the two paths. After this change that difference costs one redundant bridged snapshot instead of a failure.
Tests
bridge_transport::routing_is_decided_before_the_call— node-scoped metadata is served, a read with no node is not.collector::a_split_subtree_still_snapshots_the_node_to_convert— new; fails before the change.plugin/scripts/verify-generated.mjs—EXPECTEDcarriesmetadata; the name guard would otherwise only fire at runtime.Gates
cargo fmt --all -- --checkcargo clippy --locked --workspace --all-targets --all-features -- -D warningscargo test --workspace --releasevisual_compare::symlink_escape_refused_before_read, which fails identically on an unmodified tree (Windows symlink privilege)npm run buildinplugin/dist/rebuilt and committed