# Blog Grid Module Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Create a Blog Grid module (`dnxte/blog-grid`) by forking NextPostCarousel, replacing carousel with CSS Grid, adding category filter bar and pagination (Load More, Numbered, Infinite Scroll).

**Architecture:** Fork NextPostCarousel, strip Swiper/carousel logic, add CSS Grid layout with 3 card layouts (card/overlay/horizontal), AJAX category filtering, and 3 pagination types. Reuse existing post query patterns.

**Tech Stack:** TypeScript/React (Divi 5 VB), PHP (server-side rendering), SCSS, vanilla JS (frontend AJAX)

**Spec:** `docs/superpowers/specs/2026-03-24-blog-grid-design.md`

---

### Task 1: Create module.json

**Files:**
- Create: `divi-5/visual-builder/src/components/NextBlogGrid/module.json`

- [ ] **Step 1: Create module.json** with module identity, attributes for post query, elements, grid settings, filter bar, pagination, and card styling. Base structure on NextPostCarousel's module.json but:
  - Change `name` to `dnxte/blog-grid`, `title` to `Blog Grid`
  - Remove: `carouselSettings`, `navigationSettings`, `effectSettings` attributes
  - Add: `grid` (layout, columns, columnGap, rowGap), `filterBar` (show, allLabel, categories), `pagination` (type, loadMoreText, loadingText), `filter` styling attrs, `card` (background, backgroundHover, borderRadius, boxShadow, boxShadowHover, padding, border, borderHover), `horizontalImage` (width, default 200px), `image.objectFit` (cover/contain)
  - Keep: `post` (including `post.order`), `elements`, `module`, `title`, `content`, `category`, `author`, `date`, `button`, `image`, `imageSettings`, `contentWrapper`
  - Typography attrs: `titleFont`, `metaFont`, `categoryFont`, `excerptFont`, `readMoreFont` as Divi font groups
  - Add `customCssFields` for: filterBar, filterTab, activeTab, gridContainer, card, cardImage, cardTitle, cardMeta, cardExcerpt, readMore, pagination
  - Selectors: `{{selector}}` root, `{{selector}} .dnxte-blog-grid-container` for grid, etc.

- [ ] **Step 2: Verify JSON is valid**

Run: `node -e "require('./divi-5/visual-builder/src/components/NextBlogGrid/module.json')"`

---

### Task 2: Create TypeScript types and index

**Files:**
- Create: `divi-5/visual-builder/src/components/NextBlogGrid/types.ts`
- Create: `divi-5/visual-builder/src/components/NextBlogGrid/index.ts`
- Create: `divi-5/visual-builder/src/components/NextBlogGrid/placeholder-content.ts`
- Create: `divi-5/visual-builder/src/components/NextBlogGrid/custom-css.ts`
- Create: `divi-5/visual-builder/src/components/NextBlogGrid/module-classnames.ts`
- Create: `divi-5/visual-builder/src/components/NextBlogGrid/module-script-data.tsx`

- [ ] **Step 1: Create types.ts** — Fork from NextPostCarousel types. Remove `carouselSettings`, `navigationSettings`, `effectSettings`. Add `grid`, `filterBar`, `pagination`, `filter` interfaces.

- [ ] **Step 2: Create placeholder-content.ts** — Empty placeholder like NextPostCarousel.

- [ ] **Step 3: Create custom-css.ts** — Map custom CSS fields from module.json with labels for: Filter Bar, Filter Tab, Active Tab, Grid Container, Card, Card Image, Card Title, Card Meta, Card Excerpt, Read More, Pagination.

- [ ] **Step 4: Create module-classnames.ts** — Copy from NextPostCarousel (TextClassnames only).

- [ ] **Step 5: Create module-script-data.tsx** — Similar to NextPostCarousel but enqueue `dnext_blog_grid` instead of swiper scripts.

- [ ] **Step 6: Create index.ts** — Register module with metadata, settings, renderers. Same pattern as NextPostCarousel index.ts.

---

### Task 3: Create settings-content.tsx

**Files:**
- Create: `divi-5/visual-builder/src/components/NextBlogGrid/settings-content.tsx`

- [ ] **Step 1: Create settings-content.tsx** — Fork from NextPostCarousel settings-content.tsx:
  - Keep: Post Settings group (postLayout as grid layout selector with card/overlay/horizontal, type, number, offset, categories, orderby, dateFormat, excerptLength, excerptContent, showFeaturedImage, featuredImageSize with custom option + customImageWidth/customImageHeight, postTarget)
  - Remove: Carousel Settings, Navigation Settings, Effect Settings groups
  - Add: Grid Settings group (columns per breakpoint, columnGap, rowGap)
  - Add: Filter Bar group (show toggle, allLabel text, categories multi-select)
  - Add: Pagination group (type select: loadMore/numbered/infiniteScroll/none, loadMoreText, loadingText)
  - Add: Element toggles (showTitle, showDate, showAuthor, showCategories, showExcerpt, showReadMore, readMoreText, showCommentCount, noResultsText)

