BSP Figma โ€” Codebase Documentation

TIER 0 authority for any Figma work at Bright Side Plumbing โ€” design pulls, walker pipeline, asset export, brand tokens, REST API endpoints, plugins. Mirror of BSP_Bricks_Codebase_Documentation.html for the design half of the stack.
๐Ÿ“… Built: 2026-04-27 ยท Apr 27 structural fix (Layer 5) ๐ŸŽจ Designer: Audrey Grant (audfish, owner) ๐Ÿ Walkers: v1 ยท v2 (Apr 14, ship) ยท v3 (Apr 14, header/footer templates) ๐Ÿ”‘ Token: FIGMA_TOKEN @ /opt/nexus/nexus/config/.env (perm 600) ๐Ÿ“„ Sewer-camera file: GViYd2jKWUEpLbz1lWghby
Companion docs: Bricks Codebase Doc ยท Sacred v2 (operations dashboard) ยท Master Session History ยท Location Pages Playbook ยท Homepage Redesign Playbook

0. Apr 27 changelog โ€” doc shipped

This doc was created Apr 27 2026 as Layer 5 of the Apr 27 structural fix after the smoke-gate caught drift between dispatcher_safety.py's M2 sanitizer-chain assertion and the live endpoint. Same drift risk exists for Figma work: multiple walker iterations (v1/v2/v3), undocumented API endpoint usage, asset upload failure modes, brand token drift between Figma and Bricks. Without a TIER 0 codebase doc, every future Figma task re-investigates from scratch โ€” and that's the loop the Bricks doc was created to break.

Sources consolidated into this doc

Why now: Friday May 1 is production cutover. This doc is parallel work, not blocking the cutover, and is the structural fix so the Apr 14 footer-figma-API-violation incident (5 hours rebuilt from approximation when API access existed) cannot recur silently.

๐Ÿงญ1. Intent โ€” why this codebase doc exists

The design half of BSP's web stack is Figma. Audrey designs at figma.com, exports via the Figma REST API, the BSP walkers translate her autolayout trees into Bricks element JSON, the JSON is pasted into the Bricks editor (or POSTed to /bsp/v2/bricks/native-save), and the result renders at bricks.callbrightside.com. Every link in that chain has a known sharp edge.

This doc enforces three things:

  1. Figma API first โ€” never rebuild from a screenshot, transcript, or memory when the file key + token are sitting in .env. The Apr 14 footer-figma-API-violation cost 5 hours and an embarrassing "you have figma api access fucker" correction from Robert.
  2. Walker is the producer, not the verifier โ€” Bricks rendering reality is the verifier. Walker JSON that "looks right" is irrelevant; only what the live page renders counts.
  3. Tokens live ONE place: Figma โ€” Audrey's file is the source of truth for color/typography/spacing/copy. Brand drift events (e.g. Apr 15 wave-is-rectangle, Apr 17 trust-bar emoji vs custom icons) all came from substituting interpretation for the file.
The Theo / Mario test (carried over from Bricks doc): can I, in one sentence, explain to Kalen why each Figma walker mapping exists? If no, the mapping is speculative and gets cut. Walker v2 dropped 5 v1 mappings under this rule.

๐Ÿ›ก๏ธ2. Anti-slop principles (Audrey/Robert/Mario lessons applied to Figma)

The 5 anti-patterns that have burned us

PatternWhere it bit usFix
Building from approximation when API worksApr 14 footer rebuild v1-v5 invented tagline, wrong color #D4E9FF (real: #D5EAFF), wrong phone format, wrong layout. 5 hours wasted.Always GET /v1/files first. Memory rule feedback_figma_api_first.md. Trigger phrases: "Audrey designed/updated/wants", "dial in/fix/redesign section".
Producer-as-Verifier collapseWalker output "looks right", breakdown counts match โ€” but Bricks sanitizer wipes the JSON on subsequent read (Apr 14, Apr 16). 23/23 grep checks passed but page rendered blank.Independent reader: Playwright on rendered page or curl /bsp/v2/db/meta-full?post_id=N. Element count from a fresh DB read, not walker stdout.
Adding CSS over real Figma assetsApr 15 painted #BEE6F5 rectangle on top of wave-bg-mid-light-scaled.png โ€” the actual Figma-exported wave PNG was already attached via body::before. Hid the real wave entirely.Before adding any decorative CSS: grep for bsp-page-waves-* and body::before on live HTML. Check whether Figma exports an asset for this shape.
Substituting emoji/text for Audrey's custom iconsApr 17 trust bar shipped as emoji-prefix single text-basic #brxe-4600bc. Audrey had drawn 4 custom SVG icons (star/wrench/check/stopwatch) at frame 734:30. Robert pushed back twice.Figma frame names with 02_TrustBar, 06_kc_homeowners_say, review_card, FAQ N Question patterns are explicit handoffs from Audrey โ€” pull them, don't paraphrase them.
Element ID drift on rebuildApr 16: every build_page8_figma_exact.py run generated NEW UUIDs. functions.php CSS still hardcoded to OLD IDs. Page rendered, no styles applied.Either deterministic IDs (hash of section name) OR re-extract live ID map (/opt/nexus/nexus/scripts/output/bricks_page8_id_map.json) after every rebuild and patch CSS automatically.

The Audrey reframe lesson (Apr 15-16)

When Audrey shipped node 602:9 / 612:15, the export was absolute X/Y per section. Bricks translated that to margin-top: Npx per section. Adding margin-bottom in functions.php doubled gaps. 4 hours fighting CSS until Audrey reframed to autolayout (Shift+A) on parent + per-section autolayout with itemSpacing. New nodes 708:216 (desktop) / 606:9 (mobile) verified: every padding/spacing matched the spec sent to her, character-by-character.

