Figma Sync Procedure

Updated: 2026-07-30 12:03 ET · Audited: 2026-06-29 18:29 ET

TL;DR: Seven-phase operating procedure for rolling a new Figma design revision into the component-spec docs: capturedeltadecisionstokens/specs auditdoc roll-upreadiness auditcommit/PR. Every sync runs all phases — skipping one (most often tokens or the roll-up) is what causes doc drift. A sync is documentation-only; implementation is gated on Epic #2087. Start with Prerequisites (.fig decompression, MCP tooling) and check Gotchas before batch renames. When this sync is done, the natural next step is the Component Library Reconciliation Procedure (see Phase 7) — a separate question about the real shared library, not this doc's concern.
Figma source of record (verified 2026-07-30). The working file is owEYzHf7FrHRvWC2u82UOl ("ALPA mobile app"). Start every capture there. psH738AqHDxuMyFm897f9r is a superseded file — it is what docs/ui-refresh-1821/figma-component-specs.json records in _resync.source.file (revision 2026-06-17), so any spec citing that key was captured against the older source and is unverified against current design.

Node ids are NOT reliably shared between the two files. Do not assume a node id captured from the superseded file resolves in the working file. Sampled 2026-07-30: 4877:11274 (advocacy) and 4617:13876 (card-duty period-alt) resolve; 5048:13572 (button pill) and 5054:13801 (notification card flight status) do not. Roughly half fail. Re-verify each id against the working file before relying on or rewriting a citation.

This is why per-component file key: citations across docs/component-specifications/ have not been bulk-rewritten to the working key — doing so would point readers at a file where the cited node may not resolve. See Typography Font Mapping § 5 for the worked example and the 25 affected docs.

Repeatable procedure for ingesting a new Figma export into the component specification docs. Every sync — regardless of scope — runs all phases in order. Skipping a phase is what causes docs to drift.

Implementation is gated. A sync updates documentation only — no RCL code, no .razor files, no ViewModel renaming in C#. Implementation is gated on Epic #2087 (Presentation Extraction).

Table of Contents

  1. Overview
  2. Prerequisites and file setup
  3. Phase 1 — Capture: inventory masters and extract annotations
  4. Phase 2 — Delta: compare against current specs and flag collisions
  5. Phase 3 — Decisions: resolve all flagged collisions
  6. Phase 4 — Tokens and specs audit
  7. Phase 5 — Doc roll-up
  8. Phase 6 — Readiness audit
  9. Phase 7 — Commit and PR
  10. File inventory
  11. Naming authority order
  12. Component tier classification
  13. Gotchas

1. Overview

A Figma sync is not just component naming. Every sync must cover four things:

AreaWhat gets updated
Inventoryfigma-component-specs.json — master component list
Tokens + specsdesign-tokens.html — colors, typography, dimensions, MEC theming
Screen mappingscreen-mapping.html — which components appear where, overlays, tablet
Doc roll-upindex.html, architecture.html, domain-controls.html, naming-decisions-record.html

Missing tokens or skipping the roll-up is the most common drift source.

What triggers a sync

2. Prerequisites and File Setup

macOS TCC restriction

macOS blocks shell access to ~/Downloads and ~/Desktop. Move the .fig export before starting:

mv ~/Downloads/ALPAmobileWtablet_YYYYMMDD ~/ALPAmobileWtablet_YYYYMMDD

Decompress the .fig archive

A .fig file is a zip archive. The canvas blob uses DEFLATE (schema) + zstd (data tree).

cp ~/ALPAmobileWtablet_YYYYMMDD /tmp/figma_export.zip
cd /tmp && unzip figma_export.zip -d figma_export/

# Find the canvas blob (largest file)
ls -lh figma_export/

# Decompress zstd data layer → binary canvas
zstd -d figma_export/canvas.fig -o /tmp/canvas_data.bin
# Result: ~78 MB uncompressed binary