---

### Task 4: Create settings-design.tsx

**Files:**
- Create: `divi-5/visual-builder/src/components/NextBlogGrid/settings-design.tsx`

- [ ] **Step 1: Create settings-design.tsx** — Fork from NextPostCarousel settings-design.tsx:
  - Keep: Image Settings (width, height, border, boxShadow), Post Item (background, border, boxShadow), Post Text (title/body fonts), Category (font, border), Author (font), Date (font), Read More (button group + alignment), Spacing groups, Sizing, Border, BoxShadow, Filters, Transform, Animation
  - Remove: Arrow Settings, Color Settings (arrow/dots/progressbar), Content Position (carousel-specific)
  - Add: Filter Bar Styling group (activeBackground, activeColor, inactiveBackground, inactiveColor, inactiveBorder, borderRadius, font, spacing, alignment)
  - Add: Pagination Styling group (background, color, font, borderRadius, alignment)
  - Add: Card hover states (backgroundHover, boxShadowHover, borderHover)
  - Add: Image hover effect (zoom/grayscale/none select)
  - Add: Horizontal layout image width (visible when layout=horizontal)

---

### Task 5: Create settings-advanced.tsx

**Files:**
- Create: `divi-5/visual-builder/src/components/NextBlogGrid/settings-advanced.tsx`

- [ ] **Step 1: Create settings-advanced.tsx** — Copy from NextPostCarousel settings-advanced.tsx, update type imports to NextBlogGridAttrs.

---

### Task 6: Create styles.tsx

**Files:**
- Create: `divi-5/visual-builder/src/components/NextBlogGrid/styles.tsx`

- [ ] **Step 1: Create styles.tsx** — Fork from NextPostCarousel styles.tsx:
  - Remove: Swiper-specific selectors, arrow styles, effect styles, navigation styles
  - Keep: module style, imageSettings (width/height), post, title, content, category, image, contentWrapper, author, date, button styles
  - Add: Grid column styles via advancedStyles (grid-template-columns using columns attr)
  - Add: Filter bar color styles via advancedStyles (active/inactive background/color)
  - Add: Pagination color styles
  - Add: Image hover effect styles (zoom transform, grayscale filter)
  - Add: Card hover states
  - Add: CssStyle with new cssFields

---

### Task 7: Create edit.tsx (Visual Builder component)

**Files:**
- Create: `divi-5/visual-builder/src/components/NextBlogGrid/edit.tsx`

- [ ] **Step 1: Create edit.tsx** — Fork from NextPostCarousel edit.tsx:
  - Remove: All Swiper imports, initialization, refs, useSettings hook, arrow position logic
  - Keep: Post fetching via AJAX (`get_all_posts` action), getDnxteGlobal helper, AllPosts interface, attribute extraction
  - Replace Swiper container with CSS Grid container:
    ```tsx
    <div className="dnxte-blog-grid-container" style={gridStyle}>
      {allPosts}
    </div>
    ```
  - Grid items: Replace `swiper-slide` wrapper with `dnxte-blog-grid-card` wrapper
  - Add filter bar rendering (static/non-functional in VB, uses category data from REST API)
  - Add pagination UI rendering (static in VB)
  - Compute gridStyle from columns/gap attributes
  - Import and use Layout component (to be created)

---

### Task 8: Create Layout component for grid cards

**Files:**
- Create: `divi-5/visual-builder/src/components/NextBlogGrid/components/layout/layout.tsx`
- Create: `divi-5/visual-builder/src/components/NextBlogGrid/components/index.ts`

- [ ] **Step 1: Create layout.tsx** — Fork from NextPostCarousel Layout component:
  - Simplify to 3 layouts: card (default), overlay, horizontal
  - Card layout: image top, content below (title, meta, excerpt, read more)
  - Overlay layout: full image with gradient, content at bottom
  - Horizontal layout: image left (fixed width), content right
  - Wrap entire card in `<a>` tag linking to post permalink
  - Category tags as plain text (not links) to avoid nested `<a>`
  - Use `dnxte-blog-grid-card` CSS class prefix

- [ ] **Step 2: Create components/index.ts** — Export Layout component.

---

### Task 9: Create module.scss