Rule: request autolayout-reframed Figma before walking. Stephanie-format Slack to Audrey at drafts/slack_audrey_autolayout_request_apr15.txt is the canonical ask: 30 min for her, 4+ hrs saved on our side.

Producer-as-Verifier callouts specific to Figma

๐Ÿ—๏ธ3. Architecture overview โ€” Figma โ†’ walker โ†’ Bricks

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   FIGMA                  โ”‚    โ”‚   BSP VM (Nexus)         โ”‚    โ”‚   WORDPRESS / BRICKS     โ”‚
โ”‚                          โ”‚    โ”‚                          โ”‚    โ”‚                          โ”‚
โ”‚  Audrey designs          โ”‚    โ”‚  figma_to_bricks_        โ”‚    โ”‚  bricks.callbrightside   โ”‚
โ”‚  autolayout frames       โ”‚โ”€โ”€โ†’ โ”‚  walker_v2.py / v3.py    โ”‚โ”€โ”€โ†’ โ”‚  .com  (page 8 + tpl)    โ”‚
โ”‚                          โ”‚    โ”‚                          โ”‚    โ”‚                          โ”‚
โ”‚  files/{file_key}        โ”‚    โ”‚  Walker reads:           โ”‚    โ”‚  POST /bsp/v2/bricks/    โ”‚
โ”‚  GViYd2jKWUEpLbz1lWghby  โ”‚    โ”‚   /tmp/figma_audrey.json โ”‚    โ”‚       native-save         โ”‚
โ”‚  6Hs3YviSaG5uCzc90XKU7Q  โ”‚    โ”‚   /tmp/bricks_uploaded_  โ”‚    โ”‚  POST /bsp/v1/bricks/    โ”‚
โ”‚  (...)                   โ”‚    โ”‚      assets.json         โ”‚    โ”‚       template-create     โ”‚
โ”‚                          โ”‚    โ”‚  Walker writes:          โ”‚    โ”‚  POST /wp/v2/media        โ”‚
โ”‚  /v1/files/{key}         โ”‚โ†โ”€โ”€ โ”‚   /tmp/bricks_import_    โ”‚    โ”‚                          โ”‚
โ”‚  /v1/files/{key}/nodes   โ”‚    โ”‚      v3.json (187 elems) โ”‚    โ”‚  Bricks sanitizer chain   โ”‚
โ”‚  /v1/images/{key}        โ”‚    โ”‚                          โ”‚    โ”‚  may reject + return 200  โ”‚
โ”‚                          โ”‚    โ”‚  Auth: X-Figma-Token     โ”‚    โ”‚  โ†’ READ-AFTER-WRITE       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
        โ†‘                                  โ†“                                โ†“
        โ”‚                          drafts / .env / logs              functions.php CSS
        โ”‚                                                            mirrors Figma tokens
        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ if walk fails, request autolayout reframe โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  

Pipeline phases (Apr 14-17 canonical flow)

  1. Pull โ€” curl -H "X-Figma-Token: $FIGMA_TOKEN" https://api.figma.com/v1/files/{key} > /tmp/figma_audrey_full.json
  2. Render reference PNG โ€” /v1/images/{key}?ids=708:216&format=png&scale=2 โ†’ upload to morpheus for visual diff
  3. Export image fills โ€” /v1/images/{key}?ids=...&format=png&scale=2 for every RECTANGLE with fills[].type==IMAGE
  4. Upload to WP media โ€” POST /wp/v2/media with claude-api app password (see Auth section); save {id, url, slug, hash12} to /tmp/bricks_uploaded_assets.json
  5. Walk โ€” python3 figma_to_bricks_walker_v2.py /tmp/elements.json <frame_names...>
  6. Apply โ€” paste-import via Bricks editor UI (preferred) OR POST /bsp/v2/bricks/native-save (REST path)
  7. Read-after-write โ€” GET /bsp/v2/db/meta-full?post_id=N ยท sanitizer wipes silently on schema mismatch
  8. Verify visually โ€” Playwright screenshot, pixel-sample at decoration boundaries, side-by-side vs Figma /v1/images render
  9. Purge โ€” LiteSpeed admin + Cloudflare API (NEVER skip โ€” Apr 16 lie-loop documented)

๐ŸŒ4. Figma REST API โ€” endpoints BSP uses, annotated

Authentication

HEADER:
  X-Figma-Token: <Personal Access Token>

WHERE:
  /opt/nexus/nexus/config/.env  โ†’  FIGMA_TOKEN=...   (perm 600, owned dovew)

OWNER OF TOKEN:
  audrey.grant@callbrightside.com  (handle: audfish, role: owner on her files)
  Whoami probe: GET https://api.figma.com/v1/me  โ†’  email + handle echoed back
If token rotates: Audrey regenerates at figma.com/settings โ†’ personal access tokens. Update /opt/nexus/nexus/config/.env, re-test with /v1/me, log new auth-test timestamp to MH. Token does NOT expire on its own; rotation is manual.

Endpoint inventory (verified BSP-usage)

