diff --git a/.agents/discover-skills b/.agents/discover-skills new file mode 100755 index 0000000..8d865ae --- /dev/null +++ b/.agents/discover-skills @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +set -Eo pipefail +IFS=$'\n\t' + +# Discover available skills in both project and global directories +# Usage: .agents/discover-skills + +PROJECT_SKILLS_DIR=".skills" +PROJECT_SKILLS_DIR_LEGACY=".claude/skills" +GLOBAL_SKILLS_DIR="$HOME/.skills" +GLOBAL_SKILLS_DIR_LEGACY="$HOME/.claude/skills" + +process_skills_directory() { + local skills_dir="$1" + local location_label="$2" + + if [[ ! -d "$skills_dir" ]]; then + return 0 + fi + + local count=0 + # Count skills first + while IFS= read -r -d '' skill_file; do + count=$((count + 1)) + done < <(find "$skills_dir" -type f -name 'SKILL.md' -print0) + + if [[ $count -eq 0 ]]; then + return 0 + fi + + echo "$location_label ($count skill(s)):" + # Generate underline matching label length + local len=${#location_label} + if [[ $len -gt 0 ]]; then + local underline="" + for ((i=0; i/dev/null) + description=$(printf '%s\n' "$frontmatter" | awk -F': *' '/^description:/ {sub(/^description: */,"",$0); print substr($0, index($0,$2))}' 2>/dev/null) + + echo "Skill: ${name:-$skill_name}" + echo "Path: $skill_file" + if [[ -n "$description" ]]; then + echo "Description: $description" + fi + else + echo "Skill: $skill_name" + echo "Path: $skill_file" + echo "Description:" + head -n 5 "$skill_file" + fi + + echo "" + echo "---" + echo "" + done < <(find "$skills_dir" -type f -name 'SKILL.md' -print0) +} + +echo "Available Skills:" +echo "==================" +echo "" + +# Check project skills (new location first, then legacy) +process_skills_directory "$PROJECT_SKILLS_DIR" "Project Skills (.skills)" +process_skills_directory "$PROJECT_SKILLS_DIR_LEGACY" "Project Skills (.claude/skills - legacy)" + +# Check global skills (new location first, then legacy) +process_skills_directory "$GLOBAL_SKILLS_DIR" "Personal Skills (~/.skills)" +process_skills_directory "$GLOBAL_SKILLS_DIR_LEGACY" "Personal Skills (~/.claude/skills - legacy)" + +# If no skills found at all +if [[ ! -d "$PROJECT_SKILLS_DIR" && ! -d "$PROJECT_SKILLS_DIR_LEGACY" && ! -d "$GLOBAL_SKILLS_DIR" && ! -d "$GLOBAL_SKILLS_DIR_LEGACY" ]]; then + echo "No skills directories found." + echo "- Project skills: $PROJECT_SKILLS_DIR (or $PROJECT_SKILLS_DIR_LEGACY)" + echo "- Personal skills: $GLOBAL_SKILLS_DIR (or $GLOBAL_SKILLS_DIR_LEGACY)" +fi diff --git a/.claude/.skills/authoring-analysis/SKILL.md b/.claude/.skills/authoring-analysis/SKILL.md new file mode 100644 index 0000000..31ab51b --- /dev/null +++ b/.claude/.skills/authoring-analysis/SKILL.md @@ -0,0 +1,375 @@ +--- +name: authoring-analysis +description: Analyze content sequences and determine authoring approach (default content vs blocks). Validates block selection and section styling for import/migration to AEM Edge Delivery Services. +--- + +# Authoring Analysis + +Determine authoring approach for EACH content sequence: default content or specific block. + +## When to Use This Skill + +Use this skill when: +- You have page structure with content sequences (from identify-page-structure) +- You have block inventory (local + Block Collection) +- Ready to make authoring decisions following David's Model + +**Invoked by:** page-import skill (Step 3) + +## Prerequisites + +From identify-page-structure skill, you need: +- ✅ Section boundaries with styling notes +- ✅ Content sequences per section (neutral descriptions) +- ✅ Block inventory (local + Block Collection with purposes) +- ✅ screenshot.png for visual reference + +## Related Skills + +- **page-import** - Orchestrator that invokes this skill +- **identify-page-structure** - Provides section structure and block inventory +- **content-modeling** - This skill invokes it when block selection is unclear +- **block-collection-and-party** - This skill invokes it to validate blocks +- **generate-import-html** - Uses this skill's output to create HTML + +## **IMPORTANT: Step 3e Execution Trigger** + +After completing Step 3 (analyzing all sequences), you MUST execute Step 3e if: +- ✅ At least one section contains exactly ONE sequence that became a block +- ✅ That section has distinct background styling from identify-page-structure + +If NO sections meet these criteria → Skip Step 3e +If ANY sections meet these criteria → Execute Step 3e for EACH qualifying section + +## Authoring Analysis Workflow + +**Context:** You now have: +- Section boundaries with styles +- Content sequences per section +- Available block palette + +**FOR EACH content sequence, follow this mandatory process:** + +--- + +### Step 3a: MANDATORY - Default Content Check (FIRST!) + +**Question:** "Can an author create this with normal typing in Word/Google Docs?" + +**Default content means:** +- ✅ Headings, paragraphs, lists +- ✅ Inline images within text +- ✅ Simple quotes +- ✅ Just... typing content + +**NOT default content means:** +- ❌ Repeating structured patterns (card grids, feature lists) +- ❌ Interactive components (accordions, tabs, carousels) +- ❌ Complex layouts (side-by-side columns, split content) +- ❌ Requires specific structure for decoration + +**Decision:** +- If YES (can type normally) → **Mark as DEFAULT CONTENT, DONE** ✅ +- If NO (needs structure) → **Proceed to Step 3b** + +**Examples:** +``` +"Large centered heading, paragraph, two buttons" +→ Can author just type heading, paragraph, links? YES +→ Decision: DEFAULT CONTENT ✅ + +"Two centered buttons" +→ Can author just type two links? YES +→ Decision: DEFAULT CONTENT ✅ + +"Four items in grid, each with image, heading, description" +→ Can author just type this? NO - requires grid structure +→ Decision: Proceed to Step 3b ➡️ + +"Expandable questions and answers" +→ Can author just type this? NO - requires interaction/decoration +→ Decision: Proceed to Step 3b ➡️ +``` + +--- + +### Step 3b: Block Selection (ONLY IF NOT DEFAULT) + +**With block inventory context, ask:** "Which available block would an author choose for this?" + +**DECISION TREE: When to Invoke content-modeling** + +**OBVIOUS MATCH (Don't invoke content-modeling):** + +Pattern matches block purpose 1:1: +- "Grid of items with images/text" + see "cards" block → USE IT ✅ +- "Expandable questions" + see "accordion" block → USE IT ✅ +- "Tabbed content panels" + see "tabs" block → USE IT ✅ +- "Side-by-side content" + see "columns" block → USE IT ✅ +- "Rotating images" + see "carousel" block → USE IT ✅ + +**Criteria for OBVIOUS:** +- Content description matches block purpose exactly +- No ambiguity about structure +- Block exists in inventory + +**UNCLEAR MATCH (Invoke content-modeling):** + +Ambiguous which block to use: +- "Three items with images" - Could be cards? Could be columns? → INVOKE +- "List of features with icons" - Cards? Custom list block? → INVOKE +- "Customer quotes with photos" - Quote block? Cards? Testimonial block? → INVOKE + +Missing from inventory: +- Content needs structure BUT no matching block exists → INVOKE +- content-modeling can recommend canonical model or suggest creating custom block + +Complex authoring consideration: +- "Hero-like content but in middle of page" → INVOKE +- "Card-like items but only 2 of them" → INVOKE +- Need validation on author mental model → INVOKE + +**Criteria for UNCLEAR:** +- Multiple blocks could work +- No obvious block match +- Need authoring perspective validation +- Creating custom block might be needed + +--- + +### Step 3c: Validate Block Exists (IF NEEDED) + +**Only if block not in Block Collection common set:** + +Invoke **block-collection-and-party** skill to: +- Confirm block exists +- Get live example URL +- Review content model + +--- + +### Step 3d: Get Block HTML Structure (BEFORE generating HTML) + +**CRITICAL:** Before generating any HTML in next skill, fetch the pre-decoration HTML structure for ALL blocks you'll use. + +```bash +# Get structure examples for each block +node .claude/skills/block-collection-and-party/scripts/get-block-structure.js cards +node .claude/skills/block-collection-and-party/scripts/get-block-structure.js tabs +node .claude/skills/block-collection-and-party/scripts/get-block-structure.js accordion +node .claude/skills/block-collection-and-party/scripts/get-block-structure.js columns +``` + +**Why this prevents mistakes:** +- Shows exact row/column structure (e.g., cards: each card = 1 row with 2 columns) +- Reveals all variants (e.g., "Cards" vs "Cards (no images)") +- Displays clean HTML without decoration +- Prevents the #1 HTML generation error: wrong structure + +**Use the output to:** +1. Understand how many columns each row should have +2. See where images vs content go +3. Match your content to the correct variant +4. Generate HTML that matches the expected structure exactly + +--- + +### Step 3 Output Format + +**Complete analysis for all sequences:** + +``` +Section 1 (light): + - Sequence 1: "Large centered heading, paragraph, two call-to-action buttons" + → Decision: DEFAULT CONTENT + → Reason: Author can type heading, paragraph, links normally + → Note: Prominent styling is a CSS concern + + - Sequence 2: "Two images side-by-side" + → Decision: Columns block (2 columns) + → Reason: Side-by-side layout requires structure + → Obvious match with "columns" block in inventory + +Section 2 (light): + - Sequence 1: "Centered heading" + → Decision: DEFAULT CONTENT + → Reason: Just a heading - author types it + + - Sequence 2: "Grid of 8 items, each with icon and short text" + → Decision: Cards block + → Reason: Repeating structured pattern, needs block + → Obvious match with "cards" block in inventory + + - Sequence 3: "Two centered buttons" + → Decision: DEFAULT CONTENT + → Reason: Just two links - author types them + +Section 3 (grey): + - Sequence 1: "Eyebrow text, heading, paragraph, button stacked vertically" + → Decision: DEFAULT CONTENT + → Reason: Author types text and link normally + + - Sequence 2: "Four items in grid, each with image, category tag, heading, description" + → Decision: Cards block + → Reason: Repeating structured pattern + → Obvious match with "cards" block in inventory + +Section 4 (dark): + - Sequence 1: "Tab navigation with three switchable content panels" + → Decision: Tabs block + → Reason: Interactive component, needs decoration + → Obvious match with "tabs" block in inventory +``` + +--- + +### Step 3e: Validate Section Styling (Single-Block Sections Only) + +**⚠️ EXECUTION TRIGGER:** This step is executed AFTER Step 3 is complete. Execute this step if and only if: +- ✅ You have completed Step 3 (identified which sequences become blocks) +- ✅ At least one section contains exactly ONE sequence that became a block +- ✅ That section has distinct background styling from identify-page-structure + +**If NO sections meet these criteria → Skip Step 3e entirely and proceed to next skill** + +**If ANY sections meet these criteria → You MUST execute all sub-steps below for EACH qualifying section** + +--- + +**Why this validation matters:** + +When a section contains a single block, the background styling might be: +- **Block-specific design** (e.g., hero with dark background image) → Don't add section-metadata +- **Section container styling** (e.g., dark section with tabs block) → Add section-metadata + +Without validation, we risk adding unnecessary section-metadata that conflicts with block styling or makes authoring more complex. + +**Sections with multiple sequences:** Always keep section-metadata (styling applies to all content, not validated in Step 3e) + +--- + +**For EACH section with exactly one block, execute ALL these sub-steps:** + +**Sub-step 1: Identify the candidate sections** + +Review your Step 3 output. Find sections where: +- Section contains exactly 1 content sequence +- That sequence became a block (not default content) +- Section has distinct background styling from identify-page-structure + +**Example:** +``` +Section 1 (dark blue): + - Sequence 1: Large centered heading, paragraph, two buttons + → Decision: Hero block + +Section 3 (grey): + - Sequence 1: Tab navigation with three switchable panels + → Decision: Tabs block +``` + +--- + +**Sub-step 2: For each candidate section, examine the screenshot** + +Open screenshot.png and examine the section visually. + +**Ask these questions:** + +**Q1: Is the background an image (photo, gradient, illustration)?** +- If YES → Likely block-specific design +- If NO (solid color) → Continue to Q2 + +**Q2: Does the content fill the colored area edge-to-edge, or is there visible section padding?** +- Edge-to-edge (full-bleed) → Likely block-specific design +- Visible padding around content → Likely section container styling + +**Q3: Does the block type typically have its own background styling?** +- Hero, banner, full-width CTAs → Often have own backgrounds +- Tabs, accordion, cards, columns → Often use section backgrounds + +--- + +**Sub-step 3: Make the decision** + +Based on your analysis, decide for each single-block section: + +**SKIP section-metadata if:** +- Background is an image/gradient (block-specific) +- Content is full-bleed/edge-to-edge (no section padding visible) +- Block type typically has intrinsic background (hero, banner) + +**KEEP section-metadata if:** +- Background is solid color with visible section padding +- Block type typically inherits section styling (tabs, cards, accordion) +- Styling clearly provides container context (not block design) + +--- + +**Sub-step 4: Document your decisions** + +For each validated section, note: +- Section number +- Block type +- Background analysis (image vs solid, full-bleed vs padded) +- Decision (keep or skip section-metadata) +- Reason + +**Example output:** +``` +VALIDATED SECTIONS: + +Section 1 (dark blue): + - Block: Hero + - Background: Full-width dark blue gradient image + - Layout: Edge-to-edge, no visible section padding + - Decision: SKIP section-metadata + - Reason: Background is hero's design, not section styling + +Section 3 (grey): + - Block: Tabs + - Background: Solid grey (#f5f5f5) + - Layout: Content centered with visible padding (~80px on sides) + - Decision: KEEP section-metadata style="grey" + - Reason: Section provides container styling for tabs block +``` + +--- + +**When in doubt:** + +If you're uncertain whether background is block-specific or section-wide: +- **Default to KEEPING section-metadata** (safer, easier for authors to remove than add) +- **Add a note** in your documentation explaining the ambiguity +- Consider asking the user for guidance + +--- + +**Step 3e Completion Checklist:** + +Before proceeding to next skill, verify you have completed: +- ✅ Identified all single-block sections with background styling +- ✅ Examined original screenshot for EACH candidate section +- ✅ Answered Q1, Q2, Q3 for EACH candidate section +- ✅ Made skip/keep decision for EACH candidate section +- ✅ Documented reasoning for EACH decision +- ✅ Updated section styling notes with validated decisions + +--- + +## Final Output + +This skill provides complete authoring analysis: + +**1. Authoring decisions for all sequences:** +- Each sequence marked as DEFAULT CONTENT or specific block name +- Reasoning documented + +**2. Block structures fetched:** +- HTML structure examples for all blocks to be used + +**3. Section styling validation (if applicable):** +- Updated section list with validated styling decisions +- Some sections may be marked "no section-metadata" + +**Next step:** Pass these outputs to generate-import-html skill diff --git a/.claude/.skills/block-collection-and-party/SKILL.md b/.claude/.skills/block-collection-and-party/SKILL.md new file mode 100644 index 0000000..0615d31 --- /dev/null +++ b/.claude/.skills/block-collection-and-party/SKILL.md @@ -0,0 +1,453 @@ +--- +name: Using the Block Collection and Block Party +description: The Block Collection and Block Party are repositories for existing AEM blocks, build tools, code snippets, integration patterns, plugins, and more. Use this skill anytime you are developing something and want to find a reference to use as a starting point. +--- + +# Using the Block Collection and Block Party + +## Overview + +This skill helps you find reference implementations, code examples, and patterns from two key AEM Edge Delivery resources: + +- **Block Collection**: Adobe-maintained reference blocks following best practices +- **Block Party**: Community-driven repository of blocks, plugins, tools, and integrations + +Use the provided search scripts to discover relevant examples, then review the code to inform your implementation approach. + +## When to Use This Skill + +Use this skill when: +- Building a new block and want to see if similar implementations exist +- Looking for code patterns or snippets to solve a specific problem +- Searching for integration examples (e.g., third-party services, build tools) +- Need reference implementations for sidekick or Document Authoring plugins +- Want to understand best practices through working examples + +**Do NOT use this skill when:** +- You need official documentation (use `docs-search` instead) +- You're making minor CSS tweaks to existing code (just edit directly) +- You already know exactly which block/example you need (use it directly) + +## Related Skills + +- **building-blocks**: This skill is called from building-blocks during development +- **docs-search**: Use for official aem.live documentation +- **content-driven-development**: Use when creating content models for blocks + +## Key Concepts + +### Block Collection vs Block Party + +**Block Collection** (Prefer this when available) +- Maintained by Adobe +- Vetted for best practices +- Excellent content modeling +- High performance and accessibility standards +- Limited to commonly-needed blocks +- Documentation: https://www.aem.live/developer/block-collection +- Repository: https://github.com/adobe/aem-block-collection +- Live site: https://main--aem-block-collection--adobe.aem.live + +**Block Party** (Use for specialized needs) +- Community-driven contributions +- Broader variety of content types +- Includes experimental/innovative approaches +- Only approved entries are returned by the search script +- Contains blocks, plugins, build tools, integrations, and more +- Documentation: https://www.aem.live/developer/block-party/ +- Search index: https://www.aem.live/developer/block-party/block-party.json?sheet=curated-list-new + +**When to prefer which:** +- Start with Block Collection for standard blocks (carousels, accordions, cards, etc.) +- Use Block Party when Block Collection doesn't have what you need +- Block Party is the only source for sidekick plugins, build tools, and integrations +- Sometimes Block Party has innovative approaches worth considering even if Block Collection has a similar block + +## How to Use This Skill + +### Step 1: Identify Search Terms + +Determine what you're looking for and identify relevant search terms. **Think about similar or alternative names** for the functionality. + +**Examples:** +- Looking for FAQ block → search for "faq" AND "accordion" (Block Collection has accordion) +- Looking for image gallery → search for "gallery", "carousel", "slideshow" +- Looking for navigation → search for "navigation", "menu", "header" +- Looking for build tooling → search for "webpack", "vite", "sass", "typescript" + +**Good search terms:** +- Specific functionality names: "carousel", "tabs", "modal" +- Tool names: "sass", "webpack", "target" +- Component types: "navigation", "footer", "hero" + +**Poor search terms:** +- Too generic: "content", "page", "website" +- Too specific: "my-custom-carousel-with-auto-play" + +### Step 2: Search Block Collection + +**IMPORTANT:** Run BOTH search scripts in parallel for comprehensive results: + +```bash +# Run both searches in parallel (preferred approach) +node .claude/skills/block-collection-and-party/scripts/search-block-collection-github.js & \ +node .claude/skills/block-collection-and-party/scripts/search-block-collection.js & \ +wait +``` + +**Why use both scripts:** +- `search-block-collection-github.js` - Searches actual repository folders via GitHub API (most comprehensive) +- `search-block-collection.js` - Searches navigation page (provides display names and catches edge cases) +- Running both ensures maximum coverage and catches blocks that might be missed by either approach alone + +**Examples:** +```bash +# Search for accordion/FAQ blocks (both scripts) +node .claude/skills/block-collection-and-party/scripts/search-block-collection-github.js accordion & \ +node .claude/skills/block-collection-and-party/scripts/search-block-collection.js accordion & \ +wait + +# Search for embed block (both scripts) +node .claude/skills/block-collection-and-party/scripts/search-block-collection-github.js embed & \ +node .claude/skills/block-collection-and-party/scripts/search-block-collection.js embed & \ +wait + +# If running both is problematic, prioritize the GitHub API version +node .claude/skills/block-collection-and-party/scripts/search-block-collection-github.js carousel +``` + +### Step 3: Search Block Party + +Execute the Block Party search script from the project root: + +```bash +node .claude/skills/block-collection-and-party/scripts/search-block-party.js [--category ] [additional-terms...] +``` + +**Options:** +- `--category `: Filter by specific category (Block, Sidekick Plugin, DA Plugin, Code Snippet, Build Tooling, etc.) +- Without `--category`: Searches all categories + +**Examples:** +```bash +# Search for breadcrumb blocks +node .claude/skills/block-collection-and-party/scripts/search-block-party.js breadcrumb + +# Search for Sass integration examples +node .claude/skills/block-collection-and-party/scripts/search-block-party.js sass + +# Search only for build tooling +node .claude/skills/block-collection-and-party/scripts/search-block-party.js --category "Build Tooling" webpack + +# Multi-word search +node .claude/skills/block-collection-and-party/scripts/search-block-party.js adobe target integration +``` + +### Step 4: Review Search Results + +**Block Collection Results (type: "block"):** +```json +{ + "query": "accordion", + "source": "Adobe AEM Block Collection", + "totalItems": 26, + "matchCount": 1, + "results": [ + { + "name": "accordion", + "displayName": "Accordion", + "type": "block", + "liveExampleUrl": "https://main--aem-block-collection--adobe.aem.live/block-collection/accordion", + "jsUrl": "https://github.com/adobe/aem-block-collection/blob/main/blocks/accordion/accordion.js", + "cssUrl": "https://github.com/adobe/aem-block-collection/blob/main/blocks/accordion/accordion.css" + } + ] +} +``` + +**Block Collection Results (type: "default-content"):** +```json +{ + "query": "breadcrumb", + "source": "Adobe AEM Block Collection", + "totalItems": 26, + "matchCount": 1, + "results": [ + { + "name": "breadcrumbs", + "displayName": "Breadcrumbs", + "type": "default-content", + "liveExampleUrl": "https://main--aem-block-collection--adobe.aem.live/block-collection/breadcrumbs", + "note": "This is default content documentation, not a standalone block. Code may be part of other blocks (e.g., breadcrumbs are in the header block). Visit https://www.aem.live/developer/block-collection and the live example URL for implementation guidance.", + "documentationUrl": "https://www.aem.live/developer/block-collection" + } + ] +} +``` + +**Block Party Results:** +```json +{ + "query": "breadcrumb", + "category": "All categories", + "source": "AEM Block Party (Approved Only)", + "totalEntries": 90, + "approvedEntries": 62, + "matchCount": 1, + "results": [ + { + "title": "Breadcrumbs", + "category": "Block", + "description": "A breadcrumb navigation component...", + "githubUrl": "https://github.com/...", + "showcaseUrl": "https://...", + "githubProfile": "https://github.com/..." + } + ] +} +``` + +### Step 5: Get Block Structure Examples (CRITICAL for HTML generation) + +**IMPORTANT:** Before writing any HTML for a block, ALWAYS fetch the pre-decoration structure examples first. + +```bash +node .claude/skills/block-collection-and-party/scripts/get-block-structure.js +``` + +**Why this is critical:** +- Shows the exact HTML structure the block expects BEFORE JavaScript decoration +- Reveals the row/column pattern (e.g., each card is a row with 2 columns: image | content) +- Displays multiple variants (e.g., "Cards" vs "Cards (no images)") +- Prevents HTML structure mistakes that cause blocks to fail decoration + +**Examples:** +```bash +# Get accordion structure +node .claude/skills/block-collection-and-party/scripts/get-block-structure.js accordion + +# Get cards structure (will show multiple variants) +node .claude/skills/block-collection-and-party/scripts/get-block-structure.js cards + +# Get tabs structure +node .claude/skills/block-collection-and-party/scripts/get-block-structure.js tabs +``` + +**Output includes:** +- Block description and source code URL +- All available variants with their names +- Pre-decoration HTML for each variant (simplified, without image optimization noise) +- Structural analysis (rows, columns, content types per column) + +**When to use:** +- ✅ Before generating HTML for page migration +- ✅ Before writing block content in HTML files +- ✅ When block decoration is failing (verify your HTML matches expected structure) +- ✅ When uncertain about content model (e.g., "Is each card a row or all cards in one row?") + +**This step prevents the most common mistake:** Writing incorrect HTML structure that doesn't match what the block's JavaScript decoration expects. + +### Step 6: Examine the Code + +Use the provided URLs to review the implementation: + +**For Block Collection results with `type: "block"`:** +1. **FIRST:** Get block structure examples (Step 5) to understand the expected HTML +2. Read the JS file to understand decoration logic +3. Read the CSS file to see styling approach +4. Visit the live example URL to see the block in action + +**For Block Collection results with `type: "default-content"`:** +1. These represent standard HTML elements and patterns (breadcrumbs, buttons, headings, etc.) +2. Code exists but may be part of other blocks (e.g., breadcrumbs code is in the header block) +3. Visit the `documentationUrl` (https://www.aem.live/developer/block-collection) to find implementation details +4. Visit the `liveExampleUrl` to see examples and understand how to author the content +5. Search the Block Collection repository for related blocks that might contain the implementation + +**For Block Party entries:** +1. Visit the GitHub URL to see the code +2. Visit the showcase URL to see it in action (if available) +3. Review the description to understand the purpose and approach + +### Step 7: Apply Learnings + +Use the reference implementations to inform your approach: +- Understand the content model used +- Study decoration patterns and techniques +- Review CSS architecture and responsive approaches +- Adapt (don't copy) the code to fit your specific needs +- Ensure you follow your project's coding standards + +## Search Behavior Details + +### Block Collection Search + +- Searches block folder names in the GitHub repository +- Returns exact and partial matches (case-insensitive) +- Provides direct links to JS, CSS, and live examples +- Fast and reliable (limited to ~16 blocks) + +### Block Party Search + +- Searches title, description, and category fields +- Supports category filtering +- Returns all matching entries (not limited) +- Shows approval status for each entry +- Includes diverse content types beyond blocks + +## Examples + +### Example 1: Building an FAQ Block + +**User Request:** "I need to build an FAQ section with expandable questions" + +**Good Approach:** +1. Recognize FAQ often uses accordion pattern +2. Search Block Collection with both scripts: + ```bash + node .claude/skills/block-collection-and-party/scripts/search-block-collection-github.js accordion & \ + node .claude/skills/block-collection-and-party/scripts/search-block-collection.js accordion & \ + wait + ``` +3. Review results from both searches (they should align, but running both ensures nothing is missed) +4. Find the accordion block with JS, CSS, and live example URLs +5. Review the implementation approach +6. Adapt the pattern to your specific FAQ needs + +**Why this works:** +- Used alternative term "accordion" for "FAQ" +- Started with Block Collection (Adobe best practices) +- Ran both search scripts for comprehensive coverage +- Found a vetted, accessible, performant implementation + +### Example 2: Finding Breadcrumb Implementation + +**User Request:** "Add breadcrumb navigation to the site" + +**Good Approach:** +1. Search Block Collection first with both scripts: + ```bash + node .claude/skills/block-collection-and-party/scripts/search-block-collection-github.js breadcrumb & \ + node .claude/skills/block-collection-and-party/scripts/search-block-collection.js breadcrumb & \ + wait + ``` +2. Find that breadcrumbs is "default-content" (not a standalone block) +3. Search Block Party: `node .claude/skills/block-collection-and-party/scripts/search-block-party.js breadcrumb` +4. Find breadcrumb block in Block Party +5. Review the implementation, noting it's community-contributed +6. Evaluate if it meets your needs or needs adaptation + +**Why this works:** +- Checked Block Collection first with both scripts (best practices) +- Discovered breadcrumbs exist in Block Collection but as default content (part of header block) +- Fell back to Block Party for standalone implementation +- Aware that Block Party code may need more review + +### Example 3: Integrating Sass + +**User Request:** "Can we use Sass for our styles instead of plain CSS?" + +**Good Approach:** +1. Recognize this is a build tooling question (not a block) +2. Skip Block Collection (doesn't have build tools) +3. Search Block Party: `node .claude/skills/block-collection-and-party/scripts/search-block-party.js --category "Build Tooling" sass` +4. Find Sass integration examples +5. Review the approach and adapt to your project + +**Why this works:** +- Recognized Block Party is the right resource for build tools +- Used category filter to narrow results +- Found community examples of the integration + +### Example 4: Multiple Implementations Exist + +**User Request:** "Build a carousel for product images" + +**Scenario:** Both Block Collection and Block Party have carousel implementations + +**Good Approach:** +1. Search Block Collection with both scripts: + ```bash + node .claude/skills/block-collection-and-party/scripts/search-block-collection-github.js carousel & \ + node .claude/skills/block-collection-and-party/scripts/search-block-collection.js carousel & \ + wait + ``` +2. Find Block Collection carousel from both search results +3. Also search Block Party: `node .claude/skills/block-collection-and-party/scripts/search-block-party.js carousel` +4. Find multiple Block Party carousels +5. **Prefer Block Collection** for best practices +6. Review Block Party versions to see if they have innovative features worth considering +7. Make informed decision based on requirements + +**Why this works:** +- Searched Block Collection with both scripts for comprehensive coverage +- Searched Block Party to see all options +- Defaulted to Block Collection (Adobe vetted) +- Considered Block Party for potential innovations +- Made an informed decision rather than blindly copying + +## Important Reminders + +1. **Always search for alternative names** - "FAQ" = "accordion", "slideshow" = "carousel" +2. **Prefer Block Collection when available** - it's vetted for quality and best practices +3. **Use Block Party for specialized needs** - it has broader variety but needs more evaluation +4. **Don't copy blindly** - understand the code and adapt it to your project +5. **Review content models carefully** - how authors structure content is critical +6. **Check accessibility and performance** - especially for Block Party code +7. **Search both resources** - sometimes both have implementations with different trade-offs +8. **Category matters for Block Party** - use filters when you know what type you need + +## Common Search Patterns + +| Need | Block Collection Search | Block Party Search | +|------|------------------------|-------------------| +| FAQ section | `accordion` | `faq`, `accordion` | +| Image gallery | `carousel` | `gallery`, `carousel`, `slideshow` | +| Tabbed content | `tabs` | `tabs`, `tabbed` | +| Navigation | `header` | `navigation`, `menu`, `header` | +| Footer | `footer` | `footer` | +| Product cards | `cards` | `cards`, `product` | +| Video embed | `video`, `embed` | `video`, `embed`, `youtube` | +| Build tools | N/A | Use `--category "Build Tooling"` | +| Sidekick plugins | N/A | Use `--category "Sidekick Plugin"` | +| Integrations | N/A | Search for service name (e.g., `target`, `analytics`) | + +## Troubleshooting + +**No results from both Block Collection scripts:** +- Running both scripts ensures comprehensive coverage +- If neither script returns results, the block likely doesn't exist in Block Collection +- Try alternative search terms (e.g., "embed" vs "video", "faq" vs "accordion") +- Fall back to Block Party search +- If user insists the block exists, use WebFetch to manually check: + - `https://github.com/adobe/aem-block-collection/tree/main/blocks` +- Consider building from scratch with guidance from `building-blocks` skill + +**Different results between the two scripts:** +- This is normal - the GitHub API script searches folder names, the nav script searches the navigation +- Both results are valid - review both to ensure you haven't missed anything +- Prefer GitHub API results if there's a discrepancy (it's more direct) + +**IMPORTANT - When search returns no results but block likely exists:** +- Don't immediately accept "no results" as definitive +- Running both scripts maximizes chances of finding existing blocks +- If the user suggests a block should exist, investigate further +- Common blocks that may exist: embed, video, form, consent-management +- Use WebFetch to manually browse the GitHub repo +- Cross-reference with blocks you know exist (like video, accordion, carousel) + +**Too many results in Block Party:** +- Use `--category` to filter +- Refine search terms to be more specific +- Review descriptions to find best matches + +**Found code but seems outdated:** +- Check Block Collection for newer patterns +- Review official docs with `docs-search` skill +- Consider using as inspiration but implementing with modern approaches + +**Multiple implementations, unsure which to use:** +- Prefer Block Collection for standard functionality +- Choose Block Party for specialized or innovative features +- Consider your specific requirements (performance, accessibility, features) +- Review code quality and documentation before deciding diff --git a/.claude/.skills/block-collection-and-party/scripts/get-block-structure.js b/.claude/.skills/block-collection-and-party/scripts/get-block-structure.js new file mode 100644 index 0000000..31c8888 --- /dev/null +++ b/.claude/.skills/block-collection-and-party/scripts/get-block-structure.js @@ -0,0 +1,240 @@ +#!/usr/bin/env node + +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/** + * Fetch and parse block structure examples from Adobe Block Collection + * + * This script retrieves the .plain.html version of a block's documentation page, + * which contains pre-decoration HTML structure examples showing the expected + * content model for the block. + * + * Usage: + * node get-block-structure.js + * node get-block-structure.js accordion + * node get-block-structure.js cards + * + * Output: JSON with block metadata, variants, and HTML structure examples + */ + +import { JSDOM } from 'jsdom'; + +const BLOCK_COLLECTION_BASE = 'https://main--aem-block-collection--adobe.aem.live'; + +/** + * Simplify HTML by removing optimized picture elements and showing essential structure + */ +function simplifyHTML(html) { + // Remove picture optimization attributes that clutter the output + let simplified = html + .replace(/\s+srcset="[^"]*"/g, '') + .replace(/\s+type="[^"]*"/g, '') + .replace(/\s+media="[^"]*"/g, '') + .replace(/\s+loading="[^"]*"/g, '') + .replace(/\s+width="[^"]*"/g, '') + .replace(/\s+height="[^"]*"/g, '') + .replace(/\?width=[^"'\s]*/g, ''); // Remove image optimization params + + // Replace multi-line picture elements with simplified version + simplified = simplified.replace( + /\s*(]*>\s*)*\s*]*?)src="([^"]*)"([^>]*?)>\s*<\/picture>/gs, + (match, sources, beforeSrc, src, afterSrc) => { + const altMatch = match.match(/alt="([^"]*)"/); + const alt = altMatch ? ` alt="${altMatch[1]}"` : ''; + return ``; + } + ); + + // Compact whitespace but preserve structure + simplified = simplified + .replace(/>\s+\n<') + .replace(/\n\s*\n/g, '\n') + .trim(); + + return simplified; +} + +/** + * Analyze HTML structure to provide a human-readable description + */ +function analyzeStructure(html, blockName) { + const dom = new JSDOM(html); + const doc = dom.window.document; + const block = doc.querySelector(`.${blockName}`); + + if (!block) return 'Structure analysis unavailable'; + + const rows = Array.from(block.children); + const analysis = []; + + analysis.push(`Block has ${rows.length} row(s)`); + + rows.forEach((row, i) => { + const cols = Array.from(row.children); + const colDescriptions = cols.map(col => { + const elements = []; + if (col.querySelector('picture')) elements.push('image'); + if (col.querySelector('h1, h2, h3, h4, h5, h6')) elements.push('heading'); + if (col.querySelector('p')) elements.push('paragraph(s)'); + if (col.querySelector('ul, ol')) elements.push('list'); + if (col.querySelector('a')) elements.push('link(s)'); + return elements.length > 0 ? elements.join(', ') : 'content'; + }); + + analysis.push(` Row ${i + 1}: ${cols.length} column(s) [${colDescriptions.join(' | ')}]`); + }); + + return analysis.join('\n'); +} + +/** + * Fetch and parse block structure from Block Collection + */ +async function getBlockStructure(blockName) { + const url = `${BLOCK_COLLECTION_BASE}/block-collection/${blockName}.plain.html`; + + try { + const response = await fetch(url); + + if (!response.ok) { + if (response.status === 404) { + return { + success: false, + error: `Block "${blockName}" not found in Block Collection`, + url, + suggestion: 'Use search-block-collection-github.js to find available blocks' + }; + } + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const html = await response.text(); + const dom = new JSDOM(html); + const doc = dom.window.document; + + // Find all sections (top-level divs in body) + const sections = Array.from(doc.body.children); + + const variants = []; + let description = null; + let sourceCodeUrl = null; + + // Process each section + sections.forEach(section => { + const metadata = section.querySelector('.library-metadata'); + + if (metadata) { + // Extract metadata + const metadataRows = Array.from(metadata.children); + const metadataObj = {}; + + metadataRows.forEach(row => { + const cells = Array.from(row.children); + if (cells.length === 2) { + const key = cells[0].textContent.trim(); + const value = cells[1].textContent.trim(); + metadataObj[key] = value; + + // Extract source code link if present + const link = cells[1].querySelector('a[href*="github.com"]'); + if (link) { + sourceCodeUrl = link.href; + } + } + }); + + // Store description (usually in last metadata block) + if (metadataObj.description) { + description = metadataObj.description.replace(/\s*Source Code\s*$/, '').trim(); + } + + // Find the block content (div with class matching block name) + const blockDiv = section.querySelector(`[class*="${blockName}"]`); + + if (blockDiv && metadataObj.name) { + const blockHTML = blockDiv.outerHTML; + const simplifiedHTML = simplifyHTML(blockHTML); + const structure = analyzeStructure(blockHTML, blockName); + + variants.push({ + name: metadataObj.name, + html: simplifiedHTML, + structure + }); + } + } + }); + + if (variants.length === 0) { + return { + success: false, + error: `No block examples found in ${url}`, + url + }; + } + + return { + success: true, + blockName, + url, + description, + sourceCodeUrl, + liveExampleUrl: `${BLOCK_COLLECTION_BASE}/block-collection/${blockName}`, + variants, + totalVariants: variants.length, + usage: { + purpose: 'Pre-decoration HTML structure examples', + note: 'This shows the HTML structure BEFORE JavaScript decoration. Use this to understand the expected content model when authoring content or generating HTML.' + } + }; + + } catch (error) { + return { + success: false, + error: error.message, + url, + blockName + }; + } +} + +// CLI execution +async function main() { + const blockName = process.argv[2]; + + if (!blockName) { + console.error(JSON.stringify({ + success: false, + error: 'Missing block name argument', + usage: 'node get-block-structure.js ', + examples: [ + 'node get-block-structure.js accordion', + 'node get-block-structure.js cards', + 'node get-block-structure.js tabs' + ] + }, null, 2)); + process.exit(1); + } + + const result = await getBlockStructure(blockName); + console.log(JSON.stringify(result, null, 2)); + + if (!result.success) { + process.exit(1); + } +} + +// Run if called directly +if (import.meta.url === `file://${process.argv[1]}`) { + main(); +} diff --git a/.claude/.skills/block-collection-and-party/scripts/package.json b/.claude/.skills/block-collection-and-party/scripts/package.json new file mode 100644 index 0000000..3dbc1ca --- /dev/null +++ b/.claude/.skills/block-collection-and-party/scripts/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/.claude/.skills/block-collection-and-party/scripts/search-block-collection-github.js b/.claude/.skills/block-collection-and-party/scripts/search-block-collection-github.js new file mode 100644 index 0000000..7084501 --- /dev/null +++ b/.claude/.skills/block-collection-and-party/scripts/search-block-collection-github.js @@ -0,0 +1,165 @@ +#!/usr/bin/env node + +/** + * Search the Adobe AEM Block Collection for blocks matching a search term + * This version uses GitHub API to get actual block folders + * + * Usage: node search-block-collection-github.js + * + * Example: node search-block-collection-github.js accordion + */ + +import https from 'https'; + +// Block Collection constants +const REPO_OWNER = 'adobe'; +const REPO_NAME = 'aem-block-collection'; +const BLOCKS_PATH = 'blocks'; +const REPO_BASE_URL = `https://github.com/${REPO_OWNER}/${REPO_NAME}`; +const SITE_BASE_URL = 'https://main--aem-block-collection--adobe.aem.live'; +const API_URL = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/contents/${BLOCKS_PATH}`; + +/** + * Fetch JSON from a URL using HTTPS + */ +function fetchUrl(url) { + return new Promise((resolve, reject) => { + https.get(url, { + headers: { + 'User-Agent': 'AEM-Block-Search-Script', + 'Accept': 'application/vnd.github.v3+json' + } + }, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + if (res.statusCode === 200) { + try { + resolve(JSON.parse(data)); + } catch (e) { + reject(new Error(`Failed to parse JSON: ${e.message}`)); + } + } else { + reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`)); + } + }); + }).on('error', (err) => { + reject(err); + }); + }); +} + +/** + * Get all block folders from the GitHub repository + */ +async function getBlocks() { + try { + const contents = await fetchUrl(API_URL); + + // Filter for directories only + return contents + .filter(item => item.type === 'dir') + .map(item => ({ + name: item.name, + displayName: item.name.split('-').map(word => + word.charAt(0).toUpperCase() + word.slice(1) + ).join(' ') + })); + } catch (error) { + throw new Error(`Failed to fetch Block Collection from GitHub: ${error.message}`); + } +} + +/** + * Determine if a block is "Default Content" (no block code) or a "Sample Block" + */ +function isDefaultContent(blockName) { + // Default Content items don't have block implementations + const defaultContentItems = [ + 'breadcrumbs', 'buttons', 'code', 'headings', 'icons', 'images', + 'links', 'lists', 'metadata', 'section-metadata', 'sections', 'text' + ]; + return defaultContentItems.includes(blockName.toLowerCase()); +} + +/** + * Search for blocks matching the search term + */ +function searchBlocks(blocks, searchTerm) { + const lowerSearchTerm = searchTerm.toLowerCase(); + + return blocks + .filter(block => { + // Search in both the URL slug and display name + return block.name.toLowerCase().includes(lowerSearchTerm) || + block.displayName.toLowerCase().includes(lowerSearchTerm); + }) + .map(block => { + const result = { + name: block.name, + displayName: block.displayName, + type: isDefaultContent(block.name) ? 'default-content' : 'block', + liveExampleUrl: `${SITE_BASE_URL}/block-collection/${block.name}` + }; + + // Only add code URLs for actual blocks (not default content) + if (result.type === 'block') { + result.jsUrl = `${REPO_BASE_URL}/blob/main/${BLOCKS_PATH}/${block.name}/${block.name}.js`; + result.cssUrl = `${REPO_BASE_URL}/blob/main/${BLOCKS_PATH}/${block.name}/${block.name}.css`; + } else { + result.note = 'This is default content documentation, not a standalone block. Code may be part of other blocks (e.g., breadcrumbs are in the header block). Visit https://www.aem.live/developer/block-collection and the live example URL for implementation guidance.'; + result.documentationUrl = 'https://www.aem.live/developer/block-collection'; + } + + return result; + }); +} + +/** + * Main function + */ +async function main() { + const args = process.argv.slice(2); + + if (args.length === 0) { + console.error('Usage: node search-block-collection-github.js '); + console.error('Example: node search-block-collection-github.js accordion'); + process.exit(1); + } + + const searchTerm = args[0]; + + try { + // Fetch all blocks from GitHub API + const blocks = await getBlocks(); + + // Search for matching blocks + const results = searchBlocks(blocks, searchTerm); + + // Output results as JSON + const output = { + query: searchTerm, + source: 'Adobe AEM Block Collection (via GitHub API)', + repository: `${REPO_BASE_URL}`, + totalItems: blocks.length, + matchCount: results.length, + results: results + }; + + console.log(JSON.stringify(output, null, 2)); + + // Exit with code 0 if results found, 1 if no results + process.exit(results.length > 0 ? 0 : 1); + + } catch (error) { + console.error('Error:', error.message); + process.exit(1); + } +} + +// Run the script +main(); diff --git a/.claude/.skills/block-collection-and-party/scripts/search-block-collection.js b/.claude/.skills/block-collection-and-party/scripts/search-block-collection.js new file mode 100755 index 0000000..1ad1ea7 --- /dev/null +++ b/.claude/.skills/block-collection-and-party/scripts/search-block-collection.js @@ -0,0 +1,181 @@ +#!/usr/bin/env node + +/** + * Search the Adobe AEM Block Collection for blocks matching a search term + * + * Usage: node search-block-collection.js + * + * Example: node search-block-collection.js accordion + */ + +import https from 'https'; + +// Block Collection constants +const REPO_OWNER = 'adobe'; +const REPO_NAME = 'aem-block-collection'; +const BLOCKS_PATH = 'blocks'; +const REPO_BASE_URL = `https://github.com/${REPO_OWNER}/${REPO_NAME}`; +const SITE_BASE_URL = 'https://main--aem-block-collection--adobe.aem.live'; +const NAV_URL = `${SITE_BASE_URL}/nav.plain.html`; + +/** + * Fetch HTML/text from a URL using HTTPS + */ +function fetchUrl(url, parseJson = false) { + return new Promise((resolve, reject) => { + https.get(url, { + headers: { + 'User-Agent': 'AEM-Block-Search-Script' + } + }, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + if (res.statusCode === 200) { + if (parseJson) { + try { + resolve(JSON.parse(data)); + } catch (e) { + reject(new Error(`Failed to parse JSON: ${e.message}`)); + } + } else { + resolve(data); + } + } else { + reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`)); + } + }); + }).on('error', (err) => { + reject(err); + }); + }); +} + +/** + * Parse nav.plain.html to extract block names + */ +function parseBlocksFromNav(html) { + const blocks = []; + + // Match all Display Name + const linkPattern = /([^<]+)<\/a>/g; + let match; + + while ((match = linkPattern.exec(html)) !== null) { + const urlSlug = match[1]; + const displayName = match[2]; + + blocks.push({ + name: urlSlug, + displayName: displayName + }); + } + + return blocks; +} + +/** + * Get all blocks from the Block Collection navigation + */ +async function getBlocks() { + try { + const html = await fetchUrl(NAV_URL, false); + return parseBlocksFromNav(html); + } catch (error) { + throw new Error(`Failed to fetch Block Collection navigation: ${error.message}`); + } +} + +/** + * Determine if a block is "Default Content" (no block code) or a "Sample Block" + */ +function isDefaultContent(blockName) { + // Default Content items don't have block implementations + const defaultContentItems = [ + 'breadcrumbs', 'buttons', 'code', 'headings', 'icons', 'images', + 'links', 'lists', 'metadata', 'section-metadata', 'sections', 'text' + ]; + return defaultContentItems.includes(blockName.toLowerCase()); +} + +/** + * Search for blocks matching the search term + */ +function searchBlocks(blocks, searchTerm) { + const lowerSearchTerm = searchTerm.toLowerCase(); + + return blocks + .filter(block => { + // Search in both the URL slug and display name + return block.name.toLowerCase().includes(lowerSearchTerm) || + block.displayName.toLowerCase().includes(lowerSearchTerm); + }) + .map(block => { + const result = { + name: block.name, + displayName: block.displayName, + type: isDefaultContent(block.name) ? 'default-content' : 'block', + liveExampleUrl: `${SITE_BASE_URL}/block-collection/${block.name}` + }; + + // Only add code URLs for actual blocks (not default content) + if (result.type === 'block') { + result.jsUrl = `${REPO_BASE_URL}/blob/main/${BLOCKS_PATH}/${block.name}/${block.name}.js`; + result.cssUrl = `${REPO_BASE_URL}/blob/main/${BLOCKS_PATH}/${block.name}/${block.name}.css`; + } else { + result.note = 'This is default content documentation, not a standalone block. Code may be part of other blocks (e.g., breadcrumbs are in the header block). Visit https://www.aem.live/developer/block-collection and the live example URL for implementation guidance.'; + result.documentationUrl = 'https://www.aem.live/developer/block-collection'; + } + + return result; + }); +} + +/** + * Main function + */ +async function main() { + const args = process.argv.slice(2); + + if (args.length === 0) { + console.error('Usage: node search-block-collection.js '); + console.error('Example: node search-block-collection.js accordion'); + process.exit(1); + } + + const searchTerm = args[0]; + + try { + // Fetch all blocks from navigation + const blocks = await getBlocks(); + + // Search for matching blocks + const results = searchBlocks(blocks, searchTerm); + + // Output results as JSON + const output = { + query: searchTerm, + source: 'Adobe AEM Block Collection', + repository: `${REPO_BASE_URL}`, + totalItems: blocks.length, + matchCount: results.length, + results: results + }; + + console.log(JSON.stringify(output, null, 2)); + + // Exit with code 0 if results found, 1 if no results + process.exit(results.length > 0 ? 0 : 1); + + } catch (error) { + console.error('Error:', error.message); + process.exit(1); + } +} + +// Run the script +main(); diff --git a/.claude/.skills/block-collection-and-party/scripts/search-block-party.js b/.claude/.skills/block-collection-and-party/scripts/search-block-party.js new file mode 100755 index 0000000..8528dff --- /dev/null +++ b/.claude/.skills/block-collection-and-party/scripts/search-block-party.js @@ -0,0 +1,331 @@ +#!/usr/bin/env node + +/** + * Search the AEM Block Party index for blocks, plugins, tools, and integrations + * + * Note: Only searches approved entries from the Block Party index + * + * Usage: node search-block-party.js [--category ] [additional-terms...] + * + * Examples: + * node search-block-party.js breadcrumb + * node search-block-party.js --category "Build Tooling" sass + * node search-block-party.js adobe target integration + */ + +import https from 'https'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Block Party constants +const BLOCK_PARTY_INDEX_URL = 'https://www.aem.live/developer/block-party/block-party.json?sheet=curated-list-new'; +const CACHE_DIR = path.join(__dirname, '..', '.cache'); +const CACHE_FILE = path.join(CACHE_DIR, 'block-party-index.json'); +const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours in milliseconds + +/** + * Fetch data from a URL using HTTPS + */ +function fetchUrl(url) { + return new Promise((resolve, reject) => { + https.get(url, { + headers: { + 'User-Agent': 'AEM-Block-Party-Search-Script' + } + }, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + if (res.statusCode === 200) { + try { + resolve(JSON.parse(data)); + } catch (e) { + reject(new Error(`Failed to parse JSON: ${e.message}`)); + } + } else { + reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`)); + } + }); + }).on('error', (err) => { + reject(err); + }); + }); +} + +/** + * Check if cached index is still valid + */ +function isCacheValid() { + if (!fs.existsSync(CACHE_FILE)) { + return false; + } + + const stats = fs.statSync(CACHE_FILE); + const age = Date.now() - stats.mtimeMs; + + return age < CACHE_TTL; +} + +/** + * Load index from cache + */ +function loadFromCache() { + try { + const data = fs.readFileSync(CACHE_FILE, 'utf8'); + return JSON.parse(data); + } catch (error) { + return null; + } +} + +/** + * Save index to cache + */ +function saveToCache(data) { + try { + if (!fs.existsSync(CACHE_DIR)) { + fs.mkdirSync(CACHE_DIR, { recursive: true }); + } + fs.writeFileSync(CACHE_FILE, JSON.stringify(data, null, 2), 'utf8'); + } catch (error) { + // Silently fail - caching is not critical + console.error('Warning: Failed to save cache:', error.message); + } +} + +/** + * Fetch the Block Party index (with caching) + */ +async function getBlockPartyIndex() { + // Try cache first + if (isCacheValid()) { + const cached = loadFromCache(); + if (cached) { + return cached; + } + } + + // Fetch fresh data + try { + const data = await fetchUrl(BLOCK_PARTY_INDEX_URL); + saveToCache(data); + return data; + } catch (error) { + throw new Error(`Failed to fetch Block Party index: ${error.message}`); + } +} + +/** + * Get all unique categories from entries (case-insensitive, preserving first occurrence) + */ +function getUniqueCategories(entries) { + const categoryMap = new Map(); // lowercase -> original case + entries.forEach(entry => { + if (entry.category && entry.category.trim()) { + const trimmed = entry.category.trim(); + const lower = trimmed.toLowerCase(); + if (!categoryMap.has(lower)) { + categoryMap.set(lower, trimmed); + } + } + }); + return Array.from(categoryMap.values()).sort((a, b) => + a.toLowerCase().localeCompare(b.toLowerCase()) + ); +} + +/** + * Validate category against available categories + */ +function validateCategory(category, availableCategories) { + if (!category) { + return { valid: true }; + } + + const lowerCategory = category.toLowerCase(); + const matchingCategory = availableCategories.find(cat => + cat.toLowerCase() === lowerCategory || cat.toLowerCase().includes(lowerCategory) + ); + + if (!matchingCategory) { + return { + valid: false, + error: `Category "${category}" not found.`, + availableCategories + }; + } + + return { valid: true }; +} + +/** + * Filter entries by category + */ +function filterByCategory(entries, category) { + if (!category) { + return entries; + } + + const lowerCategory = category.toLowerCase(); + + return entries.filter(entry => { + const entryCategory = (entry.category || '').toLowerCase(); + return entryCategory.includes(lowerCategory); + }); +} + +/** + * Search entries by search terms + */ +function searchEntries(entries, searchTerms) { + if (searchTerms.length === 0) { + return entries; + } + + return entries.filter(entry => { + const searchableText = [ + entry.title || '', + entry.description || '', + entry.category || '', + entry.githubProfile || '' + ].join(' ').toLowerCase(); + + // Match all search terms (AND logic) + return searchTerms.every(term => + searchableText.includes(term.toLowerCase()) + ); + }); +} + +/** + * Format results for output + */ +function formatResults(entries) { + return entries.map(entry => ({ + title: entry.title || 'Untitled', + category: entry.category || 'Unknown', + description: entry.description || '', + githubUrl: entry.githubUrl || '', + showcaseUrl: entry.showcaseUrl || '', + githubProfile: entry.githubProfile || '' + })); +} + +/** + * Parse command line arguments + */ +function parseArgs(args) { + const result = { + category: null, + searchTerms: [] + }; + + for (let i = 0; i < args.length; i++) { + if (args[i] === '--category') { + if (i + 1 < args.length) { + result.category = args[i + 1]; + i++; // Skip the next argument + } else { + throw new Error('--category flag requires a value'); + } + } else { + result.searchTerms.push(args[i]); + } + } + + return result; +} + +/** + * Main function + */ +async function main() { + const args = process.argv.slice(2); + + if (args.length === 0) { + console.error('Usage: node search-block-party.js [--category ] [additional-terms...]'); + console.error(''); + console.error('Examples:'); + console.error(' node search-block-party.js breadcrumb'); + console.error(' node search-block-party.js --category "Build Tooling" sass'); + console.error(' node search-block-party.js adobe target integration'); + console.error(''); + console.error('Common categories:'); + console.error(' - Block'); + console.error(' - Sidekick Plugin'); + console.error(' - DA Plugin'); + console.error(' - Code Snippet'); + console.error(' - Build Tooling'); + process.exit(1); + } + + try { + // Parse arguments + const { category, searchTerms } = parseArgs(args); + + // Fetch the index + const indexData = await getBlockPartyIndex(); + const allEntries = indexData.data || []; + + // Filter to only approved entries + const approvedEntries = allEntries.filter(entry => + entry.approved === 'Yes' || entry.approved === true || entry.approved === 'true' + ); + + // Get available categories (from approved entries only) + const availableCategories = getUniqueCategories(approvedEntries); + + // Validate category if provided + const validation = validateCategory(category, availableCategories); + if (!validation.valid) { + console.error('Error:', validation.error); + console.error(''); + console.error('Available categories:'); + validation.availableCategories.forEach(cat => { + console.error(` - ${cat}`); + }); + process.exit(1); + } + + // Filter by category if specified (using approved entries only) + let entries = filterByCategory(approvedEntries, category); + + // Search by terms + entries = searchEntries(entries, searchTerms); + + // Format results + const results = formatResults(entries); + + // Output results as JSON + const output = { + query: searchTerms.join(' '), + category: category || 'All categories', + source: 'AEM Block Party (Approved Only)', + indexUrl: BLOCK_PARTY_INDEX_URL, + totalEntries: allEntries.length, + approvedEntries: approvedEntries.length, + matchCount: results.length, + results: results + }; + + console.log(JSON.stringify(output, null, 2)); + + // Exit with code 0 if results found, 1 if no results + process.exit(results.length > 0 ? 0 : 1); + + } catch (error) { + console.error('Error:', error.message); + process.exit(1); + } +} + +// Run the script +main(); diff --git a/.claude/.skills/block-inventory/SKILL.md b/.claude/.skills/block-inventory/SKILL.md new file mode 100644 index 0000000..cab764f --- /dev/null +++ b/.claude/.skills/block-inventory/SKILL.md @@ -0,0 +1,257 @@ +--- +name: block-inventory +description: Survey available blocks from local AEM Edge Delivery Services project and Block Collection to understand the block palette available for authoring. Returns block inventory with purposes to inform content modeling decisions. +--- + +# Block Inventory + +Survey and catalog available blocks to understand what authoring options exist. + +## When to Use This Skill + +Use this skill when: +- Starting a page import to understand available blocks +- Planning content structure and need to know block options +- An author would see a block library and choose from available options + +**Do NOT use this skill when:** +- You already know which specific block you need +- Building new blocks from scratch +- Only checking if one specific block exists (use block-collection-and-party directly) + +## Why This Skill Exists + +Real authors see a block library in their authoring tools. They think: "I want a hero section... oh, there's a Hero block!" + +This skill provides that same context - understanding what blocks are available BEFORE making authoring decisions. + +## Related Skills + +- **page-import** - Top-level orchestrator +- **identify-page-structure** - Invokes this skill to survey blocks (Step 2.5) +- **block-collection-and-party** - This skill uses it to search Block Collection +- **content-modeling** - Can reference block inventory but maintains independent judgment + +## Block Inventory Workflow + +### Step 1: Scan Local Project Blocks + +Check what blocks already exist in the project: + +```bash +# List all local blocks +ls -d blocks/*/ +``` + +**For each block found:** +- Record block name +- Note: Purpose/description comes from block code or documentation + +**Output:** List of local block names + +--- + +### Step 2: Search Block Collection for Common Blocks + +Search for commonly-used blocks that might not be in the project yet. + +**Common blocks to search (run in parallel):** + +```bash +# Search all common blocks in parallel +node .claude/skills/block-collection-and-party/scripts/search-block-collection-github.js hero & +node .claude/skills/block-collection-and-party/scripts/search-block-collection-github.js cards & +node .claude/skills/block-collection-and-party/scripts/search-block-collection-github.js columns & +node .claude/skills/block-collection-and-party/scripts/search-block-collection-github.js accordion & +node .claude/skills/block-collection-and-party/scripts/search-block-collection-github.js tabs & +node .claude/skills/block-collection-and-party/scripts/search-block-collection-github.js carousel & +node .claude/skills/block-collection-and-party/scripts/search-block-collection-github.js quote & +node .claude/skills/block-collection-and-party/scripts/search-block-collection-github.js fragment & +wait +``` + +**Why these specific blocks:** +- hero - Large prominent content at page top +- cards - Grid of items with images/text +- columns - Side-by-side content layouts +- accordion - Expandable Q&A sections +- tabs - Switchable content panels +- carousel - Rotating image/content displays +- quote - Highlighted testimonials or quotes +- fragment - Reusable content sections + +**Output:** Block Collection blocks with live example URLs + +--- + +### Step 3: Get Block Purposes + +For each block found (local or Block Collection): + +**If from Block Collection:** +- Purpose is clear from live example URL +- Visit live example to understand usage: `https://main--aem-block-collection--adobe.aem.live/block-collection/{block-name}` + +**If local block:** +- Check for README or comments in block code +- Infer from block name and structure +- May need to describe based on code examination + +**Output:** Block name + purpose/description + +--- + +### Step 4: Consolidate Block Inventory + +Create comprehensive block palette: + +**Format:** +``` +Available Blocks: + +LOCAL BLOCKS: +- {block-name}: {purpose} +- {block-name}: {purpose} + +BLOCK COLLECTION (can be added): +- hero: Large heading, text, and buttons at top of page +- cards: Grid of items with images, headings, and descriptions +- columns: Side-by-side content in 2-3 columns +- accordion: Expandable questions and answers +- tabs: Content organized in switchable tabs +- carousel: Rotating images or content panels +- quote: Highlighted testimonial or pullquote +- fragment: Reusable content section +``` + +**Important notes in output:** +- Local blocks are already available for use +- Block Collection blocks can be added if needed +- Link to Block Collection for authors to see examples + +**Output:** Complete block inventory + +--- + +## Usage Example + +**Scenario:** Starting WKND Trendsetters homepage import + +**Step 1 - Local blocks:** +```bash +ls -d blocks/*/ +# Output: (none found - new project) +``` + +**Step 2 - Block Collection search:** +```bash +# Run parallel searches +node .claude/skills/block-collection-and-party/scripts/search-block-collection-github.js hero & +node .claude/skills/block-collection-and-party/scripts/search-block-collection-github.js cards & +# ... (all common blocks) +wait +``` + +**Results:** +- hero ✅ Found +- cards ✅ Found +- columns ✅ Found +- accordion ✅ Found +- tabs ✅ Found +- carousel ✅ Found +- quote ✅ Found +- fragment ✅ Found + +**Step 3 - Get purposes:** +Visit live examples or read descriptions from search results + +**Step 4 - Consolidated output:** +``` +Block Inventory for Migration: + +LOCAL BLOCKS: +(None - new project) + +BLOCK COLLECTION AVAILABLE: +- hero: Large heading, paragraph, and call-to-action buttons for page introductions + Example: https://main--aem-block-collection--adobe.aem.live/block-collection/hero + +- cards: Grid layout of content items with images, headings, and descriptions + Example: https://main--aem-block-collection--adobe.aem.live/block-collection/cards + +- columns: Side-by-side content in 2-3 columns for comparisons or layouts + Example: https://main--aem-block-collection--adobe.aem.live/block-collection/columns + +- accordion: Expandable sections for FAQs or collapsed content + Example: https://main--aem-block-collection--adobe.aem.live/block-collection/accordion + +- tabs: Tabbed interface for organizing related content + Example: https://main--aem-block-collection--adobe.aem.live/block-collection/tabs + +- carousel: Rotating slideshow of images or content + Example: https://main--aem-block-collection--adobe.aem.live/block-collection/carousel + +- quote: Highlighted testimonial or pullquote with attribution + Example: https://main--aem-block-collection--adobe.aem.live/block-collection/quote + +- fragment: Reusable content section that can be embedded across pages + Example: https://main--aem-block-collection--adobe.aem.live/block-collection/fragment +``` + +--- + +## Key Principles + +**Completeness over perfection:** +- Better to show too many blocks than miss one +- Authors can ignore blocks they don't need +- Discovering a perfect-fit block later is frustrating + +**Practical purposes:** +- Describe blocks in author language, not developer terms +- "Grid of items" not "repeating collection pattern" +- "Expandable Q&A" not "interactive disclosure widget" + +**Block Collection focus:** +- Prioritize Block Collection blocks (vetted, accessible, performant) +- These are the canonical implementations +- Can be added to any project + +**Speed matters:** +- Run searches in parallel +- Don't visit every live example (time-consuming) +- Get enough info to understand purpose + +--- + +## Common Blocks Reference + +Here's a quick reference for the most common blocks: + +| Block | Purpose | When Authors Use It | +|-------|---------|-------------------| +| hero | Page introduction | "I want a big heading at the top" | +| cards | Content grid | "I want items in a grid with pictures" | +| columns | Side-by-side | "I want two things next to each other" | +| accordion | Collapsible Q&A | "I have FAQs that should expand/collapse" | +| tabs | Tabbed content | "I want content in switchable tabs" | +| carousel | Image slider | "I want images that rotate/slide" | +| quote | Testimonial | "I want to highlight a customer quote" | +| fragment | Reusable content | "I want to reuse this section on multiple pages" | + +--- + +## Limitations + +This skill does NOT: +- Determine which block to use (that's content-modeling's job) +- Validate if blocks work correctly +- Create new blocks +- Search Block Party (focuses on Block Collection + local) +- Provide detailed implementation guidance + +For those needs, use the appropriate skills: +- content-modeling: Determine which block fits +- block-collection-and-party: Deep search and code examination +- building-blocks: Create new blocks +- content-driven-development: Implementation guidance diff --git a/.claude/.skills/building-blocks/SKILL.md b/.claude/.skills/building-blocks/SKILL.md new file mode 100644 index 0000000..ac911fa --- /dev/null +++ b/.claude/.skills/building-blocks/SKILL.md @@ -0,0 +1,199 @@ +--- +name: Building Blocks +description: Guide for creating new AEM Edge Delivery blocks or modifying existing blocks. Use this skill whenever you are creating a new block from scratch or making significant changes to existing blocks that involve JavaScript decoration, CSS styling, or content model changes. +--- + +# Building Blocks + +This skill guides you through creating new AEM Edge Delivery blocks or modifying existing ones, following Content Driven Development (CDD) principles. Blocks are the reusable building blocks of AEM sites - each transforms authored content into rich, interactive experiences through JavaScript decoration and CSS styling. This skill covers the complete development process: understanding content models, implementing decoration logic, applying styles, and maintaining code quality standards. + +## Related Skills + +- **content-driven-development**: MUST be invoked before using this skill to ensure content and content models are ready +- **block-collection-and-party**: Use to find similar blocks for patterns +- **testing-blocks**: Automatically invoked after implementation for comprehensive testing + +## When to Use This Skill + +This skill should ONLY be invoked from the **content-driven-development** skill during Phase 2 (Implementation). + +If you are not already following the CDD process: +- **STOP** - Do not proceed with this skill +- **Invoke the content-driven-development skill first** +- The CDD skill will ensure test content and content models are ready before implementation + +This skill handles: +- Creating new block files and structure +- Implementing JavaScript decoration +- Adding CSS styling +- Code quality and testing + +## Prerequisites + +**REQUIRED before using this skill:** +- ✅ Test content must exist (in CMS or local drafts) +- ✅ Content model must be defined +- ✅ Test content URL must be available + +**Information needed:** +1. **Block name**: What should the block be called? +2. **Content model**: The defined structure authors will use +3. **Test content URL**: Path to test content for development + +## Process Overview + +1. Verify Prerequisites (CDD completed) +2. Find Similar Blocks (for patterns and reuse) +3. Create or Modify Block Structure (files and directories) +4. Implement JavaScript Decoration (DOM transformation) +5. Add CSS Styling (scoped, responsive styles) +6. Test the Implementation (local testing, linting) +7. Document Block (developer and author-facing docs) + +## Detailed Process + +### 1. Verify Prerequisites + +**Before proceeding, confirm with the user:** + +"Do you have: +- ✅ Test content created (URL or path)? +- ✅ Content model defined? + +If not, we need to use the content-driven-development skill first." + +If prerequisites are not met, STOP and invoke the **content-driven-development** skill. + +If prerequisites are met, get the test content URL from the user and proceed to step 2. + +### 2. Find Similar Blocks + +**For new blocks or major modifications:** + +1. Search the codebase for similar blocks that might provide useful patterns or code we can re-use +2. Use the **block-collection-and-party** skill to find relevant reference blocks + +Review the implementation patterns in similar blocks to inform your approach. + +**For minor modifications to existing blocks:** Skip to step 3. + +### 3. Create or Modify Block Structure + +**For new blocks:** + +1. Create directory: `blocks/{block-name}/` +2. Create files: `{block-name}.js` and `{block-name}.css` +3. Use the boilerplate structure (or reference templates in `resources/` if helpful): + - JS file exports a default `decorate(block)` function (can be async if needed) + - CSS file targets the `.{block-name}` class + +**For existing blocks:** + +1. Locate the existing block directory in `blocks/{block-name}/` +2. Review the current implementation before making changes +3. Understand the existing decoration logic and styles + +### 4. Implement JavaScript Decoration + +Follow patterns and conventions in `resources/js-guidelines.md`: + +- Use DOM APIs to transform the initial block HTML structure +- Keep decoration logic focused and single-purpose +- Handle variants appropriately (check block.classList for variant classes) +- Follow established patterns from similar blocks + +**Read `resources/js-guidelines.md` for detailed examples, code standards, and best practices.** + +### 5. Add CSS Styling + +Follow patterns and conventions in `resources/css-guidelines.md`: + +- All CSS selectors must be scoped to the block (start with `.{block-name}`) +- Use BEM-like naming within the block scope +- Leverage CSS custom properties for theming +- Write mobile-first responsive styles +- Keep specificity low +- Follow established patterns from similar blocks + +**Read `resources/css-guidelines.md` for detailed examples, code standards, and best practices.** + +### 6. Test the Implementation + +**After implementation is complete, invoke the testing-blocks skill:** + +The testing-blocks skill will guide you through: +- Writing unit tests for any logic-heavy utilities +- Browser testing to validate block behavior +- Taking screenshots for validation and PR documentation +- Running linting and fixing issues +- Verifying GitHub checks pass + +Provide the testing-blocks skill with: +- Block name being tested +- Test content URL (from CDD process) +- Any variants that need testing + +Return to this skill after testing is complete to proceed to step 7. + +### 7. Document Block + +Blocks require two types of documentation: + +#### Developer Documentation + +- Most blocks are simple and self-contained and only need code comments for documentation +- If a block is especially complex (has many variants, or especially complex code) consider adding a brief README.md in the block folder +- Keep any README documentation very brief so it can be consumed at a glance + +#### Author-Facing Documentation + +Author-facing documentation helps content authors understand how to use the block in the CMS. This documentation typically exists as draft/library content in the CMS itself, not in the codebase. + +**When author documentation is needed:** + +Almost all blocks should have author-facing documentation. The only exceptions are: +- Deprecated blocks that should no longer be used but can't be removed yet +- Special-purpose blocks used very infrequently on a need-to-know basis +- Auto-blocked blocks that shouldn't be used directly by authors + +**Maintaining author documentation:** + +Author documentation must be kept in sync with the block implementation: +- Update when variants are added, removed, or modified +- Update when the content structure changes +- Update when block behavior or functionality changes + +**Where author documentation lives:** + +Different projects use different approaches for author documentation: + +1. **Sidekick Library** (Google Drive/SharePoint authoring): + - Uses https://github.com/adobe/franklin-sidekick-library + - Check for `/tools/sidekick/library.html` in the codebase + - If present, guide user to add/update block documentation in the library + +2. **Document Authoring (DA) Library**: + - Uses https://docs.da.live/administrators/guides/setup-library + - Different implementation than Sidekick Library + - If in use, guide user to update block documentation in DA library + +3. **Universal Editor (UE) projects**: + - Often skip dedicated author documentation libraries + - May use inline help or other mechanisms + +4. **Simple documentation pages**: + - Some projects maintain documentation under `/drafts` or `/docs` + - Pages contain authoring guides and block examples + +**What to include in author documentation:** + +The specific content of author documentation varies by project. As an agent: +1. Identify that author documentation needs to be created or updated +2. Determine which documentation approach the project uses (check for `/tools/sidekick/library.html` as a signal) +3. Guide the user on what aspects of the block should be documented based on the changes made +4. Provide specific guidance based on the project's documentation approach + +## Reference Materials + +- `resources/js-guidelines.md` +- `resources/css-guidelines.md` diff --git a/.claude/.skills/building-blocks/resources/css-guidelines.md b/.claude/.skills/building-blocks/resources/css-guidelines.md new file mode 100644 index 0000000..e3e876e --- /dev/null +++ b/.claude/.skills/building-blocks/resources/css-guidelines.md @@ -0,0 +1,412 @@ +# CSS Guidelines for AEM Blocks + +## Block Scoping + +**All CSS selectors must be scoped to the block.** This is critical to prevent style leakage between blocks. + +**✅ Good - scoped to block:** +```css +main .my-block { + padding: 1rem; +} + +main .my-block h2 { + font-size: var(--heading-font-size-m); +} +``` + +**❌ Bad - not scoped:** +```css +/* This will affect ALL h2 elements on the page */ +h2 { + font-size: var(--heading-font-size-m); +} + +/* This will affect elements outside the block */ +.item { + padding: 1rem; +} +``` + +**Scoping pattern:** +- Always start selectors with `main .{block-name}` +- This ensures styles only apply within your block +- Use additional classes for sub-elements within the block + +**⚠️ Special note on `-wrapper` and `-container` classes:** + +The platform automatically adds `.{block-name}-wrapper` and `.{block-name}-container` divs *outside* your block. If you need to style elements with these class names *inside* your block, you must scope them carefully: + +```css +/* ❌ Bad - will affect the wrapper OUTSIDE your block */ +main .my-block-wrapper { + padding: 2rem; +} + +/* ✅ Good - only affects wrappers INSIDE your block */ +main .my-block .my-block-wrapper { + padding: 2rem; +} + +/* Better - avoid using these class names inside your block */ +main .my-block .inner-wrapper { + padding: 2rem; +} +``` + +**Best practice:** Avoid using `-wrapper` or `-container` suffix for classes inside your block to prevent confusion. + +## Naming Conventions + +Use BEM-like naming for elements within your block: + +```css +/* Block */ +main .my-block { + /* block styles */ +} + +/* Element - using descriptive class names */ +main .my-block .item { + /* item styles */ +} + +main .my-block .item-title { + /* item title styles */ +} + +/* Modifier/variant */ +main .my-block.dark { + /* dark variant styles */ +} + +main .my-block.wide .item { + /* item styles in wide variant */ +} +``` + +**Key points:** +- Use lowercase with hyphens for class names (kebab-case) +- Choose descriptive, semantic names +- Avoid generic names like `.container`, `.wrapper` - be specific to your block + +## CSS Custom Properties (Variables) + +Leverage CSS custom properties defined in `styles/styles.css` for consistency: + +**Colors:** +```css +main .my-block { + background-color: var(--background-color); + color: var(--text-color); +} + +main .my-block a:any-link { + color: var(--link-color); +} + +main .my-block a:hover { + color: var(--link-hover-color); +} +``` + +**Typography:** +```css +main .my-block h2 { + font-family: var(--heading-font-family); + font-size: var(--heading-font-size-m); +} + +main .my-block p { + font-family: var(--body-font-family); + font-size: var(--body-font-size-m); +} +``` + +**Layout:** +```css +main .my-block { + max-width: var(--max-content-width); + padding-inline: var(--inline-section-padding); +} +``` + +**Available custom properties:** +- Colors: `--clr-*`, `--link-color`, `--background-color`, `--text-color`, etc. +- Fonts: `--body-font-family`, `--heading-font-family`, `--fixed-font-family` +- Font sizes: `--heading-font-size-*`, `--body-font-size-*` +- Layout: `--max-content-width`, `--inline-section-padding` + +See `styles/styles.css` for the complete list. + +## Mobile-First Responsive Design + +Write styles mobile-first, then add media queries for larger screens: + +```css +/* Mobile styles (default) */ +main .my-block { + padding: 1rem; + flex-direction: column; +} + +/* Tablet and up */ +@media (width >= 600px) { + main .my-block { + padding: 2rem; + } +} + +/* Desktop and up */ +@media (width >= 900px) { + main .my-block { + flex-direction: row; + padding: 4rem; + } +} +``` + +**Standard breakpoints:** +- Mobile: default (< 600px) +- Tablet: `@media (width >= 600px)` +- Desktop: `@media (width >= 900px)` + +**Modern syntax:** +- Use range syntax: `(width >= 600px)` instead of `(min-width: 600px)` +- Use logical properties where appropriate + +## Modern CSS Features + +Use modern CSS features for better maintainability and performance: + +**Logical properties:** +```css +/* Use logical properties for internationalization */ +main .my-block { + padding-inline: 1rem; /* left/right in LTR, right/left in RTL */ + padding-block: 2rem; /* top/bottom */ + margin-inline-start: 1rem; /* left in LTR */ + border-inline-start: 2px solid black; +} +``` + +**Modern layout:** +```css +/* Flexbox */ +main .my-block { + display: flex; + gap: 1rem; /* Better than margin hacks */ + flex-wrap: wrap; +} + +/* Grid */ +main .my-block { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 2rem; +} +``` + +**Modern color syntax:** +```css +main .my-block { + background-color: rgb(0 0 0 / 20%); /* Modern RGB with alpha */ + color: hsl(200 50% 50%); /* HSL syntax */ +} +``` + +## Keep Specificity Low + +Avoid overly specific selectors: + +**✅ Good - low specificity:** +```css +main .my-block .item { + padding: 1rem; +} + +main .my-block .item-title { + font-size: 1.5rem; +} +``` + +**❌ Bad - high specificity:** +```css +main .my-block div div div.item { + padding: 1rem; +} + +main div.my-block > div > h2.item-title { + font-size: 1.5rem; +} +``` + +**Best practices:** +- Use classes, not tag names when possible +- Avoid ID selectors +- Keep selector chains short (2-3 levels max) +- Don't nest deeper than necessary + +## Handling Variants + +Use the variant class alongside the block class: + +```css +/* Base block */ +main .my-block { + background-color: var(--background-color); + color: var(--text-color); +} + +/* Dark variant */ +main .my-block.dark { + background-color: var(--dark-color); + color: var(--clr-white); +} + +/* Wide variant */ +main .my-block.wide { + max-width: 100%; +} + +/* Combining variants */ +main .my-block.dark.wide { + /* Styles for both dark and wide */ +} +``` + +## Performance Considerations + +**Minimize reflows and repaints:** +```css +/* Prefer transforms over position changes */ +main .my-block .item { + transform: translateX(10px); /* Better performance */ +} + +/* Avoid this: */ +main .my-block .item { + left: 10px; /* Triggers reflow */ +} +``` + +**Use will-change sparingly:** +```css +/* Only for elements that will definitely animate */ +main .my-block .animated-item { + will-change: transform; +} +``` + +**Avoid expensive properties on large elements:** +```css +/* Be careful with these on large areas: */ +/* box-shadow, border-radius, opacity, filters */ +``` + +## Code Style + +**Formatting:** +- Use 2-space indentation +- One selector per line for multiple selectors +- Opening brace on same line as selector +- One property per line +- Space after colon in property declarations +- No space before colon +- End all declarations with semicolon + +**Example:** +```css +main .my-block, +main .my-block .item { + display: flex; + padding: 1rem; + gap: 1rem; +} +``` + +**Order of properties** (recommended): +1. Layout (display, position, top, left, etc.) +2. Box model (width, height, margin, padding, border) +3. Visual (background, color, font, etc.) +4. Animation/transform + +## Common Patterns + +### Reset list styles +```css +main .my-block ul { + list-style: none; + margin: 0; + padding: 0; +} +``` + +### Center content +```css +main .my-block { + max-width: var(--max-content-width); + margin-inline: auto; +} +``` + +### Aspect ratio containers +```css +main .my-block .video-container { + aspect-ratio: 16 / 9; +} +``` + +### Truncate text +```css +main .my-block .truncated { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +``` + +## Anti-Patterns to Avoid + +**❌ Don't use !important:** +```css +/* Avoid this */ +main .my-block { + color: red !important; +} + +/* Fix specificity issues properly instead */ +``` + +**❌ Don't style elements outside your block:** +```css +/* Bad - modifies header globally */ +main .my-block { + /* ... */ +} + +header { + background: red; +} +``` + +**❌ Don't hardcode values when variables exist:** +```css +/* Bad */ +main .my-block { + font-family: 'Lato', sans-serif; + color: #666; +} + +/* Good */ +main .my-block { + font-family: var(--body-font-family); + color: var(--text-color); +} +``` + +**❌ Don't use absolute positioning for layout:** +```css +/* Prefer flexbox or grid for layout */ +/* Use absolute positioning only for visual effects */ +``` diff --git a/.claude/.skills/building-blocks/resources/js-guidelines.md b/.claude/.skills/building-blocks/resources/js-guidelines.md new file mode 100644 index 0000000..b3cffd1 --- /dev/null +++ b/.claude/.skills/building-blocks/resources/js-guidelines.md @@ -0,0 +1,336 @@ +# JavaScript Guidelines for AEM Blocks + +## Basic Block Structure + +Every block must export a default `decorate` function that receives the block element as a parameter. The function can be async if needed. + +**Basic structure:** + +```javascript +/** + * decorate the block + * @param {Element} block the block + */ +export default async function decorate(block) { + // Your decoration logic here +} +``` + +**Key points:** +- Always export the decorate function as the default export +- The function receives the block DOM element as the first parameter +- Use `async` if you need to await operations (fetching data, loading modules, etc.) +- Include JSDoc comments describing the functions + +## DOM Manipulation Patterns + +### Good Patterns + +**✅ Re-use existing DOM elements when possible:** +```javascript +// Good - re-use the existing element +const paragraph = block.querySelector('p'); +paragraph.classList.add('decorated'); + +// Also good - extract and re-use +const picture = block.querySelector('picture'); +const figure = document.createElement('figure'); +figure.append(picture); // Re-uses the picture element +block.replaceChildren(figure); + +// Avoid - creating new elements unnecessarily +const text = block.querySelector('p').textContent; +const newP = document.createElement('p'); +newP.textContent = text; +// This throws away the original

