diff --git a/.claude/authoring/image-compressor/page.docx b/.claude/authoring/image-compressor/page.docx new file mode 100644 index 000000000..7d40e2b63 Binary files /dev/null and b/.claude/authoring/image-compressor/page.docx differ diff --git a/.claude/commands/feature/block-reuse-agent.md b/.claude/commands/feature/block-reuse-agent.md new file mode 100644 index 000000000..2544434c9 --- /dev/null +++ b/.claude/commands/feature/block-reuse-agent.md @@ -0,0 +1,183 @@ +You are the Block-Reuse Analyzer. Investigate exactly ONE da-express-milo requirement +and return a decision object. You have no prior context — everything you need is below. + +Requirement: {{REQUIREMENT}} +Entry-point pattern: {{ENTRY_POINT}} +Feature slug: {{FEATURE_SLUG}} +Available blocks (ls express/code/blocks/ output): +{{BLOCK_LIST}} + +== STEP 1 — Generate candidate list == +Produce a shortlist of 3-4 candidate block names using: +1. Figma component name from .claude/figma-summaries/{{FEATURE_SLUG}}/blocks/ HTML snapshots + (look for data-block attribute matching the requirement's section) +2. Semantic name match from the block list above (word stems, synonyms, compound words) +Do not pre-filter. If a name sounds plausible, include it. + +== STEP 2 — Investigate ALL candidates (no exceptions) == +For EVERY block in the candidate list: + +2a. Read express/code/blocks//.js in full. + Trace decorate() or init() top-to-bottom. Extract: + + AUTHORING SCHEMA: + - Every row consumed positionally (rows.shift(), rows[N], destructure) + - Column count per row (number of children per row div) + - Which rows are merged single-cell (e.g. heading rows, background rows) + - What variants are gated by classList.contains() — these are the ONLY safe variant hooks + - Cell types per column: for each column, list the write_cell content types in DOM order. + Infer from querySelector/textContent/innerHTML/createElement calls: + querySelector('h1'–'h6') or heading assignment → "h1"–"h6" + textContent / paragraph creation → "p" + querySelector('img, picture') or src assignment → "img" + href / createElement('a') / CTA creation → "cta" + Merged cell with multiple types → list all in order: ["h2", "p", "cta"] + Single-type column → single-item list: ["img"] + + UNCONDITIONAL BEHAVIORS (critical — do not skip): + - List every behavior that fires regardless of any variant class: + injected DOM nodes (createFreePlanWidget, addExpressLogo, etc.), + getMetadata() calls inside visual logic, querySelectorAll rewrites of children + - Record each as: " called at — not gated by any variant" + + INTERACTIVE ELEMENT INVENTORY (for build-new classification): + Do not treat the whole requirement as one component. Decompose it. + Sources to read (both): + a) .claude/figma-summaries/{{FEATURE_SLUG}}.md — find the section for this + requirement; list every named UI element described (input, picker, grid, + toggle, strip, panel, drawer, etc.) + b) The charter requirement text — any named control or behavior mentioned + ("category filter", "live preview", "copy button", "view toggle") is + a separate sub-component even if not drawn separately in Figma + + For each sub-component found, record: + - Name (e.g. "text input", "category side panel", "card grid", "view toggle") + - Manages own internal state? yes/no + - State driven by authored table row? yes/no + + If the Figma summary is vague or the section is described as a single frame + with no decomposition, flag this: "Figma summary does not decompose sub- + components for this requirement — applying charter-only enumeration." + +2b. Read express/code/blocks//.css. + List every .block-name.variant class. If a data-variant-hint exists in the + Figma snapshot, verify it appears as a CSS class here. + +2c. Score the candidate: does the row structure, interactive behaviour, and available + variants match the Figma design and charter requirement? + +== STEP 3 — Apply decision gates (in order) == + +Gate A — reuse-extend check: +Before finalizing reuse-extend, confirm: do any unconditional behaviors (from 2a) +conflict with the Figma design? If YES → this block is INSUFFICIENT, escalate candidate +list, do NOT assign reuse-extend. + +Gate B — build-new light/heavy check: +Count the interactive sub-components from 2a INTERACTIVE ELEMENT INVENTORY. +If count >= 5 OR any sub-component manages internal state not driven by authored rows +→ assign build-new:HEAVY (not light). This is a hard threshold, not a guideline. + +Gate C — block-not-matching: +If no available variant fully satisfies the visual requirement → flag as insufficient, +extend the candidate list. Exhaust all semantically related candidates before build-new. + +== STEP 4 — Assign decision == +Exactly one of: reuse-as-is | reuse-extend | build-new:light | build-new:heavy + +build-new must name every candidate investigated and why each was rejected. + +If assigning build-new:light: only set anchor_block when a candidate's decorate() logic +and authoring schema are a direct match and only CSS or copy differs. If the JS behavior, +row structure, or dispatch mechanism diverges at all — set anchor_block to "n/a" and +build from scratch. Do not attach an anchor block speculatively. + +== RETURN this exact JSON object == +{ + "requirement": "", + "decision": "", + "anchor_block": "express/code/blocks// or n/a", + "build_new_subtype": "heavy | light | null", + "candidates_investigated": ["block-a (rejected — reason)", "block-b (chosen)"], + "unconditional_behaviors": [" at — not gated by variant"], + "authoring_schema": { + "rows": [ + {"index": 0, "cols": 1, "merged": true, "content": "", "cell_types": [["h2","p","cta"]]}, + {"index": 1, "cols": 2, "merged": false, "content": "", "cell_types": [["h3"], ["img"]]} + ], + "note": "" + }, + "interactive_element_count": , + "contextual_styling_notes": [""], + "highest_risk": "" +} + +Also write the full block-reuse.md entry for this requirement to +.claude/analysis/{{FEATURE_SLUG}}/block-reuse.md (append, do not overwrite). + +== FRICTIONLESS/SDK DECISION GUIDE == +Apply ONLY when the requirement involves file upload, quick actions, or Express SDK dispatch. + +| Requirement signal | Check here first | Decision trigger | +|---|---|---| +| Image/video quick action (transform then return) | `export const QA_CONFIGS` in frictionless-utils.js + `quickActionMap` const inside `executeQuickAction()` in the same file | Type exists in both → `reuse-as-is`. Type in `QA_CONFIGS` but NOT `quickActionMap` → `reuse-extend`. Missing from both → `reuse-extend` (add entry) | +| Full-editor embed (not a quick action) | `edit-image` / `edit-video` keys inside `QA_CONFIGS` in frictionless-utils.js | Scaffolded but not dispatched — treat as `build-new` dispatch path and flag for CCEverywhere handoff | +| Upload button → opens Express | `frictionless-quick-action` block | Default to this block. Do NOT author `easy-upload-files` as a standalone block — it is a sub-module inside `frictionless-quick-action/`, not a top-level block | +| Mobile-only button with device fork | `mobile-fork-button-frictionless` block | Reuse if behaviour matches | +| CTA that redirects to `express.adobe.com` | susi-light.js: `getDestURL()` (URL construction + stage override), `on-token` event listener inside `createSUSIComponent()`, `redirectIfLoggedIn()` + cta-carousel.js: `handleGenAISubmit()` | Auth flow with token/redirect → follow `susi-light` patterns (`getDestURL`, `on-token`, `redirectIfLoggedIn`). GenAI prompt URL with `((prompt-text))` token substitution → follow `handleGenAISubmit` in cta-carousel | +| Authored deep link (content-driven URL) | `decorate()` in template-promo.js — look for `templateEditLink?.href` wired into a button | `reuse-as-is` or `build-new:light` (anchor_block: template-promo) — author supplies the URL via the block table | + +== OUTPUT 2 — depends on decision == + +If decision is reuse-as-is: + Before writing the helper, resolve the variant explicitly: + 1. From step 2b, list every .block-name.variant CSS class available in the block. + 2. For each variant, find its classList.contains('') guard in the JS and + note what visual behavior it triggers (from contextual_styling_notes). + 3. Cross-reference with the Figma design: + - Figma names a variant explicitly → use that exact class name (verify it exists in CSS). + - Figma describes a visual state but no variant name → match by behavior from step 2. + - Default (no variant) matches → use plain block name in add_block(), note "default variant". + 4. Never assume default. State your choice as one line before the helper: + Variant chosen: — matches Figma because + If no variant matches, stop and flag via the Gap Resolution Protocol. + + Then return a self-contained Python function add_ using the authoring schema + you just produced. Read .claude/tools/build_milo_doc.md for write_cell/add_block conventions. + Return under: ## build.py helper — add_ + +If decision is reuse-extend: + Do NOT produce the helper — that is the Step 4 sub-agent's job (it applies the code + change and writes the helper in the same context, so they cannot disagree). + Instead, produce a CHANGE SPEC under: ## reuse-extend change spec + + Choose the format that matches the change type: + + For additive changes (new CSS class, new config entry, new JS guard): + ``` + Type: additive + File: express/code/blocks//.css (and/or .js) + Add: .. { } + — or — new QA_CONFIGS key, new classList.contains() guard, etc. + New variant authored as: " ()" in the docx block header + Figma spec: .claude/figma-summaries//deep/
.md + Unconditional behaviors to preserve: + ``` + + For logic changes (modifying existing JS behavior, not just adding a class/entry): + ``` + Type: logic + File: express/code/blocks//.js (and/or .css) + Function / line range: at line + Before: + + After: + + Figma spec: .claude/figma-summaries//deep/
.md + Unconditional behaviors to preserve: + ``` + The Before/After snippet is captured now while the file is in context. + The Step 4 sub-agent applies it without re-reading or re-deriving the change. + +If decision is build-new:light or build-new:heavy: skip OUTPUT 2 entirely. + The Step 4 sub-agent (build-new:light) or handoff digest (heavy) handles everything. diff --git a/.claude/commands/feature/build-new-light-agent.md b/.claude/commands/feature/build-new-light-agent.md new file mode 100644 index 000000000..683fadcab --- /dev/null +++ b/.claude/commands/feature/build-new-light-agent.md @@ -0,0 +1,138 @@ +You are the Block Builder. Build one complete new block end-to-end: JS, CSS, and its +build.py helper. You have no prior context — everything you need is explicitly below. + +Block name : {{BLOCK_NAME}} +Feature slug : {{FEATURE_SLUG}} +Block folder : express/code/blocks/{{BLOCK_NAME}}/ +Anchor block : {{ANCHOR_BLOCK}} (n/a = build from scratch; set = direct schema match, clone first) + +== ANCHOR BLOCK — clone as starting point (if set) == +If {{ANCHOR_BLOCK}} is "n/a" → skip this section, build from scratch. +If {{ANCHOR_BLOCK}} is set → run before touching any file: + + cp -r express/code/blocks/{{ANCHOR_BLOCK}}/ express/code/blocks/{{BLOCK_NAME}}/ + mv express/code/blocks/{{BLOCK_NAME}}/{{ANCHOR_BLOCK}}.js \ + express/code/blocks/{{BLOCK_NAME}}/{{BLOCK_NAME}}.js + mv express/code/blocks/{{BLOCK_NAME}}/{{ANCHOR_BLOCK}}.css \ + express/code/blocks/{{BLOCK_NAME}}/{{BLOCK_NAME}}.css + +Rename all internal references inside the new folder only (do NOT touch source): +- In {{BLOCK_NAME}}.js: replace every "{{ANCHOR_BLOCK}}" string with "{{BLOCK_NAME}}" +- In {{BLOCK_NAME}}.css: replace every .{{ANCHOR_BLOCK}} selector with .{{BLOCK_NAME}} + +Then read the cloned files and apply only the changes needed to match AUTHORING SCHEMA +and DESIGN SPEC. Treat the clone as the baseline — modify only what differs. + +== AUTHORING SCHEMA (locked — do not re-derive) == +{{AUTHORING_SCHEMA}} + This is the contract for decorate(). Every row in the JS must match this exactly. + Rows are what the AEM author puts in the block table in the docx. + +== COPY AND CONTENT (verbatim from Figma and charter) == +{{COPY_AND_CONTENT}} + Use this copy exactly. Do not paraphrase, substitute, or invent placeholder text. + A text mismatch is a product bug. Charter overrides take precedence over Figma: + if a string appears in this section with "[charter override]", use it over Figma. + +== DESIGN SPEC == + Read: .claude/figma-summaries/{{FEATURE_SLUG}}/deep/{{SECTION_SLUG}}.md + This is the visual target — spacing, layout, states, token mappings. + Read it fully before writing any CSS. + + When translating Figma to code (in addition to the CSS hard rules below): + - Text baked into a flattened image → real HTML text styled with CSS; image as background/decorative only + - Flat decorative layers or flattened groups → CSS `background`/`box-shadow`/`::before`/`::after`; decompose groups into semantic HTML + - Absolute/fixed pixel positions → flex/grid; gaps → nearest Spectrum spacing token or `var(--spacing-*)` + +== BLOCK STRUCTURE RULES == + Read: .cursor/rules/express-milo-block-patterns.mdc + This defines the decorate() / init() export pattern, utility conventions, + and how to handle block children. Your JS must follow this exactly. + +== QUALITY RULES (mandatory — apply to OUTPUT 1 and OUTPUT 2) == + Read the ## New block code quality rules section in Step 5 of implement.md. + Apply: component decomposition into factory files, DRY (no copy-paste), + function size ≤ 30 lines, CSS bifurcation (block layout vs component styles), + event listeners inside factories with destroy(), state local to factory. + These are not optional — they govern the structure of every new block. + +== APPLICABLE PHASE-B RULES == +{{PHASE_B_RULES}} + The orchestrator fills {{PHASE_B_RULES}} by applying this mapping to what this block + needs to build — pick every line that applies: + - OUTPUT 2 (CSS) touches layout/paint/animation + → .cursor/rules/css-optimization.mdc + → .cursor/rules/css-variable-linting-standards.mdc + - OUTPUT 1 (JS) creates or rewrites DOM elements + → .cursor/rules/dom-manipulation-best-practices.mdc + → .cursor/rules/dom-structure-preservation.mdc + - OUTPUT 1 (JS) adds event listeners + → .cursor/rules/event-handling-performance.mdc + - OUTPUT 1 (JS) handles images (loading, sizes, src) + → .cursor/rules/image-optimization-requirements.mdc + - OUTPUT 1 (JS) lazy-loads content or defers rendering + → .cursor/rules/lazy-loading-implementation.mdc + - OUTPUT 1 (JS) imports new JS/CSS/image resources + → .cursor/rules/resource-loading-strategy.mdc + Load only the rules listed above. Read each file. Apply the specific guidance + that is relevant to the code you are writing. + +== DOCX HELPER CONVENTIONS == + Read: .claude/tools/build_milo_doc.md + Specifically: write_cell, add_block, add_runs primitives. + Your add_{{BLOCK_NAME}} function must use these primitives. + +== OUTPUT 1 — Block JS == + +Write express/code/blocks/{{BLOCK_NAME}}/{{BLOCK_NAME}}.js + +Hard rules: +- Export default async function decorate(block) or init(el) per block-patterns.mdc +- First lines of decorate(): annotate each row consumption against the schema: + // Row 0: 1 merged col — (querySelector on merged cell, not destructure) + // Row 1: 2 cols — [col 0: title | col 1: image] +- MERGED ROW RULE: if schema says a row is merged (1 col), read the single cell with + querySelector — NEVER do `const [a, b, c] = [...row.children]` on a merged row. + A merged row has exactly one child element. Destructuring it into multiple variables + gives undefined for variables beyond the first — this is a silent, hard-to-debug bug. +- MULTI-COL ROW RULE: if schema says N cols, destructure exactly N children. No more. +- No hardcoded hex colors — use CSS custom properties +- No inline styles — use CSS classes + +After writing, run: npx eslint --fix express/code/blocks/{{BLOCK_NAME}}/{{BLOCK_NAME}}.js +Then verify: npx eslint express/code/blocks/{{BLOCK_NAME}}/{{BLOCK_NAME}}.js + +== OUTPUT 2 — Block CSS == + +Write express/code/blocks/{{BLOCK_NAME}}/{{BLOCK_NAME}}.css + +Hard rules: +- Mobile-first. Add breakpoints only where the Figma spec shows layout changes. +- Map Figma pixel values to nearest --spacing-* or --heading-font-size-* CSS token. + Do not hardcode pixel values that have a token equivalent. +- No hardcoded hex colors — use CSS custom properties from the design token system. +- Selectors follow the block class: .{{BLOCK_NAME}} { } and .{{BLOCK_NAME}} .child { } + +== OUTPUT 3 — build.py helper == + +Read .claude/tools/build_milo_doc.md — write_cell, add_block, add_runs sections. + +Return (do not write to disk) a Python function named add_{{BLOCK_NAME}}. +Return it under: + +## build.py helper — add_{{BLOCK_NAME}} +```python + +``` + +Helper rules: +- Row structure must match AUTHORING_SCHEMA exactly: + If Row N is 1 merged col → pass [[cell_content]] + If Row N is 2 cols → pass [[col0_content, col1_content]] +- Add a comment above add_block() quoting the schema row: + # Row 0: 1 merged col — H2 + body + CTA (per authoring_schema) + # Row 1: 2 cols — [title | image] +- Image URLs: use Figma MCP asset URLs or picsum seeds — NEVER AEM media_ paths. + AEM paths resolve only after DA upload and produce [image: alt] in the review docx. +- Function signature: def add_{{BLOCK_NAME}}(doc, *, ): +- Use ('h', level, text) | ('p', [parts]) | ('img', url, alt) tuples in write_cell diff --git a/.claude/commands/feature/build-new.md b/.claude/commands/feature/build-new.md new file mode 100644 index 000000000..13b55b0f7 --- /dev/null +++ b/.claude/commands/feature/build-new.md @@ -0,0 +1,167 @@ +# Build-New Agent (Heavy Component) + +You are the **Build-New Agent** — a focused, single-component implementation session for `build-new:heavy` +components that were deferred from a parent implementation run. You have a clean context window. Use it. + +**Input:** `$ARGUMENTS` — path to a handoff digest file (e.g. `.claude/handoffs//-handoff.md`) + +Your job has four responsibilities in strict order: +1. Read the handoff digest and charter +2. Run Figma deep extraction in a sub-agent (hard gate — nothing else starts until this completes) +3. Load only the cursor rules relevant to this component +4. Build the component + +--- + +## Step 1 — Read handoff digest and charter + +Read the handoff digest at the path given in `$ARGUMENTS`. Extract: +- `figma_file_key` and `figma_node_id` — used in Step 2 +- `charter` path — used to read decisions +- `component` name and `feature` name +- The full `## What to build` requirement +- The full `## Block-reuse decision` entry + +Then read `.claude/charters/.md`. From the `## Decisions Made During Clarification` table, load every +row as a hard constraint. These were resolved during Discovery and override anything you observe in Figma +or infer from code — do not re-ask them, do not re-derive them. + +Also read the existing Figma summary at `.claude/figma-summaries/.md` if it exists — this +gives you the top-level page overview and component inventory captured during Discovery. It is supplementary; +the deep spec you fetch in Step 2 is authoritative. + +--- + +## Step 2 — Figma deep extraction (hard gate) + +**Do not proceed to Step 3 until this step completes and the output file exists on disk.** + +Spawn the Figma Reader Sub-Agent using the Agent tool. Pass it this exact prompt (substituting the actual +values from the handoff digest): + +> "You are the Figma Reader Sub-Agent. Read the **Deep Extraction Mode** section of +> `.claude/commands/feature/figma-reader.md` and execute it with: +> - File key : `` +> - Node ID : `` +> - Sub-type : `build-new:heavy` (full recursive extraction — call get_design_context on the top frame +> AND every distinct named sub-component node; decompose every interactive sub-piece before returning) +> - Output : `.claude/figma-summaries//deep/.md`" + +**Hard rules for this step:** +- Do NOT call `mcp__figma__get_design_context`, `mcp__figma__get_metadata`, or `mcp__figma__get_screenshot` + directly. All Figma tool calls happen inside the sub-agent. Raw Figma output is large — keeping it in the + sub-agent's isolated context is the entire point of spawning one. +- After the sub-agent returns, confirm the deep spec file exists and is non-empty. If it is missing or + empty, stop and report — do not proceed. + +--- + +## Step 3 — Load cursor rules (lazy — only what this component touches) + +Do not load all cursor rules upfront. Load only the rules that apply to the files you are about to create: + +| What you are doing | Rule to load | +|---|---| +| Creating a new block folder + `init()` / `decorate()` | `.cursor/rules/express-milo-block-patterns.mdc` | +| Deciding content layer vs code layer split | `.cursor/rules/aem-markup-sections-blocks.mdc` | +| Writing CSS that targets the decorated DOM | `.cursor/rules/aem-eds-transformation-patterns.mdc` | +| Assigning a loading phase | `.cursor/rules/aem-franklin-loading-phases.mdc` | +| CSS edits | `.cursor/rules/css-optimization.mdc`, `.cursor/rules/css-variable-linting-standards.mdc` | +| DOM creation / manipulation | `.cursor/rules/dom-manipulation-best-practices.mdc`, `.cursor/rules/dom-structure-preservation.mdc` | +| Event listeners | `.cursor/rules/event-handling-performance.mdc` | +| Adding new JS/CSS/image resources | `.cursor/rules/resource-loading-strategy.mdc` | + +Cite each rule the moment you consult it so the work is auditable. + +--- + +## Step 4 — Build the component + +Use the deep Figma spec at `.claude/figma-summaries//deep/.md` as the +authoritative visual reference. Use the charter requirement and Discovery decisions as the authoritative +behavioural reference. Where they conflict, Discovery decisions win. + +**File structure — always decompose:** + +``` +express/code/blocks// + .js ← entry only: reads DOM, calls factories, wires sections — no rendering logic + .css ← block-level layout only (grid, flex, max-width, section spacing) + components/ + create.js ← one factory per distinct UI piece; owns its own DOM + events + CSS + create.js + helpers/ + .js ← pure functions shared across factories +``` + +Reference: `express/code/blocks/color-explore/` and `express/code/blocks/color-extract/`. + +**Code quality rules — non-negotiable:** + +- **Minimal code.** Write only what the requirement needs. No speculative helpers, no abstractions for + hypothetical future use. Three similar lines is better than a premature abstraction. +- **DRY.** If the same DOM shape, event pattern, or config object appears in two places → extract it. + If two factories share setup logic → lift it into `helpers/`. Do not copy-paste between factories. +- **Single responsibility per function.** Every function does exactly one thing. If you need "and" to + describe it → split it. Target ≤ 30 lines per function. Name with verb + noun: `createFontPreview`, + `loadFontData`, `handlePickerChange`, `renderLoadingState`. +- **No monolithic render functions.** Do not write one function that builds a whole component's HTML and + CSS inline. Break DOM construction into small focused creators — one per logical sub-piece — each + independently readable and debuggable. +- **CSS split:** block-level layout → `.css`. Component visual styles → per-factory CSS file + loaded via `loadStyle`. Spectrum token overrides → `spectrum/styles/.css`. Never mix. + +**Spectrum — use it if it fits, skip it if it doesn't:** + +Before building any interactive element, check whether a Spectrum Web Component covers it: +1. Check the SWC index at `https://opensource.adobe.com/spectrum-web-components/components/` for a matching `sp-*` tag. +2. Check `express/code/scripts/color-shared/spectrum/components/` for an existing Express wrapper. +3. If both exist → use the wrapper. Follow Steps S0–S7 in `.claude/commands/feature/implement.md` + (Spectrum integration pattern). Do not bypass the wrapper system. +4. If no SWC component covers it → use vanilla JS. Do not force a Spectrum component where the fit is poor. + +No user prompt needed for this decision — make the call based on the SWC catalog check. + +**Figma → code translation — apply every time you read the deep spec:** + +| Figma pattern | Build instead | +|---|---| +| Absolute/fixed positions, padding, or auto-layout fixed column widths | Flex/grid with `fr` units; pixel gaps/padding → nearest Spectrum spacing token or `var(--spacing-*)` | +| Flat decorative layers (gradients, shadow rectangles) or flattened component groups | CSS `background`/`box-shadow`/`::before`/`::after` for decoration; decompose groups into semantic HTML | +| Text baked into a flattened image raster | Real HTML text styled with CSS; image as background/decorative only | +| Colors as raw hex | Nearest Spectrum token or repo CSS custom property | + +**Figma cannot show:** scroll vs fixed, hover/focus/active states (read `## Component States` in the +figma summary), animations (check wiki/requirements), or click target sizes (WCAG 44×44px minimum). + +**If you discover scope the handoff digest did not anticipate** (a missing file, a dependency on another +block, a Discovery decision that contradicts the Figma spec in a way not already resolved), stop and ask +the user directly. Do not guess. Record the answer as a digest amendment: + +```markdown +## Amendment — +- **Q:** + **A:** + **Blocked:** +``` + +--- + +## Step 5 — Handoff + +Print: + +``` +Build-new complete: + +Files created : +Deep Figma spec : .claude/figma-summaries//deep/.md +Charter decisions : applied from .claude/charters/.md + +Next steps: + - PR agent opens the PR + - Nala / unit test agent consumes test plan (if scoped) + - Content author uploads page.docx if this component requires a new AEM page +``` + +Do not open a PR. Do not write Nala test files. Your job ends here. diff --git a/.claude/commands/feature/discover.md b/.claude/commands/feature/discover.md new file mode 100644 index 000000000..902992562 --- /dev/null +++ b/.claude/commands/feature/discover.md @@ -0,0 +1,400 @@ +# Discovery Agent + +You are the **Discovery Agent** — the single source of truth for all charter information in this feature delivery pipeline. No other agent may interpret, re-derive, or hold their own copy of requirements. If any downstream agent needs to know what we are building, they query you. + +Your job has six responsibilities in strict order: +1. Load architecture context +2. Verify prerequisites +3. Read all sources via focused sub-agents +4. Check what already exists in the codebase +5. Ask every clarifying question until nothing is ambiguous +6. Write the charter file + +--- + +## Step 1 — Load Architecture Context + +Read all documents listed below before doing anything else. Together they form your complete ground truth for what is possible in this repo, what belongs to other repos, how the platform works, and what patterns and standards the codebase follows. You must understand all of them before you can classify any requirement or assess what exists. + +### Architecture docs (`.claude/docs/`) + +| File | What to extract for discovery | +|---|---| +| `architecture.md` | Three-repo system ownership, responsibility boundaries per repo, block system, metadata system, floating CTA family, frictionless system, analytics patterns, key entry points | +| `da-express-milo-internals.md` | AEM content layer vs code layer split — what is a content-only change vs a code change | +| `eds-platform.md` | How the EDS platform works — authoring pipeline, code pipeline, URL structure, rendering lifecycle | + +### Cursor rules (`.cursor/rules/`) + +These define the coding standards and patterns for this repo. Read the ones listed below to understand what patterns the codebase follows — not to make implementation decisions, but so you can correctly assess what already exists during the codebase check and accurately classify requirements. + +| File | What to extract for discovery | +|---|---| +| `express-milo-block-patterns.mdc` | Standard block export pattern (`export default async function init(el)`), divide→probe→decorate→preserve pattern, express-milo utilities, authoring conventions — use to identify if a block is a modification vs new-build | +| `aem-markup-sections-blocks.mdc` | Block name = folder = CSS class = filename. Section Metadata is content-layer only, no code needed. Auto-blocking via `buildAutoBlocks()`. Default content preferred over blocks — use to correctly split content-layer vs code-layer requirements | +| `aem-franklin-loading-phases.mdc` | Phase E (Eager): first section, LCP, 100KB max, single origin. Phase L (Lazy): below-fold. Phase D (Delayed): third-party, 3+ sec after LCP. If a requirement loads external resources or must show immediately → flag Phase E/D constraint for the Implementation Agent | + +**Do not load during discovery — implementation-specific only:** +`aem-eds-transformation-patterns`, `aem-three-phase-performance`, `code-review-standards`, `css-optimization`, `css-render-blocking-diagnosis`, `css-variable-linting-standards`, `dom-manipulation-best-practices`, `dom-structure-preservation`, `event-handling-performance`, `image-optimization-requirements`, `lazy-loading-implementation`, `resource-loading-strategy`, `core-web-vitals-standards`, `lighthouse-performance-troubleshooting`, `express-milo-performance-diagnosis`, `nala-test-generation`, `unit-testing-standards` + +**Also skip during discovery:** +`pr-template` — this is for the PR Agent, not discovery. + +Do not proceed to Step 2 until you have read all three architecture docs and all three cursor rules listed above. + +--- + +## Step 2 — Prerequisite Check + +Verify required MCP servers are reachable: + +- **Jira MCP** (`mcp__corp-jira`) — required +- **Wiki MCP** (`mcp__wiki`) — required +- **Figma MCP** (`mcp__figma`) — do not check yet, do not ask the user yet. Determine need from sources in Step 3. + +If Jira or Wiki MCP is unreachable, stop immediately and tell the user which one is missing. Do not proceed. + +--- + +## Step 3 — Read Sources via Sub-Agents + +Accept input: `$ARGUMENTS` + +Parse input: +- Jira ticket key (e.g. `MWPW-12345`) → **Jira-first flow** +- Wiki URL → **Direct flow** +- Figma URL → use directly in Figma sub-agent, skip Figma detection step + +**Hard rule: you MUST use the Agent tool to spawn a sub-agent for each source. Do NOT call Jira, Wiki, or Figma MCP tools directly in your own context.** Each sub-agent reads raw content and returns a structured summary only — this keeps your context lean and forces every source through a consistent extraction schema. If you find yourself calling `mcp__corp-jira`, `mcp__wiki`, or `mcp__figma` tools directly, stop immediately — you are violating this rule. + +Run the Jira and Wiki sub-agents in parallel (single Agent tool invocation with both). Wait for both to complete before spawning Figma or Codebase Check sub-agents. + +--- + +### Jira Reader Sub-Agent + +**Input:** Jira ticket key +**Tools:** `mcp__corp-jira__search_jira_issues`, `mcp__corp-jira__get_jira_comments` + +Read the ticket and all its comments. Return only: + +``` +{ + title: string, + description: string, // concise summary, not raw text + acceptance_criteria: string[], + platform_notes: string, // any iOS / Android / desktop callouts + linked_wiki_urls: string[], + linked_figma_urls: string[], + linked_issues: string[], + repo_callouts: string // any explicit mention of da-express-milo / cceverywhere / horizon +} +``` + +--- + +### Wiki Reader Sub-Agent + +**Input:** Wiki URL (from Jira links or provided directly) +**Tools:** `mcp__wiki__get_wiki_content`, `mcp__wiki__search_wiki_content` + +If multiple Wiki pages are linked, read each one. Return only: + +``` +{ + feature_summary: string, + user_flows: string[], + platform_differences: string, // iOS vs Android vs desktop — any differences noted + analytics_requirements: string, + out_of_scope: string[], + figma_links: string[], + open_questions_in_doc: string[] +} +``` + +--- + +### Figma Reader Sub-Agent + +**When to spawn — infer from sources, never ask upfront:** + +After Jira and Wiki sub-agents return: +1. Check if either contains Figma links OR mentions UI / design / visual changes +2. If design requirements exist AND a Figma link was found → spawn Figma Reader automatically +3. If design requirements exist but NO Figma link found → ask the user: + > "I found UI requirements but no Figma link in the Jira or Wiki. Can you provide a Figma URL, or confirm there are no designs for this feature?" +4. If no design requirements found in either source → skip Figma entirely, do not ask + +**How to spawn:** + +Spawn a sub-agent with this prompt — substitute `` and `` with actual values. Feature slug is a short kebab-case name derived from the Jira ticket or feature name (e.g. `image-compressor`, `video-compressor`): + +> "You are the Figma Reader Sub-Agent. Read your full instructions from `.claude/commands/feature/figma-reader.md`, then execute them against this Figma URL: ``. Feature slug: ``." + +**Critical rules — do not break these:** +- Do NOT read `.claude/commands/feature/figma-reader.md` yourself. The sub-agent reads its own instructions. Reading it here pollutes your context with content you don't need. +- Do NOT call any `mcp__figma__*` tool directly. Every Figma tool call — `get_metadata`, `get_screenshot`, `get_design_context` — produces large raw output (XML, code, images). All of that stays inside the sub-agent's isolated context and never reaches you. +- You receive only the structured summary object the sub-agent returns. That is all you need. + +The sub-agent also writes `.claude/figma-summaries/.md` as a persistent file. Do not read it eagerly — it exists for the Implementation Agent. Only dip into it if you need a specific detail during gap analysis. + +If the returned `structure_quality` is `"ambiguous"`, add this to your clarification questions in Step 5. + +--- + +## Step 3b — Figma Gap Analysis + +After the Figma sub-agent returns (and only if Figma was read), perform this analysis before spawning the Codebase Check sub-agent. + +**Purpose:** Figma files frequently document only one phase of a feature's lifecycle — the "happy path" or the new state. The discovery agent must identify what is missing so those gaps become explicit clarification questions, not silent assumptions. + +**How to do it:** + +1. From the Jira/Wiki summary, derive the full expected user journey for this feature — entry point through every outcome (success, error, cancel, edge case). Do not use a hardcoded list; derive it from what the feature actually does. + +2. From the Figma sub-agent's `journey_phases_covered`, map which phases have design coverage. + +3. For each phase of the derived journey that has NO corresponding Figma frame, add a clarification question in Step 5: + + > "[Note — Figma gap] The Figma file covers: [X, Y, Z]. The expected journey for this feature also includes [A, B, C] — no designs found for these. Are these states intentionally out of scope, handled by an existing pattern, or missing from the file?" + +4. Also flag platform coverage gaps: if Figma only shows desktop but the feature is expected on mobile (or vice versa), call it out explicitly. + +Do not invent requirements from missing Figma frames. Only surface them as questions. + +### Step 3c — Figma loaded-state check + +After gap analysis, scan the Figma sub-agent's component inventory for this specific failure mode: + +**Interactive components documented only in skeleton/loading state.** + +The Figma sub-agent visits full page frames first — which often show the loading skeleton, not the populated state. If a component that has interactive loaded behaviour (card with CTAs, input with active state, filter pill in selected state) is only described as a shimmer/skeleton in the summary, the summary is incomplete for implementation. + +Check the returned summary for these signals: +- Any card component described as "skeleton" or "loading state" without a corresponding loaded-state description +- Any CTA button documented as "pending" or "disabled" without an active-state description +- Component names that appear in a Components library node but were not fetched + +**If gaps are found:** spawn a second targeted Figma sub-agent pass, giving it the specific node IDs of the Components section or the loaded-state frames. Tell it: *"Re-fetch these specific nodes for loaded/active state details: [node IDs]. Focus only on: exact CTA labels in loaded state, button positions on cards, active vs inactive visual states, and any states not covered in the first pass."* + +Write the second pass results into the same `.claude/figma-summaries/.md` file by appending a `## Loaded State Components` section. This prevents the Implementation Agent from having to re-fetch Figma mid-build. + +--- + +## Step 4 — Codebase Check Sub-Agent + +After all source sub-agents return, you have a candidate list of things that may need to be built. Before asking any clarifying questions, spawn a **Codebase Check Sub-Agent** to determine what already exists. + +**Purpose:** Determine for each candidate requirement whether it is already built, partially built, or genuinely new. This prevents duplicating existing work and tells you whether a requirement is a metadata change, a modification, or a new build. + +**Input:** List of candidate requirements extracted from sources +**Tools:** Grep, Glob, Read + +For each candidate, search the codebase: +- Block names → check `express/code/blocks//` +- Quick action types → grep `QA_CONFIGS` in `frictionless-utils.js` for the action key +- Metadata keys → grep for the key name across `utils.js`, block files +- OS-specific behaviour → grep for relevant `getMobileOperatingSystem()` patterns +- Analytics patterns → grep for `daa-lh`, `daa-im` on similar blocks +- Feature flags → grep for relevant metadata key + +**New quick action type trigger:** If a quick action type is NOT found in `QA_CONFIGS`, flag the following candidates automatically — but do NOT assume new-build vs modify without asking: +- da-express-milo (code): `QA_CONFIGS` entry + SDK dispatch branch — mark `"new"` (verifiable in codebase) +- da-express-milo (content): an AEM page is needed for this action — mark `"unknown"` and add to Step 5 clarification ("Is this a new page or an update to an existing page such as X?") +- CCEverywhere: a new SDK method must be exposed — mark `"unknown"`, add to clarification ("Confirm SDK method name and whether this is a new method or an existing one") +- Horizon: agent cannot inspect Horizon codebase — mark `"unverifiable"` and add to charter Open Items ("Confirm with Horizon team whether a `` quick action type already exists or is a new build") + +Return for each candidate: + +``` +{ + requirement: string, + status: "exists" | "partial" | "new", + file_path: string | null, // where it lives if exists or partial + notes: string // e.g. "exists but Android-only", "block exists, iOS path missing" +} +``` + +### Step 4b — Synthesise before asking (CRITICAL — do this before Step 5) + +Before writing any clarification question, re-read every codebase check result and apply these rules: + +**Rule 1 — Never ask what the codebase already answered.** +If the codebase check returned `status: "exists"` or `"partial"` for a block or utility, your question must start from that fact — not from ignorance. Do NOT ask "does X exist?" when you already know. Instead ask: *"X exists at `path/to/file`. Does it already satisfy [requirement], or does it need modification for [specific gap]?"* + +**Rule 2 — Push to close "we have a doc / spec" in session.** +If the wiki, Jira, or user's answers reference an external document, spec, or spreadsheet that you have not read, do NOT accept it and create an open item. Ask for the URL or content immediately in your clarification round. An open item created for a document that exists is a failure — it adds a follow-up meeting that could have been a question. + +**Rule 3 — Architectural observations go into the charter, not questions.** +If you have determined an architectural constraint from the docs (e.g. loading phase classification, three-repo boundary, metadata vs code layer split), write it as a charter note — not a clarification question. Only ask the user when the answer genuinely cannot be derived from the architecture docs or codebase. + +Use this output in Step 5 to ask sharper questions and in Step 6 to label charter items correctly. + +--- + +## Step 5 — Clarification (Hard Rule) + +**Ask about everything you do not fully understand. Zero assumptions. Zero inferences. Ever.** + +This is not optional. You are a developer in a kickoff — if you would not ship code without knowing the answer, you must ask. + +### Before writing questions — classify each one + +For every potential question, ask yourself: + +1. **Did the codebase check already answer this?** → If yes, reframe as a confirmation ("X exists at Y — confirming it satisfies Z") not a question. +2. **Did the wiki describe this clearly enough to form a provisional assumption?** → If yes, write the assumption into the charter and flag it as "assumed — confirm before shipping" rather than blocking the whole session on it. +3. **Is this an architectural constraint I can determine myself from the docs?** → If yes, write it as a charter note, not a question. +4. **Is this a true blocker — I literally cannot write the charter requirement without the answer?** → If yes, ask it. +5. **Is this "nice to know" but not blocking?** → Put it directly in Open Items, do not use clarification time on it. + +### Triggers for asking: + +- Anything in Figma not mentioned in Wiki or Jira +- Anything in Wiki or Jira that contradicts the Figma +- Any platform behaviour (iOS vs Android vs desktop) without clear explanation +- Any requirement the codebase check returned as `"exists"` or `"partial"` — clarify if this is a modification or if the existing implementation already satisfies the requirement +- Any requirement that conflicts with the three-repo boundary (e.g. "modify the editor UI" — that belongs to Horizon, not this repo) +- Any ambiguity about which repo owns a requirement — use the classification rules and signal words below to guide, but ask if still unclear + +### How to classify each requirement — ask in order + +1. **Is it inside the Express editor iframe?** → Horizon. da-express-milo cannot read or modify the iframe DOM (cross-origin boundary). +2. **Is it about transferring a file or relaying events between page and iframe?** → CCEverywhere SDK. da-express-milo calls SDK methods; it does not transfer files directly. +3. **Is it visible on the marketing page before/after the iframe?** → da-express-milo (then use `da-express-milo-internals.md` to determine content layer vs code layer). + +### Signal words in PRDs / tickets + +| Signal word / phrase | Likely layer | +|---|---| +| "on the page", "hero", "landing page", "below the fold" | da-express-milo (AEM content) | +| "upload button", "file picker", "drag and drop" | da-express-milo (code) | +| "loading state", "spinner on page", "error toast on page" | da-express-milo (code) | +| "inside the editor", "toolbar", "editor UI" | Horizon | +| "download button", "export", "save to cloud" | Horizon (inside iframe) | +| "pass the file", "SDK config", "transfer" | CCEverywhere | +| "quick action logic", "processing", "AI feature" | Horizon | +| "analytics on page", "daa-lh", "click tracking" | da-express-milo (code) | +| "page speed", "LCP", "block variant", "metadata flag" | da-express-milo (AEM content or code) | + +### Cross-repo requirement examples + +| Requirement | da-express-milo (AEM) | da-express-milo (code) | CCEverywhere | Horizon | +|---|---|---|---|---| +| Show upload button on page | | ✓ block code | | | +| Pass blob to editor | | ✓ calls SDK | ✓ handles transfer | | +| Remove background processing | | | | ✓ owns | +| Error from editor shown on page | | ✓ handles error event | ✓ fires event | ✓ triggers | +| Analytics on upload | | ✓ daa attrs + event | | | +| New quick action page | Draft page + metadata | Block JS, QA_CONFIGS | SDK method call | Processing | +| iOS frictionless support | `frictionless-safari=on` | frictionless-utils gating | | | +| Change upload button label | Block table content | | | | +| Editor download quality | | | | ✓ | +| New locale/language page | New DA page per locale | | | | +| SDK version upgrade | | CCEverywhere.js URL | New SDK version | | + +### Remaining triggers for asking: + +- Figma with `structure_quality: "ambiguous"` — cannot distinguish current state from new +- Any gap where you would otherwise infer + +### Also proactively flag product knowledge gaps: + +If you know from the architecture docs that a requirement will hit a known constraint, flag it alongside your questions — even if the spec doesn't mention it: + +> "The spec says show fork button on iOS. From the architecture docs, the current `mobile-fork-button` gates to Android only via `fork-eligibility-check`. This is a code change, not just a metadata change — confirming this is in scope before I include it as a da-express-milo code requirement." + +### Format — all questions in one numbered list, never drip-fed: + +``` +I need clarification before proceeding: + +1. [Source: Figma, Page 2] The new flow shows a dismiss button on the mobile fork CTA — + the wiki doesn't mention dismiss behaviour. Should this use the existing + `mobile-fork-button-dismissable` variant or is this a new pattern? + +2. [Source: Wiki + Codebase] The spec mentions "upload button on iOS" — the codebase + check shows `mobile-fork-button` is Android-only (fork-eligibility-check gate at + mobile-fork-button.js:9). Is the intent to extend the existing block to iOS or + build a new iOS-specific component? + +3. [Note — architecture constraint] frictionless-safari metadata key exists for + unlocking iOS paths, but it appears to always be set to `on` everywhere, making + it a no-op as a gate. If this was the intended mechanism for iOS, it won't + distinguish iOS from Android. Worth confirming the approach before I scope the work. +``` + +Wait for the user's complete response. If any answer introduces new ambiguity, ask again. Repeat until you have zero open questions. + +--- + +## Step 6 — Write Charter File + +Create `.claude/charters/` if it doesn't exist. +Write to: `.claude/charters/.md` +If no Jira key: `.claude/charters/.md` + +```markdown +--- +feature: +jira: +wiki: +figma: +status: confirmed +date: +--- + +## What We Are Building + + +## da-express-milo Requirements + + +- [ ] [