EndpointBSP useSharp edges
GET /v1/me Token auth probe. Returns {id, email, handle, img_url}. Apr 14 used to confirm token belonged to Audrey before pulling private files.
GET /v1/files/{file_key} Pull entire file document tree. Apr 14 saved 627 KB JSON to /tmp/figma_audrey_full.json. Large files balloon. ?depth=N param limits recursion. Use depth=3 for index, depth=10 for walk.
GET /v1/files/{file_key}/nodes?ids=ID1,ID2&depth=10 Node-scoped pull. Apr 17 used to extract 734:30 trust-bar frame children only. IDs URL-encoded (: stays literal). Must include depth or you get one level only.
GET /v1/images/{file_key}?ids=ID1,ID2&format=png&scale=2 PNG export at 2x. Used Apr 14 hero/cards/commercial photo, Apr 17 trust-bar icons (3x scale used). Returns {images: {id: signed-url}}. Signed URLs expire ~30 min. Download immediately, don't store the URL.
GET /v1/images/{file_key}?ids=ID&format=svg SVG export for vector logos / icon shapes (when raster scale loses fidelity). SVG export returns text/xml; same expiration on the signed URL.
GET /v1/files/{file_key}/components List components in a file. Used to discover Audrey's named instances (e.g. review_card, service_card). Returns components but NOT instance positions; cross-reference with /nodes for placement.
GET /v1/files/{file_key}/styles Pull style library (color/text/effect styles). Maps to brand tokens table in ยง9. Audrey may name styles BSP/Navy, Inter/Body-16; treat as canonical labels.
GET /v1/images/{file_key}/fills Extract image-fill imageRef URLs for RECTANGLE nodes with fills[].type=IMAGE. Returns hash โ†’ URL. Walker v2 keys self.hash_map by first 12 chars of imageRef.
Sharp edges burned in:
  • Image URL expiration โ€” signed S3 URLs from /v1/images expire fast. Walker pipeline downloads immediately, uploads to WP, stores WP media URL โ€” never the Figma signed URL.
  • Node ID drift โ€” Audrey edits nodes; IDs change on duplication. Always re-pull node IDs at start of session, never trust cached IDs from yesterday's MH log.
  • Slug โ‰  file content โ€” Apr 14: /design/GViYd2jKWUEpLbz1lWghby/Figma-basics looked like a tutorial file from the slug. It wasn't โ€” Audrey started from the Figma-basics template and renamed it to "Sewer-Camera-Inspection-Landing-Page". Don't dismiss a Figma URL based on the slug alone.
  • Rate limits โ€” Figma documents ~6000 req/min/PAT but bursts of /v1/images exports throttle aggressively. # TODO research exact /images limit; observed in practice: serial requests with 200ms gap = no throttling on Apr 14/17 sessions.

Canonical curl examples (used Apr 14-17, copy-paste ready)

FIGMA_TOKEN=$(grep ^FIGMA_TOKEN /opt/nexus/nexus/config/.env | cut -d= -f2)
KEY=GViYd2jKWUEpLbz1lWghby

# 1) Whoami (auth probe)
curl -s -H "X-Figma-Token: $FIGMA_TOKEN" https://api.figma.com/v1/me

# 2) Full file (depth=3 index)
curl -s -H "X-Figma-Token: $FIGMA_TOKEN" \
  "https://api.figma.com/v1/files/$KEY?depth=3" > /tmp/figma_index.json

# 3) Specific node tree (autolayout walk)
curl -s -H "X-Figma-Token: $FIGMA_TOKEN" \
  "https://api.figma.com/v1/files/$KEY/nodes?ids=708:216,606:9&depth=10" \
  > /tmp/figma_audrey_full.json

# 4) Reference PNG render (visual diff source)
curl -s -H "X-Figma-Token: $FIGMA_TOKEN" \
  "https://api.figma.com/v1/images/$KEY?ids=708:216&format=png&scale=2" \
  | jq -r '.images["708:216"]' \
  | xargs -I{} curl -s -o /tmp/figma_desktop.png "{}"

# 5) Trust-bar icon export (Apr 17)
curl -s -H "X-Figma-Token: $FIGMA_TOKEN" \
  "https://api.figma.com/v1/images/$KEY?ids=734:26,734:29,734:19,734:8&format=png&scale=3"
Cite-verbatim source: public docs at figma.com/developers/api. BSP usage tracked here is the subset we have actually exercised in production; for endpoints not listed here, consult the public docs and add to this table only after running them once and logging to MH.

๐Ÿงฉ5. Figma plugins โ€” community + custom

Community plugin Robert flagged Apr 27

URL: https://www.figma.com/community/plugin/991866272578143756

# TODO research โ€” page is rate-limited from VM and fetch tool denied at Apr 27 build time. Plugin ID 991866272578143756 recorded for future research; Robert to confirm name/author at next sync. Per the "no fabricated content" rule, no plugin description is shipped here. Action: on next session, paste the plugin name and a 1-paragraph "what it does" into this section and re-sync the doc.

Plugins/integrations BSP currently uses (verified)

ToolPurposeStatus
Figma REST API (no plugin)Programmatic file/node/image pulls via PATactive โ€” primary path, see ยง4
Figma desktop appAudrey's native design surface โ€” autolayout (Shift+A), components, frames, image exportsactive โ€” designer-side
Bricks paste-import (NOT a Figma plugin)Element JSON paste into Bricks editor canvas; bypasses REST sanitizeractive โ€” fallback for sanitizer rejections (Apr 14, Apr 15-16)
Custom: figma_to_bricks_walker_v3.pyHeader/footer template builder (ยง6)active
What we explicitly do NOT use: Figma-to-HTML community plugins (Anima, Locofy, etc.). Walker v2/v3 emit Bricks element JSON directly, which is the only format Bricks' element tree accepts. HTML conversion plugins produce slop that the Bricks editor can't import.