MCP server

The sync uses the Figma MCP server (server name figma, configured per-machine in ~/.claude/mcp.json) for live API access to published files; the relevant operations are get_metadata, get_variable_defs, and get_screenshot. The MCP server cannot surface Figma Dev-Mode annotation pins — those require the binary .fig extraction above.

3. Phase 1 — Capture: Inventory Masters and Extract Annotations

Rule: capture first, never overwrite decisions. Flag every collision with _flag; resolve nothing yet.

3.1 Inventory masters

Using the MCP server, enumerate all top-level component masters. Exclude variant nodes (Property 1=*, State=*, Theme=*) — count masters only.

get_metadata → list all frames at canvas top level
  → filter: exclude variant property nodes
  → record: name, nodeId, width, height, fill, stroke

Compare against docs/ui-refresh-1821/figma-component-specs.json:

Compare against the full JSON body, not just _resync.dispositions. That top-of-file summary table is a point-in-time snapshot (capped at whatever D-number existed when it was last regenerated) — it does not reflect every decision resolved since. A 2026-07-13 sync pass compared only against that stale summary and flagged 4 of 5 "new masters" that were already fully resolved D-numbered entries elsewhere in the same file (see D50's "What Was Applied" section for the full story). Before flagging anything as new/renamed/removed, grep the whole file for the node id and the component name — a top-level key with a _note citing a D-number means it's already dispositioned, even if _resync.dispositions doesn't mention it.

3.2 Extract annotations from .fig binary

Dev-Mode pins and designer annotations are embedded as UTF-8 strings in the binary canvas:

python3 -c "
import re, sys
data = open('/tmp/canvas_data.bin','rb').read()
for m in re.finditer(rb'[\x20-\x7e]{30,}', data):
    print(hex(m.start()), m.group().decode('ascii','ignore'))
" | grep -i 'restyle\|tablet\|swipe\|token\|theme\|overlay\|mec\|defer' | head -60

Record: offset address, verbatim text, inferred subject. These become the authoritative designer intent notes in the relevant spec doc.

3.3 Extract token metadata

python3 -c "
import re, sys
data = open('/tmp/canvas_data.bin','rb').read()
m = re.search(rb'tokens-themes_meta.*?}', data)
if m: print(m.group().decode('utf-8','ignore'))
"

Expected: {"core":"source","light":"enabled","dark":"disabled","theme":"enabled"} — the theme set is the MEC customization layer.

3.4 Foundations-frame capture (added 2026-06-29 — root-cause fix, AB#2194)

Component masters are not the whole design system. The dedicated foundation pages carry the canonical color ramp, type scale, spacing scale, grid, button system and form elements — and were historically never diffed against the docs, which is how foundation discrepancies (and the false "no spacing panel" note) went undetected through multiple syncs and audit passes. Every sync must capture each foundation page by node id and diff it against the docs:

Foundation pageNodeDiff against
🌈 Colors2093:566design-tokens.html color palette + theme-endpoint-contract Surface/*
Typographyobtain node URL from designdesign-tokens.html type scale
📐 Grid, Columns, Spacing1:17design-tokens.html spacing scale + grid
🎛️ Buttons1:16ActionButton + button tokens
☑️ Form Elements2093:702component-library.html / form specs
🖤 Icons3:7asset-inventory.html icon catalog
get_metadata(nodeId=<foundation page>)   # structure: names, groups, sizes
get_design_context / get_variable_defs       # exact hex / px / weight values
  → record discrepancies in foundations-reconciliation.html (capture-only; do NOT change live token/CSS values mid-sync)

Output target: foundations-reconciliation.html. Note: get_variable_defs may require a live selection in the Figma desktop app; if it returns "nothing selected," pull values via get_design_context instead, or flag the value as a capture gap.

3.5 Asset / vector export (added 2026-06-29 — root-cause fix, AB#2194)

The SOP was docs-only and never exported image assets or icon vectors, so the custom ALPA icon set was never inventoried and the docs carried a wrong "icons = FontAwesome/Material" assumption. Every sync must export and reconcile assets:

3.6 MEC per-airline page capture (added 2026-07-06 — root-cause fix)

A MEC airline page is not just a token/color capture. An investigation on 2026-07-06 found ual-mec-spec.html had only ever recorded United's color/font tokens — the page's actual frames and image assets were never inventoried, even though the Figma page (node 20657:502) carried 14-15 real frames and 29 image-filled nodes, including branded icon instances (united/committee, united/hotel) and a full Home-feed mock (Home-banner demo alt) showing a domain control placed in live branded context. This is the same failure mode as the base-design foundations gap (§3.4) — a doc that is internally consistent but was never checked against the full Figma source. Every MEC sync, whether for a new airline or a re-check of an existing one, must capture both of these, not just token values:

Output target: each airline's own spec page (docs/component-specifications/mec/{mecId}-mec-spec.html) plus the MEC Onboarding Hub status table/badges.

4. Phase 2 — Delta: Compare Against Current Specs and Flag Collisions

Compare the Phase 1 inventory against every existing spec doc. Flag (do not resolve) anything that conflicts.

Collision typeExampleAction
Name changeflight segment cardflight card-og_flag in JSON, note in decisions record
New componentcard-button not in current JSONAdd entry, flag for tier decision
Classification conflictComponent listed as scaffold in one doc, domain control in anotherFlag both locations
Dimension discrepancyNode measures 353×111 but doc says 360×120Flag in design-tokens.html
Retired componentNode no longer present in FigmaFlag for explicit retirement confirmation

All _flag fields must be resolved (converted to _note) before Phase 5.

5. Phase 3 — Decisions: Resolve All Flagged Collisions

Every _flag requires a named decision (D-number). Do not proceed to Phase 4 with open flags.

Decision authority order

  1. naming-decisions-record.html — highest authority; adopted decisions are final
  2. Per-component *-property-mapping.md files — for per-field naming
  3. naming-alignment-report.html — historical reference only

Recording a decision

  1. Assign the next D-number (check the highest current D in naming-decisions-record.html)
  2. Add the full decision block to naming-decisions-record.html under the appropriate section
  3. Update the status banner to reflect remaining open items
  4. Update the _flag_note in figma-component-specs.json citing the D-number and date
  5. Apply the decision wherever the conflicting name/classification appeared

sed rename guardrails

When renaming a Figma term across docs, exclude these two files from batch renames — they contain historical "Previous Name" columns that must not be overwritten:

6. Phase 4 — Tokens and Specs Audit

The most easily skipped phase. Tokens go stale between syncs; always re-check.

6.1 Typography

Pull the text style nodes and compare against design-tokens.html. New type styles → add to font size/weight tables. Tablet breakpoint deltas → update the Tablet Typography Mode table.

6.2 Color tokens

python3 -c "
import re
data = open('/tmp/canvas_data.bin','rb').read()
for m in re.finditer(rb'(?:Surface|Border|Text|Fills|Accents|Status)/[A-Za-z0-9/_\-]+', data):
    print(m.group().decode())
" | sort -u

Diff against the token tables in design-tokens.html. New tokens → add rows.

MEC theming tokens — the theme set defines the MEC customization layer. These four semantic hooks must always be present:

TokenPurpose
Surface/BrandMEC primary surface (nav bar, card header tint)
Border/BrandMEC accent border
Text/On-BrandText on brand surfaces
Accents/Blue, Accents/Indigo, Accents/RedInteractive accent colors

6.3 Component dimensions

get_metadata(nodeId) → width, height, fills, strokes, strokeWeight

Update the Component Dimensions table in design-tokens.html. Mark null sizes as TBD — sizing deep-dive pending.

6.4 Surface token XAML mapping note

Surface tokens are CSS-aligned for the Razor/Blazor path. XAML mapping is deferred to the native-XAML (Track B) surface work on ALPAMobile.Presentation — the shared library itself shipped as a Blazor RCL 2026-07-15 (D61); "scaffold PCL" was the planning-era shorthand:

7. Phase 5 — Doc Roll-Up

Update all hub documents to reflect the resolved decisions and new specs. Every file in this list must be touched — even if only to update a date.

FileWhat to update
figma-component-specs.jsonAll _flag_note. No open flags remaining.
naming-decisions-record.htmlEvery new D-number has a full adopted-decision block; status banner updated.
design-tokens.htmlSource comment updated, new CSS variables added, token tables updated, footer updated.
screen-mapping.htmlComponent Inventory table updated, per-screen sections updated, Node Registry JSON updated.
index.htmlScaffold component count, inline ViewModel tree, new component grid card.
architecture.htmlComponents count, hierarchy tree, ViewModel reference table, domain controls table.
domain-controls.htmlHeader count, catalog table, full .control profile for each new domain control.
New component spec pagesCreate docs/component-specifications/<name>/<name>-component.html following the small-card pattern.
After adopting a decision, grep for the old value everywhere — not just in the docs the decision text happens to name. A recurring failure mode: a decision resolves a stale node id or value in prose (and updates the doc it was raised against), but the same stale id/value survives untouched in 2-3 other docs that independently cited it — nobody grepped for it elsewhere. This happened three separate times before D50 (2026-07-13): a dated-.fig-snapshot node id lived on in figma-component-specs.json and screen-mapping.html for weeks after design-tokens.html and a component spec page had already moved on to the live node id. Before closing Phase 5, run grep -rn "<old-id-or-value>" across all of docs/component-specifications/ and docs/ui-refresh-1821/, not just the files listed in the decision's own "Status" line.

8. Phase 6 — Readiness Audit

Run this before sharing any doc set with the team. Seven passes, in order.

Note (2026-06-29, AB#2194): Passes 1–6 validate internal consistency (the docs against each other). They do not catch a doc that is self-consistent but wrong about the Figma source — which is why the 2026-06-27 audit pass missed the foundation discrepancies. Pass 7 (source fidelity) closes that gap.

Pass 1: Decision reference integrity

Every D-number mentioned in prose must have an <a href> linking to the exact anchor in naming-decisions-record.html and must reference an adopted decision.

grep -rn 'D[0-9]\{1,2\}[^0-9"#]' docs/component-specifications/ \
  --include="*.html" | grep -v 'href=\|naming-decisions-record\|id="d'

Pass 2: Logic conflicts across docs

Cross-check the same fact wherever it appears in multiple docs:

Pass 3: Decision status consistency

Verify every doc that touches a resolved decision reflects adopted status. Check: figma-component-specs.json, naming-decisions-record.html, screen-mapping.html, design-tokens.html, domain-controls.html, and all component spec pages.

Pass 4: Stub and TBD clarity

Every incomplete item must be explicitly marked. TBD / spec pending only on genuinely unspecced items. No resolved item accidentally left with a pending marker.

Pass 5: Terminology consistency

Use thisNot this
ButtonCard / ButtonCardViewModel"CTA Card", "button-card component", "card-button" (Figma provenance only)
FlightCard / FlightCardFactory"flight card-og" as a code reference
"scaffold component""base component", "generic component", "template"
"domain control""feature component", "screen component"
"composes" (domain control → scaffold)"extends", "subclasses", "wraps"

Pass 6: Cross-doc story coherence

Every doc that mentions another doc links to it. Every doc that cites a decision links to the decisions record anchor. Work item references are consistent and explained with enough context for a reader without Azure Boards access.

Pass 7: Figma-source fidelity (added 2026-06-29 — root-cause fix, AB#2194)

Validate the docs against the Figma source, not just against each other. For each foundation page captured in 3.4, confirm the doc reflects the canonical frame — catch claims that are internally consistent but wrong about Figma (e.g. "no spacing panel exists" when 117:176 proves otherwise; "icons = FontAwesome/Material" when 3:7 is a custom vector set).

9. Phase 7 — Commit and PR

Verify before committing

# Tag balance check — run on every changed HTML file
python3 -c "
import re, sys
for path in sys.argv[1:]:
    html = open(path).read()
    for tag in ['div','table','tbody','tr']:
        o = len(re.findall(f'<{tag}[\s>]', html, re.I))
        c = len(re.findall(f'</{tag}>', html, re.I))
        if o != c:
            print(f'MISMATCH {path}: <{tag}> open={o} close={c}')
" docs/component-specifications/*.html

# JSON validity
python3 -m json.tool docs/ui-refresh-1821/figma-component-specs.json > /dev/null && echo "JSON OK"

Branch and commit

# Work on a dedicated branch
git worktree add "../ALPA Mobile.worktrees/<sync-name>" -b docs/<sync-name>

# Stage specific files — never git add -A
git add docs/component-specifications/... docs/ui-refresh-1821/figma-component-specs.json

# Commit message format
git commit -m "docs: ingest <FigmaArchiveName> (<decisions resolved>)"

PR targets

PRs go to Azure DevOps. Target both docs/component-specs-sizing and DotNet10:

az repos pr create --target-branch docs/component-specs-sizing \
  --title "docs: <description>" --description "<body>"
Natural next step, not part of this procedure: once this sync's doc roll-up lands, the spec docs may now describe components the real shared library doesn't have yet, or confirm ones it does. Whether those changes should roll into ALPAMobile.Presentation/Components/Library/, and whether anything newly specced deserves to become a shared component, is a separate question this procedure doesn't answer — run the Component Library Reconciliation Procedure next.

10. File Inventory

FilePhasePurpose
docs/ui-refresh-1821/figma-component-specs.json1, 2, 3Machine-readable master component inventory
docs/component-specifications/naming-decisions-record.html3Authoritative D-number decision log
docs/component-specifications/design-tokens.html4Colors, typography, dimensions, MEC theming
docs/component-specifications/screen-mapping.html4, 5Per-screen component placement, node registry
docs/component-specifications/index.html5Scaffold component grid and inline ViewModel tree
docs/component-specifications/architecture.html5Full ViewModel hierarchy and domain controls summary
docs/component-specifications/domain-controls.html5Domain control catalog and full profiles
docs/component-specifications/<name>/<name>-component.html5Per-component spec and preview page

11. Naming Authority Order

When a component name conflict exists between Figma and the existing docs, resolve in this order (highest authority first):

  1. naming-decisions-record.html — an adopted D-number decision is final
  2. Per-component *-property-mapping.md — field-level naming
  3. naming-alignment-report.html — historical reference; informs but does not override
  4. Figma master name — starting point only; names are often non-code-safe (-og, -TABLET suffixes, spaces)

Code names must be PascalCase and not carry Figma suffixes. See D13.

12. Component Tier Classification

TierDescriptionExamples
scaffold/atomSmallest reusable unit, no childrenButtonViewModel
scaffold/primitiveComposed of atoms; still domain-freeCardViewModel, SmallCardViewModel
scaffold/containerHolds a collection of componentsCarouselViewModel, ListViewModel
scaffold/compositeScaffold component with a nested scaffold childButtonCardViewModel
nav-chromeNavigation structurenavBar-bottom, topNav
templateFull phone/tablet screen layouttemplate-home, template-tablet
domain-controlMaps domain data onto a scaffold via factoryPilot Card, Flight Card
screen-outScreen-specific layout only, not reusableOne-off onboarding panels
deferred-responsiveTablet variant; deferred until responsive layout system (post #2087)flight card-TABLET, template-tablet

13. Gotchas