Skip to content
Binary file added .claude/authoring/image-compressor/page.docx
Binary file not shown.
183 changes: 183 additions & 0 deletions .claude/commands/feature/block-reuse-agent.md
Original file line number Diff line number Diff line change
@@ -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/<block>/<block>.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: "<function name> called at <file:line> — 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/<block>/<block>.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": "<requirement label>",
"decision": "<one of the four above>",
"anchor_block": "express/code/blocks/<block>/ or n/a",
"build_new_subtype": "heavy | light | null",
"candidates_investigated": ["block-a (rejected — reason)", "block-b (chosen)"],
"unconditional_behaviors": ["<fn> at <file:line> — not gated by variant"],
"authoring_schema": {
"rows": [
{"index": 0, "cols": 1, "merged": true, "content": "<quoted from JS>", "cell_types": [["h2","p","cta"]]},
{"index": 1, "cols": 2, "merged": false, "content": "<col0 | col1>", "cell_types": [["h3"], ["img"]]}
],
"note": "<any positional-consume pattern>"
},
"interactive_element_count": <N>,
"contextual_styling_notes": ["<condition at file:line> → <visual effect>"],
"highest_risk": "<one sentence or omit>"
}

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('<variant>') 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: <class-name> — matches Figma because <one reason>
If no variant matches, stop and flag via the Gap Resolution Protocol.

Then return a self-contained Python function add_<block_name> 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_<block_name>

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/<block>/<block>.css (and/or .js)
Add: .<block>.<new-variant> { <rules from Figma spec> }
— or — new QA_CONFIGS key, new classList.contains() guard, etc.
New variant authored as: "<block-name> (<new-variant>)" in the docx block header
Figma spec: .claude/figma-summaries/<slug>/deep/<section>.md
Unconditional behaviors to preserve: <list or "none">
```

For logic changes (modifying existing JS behavior, not just adding a class/entry):
```
Type: logic
File: express/code/blocks/<block>/<block>.js (and/or .css)
Function / line range: <functionName> at line <N>–<M>
Before:
<exact current code snippet — copy verbatim from the file>
After:
<exact replacement snippet>
Figma spec: .claude/figma-summaries/<slug>/deep/<section>.md
Unconditional behaviors to preserve: <list or "none">
```
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.
138 changes: 138 additions & 0 deletions .claude/commands/feature/build-new-light-agent.md
Original file line number Diff line number Diff line change
@@ -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 — <content> (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
<function code>
```

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, *, <content params>):
- Use ('h', level, text) | ('p', [parts]) | ('img', url, alt) tuples in write_cell
Loading
Loading