๐Ÿšถ6. Walker v1 โ†’ v2 โ†’ v3 โ€” annotated source

Version timeline

VersionFileLOCShippedStatusPurpose
v1figma_to_bricks_walker.py400Apr 14 14:51supersededFirst-pass walker. Indexed iPhone + Desktop, multi-line text dumped as one element, no nav-column expansion.
v2figma_to_bricks_walker_v2.py366Apr 14 15:39active body walkerLayout-faithful walker for page 8 body (Sewer-Camera-Inspection page). Used for paste-import that shipped 122 elements rendering on staging.
v3figma_to_bricks_walker_v3.py502Apr 14 16:05active header/footer builderSchema-locked HEADER + FOOTER template emitter. Bare-string widths, 5-key image shape, tag=header / tag=footer on root. Output goes to /wp-json/bsp/v1/bricks/template-create.

v2 โ€” what it does

Source: /opt/nexus/nexus/scripts/figma_to_bricks_walker_v2.py

Module docstring (verbatim):

"""figma_to_bricks_walker_v2.py
Layout-faithful Figma -> Bricks walker.
Fixes from v1:
  - Indexes ONLY Desktop - 1 (not iPhone)
  - Multi-line TEXT in nav columns -> split into separate text-basic rows
  - Header section gets tagName:header, nav containers get tagName:nav
  - Footer section gets tagName:footer
  - Width propagation via percent (child width / parent width) for proper row layout
  - VECTOR with wide/thin aspect -> divider element
  - Big background VECTORs (wave_element, Footer shape) at Desktop root are skipped
    (they're absolutely-positioned decoration, not flow content)
  - Button frames use tel:+19139631029 and button element with proper bg + text
  - Line-height, letter-spacing carried through typography
"""

v2 element-type mapping (annotated)

Figma typeWalker outputWhy
FRAME / GROUP / COMPONENT / INSTANCE with name Menusection with settings.tag = "header"Audrey labels her menu frame literally Menu. Hardcoded.
FRAME with name 10_Footersection with settings.tag = "footer"Numeric prefix 10_ = footer in Audrey's naming convention.
FRAME with name matching ^\d{2}_sectionNumeric prefix = top-level section. 01_Hero through 10_Footer.
FRAME with layoutMode HORIZONTAL/VERTICAL (not root)block with _direction = row|columnAutolayout child = flex container at Bricks block level.
FRAME with name 10_Footer_Services / 10_Footer_Pagesblock with multi-line text expanded to per-line text-basic tag=aNAV_COLUMN_NAMES set in v2 source. First line (e.g. "Services") becomes heading h4; subsequent lines become anchor text-basic.
FRAME with "button" in name OR name == ctabutton with link.url = tel:+19139631029BUTTON_PHONE constant. Apr 17 burn: didn't trust this and rebuilt CTA twice.
TEXT with fontSize โ‰ฅ 28heading, tag h1 if โ‰ฅ44 / h2 if โ‰ฅ36 / h3 otherwiseMapping is heuristic; if Audrey wants h2 at 30px, it ships as h3. Override post-walk.
TEXT with fontSize < 28text-basicBody copy.
RECTANGLE with fills[].type=IMAGEimage with image: {id, url, size:"full"}Walker resolves imageRef[:12] via self.hash_map built from /tmp/bricks_uploaded_assets.json.
RECTANGLE solid-filledblock with _background.color.hexDecorative box.
VECTOR wide+thin (w>200, hโ‰ค4)dividerPattern: horizontal-rule shapes Audrey draws as 1px tall vectors.
VECTOR all otherlogged to self.unsupported, skippedDecorative SVG. Action: export PNG via /v1/images?format=png, upload to WP, place via image element.
VECTOR at Desktop root with name in SKIP_DESKTOP_ROOT_NAMESskippedSKIP set: {"wave element 2", "Footer", "wave_background_elements"}. These are absolute-positioned decoration handled via CSS / pseudo-elements, not flow content.
LINEdivider with stroke colorSame as wide/thin VECTOR.
Anything elselogged to self.unsupportedInvestigate before next walk.

v2 known unsupported types (logged but skipped)

v3 โ€” what it does

Source: /opt/nexus/nexus/scripts/figma_to_bricks_walker_v3.py

Module docstring (verbatim):

"""figma_to_bricks_walker_v3.py

Builds Bricks HEADER + FOOTER templates from Figma (Audrey) + reuses Walker v2
body for page content. Schema-locked per /opt/nexus/nexus/scripts/walker_v3_spec.md.

Key deltas vs v2:
  - Emits bare-string widths/paddings ("220", "16") instead of {unit,value} dicts
    (matches real Bricks exports on disk: form_pwlost.json)
  - Image elements carry full 5-key shape (id, filename, size, full, url)
  - Header uses flex row + logo/nav/cta horizontal layout (NOT stacked)
  - Nav uses text-basic links in a flex container (avoids WP nav menu term dep)
  - Footer uses 3-column nav row + BBB row + copyright row
  - Both templates emit tag=header / tag=footer on root section
  - No sticky / no shape divider (flagged in spec as known gaps)

Outputs JSON payload ready to POST to /wp-json/bsp/v1/bricks/template-create.
"""

v3 schema deltas (canonical, from walker_v3_spec.md)

Fieldv2 shapev3 shapeSource-of-truth
Width / padding values{"unit":"px","value":16}"16" (bare string)form_pwlost.json real Bricks export
Image element{id, url, size} (3 keys){id, filename, size, full, url} (5 keys)header_wplit.json real Bricks export
Color{hex}{hex} minimum, optional {rgb,hsl} richerBricks accepts both. v3 emits {hex}.
Element ID type6-char hex6-char hexVerified via diag probe on page 8 (122 elements rendering).
Parent ID"0" string OR parent's id stringinteger 0 for root, string for nestedv3 followed plugin source. v2 used string "0" universally.