**Files:**
- Create: `divi-5/visual-builder/src/components/NextBlogGrid/module.scss`

- [ ] **Step 1: Create module.scss** with styles for:
  - `.dnxte-blog-grid` — module root
  - `.dnxte-blog-grid-filter-bar` — flex container, gap, wrap
  - `.dnxte-blog-grid-filter-bar-tab` — pill button style, inactive state
  - `.dnxte-blog-grid-filter-bar-tab--active` — filled purple default
  - `.dnxte-blog-grid-container` — CSS Grid, `display: grid`, transitions
  - `.dnxte-blog-grid--loading` — opacity 0.5, pointer-events none
  - `.dnxte-blog-grid-card` — card base (block link, no text-decoration)
  - `.dnxte-blog-grid-card-image` — image container, overflow hidden
  - `.dnxte-blog-grid-card-content` — padding area
  - `.dnxte-blog-grid-card-title` — post title
  - `.dnxte-blog-grid-card-meta` — date | author | category row
  - `.dnxte-blog-grid-card-excerpt` — excerpt text
  - `.dnxte-blog-grid-card-read-more` — read more link
  - `.dnxte-blog-grid-card--overlay` — relative positioning, min-height, gradient
  - `.dnxte-blog-grid-card--horizontal` — flex layout, image fixed width
  - `.dnxte-blog-grid-pagination` — centered container
  - `.dnxte-blog-grid-pagination-load-more` — pill button
  - `.dnxte-blog-grid-pagination-numbers` — flex, page number styling
  - `.dnxte-blog-grid-pagination-spinner` — CSS spinner animation
  - `.dnxte-blog-grid-error` — error message styling
  - Responsive defaults

---

### Task 10: Create PHP module class and traits

**Files:**
- Create: `divi-5/server/Modules/NextBlogGrid/NextBlogGrid.php`
- Create: `divi-5/server/Modules/NextBlogGrid/NextBlogGridTrait/RenderCallbackTrait.php`
- Create: `divi-5/server/Modules/NextBlogGrid/NextBlogGridTrait/ModuleStylesTrait.php`
- Create: `divi-5/server/Modules/NextBlogGrid/NextBlogGridTrait/ModuleClassnamesTrait.php`
- Create: `divi-5/server/Modules/NextBlogGrid/NextBlogGridTrait/ModuleScriptDataTrait.php`
- Create: `divi-5/server/Modules/NextBlogGrid/NextBlogGridTrait/CustomCssTrait.php`

- [ ] **Step 1: Create NextBlogGrid.php** — Copy NextPostCarousel.php pattern:
  - Namespace: `DNXTE\Modules\NextBlogGrid`
  - Use `RenderCallbackTrait`
  - Define `BLOG_GRID_LAYOUTS` constant
  - Register module via `ModuleRegistration::register_module()`

- [ ] **Step 2: Create RenderCallbackTrait.php** — Fork from NextPostCarousel RenderCallbackTrait:
  - Keep: WP_Query setup, attribute extraction for post/elements, image rendering, title/meta/excerpt/readmore rendering
  - Remove: All Swiper/carousel container markup, data attributes, arrow position logic, navigation/pagination divs
  - Replace with: Grid container (`dnxte-blog-grid-container`), grid card wrappers (`dnxte-blog-grid-card`)
  - Add: Filter bar HTML rendering (category tabs from WP categories)
  - Add: Pagination HTML (load more button / numbered links / infinite scroll sentinel)
  - Add: Grid layout class (`dnxte-blog-grid-card--card`, `--overlay`, `--horizontal`)
  - Add: Whole card wrapped in `<a>` tag
  - Add: `wp_localize_script` for nonce and AJAX URL
  - Add: Data attributes on container for JS (posts_per_page, orderby, order, post_type, categories, layout, pagination type)

- [ ] **Step 3: Create ModuleStylesTrait.php** — Fork from NextPostCarousel:
  - Keep: module, imageSettings, post, title, content, category, image, contentWrapper, author, date, button element styles
  - Remove: All navigation/arrow/dots/progressbar styles, effect styles
  - Add: Filter bar color styles (active/inactive background/color via advancedStyles)
  - Add: Pagination styles
  - Add: Grid column styles
  - Add: Card hover styles
  - Add: Image hover effect styles

- [ ] **Step 4: Create ModuleClassnamesTrait.php** — Copy from NextPostCarousel (TextClassnames only), update namespace.

- [ ] **Step 5: Create ModuleScriptDataTrait.php** — Fork from NextPostCarousel:
  - Remove: `dnext_swiper`, `dnext_post_carousel` script enqueue
  - Add: `dnext_blog_grid` script enqueue
  - Keep: ElementScriptData::set() pattern

