diff --git a/README.md b/README.md index bfa9947c..27187a67 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,7 @@ Design-phase skills that run *before* implementation. Produces static HTML and J | `generate-import-html` | Generate structured HTML | | `preview-import` | Preview imported content | | `snowflake` | Static-to-EDS overlay conversion — preserves original DOM byte-for-byte (alternative path to `page-import` for AI-generated/static pages) | +| `figma-to-content` | Turn a Figma design into an EDS content page in DA — resolves each section to an existing block, a new isolated block, or default content (annotation-first, else inferred), then deploys via the DA Source API | ##### Content & Platform Reference diff --git a/plugins/aem/edge-delivery-services/.tessl-plugin/plugin.json b/plugins/aem/edge-delivery-services/.tessl-plugin/plugin.json index 08912494..b14c250e 100644 --- a/plugins/aem/edge-delivery-services/.tessl-plugin/plugin.json +++ b/plugins/aem/edge-delivery-services/.tessl-plugin/plugin.json @@ -25,6 +25,7 @@ "skills/scrape-webpage", "skills/slicc-handoff", "skills/snowflake", + "skills/figma-to-content", "skills/testing-blocks", "skills/ue-component-model", "skills/aem-cli" diff --git a/plugins/aem/edge-delivery-services/evals/figma-to-content-orchestration-and-verification/criteria.json b/plugins/aem/edge-delivery-services/evals/figma-to-content-orchestration-and-verification/criteria.json new file mode 100644 index 00000000..dc6fbb91 --- /dev/null +++ b/plugins/aem/edge-delivery-services/evals/figma-to-content-orchestration-and-verification/criteria.json @@ -0,0 +1,46 @@ +{ + "context": "Tests the skill's defining behavior as an orchestrator: given a confirmed mixed plan, does the agent commit to INVOKING the referenced sub-skills (rather than inlining them from memory) and map each to the right section, and does it split pre-publish verification into a server-side curl stage and a mandatory browser stage — correctly identifying block decoration and visual fidelity as things a curl of the block's CSS/JS can never establish. Directly guards against the two most damaging failure modes observed in practice: hand-writing blocks instead of using the block skills, and skipping browser/visual verification while still declaring the page done.", + "type": "weighted_checklist", + "checklist": [ + { + "name": "da-auth + da-content always required", + "max_score": 10, + "description": "Names da-auth (token) and da-content (DA HTML rules / Source API / preview) as required for the whole plan, and treats da-content's reference docs as the source of truth to load — not this skill's condensed summary." + }, + { + "name": "Reuse sections map to block-collection-and-party", + "max_score": 15, + "description": "Maps the hero and cards reuse (Sections 1 and 2) to block-collection-and-party to learn each block's authoring model, rather than guessing the model from CSS." + }, + { + "name": "New block maps to content-modeling + content-driven-development", + "max_score": 15, + "description": "Maps the new comparison-table block (Section 3) to content-modeling then content-driven-development, and notes that content-driven-development runs building-blocks and testing-blocks." + }, + { + "name": "Default content needs no block skills", + "max_score": 10, + "description": "States that the intro copy (Section 4) is authored as default content via da-content, invoking no block-building skills." + }, + { + "name": "Commits to invoking, not inlining", + "max_score": 10, + "description": "Explicitly commits to invoking the sub-skills (e.g. builds the new block through content-driven-development) rather than hand-writing block JS/CSS or authoring DA HTML from general knowledge." + }, + { + "name": "Splits verification into server-side and browser stages", + "max_score": 15, + "description": "Separates pre-publish checks into a server-side stage (curl the fragment + referenced assets: placeholder-free content, icons resolve, image/section counts, metadata block, block code returns 200) and a browser stage." + }, + { + "name": "Identifies browser-only checks curl cannot establish", + "max_score": 15, + "description": "Identifies that whether the new block actually DECORATED (data-block-status=loaded, transformed DOM, applied CSS) and whether the rendered result MATCHES the design require a real browser (testing-blocks); a 200 on the block's CSS/JS only proves the file exists, not decoration or visual fit." + }, + { + "name": "No-browser reporting rule", + "max_score": 10, + "description": "States that if no browser is available the browser-stage checks are UNVERIFIED, so the page is reported preview-only and never called 'done.'" + } + ] +} diff --git a/plugins/aem/edge-delivery-services/evals/figma-to-content-orchestration-and-verification/task.md b/plugins/aem/edge-delivery-services/evals/figma-to-content-orchestration-and-verification/task.md new file mode 100644 index 00000000..a5cd5ae1 --- /dev/null +++ b/plugins/aem/edge-delivery-services/evals/figma-to-content-orchestration-and-verification/task.md @@ -0,0 +1,23 @@ +# Planning a mixed Figma page migration: orchestration and verification + +## Context + +A user has already confirmed a section-resolution plan for a Figma frame with `figma-to-content`. The plan resolves as: + +- **Section 1 (hero)** → reuse the existing `hero` block (content only) +- **Section 2 (three feature cards)** → reuse the existing `cards` block (content only) +- **Section 3 (an interactive comparison table)** → build a **new** isolated block +- **Section 4 (intro copy)** → default content (no block) + +A Figma MCP is connected, a DA token is available, and a browser (`aem up` locally or a real browser) is available. + +## Output Specification + +Respond as the agent would when turning the confirmed plan into an execution + verification plan. Your response must: + +1. List every sub-skill this plan requires the agent to **invoke** (not merely summarize from memory), and map each to the section(s) that require it. +2. State explicitly how the new comparison-table block will be **built** and how it will be **verified**. +3. Describe the pre-publish verification: what can be checked **server-side** (curl of the fragment + assets) vs. what requires a **browser**, and name the specific checks a curl can NOT establish. +4. State the rule for reporting the page when the browser stage cannot run. + +Do NOT execute anything. Do NOT write code or HTML. Do NOT make tool calls beyond planning. Produce the execution/verification plan only. diff --git a/plugins/aem/edge-delivery-services/evals/figma-to-content-reuse-gate-and-legibility/criteria.json b/plugins/aem/edge-delivery-services/evals/figma-to-content-reuse-gate-and-legibility/criteria.json new file mode 100644 index 00000000..752515c2 --- /dev/null +++ b/plugins/aem/edge-delivery-services/evals/figma-to-content-reuse-gate-and-legibility/criteria.json @@ -0,0 +1,41 @@ +{ + "context": "Tests the reuse gate and the no-skinning guardrail: a section whose authoring model matches an existing block but whose visual styling diverges (dark background, light text, no matching variant) must NOT reuse the block as-is and must NOT be made to fit by editing the shared block or global CSS. Also tests that the legibility check covers every text element over the media/fill (not just the heading) and that flagging the problem in the plan is not treated as satisfying the check. Guards against the single most common autonomous-run failure: skinning a shared block, or logging an illegibility caveat and shipping anyway.", + "type": "weighted_checklist", + "checklist": [ + { + "name": "Rejects reuse-as-is", + "max_score": 20, + "description": "Concludes the section cannot reuse the existing cards block as-is, because a matching authoring model is not sufficient when the rendered look diverges from the design." + }, + { + "name": "Cites structural AND visual fit requirement", + "max_score": 10, + "description": "States the reuse rule explicitly: reuse requires both structural fit and visual fit (using only the block's defined variants), including how it treats secondary text and CTAs over the background." + }, + { + "name": "Forbids skinning shared code", + "max_score": 20, + "description": "States the agent must NOT modify the shared cards block implementation, scripts.js, or head.html, and must NOT add section- or block-specific rules to global CSS, to force the look." + }, + { + "name": "Correct resolution: new isolated block or variant", + "max_score": 15, + "description": "Gives the correct resolution: build a new isolated block (or a new dark variant as its own block/variant) via the content+code path (Phase 3B), and author content into it." + }, + { + "name": "Distinguishes allowed token retheme from disallowed restyle", + "max_score": 10, + "description": "Notes that retargeting the global design tokens (the one allowed global change) is different from restyling this specific block, which is not allowed and points to a new block." + }, + { + "name": "Legibility check covers all text over media", + "max_score": 15, + "description": "Describes a legibility check that covers every text element rendered over the dark background — headings AND secondary/body text, links/CTAs, and list items — not just the heading." + }, + { + "name": "A plan note is not a check", + "max_score": 10, + "description": "States that flagging the divergence/illegibility in the plan does not satisfy the gate — it must be resolved (new block/variant), and an unresolved item blocks publish (preview-only), never ships." + } + ] +} diff --git a/plugins/aem/edge-delivery-services/evals/figma-to-content-reuse-gate-and-legibility/task.md b/plugins/aem/edge-delivery-services/evals/figma-to-content-reuse-gate-and-legibility/task.md new file mode 100644 index 00000000..fc3fe92f --- /dev/null +++ b/plugins/aem/edge-delivery-services/evals/figma-to-content-reuse-gate-and-legibility/task.md @@ -0,0 +1,18 @@ +# Reuse gate: a block that fits structurally but not visually + +## Context + +While resolving a Figma frame with `figma-to-content`, the agent reaches a section that is clearly a "cards" layout: three cards, each with an image, a title, body copy, and a link. The project already has a `cards` block whose authoring model (rows of image + text) matches this structure **exactly**. + +But in the design, this section sits on a **dark full-bleed background**, and the card titles, body copy, and links are all **light-colored** for contrast. The project's existing `cards` block renders dark text on a light card and defines **no** dark variant. The project's global design tokens have already been retargeted to the design system's palette. + +## Output Specification + +Respond as the agent would when deciding how to handle this section. Your response must: + +1. State whether this section can reuse the existing `cards` block **as-is**, and why or why not. +2. State what the agent must **NOT** do to make `cards` match the design. +3. State the correct resolution. +4. Describe the **legibility** check that must pass before this section's page can be published — specifically which text elements it must cover. + +Do NOT execute anything. Do NOT write code or HTML. Do NOT modify files. Produce the decision and its rationale only. diff --git a/plugins/aem/edge-delivery-services/evals/figma-to-content-skill-selection/criteria.json b/plugins/aem/edge-delivery-services/evals/figma-to-content-skill-selection/criteria.json new file mode 100644 index 00000000..d7bfab5d --- /dev/null +++ b/plugins/aem/edge-delivery-services/evals/figma-to-content-skill-selection/criteria.json @@ -0,0 +1,46 @@ +{ + "context": "Tests whether the agent selects figma-to-content for a live-Figma-frame page migration and correctly rules out the adjacent migration skills (snowflake, page-import) whose input is an already-rendered page/DOM. Validates that a new-block section is routed to content-driven-development rather than hand-written or pushed through snowflake, and that the reuse-vs-build distinction is stated. Guards against the most likely misroute for this skill: treating a Figma design like exported static HTML.", + "type": "weighted_checklist", + "checklist": [ + { + "name": "Selects figma-to-content", + "max_score": 25, + "description": "Correctly identifies figma-to-content as the right skill for a Figma-frame-to-EDS-page migration, not snowflake or page-import." + }, + { + "name": "Rules out snowflake with correct rationale", + "max_score": 20, + "description": "Explains that snowflake consumes an already-rendered Source URL / DOM (to preserve it byte-for-byte) and installs an overlay substrate, which does not fit a live Figma frame as input — so snowflake is the wrong tool here." + }, + { + "name": "Rules out page-import", + "max_score": 10, + "description": "Acknowledges page-import as the other migration path and explains it is wrong here because its input is a live page/URL to scrape and rewrite, not a design frame." + }, + { + "name": "New block routed through content-driven-development", + "max_score": 15, + "description": "States that the custom module is built as a new isolated block via content-driven-development (not hand-written from scratch, not via snowflake), keeping it self-contained (no page chrome / global styles / source URL)." + }, + { + "name": "Names the figma-to-content phases", + "max_score": 10, + "description": "Names the phases: preflight, read the Figma design, resolve each section + confirm the plan, author into existing blocks / build new blocks / default content, deploy with the pre-publish gate, report (or a close paraphrase covering these)." + }, + { + "name": "States reuse vs. build distinction", + "max_score": 10, + "description": "Explains existing-block sections are content-only reuse (requiring structural AND visual fit), while a section that needs an absent block or whose look diverges becomes a new isolated block." + }, + { + "name": "Surfaces prerequisites", + "max_score": 5, + "description": "Lists what the user must provide: the Figma file + specific frame, a local project checkout, the DA org/repo + page path, and a DA token (or how to obtain one via da-auth)." + }, + { + "name": "No premature implementation", + "max_score": 5, + "description": "Does not execute any phase, write code/HTML, modify the repo, or make tool calls beyond planning." + } + ] +} diff --git a/plugins/aem/edge-delivery-services/evals/figma-to-content-skill-selection/task.md b/plugins/aem/edge-delivery-services/evals/figma-to-content-skill-selection/task.md new file mode 100644 index 00000000..29b6ebec --- /dev/null +++ b/plugins/aem/edge-delivery-services/evals/figma-to-content-skill-selection/task.md @@ -0,0 +1,21 @@ +# Figma frame migration: choosing the right skill + +## Context + +A user provides the following prompt to the agent: + +> I have a finished marketing landing page designed in **Figma** (I can share the file and the specific frame). I want to turn it into a page on our AEM Edge Delivery Services site and have it editable in DA. Some sections look like blocks we already have; at least one is a custom interactive module we don't have yet. How should we approach this? + +The repo is a standard `adobe/aem-boilerplate` clone with an existing `blocks/` palette. A Figma MCP is connected and a DA token is available. No prior conversion work exists. + +## Output Specification + +Respond as the agent would. Your response must: + +1. Identify which skill is the correct choice for this request. +2. Explain in one or two sentences *why* this is `figma-to-content` and not `snowflake` or `page-import` — grounded in the **input type** (a live Figma frame, not an already-rendered HTML page or a source URL). +3. Outline the high-level phases `figma-to-content` will execute, naming them. +4. Describe how the two kinds of sections are handled — those that map to **existing** blocks (content only) vs. the **custom module** that needs a new block — and name the sub-skill the new block will be built through. +5. State what the user must provide before work can start. + +Do NOT execute the migration. Do NOT write any code or HTML. Do NOT modify any files or make tool calls beyond what is needed to plan. Produce a planning response only. diff --git a/plugins/aem/edge-delivery-services/skills/figma-to-content/.releaserc.json b/plugins/aem/edge-delivery-services/skills/figma-to-content/.releaserc.json new file mode 100644 index 00000000..c4109d12 --- /dev/null +++ b/plugins/aem/edge-delivery-services/skills/figma-to-content/.releaserc.json @@ -0,0 +1 @@ +{"extends": "../../../../../release.config.cjs"} \ No newline at end of file diff --git a/plugins/aem/edge-delivery-services/skills/figma-to-content/CHANGELOG.md b/plugins/aem/edge-delivery-services/skills/figma-to-content/CHANGELOG.md new file mode 100644 index 00000000..95474452 --- /dev/null +++ b/plugins/aem/edge-delivery-services/skills/figma-to-content/CHANGELOG.md @@ -0,0 +1,7 @@ + + +# figma-to-content + +Draft (v0.1.0) — not yet released. The changelog will be generated on the +first semantic-release once the skill is merged into `adobe/skills`. diff --git a/plugins/aem/edge-delivery-services/skills/figma-to-content/README.md b/plugins/aem/edge-delivery-services/skills/figma-to-content/README.md new file mode 100644 index 00000000..f668d1a3 --- /dev/null +++ b/plugins/aem/edge-delivery-services/skills/figma-to-content/README.md @@ -0,0 +1,98 @@ +# figma-to-content + +Turn a **Figma design** into an Adobe Edge Delivery Services +(EDS / AEM / Franklin / Helix) **content page** and deploy it to Document +Authoring (`da.live`). Give it a design and the skill **infers** each section's +mapping against the project's existing blocks and **confirms the plan** with you +before building — no annotations needed ("just migrate this page"). If a design +*happens* to be annotated, those annotations are used as an optional +accelerator, but they are never required. + +Runs standalone in plain Claude / Coworker with just a **Figma MCP** (to read +the design) and a **DA IMS token** (to write content) — no proprietary web app +or migration tooling required. + +## Two paths + +The skill classifies each section of the design and follows the matching path: + +- **Content only** — every section maps to a block the project **already + has**. Author content into existing blocks and deploy. No code changes. +- **Content + code** — a section needs a block the project **does not have**, + or an existing block matches structurally but its **styling diverges**. The + block is created as a **new, isolated block** (via the public block-building + skills), its code is pushed, then content is authored and deployed. **Existing + blocks are never skinned** — the only global change is retargeting the + project's design tokens to the design system. + +A single design usually mixes both, plus default content. + +## How it works + +``` +preflight: Figma MCP reachable · file access · DA token · project checkout (fail fast) + ▼ +Figma frame (annotations optional) + │ Figma MCP (get_metadata, get_design_context, get_screenshot, get_variable_defs) + ▼ + section inventory ──► resolve each section (infer + confirm; annotations optional) + │ ├─ existing block (structure + visual fit) ─► author content + │ ├─ needs one / look diverges → new block ───► build isolated block + │ │ (content-driven-development), push code + │ └─ default content ──────────────────────────► author prose/media, no block + ▼ + resolution plan ──► confirm with user (ask on low-confidence; flag overwrite of an existing page) + ▼ + content/.html ──► PUT admin.da.live/source ──► POST admin.hlx.page/preview + (da-content contract) (+ Code Sync first, for new blocks) + ▼ + pre-publish gate: legibility · no placeholder · icons resolve · blocks decorate · metadata present + ▼ + da.live edit URL + aem.page preview URL +``` + +The flow is bracketed by two safety gates: a **preflight** (fail fast on a +missing Figma MCP, no file access, no DA token, or no project checkout — one +actionable message instead of a half-built page) and a **pre-publish gate** +(the page is not "done" until legibility, placeholder-free content, resolvable +icons, decorated new blocks, and a populated `metadata` block are all verified — +an un-run check counts as failed, never as a pass). + +It **orchestrates existing public skills** rather than reinventing them — the +net-new logic is reading the Figma design, resolving each section to a block +(inferred by structure + visual fit and confirmed with you, or from annotations +when the frame has them), and mapping content into it. DA rules, auth, and block-building all defer +to the skills below. + +## What's included + +``` +skills/figma-to-content/ +├── SKILL.md Entry point (agent reads first) +├── README.md This file +├── package.json +└── references/ + └── annotation-contract.md The annotation spec (optional path; proposed) +``` + +## Depends on (public skills) + +- [`da-auth`](../da-auth) — DA IMS token +- [`da-content`](../da-content) — DA HTML rules + Source API + preview/publish + media +- [`block-collection-and-party`](../block-collection-and-party) — whether a block exists + its authoring model +- [`block-inventory`](../block-inventory) — survey the available block palette +- [`content-modeling`](../content-modeling) — design a content model for a new block +- [`content-driven-development`](../content-driven-development) — full dev workflow for a reusable new block (invokes [`building-blocks`](../building-blocks), [`testing-blocks`](../testing-blocks)) +- [`testing-blocks`](../testing-blocks) — render a block + screenshot and compare to the design; reused directly for the **reuse gate's visual check** + +## Status + +**Draft (v0.1.0).** The infer-and-confirm path is the primary, fully working +flow. Before v1.0.0, close the open questions in `SKILL.md` (chiefly the default +image-hosting choice). The **annotation contract** +([`references/annotation-contract.md`](./references/annotation-contract.md)) is +an **optional enhancement** for teams that want to pre-declare section mappings +— nice to have, not required. + +Intended destination: +`adobe/skills → plugins/aem/edge-delivery-services/skills/figma-to-content`. diff --git a/plugins/aem/edge-delivery-services/skills/figma-to-content/SKILL.md b/plugins/aem/edge-delivery-services/skills/figma-to-content/SKILL.md new file mode 100644 index 00000000..c9379ac4 --- /dev/null +++ b/plugins/aem/edge-delivery-services/skills/figma-to-content/SKILL.md @@ -0,0 +1,967 @@ +--- +name: figma-to-content +description: "Use this to turn a Figma design into an AEM Edge Delivery Services (EDS / AEM / Franklin / Helix) content page in Document Authoring (DA, da.live). Triggers: \"build this Figma frame in EDS\", \"turn this Figma design into a DA page\", \"publish this design to da.live\", or providing a figma.com URL for a page. Reads the frame (and any annotations) via a Figma MCP, resolves each section to an existing block, a new isolated block, or default content (inferred against the project's existing blocks and confirmed with you, or read from annotations when the frame happens to have them), generates DA-compliant body-fragment HTML, and deploys via the DA Source API + preview." +license: Apache-2.0 +metadata: + version: "0.1.0" +--- + +# figma-to-content — Figma design → EDS content page in DA + +Read a Figma frame, assemble a page from EDS blocks and default content, and +publish it to Document Authoring. Runs with a **Figma MCP** (to read the +design) and a **DA IMS token** (to write content) — no proprietary tooling +required. + +This skill **orchestrates existing skills**; it does not reimplement DA rules, +block knowledge, or block-building. **Invoke those skills — do not inline them.** +The condensed rules quoted in this file are *pointers* to jog the right skill, +never a substitute for loading it: when a phase names a skill, invoke it and work +from its actual guidance. Running this file as a self-contained procedure — +hand-writing blocks, authoring DA HTML from memory, skipping the browser/visual +check — is the single most common way a run goes wrong. Phase 2.3 turns the +confirmed plan into an explicit manifest of the sub-skills you must invoke. + +## Two paths + +Classify each section of the design, then follow the matching path: + +- **Content only** — every section maps to a block that **already exists** in + the target project, or to **default content** (plain headings/paragraphs/ + images/buttons — no block). Author content and deploy. No code changes. +- **Content + code** — a section needs a block the project **does not have**, + or an existing block matches structurally but its **styling diverges** (the + look lives in block-specific CSS you'd have to edit). Create it as a **new, + isolated block** (via the block-building skills), push the code, then author + content and deploy. **Never skin an existing block or add per-section rules + to global CSS** — new, additive blocks only. (Retargeting the project's + global design tokens is a separate, allowed theming step; see Guardrails.) + +A single design usually mixes all three (known blocks + default content + one +or two new blocks). + +## When to use + +- The user has a Figma frame representing a page and wants it as an EDS page in DA. + The common case: a customer **already on EDS**, with their own blocks, gets a + new design for a new page — some sections reuse existing blocks, some need new + ones. +- **The default path is infer-and-confirm.** Usually the frame is **not** + annotated (e.g. the user just says "migrate this page"): the skill **infers** + each section's mapping against the project's existing block palette and + **confirms the plan** before building, asking whenever a section is ambiguous + (Phase 2). This path needs nothing but the design itself. +- **Annotations are an optional accelerator — never required.** If a frame + happens to declare each section's block / default content / new block (see + [references/annotation-contract.md](./references/annotation-contract.md)), + those declarations are taken as authoritative and skip the inference for that + section. Absent them, nothing is lost — the skill infers and confirms. + +### When NOT to use + +- **Redesign / restyle an existing EDS site**, or convert arbitrary generated + static HTML (Mobirise, Relume, v0, exported Figma HTML). Use **snowflake**. +- **Universal Editor or AEM Cloud Service (Java/OSGi/JCR).** Out of scope. + +## Related skills — orchestrated by this one + +| For | Use skill | +|---|---| +| DA IMS token (`DA_TOKEN`) | **da-auth** | +| DA body-fragment HTML rules, Source API, preview/publish, media | **da-content** | +| Whether a block exists + its authoring model & examples | **block-collection-and-party** | +| Surveying the whole available block palette | **block-inventory** | +| Designing a content model for a **new** block | **content-modeling** | +| Building a **new** block (full dev workflow) | **content-driven-development** (invokes **building-blocks**, **testing-blocks**) | +| Rendering a block + **visual comparison to the design** (the reuse gate) | **testing-blocks** (browser/Playwright screenshot + "compare implementation to design") | + +The DA-write contract in Phase 5 is the same one **da-content** documents +(see its `references/html-content.md` and `references/platform.md`). + +--- + +## Inputs (gather before Phase 1; ask if missing — never guess) + +- **Figma reference** — file key + node id of the page frame (from the + figma.com URL or the current Figma MCP selection). A file usually holds + **many frames** — desktop/mobile variants, A/B versions, work-in-progress + copies of the same page. Confirm **exactly which frame** to build; don't + assume the first or largest. Two frames that are variants of the *same* page + are one page, not two — ask which is canonical rather than deploying both. +- **Target project** — a local checkout of the EDS project repo (needed to see + existing blocks under `blocks/`, and required for the content+code path to + add block code). Its GitHub `{owner}`/`{repo}` and the deploy `{branch}`. +- **DA location** — `daOrg`, `daRepo` (the DA namespace), page `PATH` (no + extension, lowercase/dash only — see da-content platform rules). In the + standard EDS+DA setup `daOrg`/`daRepo` **equal** the GitHub `{owner}`/`{repo}`; + confirm, because Phase 5 writes to `daOrg`/`daRepo` but previews/renders on + the GitHub `{owner}`/`{repo}`/`{branch}`. +- **`DA_TOKEN`** — via **da-auth**, which exports `$DA_TOKEN` and caches it at + `~/.aem/da-token.json` (valid ~1h). Prefer the `$DA_TOKEN` da-auth already set + in this session; read the cache file only if that's unset. Two distinct + failures: a `401` with an empty body means the token **expired** → re-auth; a + cache file that **can't be read because the execution sandbox has no `$HOME` + access** means the token is *unreachable*, not expired (see Phase 0 step 3) — + don't conflate them. + +--- + +## Phase 0 — Preflight (fail fast, before any read or write) + +Verify the run can actually complete **before** reading the design or writing to +DA — a missing prerequisite caught here is one actionable message; caught mid-run +it is a confusing, half-built page. Run these checks in order and, on the first +that fails, **stop with the specific remediation below** — do not proceed on a +guess or a partial capability. + +1. **Figma MCP reachable.** Confirm a Figma MCP is connected and responds via a + cheap call (e.g. `whoami`, or listing its tools). If **no Figma MCP tool is + available at all**, stop: *"No Figma MCP is connected. Connect one (Claude + desktop Dev Mode, an IDE Figma integration, or a remote Figma MCP) and + re-run."* Record the authenticated identity (`whoami`) for the next check. +2. **Access to the specific file.** Make one lightweight call against the target + `fileKey` (e.g. `get_metadata` scoped to the frame, or `get_design_context` + on the node). A **permission / not-found** error (`403`/`404`/"no access") + means the file is not shared with the authenticated account → stop: *"Figma + reports no access to `` as ``. Share the file with that + account, switch accounts, or provide a file you can open."* **Distinguish this + from a transport cap** — a truncated, garbled, or JSON-parse-error response is + the size cap (see Phase 1), **not** an access failure: retry narrower, do not + report it as no access. +3. **DA write path available.** Confirm a `DA_TOKEN` is obtainable via **da-auth** + — prefer the `$DA_TOKEN` it exports into the session, else its cache at + `~/.aem/da-token.json`, else freshly minted. **If the cache exists but can't be + read because this execution sandbox has no `$HOME` access**, the token is not + missing — it is *unreachable*; do **not** loop re-minting. Stop with that + distinction spelled out: *"A DA token exists but this sandbox can't read + `~/.aem/da-token.json` — run where the cache is readable, or provide the token + as `$DA_TOKEN` (or a readable path)."* If no token can be obtained at all, + stop: *"Can't obtain a DA token (da-auth) — authenticate to DA and re-run."* + Either way, don't spend a full Figma read only to fail at the deploy step. +4. **Project checkout + orchestrated skills present.** The target repo is checked + out locally (needed to see `blocks/` and to add new-block code) and the skills + this one orchestrates (**da-auth**, **da-content**, the block skills) are + available. If the checkout path is unknown, ask for it. + +On all-pass, print a one-line preflight summary — Figma identity, the file/frame, +and the DA `org/repo` + `branch` you will write to — then proceed to Phase 1. + +--- + +## Phase 1 — Read the Figma design (Figma MCP) + +Use a Figma MCP (Claude desktop / IDE / external). **Introspect the actual tool +schemas** — signatures differ between MCP implementations (the local Dev Mode +server often works off the current selection and may not take a `fileKey`; the +remote/desktop server takes `fileKey` + optional `nodeId`). The tools you need, +by capability: + +- **Structure** (e.g. `get_metadata`) — the frame's section/layer tree; node + ids, names, positions, sizes. Derive the section list from the **content + groups** in visual order (sort by `y`) — **not** the raw child list: full- + bleed background rectangles, overlays, and decorative shapes are *part of* a + section (its background), not sections of their own, and a single visual + section is often split across sibling nodes (e.g. a background rect + a tab + strip + a text group). Ignore the decorative layers and group the rest into + sections by position. Usually `fileKey` required, `nodeId` optional. **Some + MCP servers cap response size — even a single frame's structure dump can + exceed it; scope the call to the frame or, if that still fails, one section + at a time. A truncated, garbled, or JSON parse-error response *is* the cap + being hit — retry narrower; do not read it as "no structure."** +- **Visual** (e.g. `get_screenshot`) — a per-section reference image to + sanity-check the block/content mapping. +- **Content & assets** (e.g. `get_design_context`) — text, links, and image + asset download URLs for a node. For the content+code path this also provides + the layout/structure a new block must reproduce. **Request the lean form** — + exclude the screenshot from the context call (fetch visuals separately with + the screenshot tool) and disable any Code Connect lookup (e.g. + `excludeScreenshot` / `disableCodeConnect`-style options) unless you are + mapping to a real component library; both add payload and round-trips and can + push a large response over the transport cap. Icons are usually **component + instances**, not raster fills — obtain their **SVG** (export/copy as SVG), + never a PNG, for the `/icons` or DA `/media` reference in Phase 4. +- **Design tokens** (e.g. `get_variable_defs`) — colors, spacing, type. Read + annotation values and, for new blocks, source token values. + +**Call budget & order — Figma MCP calls are rate-limited and payload-capped, so +spend them deliberately rather than re-fetching:** + +1. **`get_metadata` first** (scoped to the frame) — the structure/section tree. + The cheapest orienting call; every later call keys off the node ids it returns. +2. **`get_screenshot` of the whole frame early** — one full-frame reference image + up front is the anchor you reconcile the section count against (segmentation + heuristic) and, later, compare the rendered page to (Phase 5 Stage B). Take + per-section crops afterwards, only for the sections you actually build. +3. **`get_design_context` targeted and lean, per section** — request the lean + form (exclude the screenshot, disable Code Connect) and scope it to **one + section's node at a time**. A whole-frame context dump is the single call most + likely to blow the transport cap. +4. **Asset download last** (`download_assets` / export-as-SVG) — only for the + assets the **confirmed** plan references, after Phase 2. Don't pull binaries + for sections that end up reusing an existing block or being cut. + +A `429`/rate-limit or a truncated/garbled response is a **budget/cap signal, not +"no data"**: back off, narrow the scope (frame → section), and retry — never read +it as an empty design or as missing access (Phase 0 step 2 draws the same line). + +Produce an ordered **section inventory**: `{ sectionNodeId, annotation, +screenshot, content, background }` — capture each section's **background / +theme** (e.g. alternating light and dark sections), because the global token +retheme (Guardrails) recolors blocks but does **not** switch a section's +background: that carries via a `section-metadata` `Style` class or a block's +own defined dark/light variant (Phase 4). Read annotations per +[references/annotation-contract.md](./references/annotation-contract.md). + +> **Segmentation heuristic** — when the frame has no explicit grouping, derive +> the section list like this, not from the raw child order: +> 1. Sort the frame's direct children by `y` (top to bottom). +> 2. **Drop pure-decoration layers** from the section list — full-bleed +> background rectangles, gradients, blurs, absolutely-positioned shapes with +> no text or interactive child. Record each as the *background* of the +> content it sits behind (→ Phase 4 `section-metadata`); don't emit it as a +> section of its own. +> 3. **Merge siblings that form one visual band** — nodes whose vertical +> extents overlap or sit within ~one line-height of each other (a background +> rect + a heading group + a button row are *one* section, not three). +> 4. **Reconcile the count against the screenshot** before resolving: the eye +> sees the real sections; a mismatch means you over- or under-split — fix it +> before Phase 2. + +> **Placeholder content is common — don't ship it.** Designs routinely contain +> dummy copy (`Lorem ipsum`, a CTA literally labelled "Button" or "Lorem +> Ipsum", the same card title repeated across every card) and unfilled slots +> (empty or transparent image cells, blank stat boxes). Author from the **real +> text and media in the design context** — not from the placeholder and not from +> invented filler. Where it's clearly placeholder, **flag it in the plan and +> confirm the real copy/media with the user** rather than publishing "Lorem +> Ipsum" to a live page. Distinct items (cards, tabs, news entries) need +> **distinct** copy and images — repeated-identical content is itself a +> placeholder smell. If the design *itself* carries only placeholder, you cannot +> manufacture the real content: stop and get it from the user before publish. + +> Site chrome (nav bar, footer) is usually **not page body** — in EDS it is +> sourced from separate `/nav` and `/footer` documents via the header/footer +> blocks. Don't author it into the page unless the user asks. + +--- + +## Phase 2 — Resolve each section + +Every section resolves to exactly one of: **existing block** (→ 3A), +**default content** (→ 3C), or **new block** (→ 3B). How that decision is +reached depends on whether the section is annotated. + +### 2.0 — Know the project's block palette (always) + +Before resolving anything, enumerate what the project **already has**: +`ls -d blocks/*/` plus **block-inventory** / **block-collection-and-party** for +each block's **authoring model** (row/cell structure, variants) **and a +rendered example** — the block's `liveExampleUrl` when it comes from the Block +Collection, or the project's own block rendered at `localhost:3000`. That +rendered example is the "block side" of the 2.1 / Phase 3A visual check. This +is the reuse-candidate set — essential when the customer is already on EDS with +their own blocks. + +### 2.1 — Resolve each section (annotation-first, else infer) + +**If the section is annotated** (see +[references/annotation-contract.md](./references/annotation-contract.md)), the +annotation is **authoritative**: named block that exists → existing block (3A); +marked `new` (or absent-and-user-confirmed) → new block (3B); plain prose/media +→ default content (3C). + +**If it is not annotated** (e.g. "just migrate this page"), **infer** the +mapping — do not dump it as unresolved: + +1. Plain prose/media (headings, paragraphs, images, a standalone link) with no + repeating structure → **default content** (3C). +2. Otherwise match it against the 2.0 palette using the **reuse gate (structure + AND visual, Phase 3A)**: does its content model fit an existing block *and* + does that block's rendered example — under the project theme — look like the + section, allowing only token differences and variants the block defines? + - **Both fit → existing block** (3A). + - **Structure fits but the look diverges** (bespoke card/layout/decoration + the block's CSS can't produce without editing it), **or nothing fits → + new block** (3B). + - **A section carrying an interactive control** — tabs / segmented switch, + accordion, carousel or slider, toggle — is structural divergence no static + block reproduces: route it to a **new block** (3B), or, if the control is + non-essential chrome, **confirm with the user** whether to keep it or + flatten it to static content. Don't silently drop the interaction or fake + it with a look-alike static block. +3. Attach a **confidence** to every inference: `high` (clear reuse match, or + clearly novel) or `low` (structure fits but styling is borderline; two + blocks plausibly fit; new-variant-vs-new-block; content model ambiguous). + +### 2.2 — Confirm the plan before deploying (never deploy a guess) + +Present a **resolution plan** — one line per section: decision (reuse `X` / +default content / new block `Y`), confidence, a one-clause rationale, and a +**content flag** on any section whose copy or media is placeholder (Phase 1) +and needs real content before publish. + +- **High-confidence sections auto-proceed through building** (Phases 3–4) — + don't block on them. +- **Stop and ask before building** any `low`-confidence section or genuine + ambiguity, offering the concrete choice (reuse this block vs. new block; + which block; new variant vs. new block). Wait for the answer. +- **Pause once before deploying (Phase 5)** whenever the plan contains any + **inferred** (unannotated) mapping: show the final plan and get a single + confirmation before the da.live write/preview — deploy is outward-facing and + hard to reverse. Skip this pause only if the user pre-authorized an + unattended run. A **fully annotated** plan needs no pause — the annotations + are the authorization. +- **Flag an existing target page.** Before confirming, check whether the target + `content/.html` already exists in DA (a cheap Source-API `GET`, Phase 5); + if it does, deploying **overwrites** it — say so in the plan and get explicit + overwrite confirmation. Never silently clobber a page you didn't create, even + on an otherwise pre-authorized unattended run. **Record two facts per path** for + Phase 5 to enforce: `PLANNED_STATE` (`new` if the check returned 404, `exists` + if 200) and `OVERWRITE_OK` (`yes` only when the user confirmed overwriting an + existing page). Phase 5 re-checks existence right before writing and **refuses** + if the state changed since planning (a page appeared in the gap) or overwrite + was never confirmed — the plan-time check alone is not a license to clobber. +- The user can override any line. + +Never silently drop a section, and never deploy an **inferred** mapping the +user has not seen. + +**Worked example** — an unannotated 4-section frame; this is the plan you +present in 2.2 (one line per section): + +| # | Section | Decision | Conf. | Why | Content | +|---|---|---|---|---|---| +| 1 | Hero band — heading + 2 CTAs over a photo | reuse `hero` | high | model fits; heading **and** CTAs stay legible on the media under the theme | ok | +| 2 | 3 feature blurbs — icon + title + text | reuse `cards` | high | content model and rendered look both fit | ok | +| 3 | Metric strip — 3 big numbers + labels | **new block** `stat-cards` | high | bespoke panel look no existing block produces (3B) | ok | +| 4 | Newsletter row — heading + email field + button | **new block** / confirm | low | carries an interactive control (input) — ask keep vs. flatten (G5) | ⚠ placeholder copy | + +Then act on it: sections 1–2 build without blocking; #3 builds (high-confidence +new block); **#4 stops for a decision** (low-confidence + interactive control); +and because the plan contains inferred mappings, the whole thing gets **one +pre-deploy confirmation** before the da.live write. Section #4's ⚠ flag means +its real copy must be supplied before publish, not shipped as placeholder. + +### 2.3 — Lock the orchestration manifest (which sub-skills this plan requires) + +Turn the confirmed plan into an explicit **manifest** of the sub-skills it +requires and **invoke each one** — this is where the intro's *orchestrate, don't +inline* rule becomes a concrete, ticked list. This file's summaries never +substitute for loading the named skill. + +Derive the manifest from the plan: + +| The plan contains… | You MUST invoke | +|---|---| +| **Any** section (always) | **da-auth** (token) and **da-content** — load its real `references/html-content.md`, `platform.md`, and `media.md`, *not* the condensed rules in this file — before authoring (Phase 4) and deploying (Phase 5). | +| An **existing-block reuse** (3A) | **block-collection-and-party** (authoring model + a rendered example) **and testing-blocks** for the visual reuse gate (rendered block vs. the Figma section screenshot). | +| A **new block** (3B) | **content-modeling** (design the authoring model), then **content-driven-development** (which runs **building-blocks** and **testing-blocks**). Do **not** hand-write block JS/CSS from this file. | +| **Default content** (3C) | **da-content** only (no block skills). | + +Record the manifest as an evidence-bearing checklist and tick each item **only +after you actually invoked the skill** — "I know what it does" is not invocation, +and an un-invoked required skill means this phase is **not complete**: + +- [ ] **da-content** reference docs loaded (`html-content.md` / `platform.md` / `media.md`) +- [ ] **block-collection-and-party** invoked for every reused block *(if any 3A)* +- [ ] **content-modeling** + **content-driven-development** invoked for every new block *(if any 3B)* +- [ ] **Default-content** sections authored via **da-content** alone — **no** block-building skills invoked for them *(if any 3C)* +- [ ] **testing-blocks** invoked — its browser render + visual comparison **is** the + Stage B pre-publish check (Phase 5); a run with **no** browser available + reports the page **preview-only, UNVERIFIED**, never "done". + +If the environment genuinely cannot run a required skill (e.g. no browser for +testing-blocks), **say so explicitly in the report and mark the affected checks +unverified** — never silently substitute this file's summary and call it passed. + +--- + +## Phase 3A — Map content into an EXISTING block + +**Reuse gate — structure AND visual.** An existing block is a valid target +only when the section both (a) **fits the block's authoring model** (its +row/cell structure and field types) *and* (b) **matches the block's rendered +appearance** under the project theme, using only tokens and variants the block +already defines. Structural fit alone is **not** enough: if the section's +visual identity — bespoke layout, corner radius, shadow, decorative treatment — +lives in that block's own CSS, you cannot reproduce it without editing the +block (forbidden), so route the section to **Phase 3B** (new block). Global, +token-level differences (palette, fonts, type scale) do **not** break reuse — +they are absorbed once by retargeting the project's design tokens (see +Guardrails). **How to run the visual check — reuse testing-blocks, don't invent +one:** get a rendered example of the candidate block — its `liveExampleUrl` +(block-collection-and-party / block-inventory) or the project's own block +rendered at `localhost:3000` with the section's **actual** content — including +secondary text, captions, and CTAs over whatever background or media the block +places them on, not just placeholder cells — then follow **testing-blocks**' +browser/Playwright-MCP screenshot pass (mobile/tablet/desktop) and its "compare +implementation to design" step, comparing that screenshot against the Figma +**section screenshot** from Phase 1. Watch for treatments a block applies to +only its primary element: one that (say) whitens a heading over dark media but +leaves the supporting text and buttons at body color passes a structural check +yet renders that text illegibly — a divergence the token retheme cannot fix. +Divergence beyond what the token retheme explains ⇒ new block (or a new +variant), not reuse. This outcome is **blocking**: the section is not resolved +until its rendered look — that text included — is faithful, and the fix is a new +isolated block/variant, never an edit to the shared block. Recording the gap in +the plan and reusing the block anyway is a **plan note, not a fix** — the Phase 5 +pre-publish gate treats such a box as failed. + +Once the gate passes, **invoke block-collection-and-party** to learn the block's +authoring model (its examples show the row/cell structure and variants) — read +the block from the skill, don't guess its model from its CSS source. Then pour +the Figma content into that structure: + +- **Text** → matching cells; preserve heading levels from the design. +- **Variants** → extra class tokens on the block (e.g. `cards highlight`). + Only apply a variant the block actually defines. (Adding a *new* variant = + modifying an existing block = Phase 3B, not 3A.) +- **Links/buttons** → a **standalone link** (the only content of its + paragraph) auto-promotes to a button; wrap in `` for a primary + button, `` for secondary. Do not add `target="_blank"` (decoration + handles external links). Validate the href's URL scheme and escape the + link text/attributes before emitting — see Phase 4, *Sanitize everything + derived from the design*. *(da-content html-content.md §8)* +- **Images** → Phase 4 (they need real URLs). + +--- + +## Phase 3B — Create a NEW block (content + code) + +Only for sections Phase 2 routed here (a needed block is missing, or an existing +block's look diverges) — the **3B** case. **Guardrails (strict):** + +- Create **new, isolated block folders** only (`blocks//`). +- **Do NOT** skin this block by editing an existing block, `scripts.js`, or + `head.html`, or by adding block-specific rules to global CSS — keep it + self-contained under `blocks//`. *(Retargeting the project's + global design tokens in `styles/styles.css` — the `:root` custom properties + and base typography — is a separate, allowed project-theming step, not part + of building this block; see Guardrails.)* +- New block **names and variant tokens** must obey EDS block-name rules + (da-content html-content.md §3.3): lowercase alphanumeric + single hyphens, + **no underscores, no double dashes, must not start with a digit** + (`pricing-table` ✓, `pricing_table` / `2col` / `promo--wide` ✗). Names must + be unique and not collide with existing blocks. + +**Build route — invoke content-driven-development (don't hand-write the block).** +Build every new block by **invoking content-driven-development** — not by writing +block JS/CSS from scratch off this file's summary. It invokes **content-modeling** +(design the authoring model from the Figma structure/tokens) then +**building-blocks** and **testing-blocks**, and produces a self-contained +`blocks//` — no source URL, no installed substrate, no page chrome, and no +global styles. That is the route that honors the 3B guardrails above, and its +testing-blocks pass is the block's Stage B verification (Phase 5). Build a **bespoke, one-off** section +the same way — it is still an ordinary isolated block, and "one-off" changes +nothing about how it is generated. + +> **Do not use snowflake here.** Snowflake converts an *already-rendered* page: +> it requires a reachable **Source URL**, **installs an overlay substrate** into +> the repo, and in block mode emits **header/footer fragments and global +> styles/tokens** — each of which violates this skill's constraints (isolated new +> block, don't touch globals, work from the **Figma frame**, not a live URL). +> Snowflake is the right tool for a *different* entry point — converting an +> existing static/rendered site — as noted under "When NOT to use". + +Use the Figma design context/tokens from Phase 1 as the source of truth for +layout and styling. New-block **CSS must target structure, not authored +classes** — inline wrappers like `` are stripped inside block +cells at delivery (da-content html-content.md §3.9), so a class you emit in a +cell will not survive. + +**Make the block responsive.** A Figma page frame is almost always a single +**desktop** width, but EDS pages are responsive. Author the block mobile-first +(or with explicit breakpoints) so a multi-column layout collapses to one column +on narrow viewports, and verify at mobile / tablet / desktop via +**testing-blocks** — don't ship a fixed desktop-width block. If the design +provides a **separate mobile frame**, use it to derive the breakpoint behavior +(what stacks, what hides, how type scales) — it's the *same page*, so it feeds +one responsive block, **not** a second page (see Inputs on frame variants). + +The new block's code must be **committed and pushed to the deploy branch on +GitHub and built by Code Sync** before the page can render it — see Phase 5 +(content+code). + +--- + +## Phase 3C — Author DEFAULT CONTENT (no block) + +For sections Phase 2 routed to default content — the **3C** case — emit standard +document elements directly inside the section `
` (see Phase 4 skeleton) — no +block wrapper: + +- Headings `

`–`

` (preserve levels), paragraphs, lists, images. +- A **standalone link** in its own `

` becomes a button (``/`` + for primary/secondary) — same rule as 3A. +- Do **not** add `class`, `id`, or `style` — decoration adds them at delivery. + +*(da-content html-content.md §6)* + +--- + +## Phase 4 — Generate DA body-fragment HTML (da-content) + +Emit a **body fragment** (not a full HTML document) per **da-content**. **Invoke +da-content and load its `references/html-content.md`, `platform.md`, and +`media.md` now** — the rules quoted throughout this phase are reminders to jog the +right skill, not the source of truth. Subtle authoring rules (block-cell inline- +tag normalization, media MIME/extension derivation, metadata keys) live in those +docs; authoring from this summary alone is how they get missed. Write one file per +page to `content/.html`. + +**Mandatory skeleton** (da-content html-content.md §1–§2): wrap everything in +`` with an (empty) `

`/`