v3 brand constants (hardcoded in source)

BRAND_NAVY   = "#1D1760"
BRAND_BLUE   = "#30C5FF"
BRAND_YELLOW = "#FFEA00"
PHONE_DISPLAY = "(913) 963-1029"
PHONE_TEL     = "tel:+19139631029"

NAV_LABELS = ["Services", "Service Areas", "Blog", "About Us"]
NAV_URLS   = ["/services/", "/service-areas/", "/blog/", "/about/"]   # placeholders

FOOTER_SERVICES = ["Sewer Camera Inspection","Drain Cleaning","Water Heaters",
                   "Sump Pumps","Leak Detection"]
FOOTER_AREAS    = ["Overland Park","Leawood","Olathe","Lenexa","Kansas City"]
FOOTER_CONTACT_LINES = ["Bright Side Plumbing",
                        "12022 Blue Valley Pkwy",
                        "Overland Park, KS 66213",
                        PHONE_DISPLAY]
v3 known gaps (flagged in walker_v3_spec.md ยง1.2):
  • No _bricks_template_settings meta โ€” sticky / position / max-width set on root section element settings instead.
  • NAV_URLS are placeholder slugs; real internal links wired in a follow-up.
  • _bricks_template_conditions shape [{"main":"0"}] (= entire website) is INFERRED, validated post-deploy by reading from DB.
  • Apr 14 deployment: header/footer rows stored in DB (bricks_template posts 105/106) but did NOT inject into homepage <header>/<footer> tags. Render hook gap, fixed Apr 15-17 via functions.php force-render hooks (see Bricks doc ยง4).

How to invoke (CLI)

# Walker v2 โ€” body content
python3 /opt/nexus/nexus/scripts/figma_to_bricks_walker_v2.py \
  /tmp/page8_elements.json \
  01_Hero 02_CTA_TrustBar 03_Reveals 04_Process_Steps \
  05_Services 06_kc_homeowners_say 07_Commercial 08_FAQs 09_Final_CTA

# Walker v3 โ€” header + footer templates
python3 /opt/nexus/nexus/scripts/figma_to_bricks_walker_v3.py \
  --emit both
# emits /tmp/header_v3_payload.json + /tmp/footer_v3_payload.json
# POST each to /wp-json/bsp/v1/bricks/template-create

๐Ÿ“7. BSP Figma file inventory

File keyTitleUsed forKey node IDs
GViYd2jKWUEpLbz1lWghby Sewer-Camera-Inspection-Landing-Page Page 8 (sewer-camera-inspection) โ€” primary BSP service landing. Audrey's working file. 602:9 Desktop-1 (original, Apr 14 export)
708:216 Desktop-1 (autolayout reframe, Apr 15-16)
606:9 iPhone / mobile (Apr 14)
722:55 mobile inner frame (Apr 17 backfill rule)
707:14 desktop inner frame (Apr 17 backfill rule)
612:15 wave_background_elements (4 SVG nodes; verified Apr 15 = 90% rectangle, curve only at top/bottom edges)
734:30 02_TrustBar frame; child icons 734:26 star, 734:29 5-gen wrench, 734:19 licensed check, 734:8 stopwatch
652:247 10_Footer frame
6Hs3YviSaG5uCzc90XKU7Q Emergency Plumbing Landing Page Emergency landing page (10-13 frames per Apr 14 brief). unbuilt โ€” pending walker run # TODO research โ€” node IDs not yet enumerated in MH; pull index via /v1/files/{key}?depth=2 at next session.
Audrey's frame naming convention (canonical, verified Apr 14-17):
  • 00_Menu, 01_Hero, 02_CTA_TrustBar, 03_Reveals, 03b_Mid_Photo, 04_Process_Steps, 05_Services, 06_kc_homeowners_say (a.k.a. 06_kc_home_owners_grid), 07_Commercial, 08_FAQs, 09_Final_CTA, 10_Footer, 10_Footer_Services, 10_Footer_Pages
  • Components: review_card, reviewer_name, review_text, service_card, FAQ N Question, FAQ N Answer, Trust Bar Text
  • Trigger phrases that mean "pull from Figma first": "Audrey designed/updated/wants/has anything", "dial in / fix / redesign section on sewer-camera page".
Source: memory/feedback_figma_api_first.md, MH bsp-apr20-memory-backfill-figma-api-first.

๐Ÿ–ผ๏ธ8. Asset pipeline โ€” Figma export โ†’ WP media โ†’ Bricks image src

[1] Figma node with fill IMAGE
       โ”‚
       โ”‚  imageRef = "abc123def4567890..."     (full hash)
       โ”‚
       โ–ผ
[2] /v1/images/{key}?ids=NODE&format=png&scale=2
       โ”‚
       โ”‚  โ†’ signed S3 URL (expires ~30 min)
       โ”‚
       โ–ผ
[3] curl -o /tmp/audrey_assets/{slug}.png <signed URL>     # download immediately
       โ”‚
       โ–ผ
[4] POST /wp-json/wp/v2/media     (auth: claude-api app password GaW1...LHBP)
       โ”‚
       โ”‚  โ†’ returns {id, source_url, slug, ...}
       โ”‚
       โ–ผ