- [ ] **Step 6: Create CustomCssTrait.php** — Update to use `dnxte/blog-grid` block type registry.

---

### Task 11: Create PHP layout files

**Files:**
- Create: `divi-5/server/Modules/NextBlogGrid/layout/layout-card.php`
- Create: `divi-5/server/Modules/NextBlogGrid/layout/layout-overlay.php`
- Create: `divi-5/server/Modules/NextBlogGrid/layout/layout-horizontal.php`

- [ ] **Step 1: Create layout-card.php** — Based on NextPostCarousel layout-one.php:
  - Card wrapped in `<a>` tag with post permalink
  - Image wrapper, title, meta (date | author | category as text), excerpt, read more
  - Use `dnxte-blog-grid-card` class prefix

- [ ] **Step 2: Create layout-overlay.php** — Full image background with gradient overlay, content at bottom.

- [ ] **Step 3: Create layout-horizontal.php** — Flex layout, image left, content right.

---

### Task 12: Create AJAX handler for filtering and pagination

**Files:**
- Create: `divi-5/server/Modules/NextBlogGrid/NextBlogGridTrait/AjaxHandlerTrait.php`
- Modify: `divi-5/server/Modules/NextBlogGrid/NextBlogGrid.php` (add AJAX hook registration)

- [ ] **Step 1: Create AjaxHandlerTrait.php** with:
  - `register_ajax_handlers()` method — hooks `wp_ajax_` and `wp_ajax_nopriv_` for `dnxte_blog_grid_filter`
  - `handle_filter_request()` method:
    - Verify nonce (`dnxte_blog_grid_nonce`)
    - Sanitize inputs (category, page, posts_per_page, orderby, order, post_type, layout, element toggles)
    - Build WP_Query args
    - Loop through posts, render cards using layout files
    - Return JSON: `{ success: true, data: { html, max_pages, current_page, found_posts } }`
    - Error handling with `wp_send_json_error()`

- [ ] **Step 2: Update NextBlogGrid.php** — Call `register_ajax_handlers()` in `load()`.

---

### Task 13: Create frontend JavaScript for AJAX interactions

**Files:**
- Create: `divi-5/visual-builder/src/components/NextBlogGrid/frontend/blog-grid.js`

- [ ] **Step 1: Create blog-grid.js** with:
  - Module initialization (find all `.dnxte-blog-grid` instances)
  - Filter bar click handler:
    - Update active tab class
    - Add loading state to grid
    - AJAX POST to admin-ajax.php with `dnxte_blog_grid_filter` action
    - Replace grid HTML, remove loading state
    - Reset pagination to page 1
    - Error handling with inline error message
  - Load More handler:
    - Increment page, AJAX fetch, append new cards
    - Hide button when max_pages reached
    - Update button text during loading
  - Numbered pagination handler:
    - AJAX fetch page, replace grid content
    - Smooth scroll to module top
    - Update active page number
  - Infinite scroll handler:
    - IntersectionObserver with rootMargin "0px 0px 200px 0px"
    - Append cards, show/hide spinner
    - Disconnect observer when no more posts
  - Accessibility: aria-busy, focus management

---

### Task 14: Register module in index.ts and Modules.php

**Files:**
- Modify: `divi-5/visual-builder/src/index.ts`
- Modify: `divi-5/server/Modules/Modules.php`

- [ ] **Step 1: Add import to index.ts:**
  ```typescript
  import { NextBlogGrid } from "./components/NextBlogGrid";
  ```

- [ ] **Step 2: Add to moduleMapping in index.ts:**
  ```typescript
  'dnxte-blog-grid': NextBlogGrid,
  ```

- [ ] **Step 3: Add to Modules.php module_mapping array:**
  ```php
  'dnxte-blog-grid' => 'NextBlogGrid',
  ```

---

### Task 15: Register and enqueue frontend script

**Files:**
- Modify: Plugin's asset enqueue file (check `divi-5.php` or similar)

- [ ] **Step 1: Register `dnext_blog_grid` script** — Enqueue the frontend JS file when the module is on the page.

- [ ] **Step 2: Localize script** with nonce and AJAX URL via `wp_localize_script()`.

---

### Task 16: Build and verify

- [ ] **Step 1: Run build**
  ```bash
  npm run build:divi5
  ```

- [ ] **Step 2: Verify module appears in Visual Builder**

- [ ] **Step 3: Verify frontend rendering**

- [ ] **Step 4: Test filter bar AJAX**

- [ ] **Step 5: Test pagination (all 3 types)**

- [ ] **Step 6: Commit**