and creates a new one +``` + +**✅ Use semantic HTML:** +```javascript +const blockquote = document.createElement('blockquote'); +const figure = document.createElement('figure'); +``` + +**✅ Use spread operator for multiple elements:** +```javascript +const pars = block.querySelectorAll('p'); +const container = document.createElement('div'); +container.append(...pars); +``` + +**✅ Replace content efficiently:** +```javascript +block.replaceChildren(newElement); +``` + +**✅ Query within the block scope:** +```javascript +// Good - scoped to block +const links = block.querySelectorAll('a'); + +// Avoid - queries entire document +const links = document.querySelectorAll('a'); +``` + +### Bad Patterns + +**❌ Don't use innerHTML for complex structures:** +```javascript +// Bad - hard to maintain, XSS risk +block.innerHTML = '

Text

'; + +// Good - use DOM APIs +const wrapper = document.createElement('div'); +wrapper.className = 'wrapper'; +const p = document.createElement('p'); +p.textContent = 'Text'; +wrapper.append(p); +block.append(wrapper); +``` + +**❌ Don't mutate elements from other blocks:** +```javascript +// Bad - affects global state +const header = document.querySelector('header'); +header.classList.add('modified-by-my-block'); + +// Good - only modify your block +block.classList.add('has-special-behavior'); +``` + +**❌ Don't leave temporary DOM elements:** +```javascript +// Bad - leaves empty paragraphs +const text = block.querySelector('p').textContent; +const newDiv = document.createElement('div'); +newDiv.textContent = text; +block.append(newDiv); + +// Good - remove the original +const p = block.querySelector('p'); +const newDiv = document.createElement('div'); +newDiv.textContent = p.textContent; +p.replaceWith(newDiv); +``` + +## Handling Variants + +Blocks can have variant classes applied (e.g., `
`). Check for variants using `classList`: + +```javascript +export default async function decorate(block) { + const isDark = block.classList.contains('dark'); + const isWide = block.classList.contains('wide'); + + // Apply variant-specific logic + if (isDark) { + // Handle dark variant + } +} +``` + +**Key points:** +- Variant classes are added alongside the block class +- Use `classList.contains()` to check for variants +- CSS should handle most variant styling; use JS only for behavior changes + +## Common Patterns + +### Working with Images + +Images in authored content come with srcset and alt attributes from the platform. You can also create optimized images programmatically using `createOptimizedPicture()` from aem.js: + +```javascript +import { createOptimizedPicture } from '../../scripts/aem.js'; + +export default async function decorate(block) { + // Working with existing images + const images = block.querySelectorAll('img'); + images.forEach((img) => { + // Images come with srcset and alt from the platform + // Wrap in figure if needed + const figure = document.createElement('figure'); + img.replaceWith(figure); + figure.append(img); + }); + + // Creating optimized pictures programmatically + const picture = createOptimizedPicture('/path/to/image.jpg', 'Alt text', false, [{ width: '750' }]); + block.append(picture); +} +``` + +### Fetching Data + +Use async/await for data fetching: + +```javascript +export default async function decorate(block) { + try { + const response = await fetch('/path/to/data.json'); + const data = await response.json(); + + // Use the data + renderData(block, data); + } catch (error) { + // eslint-disable-next-line no-console + console.error('Failed to load data:', error); + } +} +``` + +### Loading Additional Modules + +Use regular imports for modules that are always needed: + +```javascript +import { someFunction } from '../../scripts/utils.js'; + +export default async function decorate(block) { + someFunction(block); +} +``` + +Use dynamic imports for modules that are conditionally needed or can be loaded later: + +```javascript +export default async function decorate(block) { + // Conditionally load a module + if (block.classList.contains('advanced')) { + const { advancedFunction } = await import('../../scripts/advanced.js'); + advancedFunction(block); + } + + // Or defer loading until later + setTimeout(async () => { + const { heavyModule } = await import('../../scripts/heavy.js'); + heavyModule.init(block); + }, 0); +} +``` + +### Handling Multiple Content Patterns + +Sometimes a block might have different content structures: + +```javascript +export default async function decorate(block) { + const rows = block.querySelectorAll(':scope > div'); + + // Pattern 1: Single row with image and text + if (rows.length === 1) { + // Handle simple pattern + } + + // Pattern 2: Multiple rows + if (rows.length > 1) { + // Handle complex pattern + } +} +``` + +**Note:** Try to avoid this. Multiple content patterns increase complexity. Work with content authors to agree on a single, clear content model when possible. + +## Code Style + +This project uses Airbnb ESLint configuration with some modifications: + +**Key rules:** +- Use ES6+ features (const, let, arrow functions, template literals) +- Always include `.js` extension in imports: `import { foo } from './bar.js';` +- Use single quotes for strings +- No semicolons are enforced, but be consistent +- Use 2-space indentation +- Unix line endings (LF) +- Parameter reassignment is allowed for properties: `block.foo = 'bar'` + +**Naming conventions:** +- Use camelCase for variables and functions +- Use PascalCase for classes +- Use UPPER_CASE for constants + +**File naming:** +- Block files must match block name: `my-block.js`, `my-block.css` +- Use kebab-case for file names + +## Performance Considerations + +**Minimize work in the decoration function:** +- Only do what's necessary to achieve the desired structure +- Defer expensive operations (heavy calculations, large data fetching) when possible +- Remember: blocks in the first section load eagerly and affect LCP + +**Example - defer heavy work:** +```javascript +export default async function decorate(block) { + // Do minimal initial decoration + block.classList.add('initialized'); + + // Defer expensive work + setTimeout(async () => { + const data = await fetchHeavyData(); + renderComplexUI(block, data); + }, 0); +} +``` + +**Example - use Intersection Observer to load only when viewed:** +```javascript +export default async function decorate(block) { + // Useful for embeds like YouTube videos, heavy widgets, etc. + const observer = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting) { + observer.disconnect(); + // Load heavy content only when block is visible + loadYouTubeEmbed(block); + } + }); + + observer.observe(block); +} +``` + +## Helper Functions + +You can import helpful utilities from `scripts/aem.js`: + +```javascript +import { + buildBlock, + decorateBlock, + loadBlock, + loadCSS, + loadScript, + toClassName, + getMetadata, + createOptimizedPicture, +} from '../../scripts/aem.js'; +``` + +Common helpers: +- `toClassName(text)` - Converts text to a valid CSS class name +- `getMetadata(name)` - Gets page metadata value +- `loadCSS(href)` - Loads a CSS file and returns a promise that resolves when loaded +- `loadScript(url, attrs)` - Loads a JavaScript file with optional attributes (async, type, etc.) +- `createOptimizedPicture(src, alt, eager, breakpoints)` - Creates a responsive picture element with optimized images +- `buildBlock(name, cells)` - Programmatically creates a block DOM structure. **Note:** For the block to fully display, it must be decorated and loaded after being built and added to the DOM. In most cases, you'll need to call `decorateBlock()` and `loadBlock()` after building. + +**Example - building and loading a block:** +```javascript +// Create the block +const myBlock = buildBlock('my-block', [[document.createElement('p')]]); + +// Blocks must be wrapped in a div and placed inside a section +const wrapper = document.createElement('div'); +wrapper.append(myBlock); + +// Add to a section (either find existing or create new) +const section = document.querySelector('main .section') || document.querySelector('main > div'); +section.append(wrapper); + +// Decorate and load (required in most contexts) +decorateBlock(myBlock); +await loadBlock(myBlock); +``` + +See `scripts/aem.js` for the complete list (but remember: NEVER MODIFY aem.js). diff --git a/.claude/.skills/code-review/SKILL.md b/.claude/.skills/code-review/SKILL.md new file mode 100644 index 0000000..dbe6b5d --- /dev/null +++ b/.claude/.skills/code-review/SKILL.md @@ -0,0 +1,1235 @@ +--- +name: Code Review +description: Review code for AEM Edge Delivery Services projects. Use at the end of development (before PR) for self-review, or to review pull requests. Validates code quality, performance, accessibility, and adherence to EDS best practices. +--- + +# Code Review + +Review code for AEM Edge Delivery Services (EDS) projects following established coding standards, performance requirements, and best practices. + +## When to Use This Skill + +This skill supports **two modes** of operation: + +### Mode 1: Self-Review (End of Development) + +Use this mode when you've finished writing code and want to review it before committing or opening a PR. This is the recommended workflow integration point. + +**When to invoke:** +- After completing implementation in the **content-driven-development** workflow (between Step 5 and Step 6) +- Before running `git add` and `git commit` +- When you want to catch issues early, before they reach PR review + +**How to invoke:** +- Automatically: CDD workflow invokes this skill after implementation +- Manually: `/code-review` (reviews uncommitted changes in working directory) + +**What it does:** +- Reviews all modified/new files in working directory +- Checks code quality, patterns, and best practices +- Validates against EDS standards +- Identifies issues to fix before committing +- Captures visual screenshots for validation + +### Mode 2: PR Review + +Use this mode to review an existing pull request (your own or someone else's). + +**When to invoke:** +- Reviewing a PR before merge +- Automated review via GitHub Actions workflow +- Manual review of a specific PR + +**How to invoke:** +- Manually: `/code-review ` or `/code-review ` +- Automated: Via GitHub workflow on `pull_request` event + +**What it does:** +- Fetches PR diff and changed files +- Validates PR structure (preview URLs, description) +- Reviews code quality +- Posts review comment with findings and screenshots +- Provides actionable fixes via GitHub suggestions or commits +- Explains reasoning for each fix with references to review feedback + +--- + +## Review Workflow + +### Step 1: Identify Review Mode and Gather Context + +**For Self-Review (no PR number provided):** + +```bash +# See what files have been modified +git status + +# See the actual changes +git diff + +# For staged changes +git diff --staged +``` + +**Understand the scope:** +- What files were modified? +- What type of change is this? (new block, bug fix, feature, styling, refactor) +- What is the test content URL? (from CDD workflow) + +**For PR Review (PR number provided):** + +```bash +# Get PR details +gh pr view --json title,body,author,baseRefName,headRefName,files,additions,deletions + +# Get changed files +gh pr diff + +# Get PR comments and reviews +gh api repos/{owner}/{repo}/pulls//comments +gh api repos/{owner}/{repo}/pulls//reviews +``` + +**Understand the scope:** +- What type of change is this? (new block, bug fix, feature, styling, refactor) +- What files are modified? +- Is there a related GitHub issue? +- Are there test/preview URLs provided? + +--- + +### Step 2: Validate Structure (PR Review Mode Only) + +**Skip this step for Self-Review mode.** + +**Required elements for PRs (MUST HAVE):** + +| Element | Requirement | Check | +|---------|-------------|-------| +| Preview URLs | Before/After URLs showing the change | Required | +| Description | Clear explanation of what changed and why | Required | +| Scope alignment | Changes match PR title and description | Required | +| Issue reference | Link to GitHub issue (if applicable) | Recommended | + +**Preview URL format:** +- Before: `https://main--{repo}--{owner}.aem.page/{path}` +- After: `https://{branch}--{repo}--{owner}.aem.page/{path}` + +**Flag if missing:** +- Missing preview URLs (blocks automated PSI checks) +- Vague or missing description +- Scope creep (changes unrelated to stated purpose) +- Missing issue reference for bug fixes + +--- + +### Step 3: Code Quality Review + +#### 3.1 JavaScript Review + +**Linting & Style:** +- [ ] Code passes ESLint (airbnb-base configuration) +- [ ] No `eslint-disable` comments without justification +- [ ] No global `eslint-disable` directives +- [ ] ES6+ features used appropriately +- [ ] `.js` extensions included in imports + +**Architecture:** +- [ ] No frameworks in critical rendering path (LCP/TBT impact) +- [ ] Third-party libraries loaded via `loadScript()` in blocks, not `head.html` +- [ ] Consider `IntersectionObserver` for heavy libraries +- [ ] `aem.js` is NOT modified (submit upstream PRs for improvements) +- [ ] No build steps introduced without team consensus + +**Code Patterns:** +- [ ] Existing DOM elements re-used, not recreated +- [ ] Block selectors scoped appropriately +- [ ] No hardcoded values that should be configurable +- [ ] Console statements cleaned up (no debug logs) +- [ ] Proper error handling where needed + +**Common Issues to Flag:** +```javascript +// BAD: CSS in JavaScript +element.style.backgroundColor = 'blue'; + +// GOOD: Use CSS classes +element.classList.add('highlighted'); + +// BAD: Hardcoded configuration +const temperature = 0.7; + +// GOOD: Use config or constants +const { temperature } = CONFIG; + +// BAD: Global eslint-disable +/* eslint-disable */ + +// GOOD: Specific, justified disables +/* eslint-disable-next-line no-console -- intentional debug output */ +``` + +#### 3.2 CSS Review + +**Linting & Style:** +- [ ] Code passes Stylelint (standard configuration) +- [ ] No `!important` unless absolutely necessary (with justification) +- [ ] Property order maintained (don't reorder in functional PRs) + +**Scoping & Selectors:** +- [ ] All selectors scoped to block: `.{block-name} .selector` or `main .{block-name}` +- [ ] Private classes/variables prefixed with block name +- [ ] Simple, readable selectors (add classes rather than complex selectors) +- [ ] ARIA attributes used for styling when appropriate (`[aria-expanded="true"]`) + +**Responsive Design:** +- [ ] Mobile-first approach (base styles for mobile, media queries for larger) +- [ ] Standard breakpoints used: `600px`, `900px`, `1200px` (all `min-width`) +- [ ] No mixing of `min-width` and `max-width` queries +- [ ] Layout works across all viewports + +**Frameworks & Preprocessors:** +- [ ] No CSS preprocessors (Sass, Less, PostCSS) without team consensus +- [ ] No CSS frameworks (Tailwind, etc.) without team consensus +- [ ] Native CSS features used (supported by evergreen browsers) + +**Common Issues to Flag:** +```css +/* BAD: Unscoped selector */ +.title { color: red; } + +/* GOOD: Scoped to block */ +main .my-block .title { color: red; } + +/* BAD: !important abuse */ +.button { color: white !important; } + +/* GOOD: Increase specificity instead */ +main .my-block .button { color: white; } + +/* BAD: Mixed breakpoint directions */ +@media (max-width: 600px) { } +@media (min-width: 900px) { } + +/* GOOD: Consistent mobile-first */ +@media (min-width: 600px) { } +@media (min-width: 900px) { } + +/* BAD: CSS in JS patterns */ +element.innerHTML = ''; + +/* GOOD: Use external CSS files */ +``` + +#### 3.3 HTML Review + +- [ ] Semantic HTML5 elements used appropriately +- [ ] Proper heading hierarchy maintained +- [ ] Accessibility attributes present (ARIA labels, alt text) +- [ ] No inline styles or scripts in `head.html` +- [ ] Marketing tech NOT in `` (performance impact) + +--- + +### Step 4: Performance Review + +**Critical Requirements:** +- [ ] Lighthouse scores green (ideally 100) for mobile AND desktop +- [ ] No third-party libraries in critical path (`head.html`) +- [ ] No layout shifts introduced (CLS impact) +- [ ] Images optimized and lazy-loaded appropriately + +**Performance Checklist:** +- [ ] Heavy operations use `IntersectionObserver` or delayed loading +- [ ] No synchronous operations blocking render +- [ ] Bundle size reasonable (no minification unless measurable Lighthouse gain) +- [ ] Fonts loaded efficiently + +**Preview URL Verification:** +If preview URLs provided, check: +- PageSpeed Insights scores +- Core Web Vitals (LCP, CLS, INP) +- Mobile and desktop performance + +--- + +### Step 5: Visual Validation with Screenshots + +**Purpose:** Capture screenshots of the preview URL to validate visual appearance. For self-review, this confirms your changes look correct before committing. For PR review, this provides visual evidence in the review comment. + +**When to capture screenshots:** +- Always capture at least one screenshot of the primary changed page/component +- For responsive changes, capture mobile (375px), tablet (768px), and desktop (1200px) +- For visual changes (styling, layout), capture before AND after for comparison +- For block changes, capture the specific block area + +**How to capture screenshots:** + +**Option 1: Playwright (Recommended for automation)** + +```javascript +// capture-screenshots.js +import { chromium } from 'playwright'; + +async function captureScreenshots(afterUrl, outputDir = './screenshots') { + const browser = await chromium.launch(); + const page = await browser.newPage(); + + // Desktop screenshot + await page.setViewportSize({ width: 1200, height: 800 }); + await page.goto(afterUrl, { waitUntil: 'networkidle' }); + await page.waitForTimeout(1000); // Wait for animations + await page.screenshot({ + path: `${outputDir}/desktop.png`, + fullPage: true + }); + + // Tablet screenshot + await page.setViewportSize({ width: 768, height: 1024 }); + await page.screenshot({ + path: `${outputDir}/tablet.png`, + fullPage: true + }); + + // Mobile screenshot + await page.setViewportSize({ width: 375, height: 667 }); + await page.screenshot({ + path: `${outputDir}/mobile.png`, + fullPage: true + }); + + // Optional: Capture specific block/element + const block = page.locator('.my-block'); + if (await block.count() > 0) { + await block.screenshot({ path: `${outputDir}/block.png` }); + } + + await browser.close(); + + return { + desktop: `${outputDir}/desktop.png`, + tablet: `${outputDir}/tablet.png`, + mobile: `${outputDir}/mobile.png` + }; +} + +// Usage +captureScreenshots('https://branch--repo--owner.aem.page/path'); +``` + +**Option 2: Using MCP Browser Tools** + +If you have MCP browser or Playwright tools available: +1. Navigate to the After preview URL +2. Take screenshots at different viewport sizes +3. Optionally take element-specific screenshots of changed blocks + +**Option 3: Manual capture with guidance** + +Instruct the reviewer or PR author to: +1. Open the After preview URL +2. Use browser DevTools to set viewport sizes +3. Take screenshots and attach to PR + +**Uploading screenshots to GitHub:** + +```bash +# Upload screenshot as PR comment with image +# First, upload to a hosting service or use GitHub's image upload + +# Option A: Embed in PR comment (drag & drop in GitHub UI) +gh pr comment --body "## Visual Preview + +### Desktop (1200px) +![Desktop Screenshot](screenshot-url-or-drag-drop) + +### Mobile (375px) +![Mobile Screenshot](screenshot-url-or-drag-drop) +" + +# Option B: Use GitHub's attachment API (for automation) +# Screenshots can be uploaded as part of the comment body +``` + +**Screenshot checklist:** +- [ ] Primary page/component captured at desktop width +- [ ] Mobile viewport captured (if responsive changes) +- [ ] Specific block/component captured (if block changes) +- [ ] Before/After comparison (if significant visual changes) +- [ ] No sensitive data visible in screenshots +- [ ] Screenshots uploaded and embedded in PR comment + +**Visual issues to look for:** +- Layout breaks or misalignment +- Text overflow or truncation +- Image sizing or aspect ratio issues +- Color/contrast problems (especially in dark mode) +- Missing or broken icons +- Responsive layout issues at breakpoints +- Unexpected visual differences from main branch + +--- + +### Step 6: Content & Authoring Review + +**Content Model (if applicable):** +- [ ] Content structure author-friendly +- [ ] Backward compatibility maintained with existing content +- [ ] No breaking changes requiring content migration +- [ ] New content features only available after preview/publish + +**Static Resources:** +- [ ] No binaries/static assets committed (unless code-referenced) +- [ ] User-facing strings sourced from content (placeholders, spreadsheets) +- [ ] No hardcoded literals that should be translatable + +--- + +### Step 7: Security Review + +- [ ] No sensitive data committed (API keys, passwords, secrets) +- [ ] No XSS vulnerabilities (unsafe innerHTML, unsanitized user input) +- [ ] No SQL injection or command injection vectors +- [ ] CSP headers appropriate for tool pages +- [ ] External links have `rel="noopener noreferrer"` + +--- + +### Step 8: Generate Review Summary + +**Output depends on the review mode:** + +#### For Self-Review Mode (End of Development) + +Report findings directly to continue the development workflow: + +```markdown +## Code Review Summary + +### Files Reviewed +- `blocks/my-block/my-block.js` (new) +- `blocks/my-block/my-block.css` (new) + +### Visual Validation +![Desktop Screenshot](path/to/screenshot.png) + +✅ Layout renders correctly across viewports +✅ No console errors +✅ Responsive behavior verified + +### Issues Found + +#### Must Fix Before Committing +- [ ] `blocks/my-block/my-block.js:45` - Remove console.log debug statement +- [ ] `blocks/my-block/my-block.css:12` - Selector `.title` needs block scoping + +#### Recommendations +- [ ] Consider using `loadScript()` for the external library + +### Ready to Commit? +- [ ] All "Must Fix" issues resolved +- [ ] Linting passes: `npm run lint` +- [ ] Visual validation complete +``` + +**After self-review:** Fix any issues found, then proceed with committing and opening a PR. + +#### For PR Review Mode + +Structure the review comment for GitHub: + +```markdown +## PR Review Summary + +### Overview +[Brief summary of the PR and its purpose] + +### Preview URLs Validated +- [ ] Before: [URL] +- [ ] After: [URL] + +### Visual Preview + +#### Desktop (1200px) +![Desktop Screenshot](url-or-embedded-image) + +#### Mobile (375px) +![Mobile Screenshot](url-or-embedded-image) + +
+Additional Screenshots + +#### Tablet (768px) +![Tablet Screenshot](url-or-embedded-image) + +#### Block Detail +![Block Screenshot](url-or-embedded-image) + +
+ +### Visual Assessment +- [ ] Layout renders correctly across viewports +- [ ] No visual regressions from main branch +- [ ] Colors and typography consistent +- [ ] Images and icons display properly + +### Checklist Results + +#### Must Fix (Blocking) +- [ ] [Critical issue with file:line reference] + +#### Should Fix (High Priority) +- [ ] [Important issue with file:line reference] + +#### Consider (Suggestions) +- [ ] [Nice-to-have improvement] + +### Detailed Findings + +#### [Category: e.g., JavaScript, CSS, Performance] +**File:** `path/to/file.js:123` +**Issue:** [Description of the issue] +**Suggestion:** [How to fix it] +``` + +--- + +### Step 9: Provide Actionable Fixes (PR Review Mode Only) + +**Skip this step for Self-Review mode** - in self-review, you fix issues directly in your working directory. + +After identifying issues in a PR review, provide actionable fixes to make it easier for the PR author to address them. **The goal is to provide one-click fixes whenever possible.** + +#### Quick Reference + +**PRIMARY METHOD: GitHub Suggestions** (use for ~70-80% of fixable issues) +- One-click acceptance by PR author +- Proper git attribution +- Works for changes < 20 lines +- Native GitHub UI integration + +**SECONDARY: Guidance Comments** (~20-30% of issues) +- For subjective or architectural issues +- When multiple approaches exist +- "Consider" level suggestions + +**RARE: Fix Commits** (avoid unless necessary) +- Only when suggestions won't work +- Multi-file complex refactors +- Changes requiring extensive testing + +--- + +#### Decision Tree: When to Use Which Approach + +| Approach | When to Use | Examples | +|----------|-------------|----------| +| **GitHub Suggestions** (PRIMARY) | Any change that can be expressed as a code replacement | Remove console.log, fix typos, add comments, refactor selectors, update functions, add error handling | +| **Fix Commits** (SECONDARY) | Changes that need testing, span many files, or are too large for suggestions | Complex multi-file refactors, security fixes requiring validation, changes >20 lines | +| **Guidance Only** (FALLBACK) | Architectural changes, subjective improvements, or when multiple approaches exist | "Consider using IntersectionObserver", design pattern suggestions, performance optimizations | + +**IMPORTANT:** Always prefer GitHub Suggestions when possible - they provide the best user experience with one-click acceptance and proper git attribution. + +#### Approach 1: GitHub Inline Suggestions (PRIMARY APPROACH - Recommended) + +Use GitHub's native suggestion feature for most fixes. This provides the best user experience with **one-click acceptance** and proper git attribution. + +**When to use:** +- ✅ ANY change that can be expressed as a line replacement +- ✅ Single-line to multi-line changes (up to ~20 lines works well) +- ✅ Clear, actionable fixes with known solutions +- ✅ Independent changes that can be applied separately +- ✅ Changes that don't require extensive testing before commit + +**When NOT to use:** +- ❌ Changes requiring testing/validation before commit +- ❌ Very large changes (>20 lines become unwieldy in GitHub UI) +- ❌ Changes spanning many files (better as fix commits) +- ❌ Subjective/architectural suggestions with multiple valid approaches + +**Benefits:** +- 🚀 One-click acceptance by PR author +- ✅ Proper co-author attribution in git history +- 🎯 Batch multiple suggestions into single commit +- 📱 Works in GitHub mobile app +- ⚡ Zero copy/paste errors +- 🔍 Clear before/after diff in GitHub UI + +**How to create suggestions:** + +GitHub suggestions are created using the Pull Request Reviews API with a special markdown syntax in the comment body: + +````markdown +```suggestion +// The corrected code here +``` +```` + +**Complete workflow with examples:** + +```bash +# Step 1: Get PR information +PR_NUMBER=196 +OWNER="adobe" +REPO="helix-tools-website" + +# Get the current HEAD commit SHA (required for review API) +COMMIT_SHA=$(gh api repos/$OWNER/$REPO/pulls/$PR_NUMBER --jq '.head.sha') + +# Step 2: Analyze the diff to find line positions +# IMPORTANT: Use 'position' in the diff, NOT 'line' in the original file +# Position is the line number in the unified diff output starting from the first diff hunk + +# Get the diff to understand positions +gh pr diff $PR_NUMBER --repo $OWNER/$REPO > /tmp/pr-$PR_NUMBER.diff + +# Step 3: Create review JSON with suggestions +# Each comment needs: +# - path: file path relative to repo root +# - position: line number IN THE DIFF (not in the file!) +# - body: description + ```suggestion block + +cat > /tmp/review-suggestions.json <} Content, status, and success flag\\n\`\`\`" + }, + { + "path": "tools/page-status/diff.js", + "position": 12, + "body": "**Fix: Return consistent result object**\\n\\n\`\`\`suggestion\\n return { content: null, status: res.status, ok: false };\\n\`\`\`" + }, + { + "path": "tools/page-status/diff.js", + "position": 16, + "body": "**Fix: Include ok flag in success case**\\n\\n\`\`\`suggestion\\n return { content, status: res.status, ok: true };\\n\`\`\`" + }, + { + "path": "tools/page-status/diff.css", + "position": 41, + "body": "**Fix: Remove stylelint-disable by refactoring selector**\\n\\nUse \\\`.diff-new-page\\\` as intermediate selector to avoid specificity conflict:\\n\\n\`\`\`suggestion\\n.page-diff .diff-new-page .doc-diff-side-header {\\n padding: var(--spacing-s) var(--spacing-m);\\n\`\`\`" + } + ] +} +JSON + +# Step 4: Submit the review with all suggestions at once +gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews \ + --input /tmp/review-suggestions.json + +# Step 5: Add a friendly summary comment +gh pr comment $PR_NUMBER --repo $OWNER/$REPO --body "$(cat <<'EOF' +## ✨ One-Click Fix Suggestions Added! + +I've added **GitHub Suggestions** that you can apply with a single click! + +### How to Apply + +1. Go to the **Files changed** tab +2. Find the inline comments with suggestions +3. Click **"Commit suggestion"** to apply individually +4. Or click **"Add suggestion to batch"** on multiple, then **"Commit suggestions"** to batch + +### What's Included + +- ✅ [BLOCKING] XSS safety documentation +- ✅ Error handling improvements +- ✅ CSS selector refactoring (removes linter disables) + +After applying, run \`npm run lint\` to verify all checks pass! +EOF +)" + +echo "✅ Review with suggestions posted successfully!" +echo "View at: https://github.com/$OWNER/$REPO/pull/$PR_NUMBER" +``` + +**Key points:** +- **Use `position` (diff position) NOT `line` (file line number)** - This is critical! +- The `position` is the line number in the unified diff output, counting from the first `@@` hunk marker +- Multiple suggestions in one review enables batch application +- Each suggestion creates a properly attributed co-authored commit when accepted +- Escape special characters in JSON (quotes, backticks, newlines) +- Always include context in the body before the suggestion block + +**How to determine the correct `position` value:** + +The `position` is the line number in the unified diff, NOT the line number in the file. Here's how to find it: + +1. **Get the diff:** + ```bash + gh pr diff > pr.diff + ``` + +2. **Open the diff and count lines** from the top, including: + - File headers (`--- a/file` and `+++ b/file`) + - Hunk headers (`@@ -old,lines +new,lines @@`) + - Context lines (no prefix or space prefix) + - Removed lines (- prefix) + - Added lines (+ prefix) + +3. **The position is the line number of the ADDED line** you want to comment on + +**Example from actual diff:** +```diff +--- a/tools/page-status/diff.js ++++ b/tools/page-status/diff.js + async function fetchContent(url) { + const res = await fetch(url); +- if (!res.ok) throw new Error(`Failed to fetch ${url}: ${res.status}`); +- return res.text(); ++ if (!res.ok) { ++ return { content: null, status: res.status }; ← Position 12 (counting from top) ++ } +``` + +**Best practices for suggestions:** +- Keep suggestions focused and atomic (one fix per suggestion) +- **Always include context** - explain WHY the change is needed before the suggestion block +- Test the suggestion mentally or locally before posting +- Only suggest changes you're confident are correct +- Include surrounding lines for context (GitHub shows ~3 lines of context) +- Use `position` in the diff, not `line` in the file (critical!) +- Batch related suggestions in one review for easier application +- Add a friendly summary comment explaining how to apply +- Format suggestion body with markdown (bold headers, inline code) +- Escape special characters properly in JSON (quotes, backticks, backslashes) + +#### Approach 2: Fix Commits (For Complex or Multi-File Fixes) + +For more complex fixes, create commits directly on the PR branch. This is especially useful when: +- Fixes span multiple files +- Changes need to be tested together +- Refactoring is required +- You want to demonstrate the fix working + +**Prerequisites:** +- Ensure you have write access to the repository or the PR is from a fork you can access +- Verify the PR author has enabled maintainer edits (usually default for same-org PRs) + +**Workflow:** + +```bash +# 1. Get PR branch information +PR_INFO=$(gh pr view --json headRefName,headRepository,headRepositoryOwner) +BRANCH=$(echo $PR_INFO | jq -r '.headRefName') +REPO_OWNER=$(echo $PR_INFO | jq -r '.headRepositoryOwner.login') + +# 2. Fetch the PR branch +git fetch origin pull//head:pr- + +# 3. Check out the PR branch +git checkout pr- + +# 4. Make fixes based on review findings +# Example: Fix CSS linter issues by refactoring selectors + +# 5. Test your fixes +npm run lint +# Run any relevant tests + +# 6. Commit with detailed reasoning +git commit -m "$(cat <<'EOF' +fix: refactor CSS selectors to eliminate linter disables + +Refactored CSS selectors in diff.css to resolve specificity conflicts +without using stylelint-disable comments. Changes: + +- Increased specificity using .diff-new-page parent selector +- Reordered rules to prevent descending specificity issues +- Maintains same visual appearance and functionality + +Addresses code review feedback: https://github.com/{owner}/{repo}/pull/#issuecomment-XXXXX +EOF +)" + +# 7. Push to the PR branch +git push origin pr-:$BRANCH + +# 8. Add comment to PR explaining what you fixed +gh pr comment --body "$(cat <<'EOF' +## Fixes Applied + +I've pushed commits to address some of the review findings: + +### Commit 1: Refactor CSS selectors +- ✅ Eliminated all `stylelint-disable` comments +- ✅ Resolved specificity conflicts properly +- ✅ Maintained visual appearance + +### Commit 2: Standardize error handling +- ✅ Updated fetchContent to return consistent result objects +- ✅ Updated all callers to use new pattern +- ✅ Added JSDoc for clarity + +### Still Need Action From You: +- [ ] **[BLOCKING]** Add XSS safety comment (see suggestion in review) +- [ ] Consider extracting renderDiffPanel helper (optional) + +All linting passes now. Please review the commits and address the remaining item. +EOF +)" +``` + +**Commit message format for fixes:** + +``` +fix: + + + +Changes: +- +- + +Addresses review feedback: +``` + +#### Approach 3: Hybrid Approach (RECOMMENDED FOR MOST PRs) + +**For the majority of PRs, use this hybrid strategy:** + +1. **GitHub suggestions** for all fixable issues (primary method) +2. **Guidance/comments** for subjective or architectural issues +3. **Fix commits** only for very complex multi-file refactors + +**Typical workflow:** + +```bash +# 1. Post comprehensive review comment with summary (from Step 8) +gh pr comment --repo {owner}/{repo} --body "" + +# 2. Submit a review with GitHub suggestions for ALL fixable issues +# (Create JSON as shown in Approach 1 with all suggestions) +gh api POST repos/{owner}/{repo}/pulls//reviews \ + --input /tmp/review-suggestions.json + +# 3. Add a friendly summary about the suggestions +gh pr comment --repo {owner}/{repo} --body "$(cat <<'EOF' +## ✨ One-Click Suggestions Added! + +I've added GitHub Suggestions for the fixable issues: +- ✅ [BLOCKING] Security documentation +- ✅ Error handling improvements +- ✅ CSS selector refactoring + +Go to **Files changed** tab and click **"Commit suggestion"** to apply! +EOF +)" + +# 4. For subjective/architectural issues, add guidance comments separately +# (Only if needed - most issues should have suggestions) +``` + +**Real-world example distribution:** + +For a typical PR with 10 issues: +- **GitHub suggestions**: 7-8 issues (concrete fixes) +- **Guidance comments**: 2-3 issues (architectural, subjective, or "consider" level) +- **Fix commits**: 0-1 (only if critically needed) + +**This approach provides:** +- ✅ Maximum ease of use (one-click fixes) +- ✅ Clear distinction between concrete fixes and suggestions +- ✅ Reduced review cycles +- ✅ Better PR author experience + +#### Real-World Example + +**PR #196: Support unpublished pages in diff tool** + +Issues identified in review: +1. XSS safety documentation needed (BLOCKING) +2. Four CSS stylelint-disable comments (should fix) +3. Inconsistent error handling (should fix) +4. CSS variable fallback inconsistency (optional) + +**Action taken:** +```bash +# Created two reviews with 9 total GitHub Suggestions +# - 4 suggestions for diff.js (XSS comment, error handling) +# - 5 suggestions for diff.css (selector refactoring) + +# Posted friendly summary comment explaining one-click application + +# Result: PR author can fix all issues in ~30 seconds vs 5-10 minutes of manual work +``` + +**View the actual implementation:** +- JavaScript suggestions: https://github.com/adobe/helix-tools-website/pull/196#pullrequestreview-3747855930 +- CSS suggestions: https://github.com/adobe/helix-tools-website/pull/196#pullrequestreview-3747857266 +- Summary comment: https://github.com/adobe/helix-tools-website/pull/196#issuecomment-3843945119 + +**Time saved:** +- Traditional review: Author spends 5-10 minutes copying code, editing files, testing +- With suggestions: Author spends 30 seconds clicking "Commit suggestions" +- **Time savings: 90%+ reduction in fix application time** + +#### Guidelines for Choosing Approach + +**Use GitHub Suggestions when:** (DEFAULT - use this ~70-80% of the time) +- ✅ You know the exact fix needed +- ✅ Change is < 20 lines (works well in GitHub UI) +- ✅ Fix is objective and unambiguous +- ✅ Can be applied without extensive testing +- ✅ Independent of other changes +- ✅ **Examples:** Add comments, remove debug code, fix typos, refactor selectors, update error handling, add JSDoc, fix linting issues + +**Use Fix Commits when:** (RARE - use only when suggestions won't work) +- ✅ Changes span many files (>5 files) +- ✅ Complex refactoring requiring testing +- ✅ Changes are interdependent and must be tested together +- ✅ Security fixes that need immediate attention and validation +- ✅ You're already working on the PR branch for other reasons +- ✅ **Examples:** Large refactors, multi-file renames, complex security patches + +**Use Guidance Only when:** (~20-30% of issues) +- ✅ Architectural decisions needed +- ✅ Multiple valid approaches exist +- ✅ Requires domain knowledge or context from PR author +- ✅ Subjective improvements ("Consider", "Think about") +- ✅ Performance optimizations with trade-offs +- ✅ **Examples:** "Consider using IntersectionObserver", "You might want to extract this to a util", "Have you thought about caching?" + +**Decision flowchart:** +``` +Issue identified + ↓ +Do I know the exact fix? ──NO──→ Use Guidance + ↓ YES +Is it < 20 lines? ──NO──→ Use Fix Commit or Guidance + ↓ YES +Use GitHub Suggestion ✅ (BEST OPTION) +``` + +#### Quality Standards for Fixes + +**Before posting suggestions or commits:** + +1. **Verify correctness:** Test locally or mentally verify the fix is correct +2. **Check scope:** Ensure your fix doesn't introduce new issues +3. **Maintain style:** Match existing code style and patterns +4. **Run linters:** Ensure fixes don't break linting +5. **Be respectful:** Frame fixes as helpful suggestions, not criticism +6. **Link context:** Reference the review comment explaining WHY + +**Don't:** +- Don't fix issues outside the PR scope +- Don't change code style unrelated to the issue +- Don't add features or enhancements +- Don't push commits without explaining what you fixed +- Don't fix subjective issues without discussion + +#### Troubleshooting GitHub Suggestions + +**Common issues and solutions:** + +| Problem | Cause | Solution | +|---------|-------|----------| +| "Validation Failed: line could not be resolved" | Used `line` instead of `position` | Use `position` (diff line number) not `line` (file line number) | +| Suggestion doesn't appear inline | Wrong position value | Count lines in the diff carefully, including headers | +| Can't apply suggestion | Merge conflict or branch updated | Author needs to update branch first | +| Suggestion formatting broken | Unescaped JSON characters | Escape quotes, backticks, newlines in JSON | +| "Invalid commit_id" | Used old commit SHA | Get current HEAD SHA before creating review | + +**How to verify your review before posting:** + +```bash +# 1. Validate JSON syntax +cat /tmp/review-suggestions.json | jq . > /dev/null && echo "✅ Valid JSON" + +# 2. Check position values against diff +gh pr diff > pr.diff +# Manually verify each position value in your JSON matches an added line + +# 3. Test with one suggestion first +# Before posting 10 suggestions, test with 1 to ensure positions are correct +``` + +#### Success Criteria + +A good fix provision should: +- [ ] **Use GitHub Suggestions as the primary method** (for ~70-80% of fixable issues) +- [ ] Make it easy for the author to address issues (one-click accept) +- [ ] Provide working, tested code (not untested suggestions) +- [ ] Explain the reasoning behind each fix in the comment body +- [ ] Reference the original review feedback from Step 8 +- [ ] Be respectful and collaborative in tone +- [ ] Focus only on issues identified in the review +- [ ] Not introduce new issues or regressions +- [ ] Include a friendly summary comment explaining how to apply suggestions +- [ ] Use proper `position` values (diff lines, not file lines) +- [ ] Batch related suggestions in one review for efficient application + +--- + +### Quick Start Template + +**Copy this template to quickly create a review with GitHub Suggestions:** + +```bash +#!/bin/bash +# Quick script to create GitHub Suggestions for PR review + +PR_NUMBER=YOUR_PR_NUMBER +OWNER="adobe" +REPO="helix-tools-website" + +# Get commit SHA +COMMIT_SHA=$(gh api repos/$OWNER/$REPO/pulls/$PR_NUMBER --jq '.head.sha') +echo "✅ PR Head SHA: $COMMIT_SHA" + +# Create review JSON +cat > /tmp/review-$PR_NUMBER.json < +Mobile View + +![Mobile Screenshot](url-or-embedded-image) + + + +**Verified:** +- [x] Code quality and linting +- [x] Performance (Lighthouse scores) +- [x] Visual appearance (screenshots captured) +- [x] Responsive behavior +- [x] Accessibility basics + +[Any additional notes] +``` + +### Request Changes +```markdown +## Changes Requested + +### Blocking Issues +[List with file:line references] + +### Suggestions +[List of recommendations] + +Please address the blocking issues before merge. +``` + +### Comment +```markdown +## Review Notes + +[Non-blocking observations and questions] +``` + +--- + +## Integration with GitHub Workflow + +When triggered via GitHub Actions, the skill should: + +1. **Receive:** PR number, repository info, event context +2. **Execute:** Full review workflow above +3. **Output:** + - Review comment on the PR + - Appropriate review status (approve/request-changes/comment) + - Summary posted as PR comment + +**GitHub Actions integration points:** +- `pull_request` event triggers +- `gh pr review` for posting reviews +- `gh pr comment` for detailed feedback + +--- + +## Resources + +### EDS-Specific Resources +- **EDS Development Guidelines:** https://www.aem.live/docs/dev-collab-and-good-practices +- **Performance Best Practices:** https://www.aem.live/developer/keeping-it-100 +- **Block Development:** https://www.aem.live/developer/block-collection +- **David's Model:** https://www.aem.live/docs/davidsmodel + +### GitHub Code Review Resources +- **GitHub Suggestions Documentation:** https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/incorporating-feedback-in-your-pull-request +- **Pull Request Reviews API:** https://docs.github.com/en/rest/pulls/reviews +- **Review Comments API:** https://docs.github.com/en/rest/pulls/comments +- **Creating Review Comments:** https://docs.github.com/en/rest/pulls/comments#create-a-review-comment-for-a-pull-request + +--- + +## Success Criteria + +### For Self-Review Mode + +A complete self-review should: +- [ ] Review all modified/new files +- [ ] Check code against all quality criteria +- [ ] Run linting and fix issues +- [ ] Capture visual screenshots of test content +- [ ] Verify responsive behavior across viewports +- [ ] Identify issues to fix before committing +- [ ] Confirm code is ready for commit and PR + +### For PR Review Mode + +A complete PR review should: +- [ ] Validate all PR structure requirements (preview URLs, description) +- [ ] Check code against all quality criteria +- [ ] Verify performance requirements +- [ ] Capture and include visual screenshots +- [ ] Assess visual appearance for regressions +- [ ] Assess content/authoring impact +- [ ] Identify security concerns +- [ ] Provide actionable feedback with specific references +- [ ] Include screenshots in PR review comment +- [ ] **Provide GitHub Suggestions for all fixable issues (primary method - ~70-80% of issues)** +- [ ] **Submit suggestions as a single review for batch application** +- [ ] **Include a friendly summary comment explaining how to apply suggestions** +- [ ] **Explain reasoning for each fix in the suggestion comment body** +- [ ] Use guidance comments only for subjective/architectural issues +- [ ] Use fix commits only when suggestions won't work (rare) +- [ ] Use appropriate review status (approve/request-changes/comment) + +--- + +## Integration with Content-Driven Development + +This skill integrates with the **content-driven-development** workflow: + +``` +CDD Workflow: +Step 1: Start dev server +Step 2: Analyze & plan +Step 3: Design content model +Step 4: Identify/create test content +Step 5: Implement (building-blocks skill) + └── testing-blocks skill (browser testing) + └── **code-review skill (self-review)** ← Invoke here +Step 6: Lint & test +Step 7: Final validation +Step 8: Ship it (commit & PR) +``` + +**Recommended invocation point:** After implementation and testing-blocks skill complete, before final linting and committing. + +**What this catches before PR:** +- Code quality issues +- EDS pattern violations +- Security concerns +- Performance problems +- Visual regressions + +**Benefits of self-review:** +- Catch issues early (cheaper to fix) +- Cleaner PRs with fewer review cycles +- Learn from immediate feedback +- Consistent code quality diff --git a/.claude/.skills/code-review/resources/review-checklist.md b/.claude/.skills/code-review/resources/review-checklist.md new file mode 100644 index 0000000..4c5d35c --- /dev/null +++ b/.claude/.skills/code-review/resources/review-checklist.md @@ -0,0 +1,394 @@ +# PR Review Checklist + +Detailed checklist for reviewing Edge Delivery Services pull requests. Use this as a reference when reviewing PRs. + +## PR Structure Checklist + +### Required Elements + +| Item | Description | Priority | +|------|-------------|----------| +| Preview URL (Before) | URL showing current state on main branch | BLOCKING | +| Preview URL (After) | URL showing changes on feature branch | BLOCKING | +| PR Description | Clear explanation of what and why | BLOCKING | +| Scope Alignment | Changes match title/description | HIGH | +| Issue Reference | Link to GitHub issue (for bug fixes) | RECOMMENDED | +| Test Plan | How changes were tested | RECOMMENDED | + +### Preview URL Format + +``` +Before: https://main--{repo}--{owner}.aem.page/{path-to-test-content} +After: https://{branch}--{repo}--{owner}.aem.page/{path-to-test-content} +``` + +### PR Description Template + +```markdown +## Description +[What changed and why] + +## Related Issue +Fix # + +## Test URLs +- Before: https://main--{repo}--{owner}.aem.page/{path} +- After: https://{branch}--{repo}--{owner}.aem.page/{path} + +## Types of Changes +- [ ] Bug fix +- [ ] New feature +- [ ] Breaking change +- [ ] Styling change +- [ ] Refactor + +## Checklist +- [ ] Linting passes +- [ ] Tested across viewports +- [ ] No console errors +- [ ] Performance validated +``` + +--- + +## JavaScript Checklist + +### Linting & Style + +| Check | Severity | Notes | +|-------|----------|-------| +| ESLint passes | BLOCKING | airbnb-base config | +| No `eslint-disable` without justification | HIGH | Specific disables with comments OK | +| No global `eslint-disable` | BLOCKING | Never acceptable | +| ES6+ features | MEDIUM | Use modern syntax | +| Import extensions | MEDIUM | Include `.js` extensions | + +### Architecture + +| Check | Severity | Notes | +|-------|----------|-------| +| No frameworks in critical path | HIGH | Affects LCP/TBT | +| Libraries via `loadScript()` | HIGH | Not in head.html | +| Consider IntersectionObserver | MEDIUM | For heavy libraries | +| aem.js unmodified | BLOCKING | Submit upstream PRs | +| No new build steps | HIGH | Without team consensus | + +### Code Patterns + +| Check | Severity | Notes | +|-------|----------|-------| +| Re-use existing DOM | MEDIUM | Don't recreate elements | +| Scoped selectors | HIGH | Block-specific | +| No hardcoded config | MEDIUM | Externalize values | +| No debug console logs | HIGH | Clean up before merge | +| Proper error handling | MEDIUM | Where appropriate | +| No CSS in JS | HIGH | Use CSS classes | + +### Anti-Patterns to Flag + +```javascript +// ANTI-PATTERN: Inline styles +element.style.color = 'red'; +element.style.display = 'none'; + +// PREFERRED: CSS classes +element.classList.add('error'); +element.classList.add('hidden'); + +// ANTI-PATTERN: innerHTML with styles +el.innerHTML = ''; + +// PREFERRED: External CSS +// Add styles to block's CSS file + +// ANTI-PATTERN: Hardcoded values +const API_URL = 'https://api.example.com'; +const TIMEOUT = 5000; + +// PREFERRED: Configuration +import { API_URL, TIMEOUT } from './config.js'; + +// ANTI-PATTERN: Global disable +/* eslint-disable */ +function doSomething() { } + +// PREFERRED: Specific disable with reason +/* eslint-disable-next-line no-param-reassign -- DOM element modification */ +element.disabled = true; +``` + +--- + +## CSS Checklist + +### Linting & Style + +| Check | Severity | Notes | +|-------|----------|-------| +| Stylelint passes | BLOCKING | Standard config | +| No `!important` without justification | HIGH | Prefer specificity | +| Property order preserved | MEDIUM | Don't reorder in functional PRs | + +### Scoping & Selectors + +| Check | Severity | Notes | +|-------|----------|-------| +| Block-scoped selectors | BLOCKING | `.block-name .selector` | +| Prefixed private classes | MEDIUM | `block-name-private` | +| Simple, readable selectors | MEDIUM | Add classes vs complex selectors | +| ARIA attributes for states | LOW | `[aria-expanded="true"]` | + +### Responsive Design + +| Check | Severity | Notes | +|-------|----------|-------| +| Mobile-first approach | HIGH | Base = mobile, media queries for larger | +| Standard breakpoints | HIGH | 600px, 900px, 1200px | +| min-width only | MEDIUM | Don't mix with max-width | +| All viewports tested | HIGH | Mobile, tablet, desktop | + +### Frameworks & Preprocessors + +| Check | Severity | Notes | +|-------|----------|-------| +| No Sass/Less/PostCSS | HIGH | Without team consensus | +| No Tailwind/etc | HIGH | Without team consensus | +| Native CSS features | MEDIUM | Evergreen browser support | + +### Anti-Patterns to Flag + +```css +/* ANTI-PATTERN: Unscoped selector */ +.title { + font-size: 2rem; +} + +/* PREFERRED: Block-scoped */ +main .hero .title { + font-size: 2rem; +} + +/* ANTI-PATTERN: !important overuse */ +.button { + background: blue !important; + color: white !important; +} + +/* PREFERRED: Higher specificity */ +main .hero .button { + background: blue; + color: white; +} + +/* ANTI-PATTERN: Mixed breakpoint directions */ +@media (max-width: 599px) { + .block { padding: 1rem; } +} +@media (min-width: 900px) { + .block { padding: 2rem; } +} + +/* PREFERRED: Consistent mobile-first */ +.block { + padding: 1rem; /* mobile */ +} +@media (min-width: 600px) { + .block { padding: 1.5rem; } +} +@media (min-width: 900px) { + .block { padding: 2rem; } +} + +/* ANTI-PATTERN: Complex, unreadable selectors */ +.block > div:first-child > ul > li:nth-child(2) > a { + color: red; +} + +/* PREFERRED: Add semantic classes */ +.block .nav-link-secondary { + color: red; +} +``` + +--- + +## Performance Checklist + +| Check | Severity | Notes | +|-------|----------|-------| +| Lighthouse mobile green | BLOCKING | Ideally 100 | +| Lighthouse desktop green | BLOCKING | Ideally 100 | +| No libs in head.html | BLOCKING | Critical path | +| No layout shifts | HIGH | CLS impact | +| Images optimized | HIGH | Size and format | +| IntersectionObserver for heavy ops | MEDIUM | Defer loading | +| No sync blocking operations | HIGH | Render blocking | +| Reasonable bundle size | MEDIUM | No unnecessary minification | + +### Performance Testing + +```bash +# Check preview URLs with PSI +# Mobile +https://pagespeed.web.dev/analysis?url=https://{branch}--{repo}--{owner}.aem.page/{path}&form_factor=mobile + +# Desktop +https://pagespeed.web.dev/analysis?url=https://{branch}--{repo}--{owner}.aem.page/{path}&form_factor=desktop +``` + +--- + +## Content & Authoring Checklist + +| Check | Severity | Notes | +|-------|----------|-------| +| Author-friendly structure | MEDIUM | Easy to understand | +| Backward compatibility | HIGH | Existing content works | +| No breaking migrations | HIGH | Content shouldn't need updates | +| No hardcoded strings | MEDIUM | Use placeholders/spreadsheets | +| No committed binaries | HIGH | Unless code-referenced | + +--- + +## Security Checklist + +| Check | Severity | Notes | +|-------|----------|-------| +| No secrets committed | BLOCKING | API keys, passwords, etc | +| No XSS vulnerabilities | BLOCKING | Sanitize user input | +| No innerHTML with user data | BLOCKING | Use textContent | +| CSP headers (tools) | HIGH | For tool pages | +| External links safe | MEDIUM | rel="noopener noreferrer" | +| No eval() or Function() | BLOCKING | Code injection risk | + +### Security Patterns + +```javascript +// ANTI-PATTERN: XSS vulnerability +element.innerHTML = userInput; + +// PREFERRED: Safe text content +element.textContent = userInput; + +// ANTI-PATTERN: Unsafe URL construction +const url = `https://api.example.com?q=${userInput}`; + +// PREFERRED: URL encoding +const url = `https://api.example.com?q=${encodeURIComponent(userInput)}`; + +// ANTI-PATTERN: Missing rel attribute +
Link + +// PREFERRED: Secure external links +Link +``` + +--- + +## Visual Validation Checklist + +### Screenshot Capture + +| Check | Severity | Notes | +|-------|----------|-------| +| Desktop screenshot captured | HIGH | 1200px viewport | +| Mobile screenshot captured | HIGH | 375px viewport | +| Tablet screenshot captured | MEDIUM | 768px viewport | +| Block-specific screenshot | MEDIUM | If block changes | +| Before/After comparison | MEDIUM | For visual changes | + +### Visual Assessment + +| Check | Severity | Notes | +|-------|----------|-------| +| Layout correct across viewports | HIGH | No broken layouts | +| No visual regressions | HIGH | Compare to main branch | +| Colors consistent | MEDIUM | With design system | +| Typography correct | MEDIUM | Font sizes, weights | +| Images display properly | HIGH | Size, aspect ratio | +| Icons visible | HIGH | All icons render | +| Dark mode works | MEDIUM | If applicable | +| Animations smooth | LOW | If applicable | + +### Common Visual Issues + +| Issue | What to Look For | +|-------|------------------| +| Layout breaks | Elements overlapping, overflowing, misaligned | +| Text issues | Truncation, overflow, wrong font, poor contrast | +| Image problems | Wrong size, aspect ratio, missing, broken | +| Responsive failures | Layout not adapting to viewport | +| Spacing issues | Inconsistent margins, padding | +| Color problems | Wrong colors, poor contrast, dark mode issues | +| Icon issues | Missing, wrong size, wrong color | + +### Screenshot Commands + +```bash +# Using the capture-screenshots.js utility +cd .claude/skills/pr-review/scripts +npm install +node capture-screenshots.js [before-url] [output-dir] [block-selector] + +# Examples +node capture-screenshots.js https://branch--repo--owner.aem.page/path +node capture-screenshots.js https://branch--repo--owner.aem.page/path https://main--repo--owner.aem.page/path +node capture-screenshots.js https://branch--repo--owner.aem.page/path "" ./screenshots ".hero" +``` + +### Embedding Screenshots in PR Comment + +```markdown +## Visual Preview + +### Desktop (1200px) +![Desktop Screenshot](path/to/desktop.png) + +### Mobile (375px) +![Mobile Screenshot](path/to/mobile.png) + +
+Additional Screenshots + +### Tablet (768px) +![Tablet Screenshot](path/to/tablet.png) + +### Block Detail +![Block Screenshot](path/to/block.png) + +
+``` + +--- + +## Accessibility Checklist + +| Check | Severity | Notes | +|-------|----------|-------| +| Semantic HTML | HIGH | Proper element usage | +| Heading hierarchy | HIGH | Sequential, no skips | +| Alt text for images | HIGH | Descriptive | +| ARIA labels | MEDIUM | For interactive elements | +| Keyboard navigation | HIGH | Tab order, focus states | +| Color contrast | HIGH | WCAG AA compliance | +| Focus visible | MEDIUM | Clear focus indicators | + +--- + +## Review Severity Guide + +### BLOCKING (Must Fix) +- PR cannot be merged until resolved +- Examples: security issues, linting failures, missing preview URLs, breaking changes + +### HIGH (Should Fix) +- Should be resolved before merge +- Examples: performance issues, accessibility violations, code quality concerns + +### MEDIUM (Recommended) +- Strong recommendation to fix +- Examples: best practice violations, maintainability concerns + +### LOW (Consider) +- Nice-to-have improvements +- Examples: code style preferences, documentation enhancements diff --git a/.claude/.skills/code-review/scripts/capture-screenshots.js b/.claude/.skills/code-review/scripts/capture-screenshots.js new file mode 100644 index 0000000..0be0497 --- /dev/null +++ b/.claude/.skills/code-review/scripts/capture-screenshots.js @@ -0,0 +1,223 @@ +/** + * Screenshot capture utility for PR reviews + * + * Captures screenshots of preview URLs at multiple viewport sizes + * for visual validation during PR review. + * + * Usage: + * node capture-screenshots.js [before-url] [output-dir] + * + * Examples: + * node capture-screenshots.js https://branch--repo--owner.aem.page/path + * node capture-screenshots.js https://branch--repo--owner.aem.page/path https://main--repo--owner.aem.page/path + * node capture-screenshots.js https://branch--repo--owner.aem.page/path "" ./my-screenshots + */ + +import { chromium } from 'playwright'; +import { mkdir, writeFile } from 'fs/promises'; +import { existsSync } from 'fs'; +import path from 'path'; + +// Viewport configurations +const VIEWPORTS = { + mobile: { width: 375, height: 667, name: 'mobile' }, + tablet: { width: 768, height: 1024, name: 'tablet' }, + desktop: { width: 1200, height: 800, name: 'desktop' }, +}; + +/** + * Capture screenshots of a URL at multiple viewport sizes + * @param {string} url - The URL to capture + * @param {string} outputDir - Directory to save screenshots + * @param {string} prefix - Filename prefix (e.g., 'after' or 'before') + * @param {string} [blockSelector] - Optional CSS selector to capture specific block + * @returns {Promise} - Paths to captured screenshots + */ +async function captureUrl(browser, url, outputDir, prefix, blockSelector = null) { + const page = await browser.newPage(); + const screenshots = {}; + + try { + // Navigate and wait for page to load + await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 }); + + // Wait for any animations to settle + await page.waitForTimeout(1500); + + // Capture at each viewport size + for (const [key, viewport] of Object.entries(VIEWPORTS)) { + await page.setViewportSize({ width: viewport.width, height: viewport.height }); + await page.waitForTimeout(500); // Wait for responsive adjustments + + const filename = `${prefix}-${viewport.name}.png`; + const filepath = path.join(outputDir, filename); + + await page.screenshot({ + path: filepath, + fullPage: true, + }); + + screenshots[key] = filepath; + console.log(` Captured ${viewport.name} (${viewport.width}x${viewport.height}): ${filename}`); + } + + // Capture specific block if selector provided + if (blockSelector) { + const block = page.locator(blockSelector); + if (await block.count() > 0) { + // Use desktop viewport for block screenshot + await page.setViewportSize({ width: VIEWPORTS.desktop.width, height: VIEWPORTS.desktop.height }); + await page.waitForTimeout(500); + + const filename = `${prefix}-block.png`; + const filepath = path.join(outputDir, filename); + + await block.first().screenshot({ path: filepath }); + screenshots.block = filepath; + console.log(` Captured block (${blockSelector}): ${filename}`); + } else { + console.log(` Block selector "${blockSelector}" not found, skipping block screenshot`); + } + } + } finally { + await page.close(); + } + + return screenshots; +} + +/** + * Generate markdown for PR comment with embedded screenshots + * @param {Object} afterScreenshots - Paths to "after" screenshots + * @param {Object} beforeScreenshots - Paths to "before" screenshots (optional) + * @returns {string} - Markdown content + */ +function generateMarkdown(afterScreenshots, beforeScreenshots = null) { + let markdown = '## Visual Preview\n\n'; + + if (beforeScreenshots) { + // Side-by-side comparison format + markdown += '### Comparison\n\n'; + markdown += '| Viewport | Before | After |\n'; + markdown += '|----------|--------|-------|\n'; + + for (const [key, afterPath] of Object.entries(afterScreenshots)) { + if (key === 'block') continue; + const beforePath = beforeScreenshots[key]; + const viewportName = key.charAt(0).toUpperCase() + key.slice(1); + markdown += `| ${viewportName} | ![Before](${beforePath}) | ![After](${afterPath}) |\n`; + } + + if (afterScreenshots.block || beforeScreenshots?.block) { + markdown += '\n### Block Detail\n\n'; + if (beforeScreenshots?.block) { + markdown += `**Before:**\n![Before Block](${beforeScreenshots.block})\n\n`; + } + if (afterScreenshots.block) { + markdown += `**After:**\n![After Block](${afterScreenshots.block})\n`; + } + } + } else { + // After-only format + markdown += '### Desktop (1200px)\n'; + markdown += `![Desktop Screenshot](${afterScreenshots.desktop})\n\n`; + + markdown += '### Mobile (375px)\n'; + markdown += `![Mobile Screenshot](${afterScreenshots.mobile})\n\n`; + + markdown += '
\nAdditional Screenshots\n\n'; + markdown += '### Tablet (768px)\n'; + markdown += `![Tablet Screenshot](${afterScreenshots.tablet})\n\n`; + + if (afterScreenshots.block) { + markdown += '### Block Detail\n'; + markdown += `![Block Screenshot](${afterScreenshots.block})\n\n`; + } + markdown += '
\n'; + } + + markdown += '\n### Visual Assessment\n'; + markdown += '- [ ] Layout renders correctly across viewports\n'; + markdown += '- [ ] No visual regressions from main branch\n'; + markdown += '- [ ] Colors and typography consistent\n'; + markdown += '- [ ] Images and icons display properly\n'; + + return markdown; +} + +/** + * Main function + */ +async function main() { + const args = process.argv.slice(2); + + if (args.length < 1) { + console.log('Usage: node capture-screenshots.js [before-url] [output-dir] [block-selector]'); + console.log(''); + console.log('Examples:'); + console.log(' node capture-screenshots.js https://branch--repo--owner.aem.page/path'); + console.log(' node capture-screenshots.js https://branch--repo--owner.aem.page/path https://main--repo--owner.aem.page/path'); + console.log(' node capture-screenshots.js https://branch--repo--owner.aem.page/path "" ./screenshots ".hero"'); + process.exit(1); + } + + const afterUrl = args[0]; + const beforeUrl = args[1] && args[1] !== '' ? args[1] : null; + const outputDir = args[2] || './pr-screenshots'; + const blockSelector = args[3] || null; + + // Create output directory + if (!existsSync(outputDir)) { + await mkdir(outputDir, { recursive: true }); + } + + console.log('PR Screenshot Capture'); + console.log('====================='); + console.log(`After URL: ${afterUrl}`); + if (beforeUrl) { + console.log(`Before URL: ${beforeUrl}`); + } + console.log(`Output directory: ${outputDir}`); + if (blockSelector) { + console.log(`Block selector: ${blockSelector}`); + } + console.log(''); + + const browser = await chromium.launch(); + + try { + // Capture "after" screenshots + console.log('Capturing "After" screenshots...'); + const afterScreenshots = await captureUrl(browser, afterUrl, outputDir, 'after', blockSelector); + + // Capture "before" screenshots if URL provided + let beforeScreenshots = null; + if (beforeUrl) { + console.log('\nCapturing "Before" screenshots...'); + beforeScreenshots = await captureUrl(browser, beforeUrl, outputDir, 'before', blockSelector); + } + + // Generate markdown + const markdown = generateMarkdown(afterScreenshots, beforeScreenshots); + + // Save markdown file + const markdownPath = path.join(outputDir, 'visual-preview.md'); + await writeFile(markdownPath, markdown); + console.log(`\nMarkdown saved to: ${markdownPath}`); + + // Output markdown to console + console.log('\n--- PR Comment Markdown ---\n'); + console.log(markdown); + console.log('--- End Markdown ---\n'); + + console.log('Screenshots captured successfully!'); + console.log(`Files saved in: ${outputDir}`); + } finally { + await browser.close(); + } +} + +main().catch((error) => { + console.error('Error capturing screenshots:', error.message); + process.exit(1); +}); diff --git a/.claude/.skills/code-review/scripts/package.json b/.claude/.skills/code-review/scripts/package.json new file mode 100644 index 0000000..5f59bf1 --- /dev/null +++ b/.claude/.skills/code-review/scripts/package.json @@ -0,0 +1,12 @@ +{ + "name": "code-review-scripts", + "version": "1.0.0", + "description": "Screenshot capture utilities for code review skill", + "type": "module", + "scripts": { + "capture": "node capture-screenshots.js" + }, + "dependencies": { + "playwright": "^1.40.0" + } +} diff --git a/.claude/.skills/content-driven-development/SKILL.md b/.claude/.skills/content-driven-development/SKILL.md new file mode 100644 index 0000000..1a4b4e1 --- /dev/null +++ b/.claude/.skills/content-driven-development/SKILL.md @@ -0,0 +1,294 @@ +--- +name: Using Content Driven Development +description: Apply a Content Driven Development process to AEM Edge Delivery Services development. Use for all development tasks, including building new blocks, modifying existing blocks, making changes to core decoration functionality, etc. +--- + +# Using Content Driven Development (CDD) + +Content Driven Development is a mandatory process for AEM Edge Delivery Services development that prioritizes content and author needs over developer convenience. This skill orchestrates the development workflow to ensure code is built against real content with author-friendly content models. + +## Why Content-First Matters + +**Author needs come before developer needs.** When building for AEM Edge Delivery, authors are the primary users of the structures we create. Content models must be intuitive and easy to work with, even if that means more complex decoration code. + +**Efficiency through preparation.** Creating or identifying test content before coding provides: +- **Immediate testing capability**: No need to stop development to create test content +- **Better PR workflows**: Test content doubles as PR validation links for PSI checks +- **Living documentation**: Test content often serves as author documentation and examples +- **Fewer assumptions**: Real content reveals edge cases code-first approaches miss + +**NEVER start writing or modifying code without first identifying or creating the content you will use to test your changes.** + +## When to Apply This Skill + +Apply Content Driven Development principles to ALL AEM development tasks: + +- ✅ Creating new blocks +- ✅ Modifying existing blocks (structural or functional changes) +- ✅ Changes to core decoration functionality +- ✅ Bug fixes that require validation +- ✅ Any code that affects how authors create or structure content + +Skip CDD only for: +- ⚠️ Trivial CSS-only styling tweaks (but still identify test content for validation) +- ⚠️ Configuration changes that don't affect authoring + +When in doubt, follow the CDD process. The time invested pays dividends in quality and efficiency. + +## Related Skills + +This skill orchestrates other skills at the appropriate stages: + +- **content-modeling**: Invoked when new content models need to be designed or existing models modified +- **building-blocks**: Invoked during implementation phase for block creation or modification +- **testing-blocks**: Referenced during validation phase for comprehensive testing +- **block-collection-and-party**: Used to find similar blocks and reference implementations + +## The Content-First Process + +Follow these phases in order. Do not skip steps. + +### Phase 1: Content Discovery and Modeling + +The first phase establishes what content you're working with and ensures the content model is author-friendly. + +#### Step 1.1: Determine Content Availability + +**For new blocks:** + +Skip to Step 1.2 (Content Model Design). Searching for content that doesn't exist is a waste of time. + +**For modifications to existing blocks:** + +Ask the user: "Does content using this block already exist that we can use for testing?" + +- **YES** → Identify existing content to test against + - Use the `scripts/find-block-content.js` script to search for pages containing the block + - Or ask the user: "What are the path(s) to page(s) with this block?" + - Validate the content loads correctly in your local dev environment + - Proceed to Phase 2 (skip content modeling if structure isn't changing) + +- **NO existing content** → Proceed to Step 1.2 + +#### Step 1.2: Content Model Design + +**REQUIRED for:** +- All new blocks +- Structural changes to existing blocks (adding/removing/modifying sections, variants, or the authoring structure) + +**Ask the user:** +"This requires a new content model. Would you like me to use the content-modeling skill to design an author-friendly content model now?" + +- **YES** → Invoke the **content-modeling** skill + - Follow the content modeling process completely + - Return to this skill when content model is defined + - Proceed to Step 1.3 + +- **NO** → The user may want to define it themselves + - Ask: "Please describe the content structure authors will use" + - Document their description for reference + - Proceed to Step 1.3 + +#### Step 1.3: Content Creation + +Once the content model is defined (from Step 1.2), you need test content. + +**Ask the user:** +"We need test content for development and validation. This content will serve multiple purposes: +- Testing during development +- PR validation link for PSI checks +- Author documentation and examples + +Would you like to: +1. Create this content in the CMS now (Google Drive/SharePoint/DA/Universal Editor) +2. Create temporary local HTML files for testing (will need CMS content before PR)" + +**Option 1: CMS Content (Recommended)** +- Guide the user through creating content in their CMS +- Wait for user confirmation that content is created and published +- Get the content URL(s) from the user +- Validate content loads in local dev environment +- Proceed to Phase 2 + +**Option 2: Local HTML Files (Temporary)** +- Create HTML file(s) in `drafts/` folder matching the content model structure +- Reference the [HTML Structure Guide](resources/html-structure.md) for proper file format +- Remind user: "Restart your dev server with: `aem up --html-folder drafts`" +- Note: "You will need to create actual CMS content before raising a PR" +- Proceed to Phase 2 + +##### Making Test Content Serve as Author Documentation + +Test content can often double as author-facing documentation, saving time and keeping documentation current. Consider this when creating test content: + +**When test content IS sufficient as author documentation:** +- The block is straightforward with clear patterns +- Test content shows all variants and use cases +- Content demonstrates best practices authors should follow +- Examples are realistic and relatable to actual use cases + +**When separate author documentation is needed:** +- Block has complex configuration or many variants requiring explanation +- There are edge cases or gotchas authors need to understand +- Project standards require formal documentation in a specific location/format +- Block behavior isn't self-evident from examples alone + +**Structuring test content to serve both purposes:** +1. **Create comprehensive examples**: Show all variants, edge cases, and common patterns +2. **Use realistic content**: Avoid "lorem ipsum" or technical placeholders +3. **Demonstrate best practices**: Structure content the way authors should +4. **Consider location**: Place content where it can serve as documentation + - Sidekick Library projects: Consider creating in `/tools/sidekick/library/` or appropriate library location + - Document Authoring: Place in DA Library structure + - Simple documentation: Use `/drafts/docs/` or `/drafts/library/` + - Universal Editor: Follow project-specific documentation patterns + +**Ask the user about documentation approach:** +"Should this test content also serve as author documentation? If so, we can structure it accordingly and place it in an appropriate location (e.g., `/drafts/library/{block-name}` or your project's library system)." + +If yes, guide content creation with documentation in mind. If no, proceed with test-focused content and note that author documentation will be needed later. + +### Phase 2: Implementation + +**CRITICAL: Do not begin Phase 2 until you have confirmed test content exists and is accessible.** + +Now that test content exists, proceed with implementation: + +#### For Block Development + +Invoke the **building-blocks** skill: +- Provide the skill with the content model and test content URL(s) +- Follow the building-blocks process for implementation +- Return to this skill when implementation is complete +- Proceed to Phase 3 + +#### For Core Functionality Changes + +Follow standard development practices: +- Make changes to scripts, styles, or configuration +- Test against the identified content throughout development +- Ensure changes don't break existing blocks or content models +- Proceed to Phase 3 + +### Phase 3: Validation + +The final phase ensures the implementation works correctly with real content. + +#### Step 3.1: Test with Real Content + +**Mandatory testing:** +- ✅ View test content in local dev environment +- ✅ Verify all variants render correctly +- ✅ Check responsive behavior (mobile, tablet, desktop) +- ✅ Test edge cases revealed by the actual content +- ✅ Validate accessibility basics (keyboard navigation, screen reader friendly) + +#### Step 3.2: Run Quality Checks + +**Required before considering implementation complete:** + +```bash +npm run lint +``` + +If linting fails, fix issues with: +```bash +npm run lint:fix +``` + +#### Step 3.3: Comprehensive Testing + +**The testing-blocks skill is automatically invoked by building-blocks** for block development. + +For other code changes, or for additional testing guidance, invoke the **testing-blocks** skill which provides: +- Unit testing strategies for logic-heavy utilities +- Browser testing with Playwright/Puppeteer +- Linting and code quality checks +- Performance validation with GitHub checks +- Guidance on keeper vs throwaway tests + +#### Step 3.4: PR Preparation + +**Before raising a PR, ensure:** +- ✅ Test content exists in the CMS (not just local HTML) +- ✅ Test content URL is accessible for PSI checks +- ✅ All linting passes +- ✅ Author documentation is updated (if applicable) + +The test content URL will be used as the PR validation link. + +## Anti-Patterns to Avoid + +**Common mistakes:** +- ❌ Starting with code before understanding the content model +- ❌ Making assumptions about content structure without seeing real examples +- ❌ Creating developer-friendly but author-hostile content models +- ❌ Skipping content creation "to save time" (costs more time later) +- ❌ Testing against imagined content instead of real content +- ❌ Treating test content creation as separate from development workflow + +## Workflow Summary + +**Quick reference for the CDD process:** + +``` +1. CONTENT DISCOVERY + └─ Existing content? → Use it + └─ New block/structure? → Design content model → Create test content + +2. IMPLEMENTATION + └─ Build code against the real content model + └─ Test continuously with actual content + +3. VALIDATION + └─ Comprehensive testing with test content + └─ Quality checks (linting, accessibility) + └─ PR preparation with test URL + +KEY RULE: Never proceed to implementation without test content +``` + +## Scripts and Tools + +### Finding Existing Block Content + +Use the provided script to search for pages containing a specific block: + +```bash +# Search on localhost (default) +node .claude/skills/content-driven-development/scripts/find-block-content.js + +# Search for specific variant +node .claude/skills/content-driven-development/scripts/find-block-content.js localhost:3000 + +# Search on live site +node .claude/skills/content-driven-development/scripts/find-block-content.js main--repo--owner.aem.live + +# Search on preview with variant +node .claude/skills/content-driven-development/scripts/find-block-content.js main--repo--owner.aem.page +``` + +**Examples:** +```bash +node .claude/skills/content-driven-development/scripts/find-block-content.js hero +node .claude/skills/content-driven-development/scripts/find-block-content.js hero localhost:3000 dark +node .claude/skills/content-driven-development/scripts/find-block-content.js cards main--site--owner.aem.live three-up +``` + +This script queries the site's query-index to find all pages containing the specified block (and optional variant) and returns their URLs. The script uses proper DOM parsing to accurately identify blocks. + +## Integration with Other Skills + +This skill acts as the orchestrator for AEM development workflows: + +**At Content Modeling stage:** +→ Invoke **content-modeling** skill for author-friendly design + +**At Implementation stage:** +→ Invoke **building-blocks** skill for block development +→ Reference **block-collection-and-party** skill for patterns + +**At Validation stage:** +→ Reference **testing-blocks** skill for comprehensive testing + +Following this orchestration ensures all development follows content-first principles. diff --git a/.claude/.skills/content-driven-development/resources/html-structure.md b/.claude/.skills/content-driven-development/resources/html-structure.md new file mode 100644 index 0000000..b41c03b --- /dev/null +++ b/.claude/.skills/content-driven-development/resources/html-structure.md @@ -0,0 +1,542 @@ +# HTML File Structure for Test Content + +When creating local `.plain.html` files for testing blocks in the `drafts/` folder, follow this structure to match how AEM Edge Delivery Services processes authored content. + +## Important Change: Plain HTML Format + +**The AEM CLI now automatically wraps HTML content with the headful structure (head, header, footer).** When you create `.plain.html` files, you ONLY need to provide the section content. + +**What you create:** +- ✅ Section divs with content: `
...
` (one per section) +- ✅ Blocks as `
` with nested divs +- ✅ Default content (headings, paragraphs, links, images) +- ✅ Section metadata blocks when needed + +**What the AEM CLI adds automatically:** +- ❌ ``, ``, `` tags +- ❌ `
` and `