[5] Append to /tmp/bricks_uploaded_assets.json:
       โ”‚
       โ”‚  { "fill_abc123def456": { "id": 153,
       โ”‚                           "url": "https://bricks.callbrightside.com/wp-content/uploads/...",
       โ”‚                           "slug": "bsp-trust-licensed",
       โ”‚                           "hash12": "abc123def456" } }
       โ”‚
       โ–ผ
[6] Walker v2 reads assets, keys hash_map by hash12 โ†’ resolves to {id, url, size}
       โ”‚
       โ–ผ
[7] Bricks image element renders <img src="..."> on live page
       โ”‚
       โ–ผ
[8] Visual verify: Playwright screenshot ยท pixel-sample @ image bounding box

Auth for WP media upload

USER:  claude-api          (NOT morpheus โ€” that one is DAOT...)
PASS:  GaW1 p28e 2JLq xrwv yIf0 LHBP   (Apr 16 verified working)
ENV:   /opt/nexus/nexus/config/.env  โ†’  WP_APP_PASSWORD_CLAUDE=GaW1...LHBP

curl -u 'claude-api:GaW1 p28e 2JLq xrwv yIf0 LHBP' \
  -F 'file=@/tmp/audrey_assets/bsp-trust-licensed.png' \
  -F 'title=BSP Trust Licensed' \
  -F 'slug=bsp-trust-licensed' \
  https://bricks.callbrightside.com/wp-json/wp/v2/media

Inventory: assets shipped via this pipeline (Apr 14-17)

Asset IDSlugSourceUse
149bright-side-plumbing-horizontal-rgbAudrey delivery (Slack)Header logo (replaced Apr 17 by 150 in footer, 149 stays in header)
150bright-side-plumbing-vertical-logo-footerAudrey deliveryFooter brand logo
151bsp-trust-starFigma node 734:26 PNG @3xTrust bar row 1 (4.9 stars / 394+ reviews)
152bsp-trust-5genFigma node 734:29 PNG @3xTrust bar row 2 (5-generation plumbing family)
153bsp-trust-licensedFigma node 734:19 PNG @3xTrust bar row 3 (Licensed & Insured)
154bsp-trust-ontimeFigma node 734:8 PNG @3xTrust bar row 4 (Same-day service available)
โ€”wave-bg-mid-light-scaledFigma node 612:15 PNG (2560ร—2196)Mid-page wave decoration. Apr 15 verified ~90% solid rectangle, curve at top/bottom edges (this is Audrey's intent).
Failure modes seen:
  • Signed URL expired before download โ€” pipeline must download immediately, not store the Figma signed URL.
  • WP media uploaded but not in /tmp/bricks_uploaded_assets.json โ†’ walker logs MISSING_IMG in self.unsupported; image element silently dropped. Audit self.unsupported stdout after every walk.
  • Image hash collision unlikely but unhandled โ€” walker keys on first 12 hex chars; if two Figma images have colliding hash12, only the first wins. Bump to 16 chars if observed.

๐ŸŽจ9. Brand tokens โ€” Audrey's design system

Colors (verified from Figma file GViYd2jKWUEpLbz1lWghby, Apr 14 spec extract)

TokenHexUse
BSP Navy#1D1760Primary text, brand wordmark, button text on yellow CTA
BSP Blue#30C5FFAccent, link color, secondary CTA
BSP Yellow#FFEA00Primary CTA fill (Call Now button)
BSP Light Blue#BEE6F5Wave background, process-section fill
Off-white card#F8FAFCService card / reveal card background (verified Apr 15)
Footer light#D5EAFFFooter background (Apr 14 burn: invented #D4E9FF โ€” wrong)
White#FFFFFFHeader background, body negative space

Typography

PropertyValue
Font familyInter (100% โ€” verified via Figma styles pull, 19 type specs all Inter)
Weights used400 (regular), 600 (semibold for nav/labels), 700 (bold for headings/CTA)
Sizes13px (small caption) โ†’ 16px (body) โ†’ 18px (lead) โ†’ 20px (reviewer name) โ†’ 24-30px (h3) โ†’ 36px (h2) โ†’ 44-48px (h1)
Line height1.4 (nav/CTA) โ€” 1.65 (body) โ€” extracted from Figma lineHeightPx via walker

Spacing (autolayout itemSpacing โ€” Audrey reframe verified Apr 16)

SectionTop paddingBottom paddingitemSpacing (gap)
Parent Desktop-1โ€”โ€”56 (between sections)
01_Hero0032
02_CTA_TrustBar0020
03_Reveals404023
04_Process_Steps806024 (wave overlap zone)
05_Services404023
06_Reviews404024
07_Commercial404024
08_FAQs404011 (tight FAQ items)
09_Final_CTA0024

Decorative assets (NOT walker-emitted โ€” handled via CSS / pseudo-elements)

Brand drift cross-reference: see Bricks Codebase Doc ยง4 (child theme CSS) and Sacred v2 brand row. Any hex changes here must be propagated to all three docs in the same PR.

๐Ÿ’€10. Failure modes encountered + workarounds

#DateFailureRoot causeWorkaround / fixMH section
1Apr 14 Footer rebuilt v1-v5 from approximation (5 hours) Built from memory while FIGMA_TOKEN was already in .env. Invented tagline, wrong color (#D4E9FF vs #D5EAFF), wrong phone format, wrong layout, wrong ยฉ symbol. Run extract_footer_spec.py against /v1/files. Memory rule: Figma API first. bsp-apr14-footer-figma-api-violation-rebuild
2Apr 14 Walker v3 stored templates in DB but homepage rendered no <header>/<footer> Bricks Database saw templates active; render hook didn't inject. _bricks_template_settings not written. functions.php force-render hooks: bsp_render_bricks_template() via wp_body_open (priority 1) + wp_footer (priority 999). See Bricks doc ยง4. bsp-apr14-walker-v3-deployed
3Apr 15 Painted #BEE6F5 rectangle on top of Figma's wave PNG Didn't grep bsp-page-waves-8 style block. Real wave-bg-mid-light-scaled.png was already attached to body::before. Solid rectangle hid it. background: transparent on #brxe-a9bd17; z-index raised; pixel-sample at y=2479 confirmed wave visible. bsp-apr15-real-figma-wave-png-live
4Apr 15 Robert challenged "wave is a flat blue box" โ€” agent re-verified from Figma API Audrey's wave PNG IS ~90% rectangular. Wave shape only at top/bottom edges. Source-of-truth check via fresh /v1/images export. Inline SVG wave divider added on top of section for stronger curve effect (M0,80 C240,20 ... L1440,80 Z). Section bg #BEE6F5 (intentional, matches Figma). bsp-apr15-figma-source-verified-wave-is-rectangle
5Apr 14-16 Walker JSON POSTed to /bsp/v1/bricks/apply-v2 returns 200 with verify_count=187, but immediate read returns 0 elements Bricks sanitizer chain rejects unknown schema silently (this is the same drift class that triggered the Apr 27 dispatcher_safety smoke gate). Use Bricks editor paste-import (clipboard or 3-dot menu โ†’ Import elements). When pasted via UI, sanitizer runs in editor context and accepts. Long-term: match schema byte-for-byte (Walker v3 + walker_v3_spec.md is that path). bsp-apr15-figma-v3-paste-import-ready-auth-fixed
6Apr 15 4 hours fighting margin-doubling Figma 602:9 export used absolute X/Y per section โ†’ Bricks margin-top: Npx per section. Adding child-theme margin-bottom: N doubled gaps. Audrey reframed to autolayout (Shift+A): parent VERTICAL itemSpacing 56, per-section autolayout with itemSpacing. New nodes 708:216 / 606:9. Single source of truth per gap. bsp-apr15-audrey-reframe-PATH-FORWARD ยท bsp-apr15-audrey-autolayout-reframe-VERIFIED-match-spec
7Apr 16 Multiple CSS deploys claimed working but nothing changed visually (a) LiteSpeed cache not purged (only Cloudflare was), (b) no Playwright visual verify, (c) re-used absolute-positioning anti-pattern after telling Audrey not to. RULE: every deploy = LiteSpeed admin purge + Cloudflare API purge. Never claim done without Playwright screenshot delta. Memory rule: feedback_always_purge_cache.md. bsp-apr16-bricks-fuckup-nothing-changed-visually
8Apr 16 23/23 grep checks passed, page rendered blank Builder generated NEW UUID on every run; functions.php CSS hardcoded to OLD IDs. Selectors targeted nonexistent elements. Re-extract live ID map after every rebuild โ†’ /opt/nexus/nexus/scripts/output/bricks_page8_id_map.json โ†’ sed-replace IDs in functions.php. Long-term: deterministic IDs (hash of section name). bsp-apr16-bricks-23-of-23-checks-pass-wave-faq-spam-fixed
9Apr 17 Trust bar shipped as emoji-prefix text-basic, not Audrey's custom icons Robert pushed back twice in same session: expected Figma API pull. I was applying transcript directives from guesses. Pulled icons from 734:30 children (star/wrench/check/stopwatch) at scale=3, uploaded as 151-154, rebuilt 4 row blocks. CSS: flex-direction row, flex-wrap nowrap, gap 12, image 24x24 fixed. bsp-apr17-trust-bar-figma-icons
10Apr 14 Dismissed Audrey's Figma URL as a tutorial file based on slug URL had /Figma-basics in path. File was started from the tutorial template, then renamed to "Sewer-Camera-Inspection-Landing-Page". Always probe via /v1/files even if slug looks irrelevant. Slug is duplicate of original template name, NOT current file name. bsp-apr14-figma-audrey-design-pulled (correction note)

๐Ÿ“‹11. Canonical Build SOP โ€” Figma-driven page work

Mirrors ยง13 of the Bricks doc. Follow this order for any page or section that originates from a Figma file. Pre-flight (Rule 0 web check) is REQUIRED before step 1.

Pre-flight (BSP CLAUDE.md Rule 0 โ€” 4 checks)

# 1. Intent prepare
curl 'http://localhost:5544/api/context/prepare?intent=figma_page_build'

# 2. Zeus RAG search prior context
curl 'http://localhost:5544/api/zeus/search?q=figma+walker+page8'

# 3. MH grep โ€” has this been done before?
ssh dovew@34.55.179.122 \
  "grep -i 'figma.*<page-slug>' /opt/nexus/nexus/scripts/output/playbooks/BSP_Master_Session_History.html | tail -5"

# 4. Financial validator (only if any $ involved โ€” usually N/A for Figma work)
python3 /opt/nexus/nexus/scripts/financial_validator.py

Build steps

  1. Confirm autolayout โ€” pull /v1/files/{key}/nodes?ids=<target>&depth=2; check root has layoutMode: VERTICAL and per-section autolayout. If absent โ†’ Slack Audrey using drafts/slack_audrey_autolayout_request_apr15.txt template. Stop here until reframed.
  2. Pull full file tree โ€” /v1/files/{key}?depth=10 โ†’ /tmp/figma_audrey_full.json. Verify size > 100 KB (Apr 14 baseline 627 KB for sewer-camera).
  3. Render reference PNG โ€” /v1/images/{key}?ids=<desktop-node>&format=png&scale=2 โ†’ upload to morpheus.callbrightside.com/documents/figma_<page>_desktop.png for visual diff later.
  4. Export image fills โ€” for every RECTANGLE with fills[].type=IMAGE, hit /v1/images, download, upload to WP via POST /wp/v2/media, append to /tmp/bricks_uploaded_assets.json.
  5. Walk โ€” python3 figma_to_bricks_walker_v2.py /tmp/elements.json <frame_names>. Inspect stdout: element count, asset count, self.unsupported list. If unsupported > 5, stop and triage.
  6. Apply โ€” paste-import via Bricks editor (preferred) OR POST /bsp/v2/bricks/native-save. Read-after-write: GET /bsp/v2/db/meta-full?post_id=N, count elements. If sanitizer wiped, fall back to paste-import.
  7. Patch ID-dependent CSS โ€” re-extract live ID map (bricks_page8_id_map.json), sed-replace old IDs in functions.php if rebuilding. Deploy via POST /bsp/v2/theme/install-child.
  8. Purge โ€” LiteSpeed admin + Cloudflare API. Wait 5 sec.
  9. Verify visually โ€” Playwright screenshot at 1440 + 768 + 390 viewports. Side-by-side vs the Figma reference PNG from step 3. Pixel-sample at decoration boundaries (waves, doodles, image overlaps).
  10. Log to MH โ€” nexus_html_logger.py with section ID bsp-aprNN-<page-slug>-figma-shipped, severity success, content includes element count, asset IDs, sha + bytes of functions.php, MH cross-links.
Stop conditions (per BSP CLAUDE.md Rule 6 โ€” two-failure stop):
  • Sanitizer wipes element JSON twice โ†’ STOP and post: walker stdout, REST verify_count, post-write read count, what schema was attempted. Do not scatter alternatives.
  • Visual diff still off after 2 deploy iterations โ†’ STOP and ask Audrey to either re-export the section or confirm the live state matches her intent.

๐Ÿงต12. Cross-cutting threads

Walker evolution

v1 (Apr 14 14:51, 400 LOC)
  โ”œโ”€โ”€ indexed BOTH iPhone and Desktop frames โ†’ confused output
  โ”œโ”€โ”€ multi-line TEXT in nav columns shipped as one element
  โ”œโ”€โ”€ no nav-column expansion
  โ””โ”€โ”€ superseded 48 min later by v2

v2 (Apr 14 15:39, 366 LOC)  โ˜… active body walker
  โ”œโ”€โ”€ Desktop-1 only
  โ”œโ”€โ”€ nav column expansion (Services/Pages headers + per-line text-basic)
  โ”œโ”€โ”€ tagName: header / nav / footer
  โ”œโ”€โ”€ width propagation via percent (child / parent)
  โ”œโ”€โ”€ VECTOR wide+thin โ†’ divider element
  โ”œโ”€โ”€ SKIP_DESKTOP_ROOT_NAMES = {"wave element 2", "Footer", "wave_background_elements"}
  โ”œโ”€โ”€ button frames โ†’ tel:+19139631029 button element
  โ””โ”€โ”€ used in Apr 14 paste-import that shipped 122 elements rendering

v3 (Apr 14 16:05, 502 LOC)  โ˜… active header/footer template builder
  โ”œโ”€โ”€ schema-locked per walker_v3_spec.md (248 lines, evidence-classed)
  โ”œโ”€โ”€ bare-string widths/paddings ("16" not {unit,value})
  โ”œโ”€โ”€ 5-key image shape (id, filename, size, full, url)
  โ”œโ”€โ”€ horizontal header layout (logo / nav row / cta) NOT stacked
  โ”œโ”€โ”€ 4-col footer (logo+social / Services / Pages / Contact+Hours+BBB)
  โ”œโ”€โ”€ tag=header / tag=footer on root section
  โ”œโ”€โ”€ BRAND_NAVY/BLUE/YELLOW + PHONE_TEL hardcoded
  โ”œโ”€โ”€ outputs to /tmp/header_v3_payload.json + /tmp/footer_v3_payload.json
  โ””โ”€โ”€ POST to /wp-json/bsp/v1/bricks/template-create

Asset upload events (Apr 14-17)

Brand drift events (color/spec corrections)

๐Ÿงน13. Cleanup candidates

ItemStatusAction
figma_to_bricks_walker.py (v1, 400 LOC)supersededMove to scripts/archive/ after May 1 cutover. v2 is the active body walker.
/tmp/figma_audrey_v2.json + /tmp/figma_audrey_full.jsonstaleApr 14-17 vintage. Re-pull at session start; never trust cached JSON.
NAV_URLS placeholders in v3 (/services/, /blog/, etc.)placeholderWire real internal links once page slugs lock in (post-cutover).
_bricks_template_conditions: [{"main":"0"}] in v3inferredVerify against deployed template-create response in DB; document canonical conditions shape.
v2 hardcoded SKIP_DESKTOP_ROOT_NAMES setload-bearingKeep โ€” without it, Desktop root VECTOR shapes leak into flow.
Plugin URL 991866272578143756 not yet identifiedpendingConfirm with Robert next session; populate ยง5.
walker_v3_spec.md ยง1.2 sticky/position gapknown gapDecide: encode sticky in element settings (current) vs write _bricks_template_settings. Defer post-cutover.

๐Ÿ”—14. References

Public Figma docs

BSP scripts (cite verbatim before reinventing)

MH sections (canonical Figma history)

Companion playbooks