diff --git a/docs/superpowers/plans/2026-07-25-team-grid-block.md b/docs/superpowers/plans/2026-07-25-team-grid-block.md new file mode 100644 index 0000000..afaeea9 --- /dev/null +++ b/docs/superpowers/plans/2026-07-25-team-grid-block.md @@ -0,0 +1,614 @@ +# Team Grid Block 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:** Build a new `team-grid` ACF block for the Team page (and any other page that needs a team grid) that renders a responsive grid of team members. Each member shows their photo (rounded rectangle), name, and title. The bio is collapsed by default and expands vertically with a smooth animation when the photo, name, or title is clicked. + +**Architecture:** One new ACF block (`acf/team-grid`) with a single repeater of `members` (image, name, title, bio). The block uses native HTML `
`/`` for the click-to-expand interaction, animated with the `grid-template-rows: 0fr → 1fr` CSS pattern (no JavaScript). The block has no built-in background — the editor wraps it in an existing Section block when a dark/light background is needed. The page template is unchanged; the editor adds the block to the Team page in Gutenberg. + +**Tech Stack:** WordPress 6.x, PHP 8.x, Tailwind CSS v4, Playwright, axe-core, PHPCS (WordPress standard). + +## Global Constraints + +- Tabs for PHP indentation (project standard). +- `static/dist/theme.css` is committed (not gitignored) per project convention. +- PHPCS uses the WordPress coding standard; `composer lint` must pass for all touched files. +- All design colors use the project's existing CSS custom properties from `styles/base/colors.css`. No hardcoded hex values for theme colors. +- Reuse existing design tokens where possible (e.g. `text-cwc-blue-01`, `var(--color-secondary)`). +- The headshot uses `rounded-md` to match other image cards in the project. +- All commits follow the project's emoji-prefixed commit-message style (e.g. `✨ feat: …`). +- This work does **not** edit `header.php`, `footer.php`, `views/partials/page-hero.php`, or `views/partials/page-hero-services.php`. +- `page.php` is not modified. +- No JavaScript files are added. +- Reuse the same `regACFBlocks()` auto-registration pattern as `services-list`. +- Tailwind v4 picks up class names from source files at build time. The dynamic class names used in the CSS (`grid-cols-1`, `sm:grid-cols-2`, `lg:grid-cols-3`, `w-32 h-32`, etc.) must all be literal in the source for the build to emit them. + +## File Structure + +| File | Responsibility | Created/Modified | +| --- | --- | --- | +| `views/blocks/team-grid/team-grid.php` | Block template (PHP) — renders the grid. | Create | +| `views/blocks/team-grid/team-grid.css` | Block styles (CSS) — layout, photo, summary, animation. | Create | +| `views/blocks/team-grid/block.json` | Block registration metadata. | Create | +| `acf/group_team_grid.json` | ACF field group for the `acf/team-grid` block. | Create | +| `styles/blocks/index.css` | Import the new block's CSS. | Modify | +| `tests/team-page.spec.js` | Playwright tests for the rendered block. | Create | +| `static/dist/theme.css` | Rebuilt via `npm run build`. | Modified | + +--- + +## Task 1: ACF Field Group + +**Files:** +- Create: `acf/group_team_grid.json` + +**Interfaces:** +- Produces: ACF group with `key: "group_team_grid"`, location scoped to `block == acf/team-grid`, fields: `members` (repeater) → `image`, `name`, `title`, `bio`. + +- [ ] **Step 1: Create the ACF field group JSON file** + +Create the file `acf/group_team_grid.json` with the following exact content: + +```json +{ + "key": "group_team_grid", + "title": "Team Grid", + "fields": [ + { + "key": "field_team_grid_members", + "label": "Members", + "name": "members", + "aria-label": "", + "type": "repeater", + "instructions": "Add team members. Click a card to expand the bio.", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "layout": "block", + "pagination": 0, + "min": 0, + "max": 0, + "collapsed": "", + "button_label": "Add Team Member", + "rows_per_page": 20, + "sub_fields": [ + { + "key": "field_team_grid_member_image", + "label": "Image", + "name": "image", + "aria-label": "", + "type": "image", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "return_format": "array", + "library": "all", + "min_width": "", + "min_height": "", + "min_size": "", + "max_width": "", + "max_height": "", + "max_size": "", + "mime_types": "", + "allow_in_bindings": 0, + "preview_size": "medium" + }, + { + "key": "field_team_grid_member_name", + "label": "Name", + "name": "name", + "aria-label": "", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "default_value": "", + "maxlength": "", + "allow_in_bindings": 0, + "placeholder": "", + "prepend": "", + "append": "" + }, + { + "key": "field_team_grid_member_title", + "label": "Title", + "name": "title", + "aria-label": "", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "default_value": "", + "maxlength": "", + "allow_in_bindings": 0, + "placeholder": "", + "prepend": "", + "append": "" + }, + { + "key": "field_team_grid_member_bio", + "label": "Bio", + "name": "bio", + "aria-label": "", + "type": "wysiwyg", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "default_value": "", + "tabs": "visual", + "toolbar": "basic", + "media_upload": 0, + "delay": 0, + "allow_in_bindings": 0 + } + ] + } + ], + "location": [ + [ + { + "param": "block", + "operator": "==", + "value": "acf/team-grid" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": "", + "active": true, + "description": "Team grid block. Each member is a card with photo, name, title, and a collapsible bio.", + "show_in_rest": 0, + "display_title": "", + "allow_ai_access": false, + "ai_description": "", + "modified": 1753401600 +} +``` + +- [ ] **Step 2: Verify the JSON parses** + +Run: `node -e "JSON.parse(require('fs').readFileSync('acf/group_team_grid.json','utf8')); console.log('OK')"` +Expected: prints `OK` and exits 0. + +- [ ] **Step 3: Commit** + +```bash +cd "C:/Users/ksolo/Herd/community-works-collaborative/wp-content/themes/community-works-collaborative" +git add acf/group_team_grid.json +git commit -m "✨ feat: Add ACF field group for team-grid block" +``` + +--- + +## Task 2: Block Registration JSON + +**Files:** +- Create: `views/blocks/team-grid/block.json` + +**Interfaces:** +- Produces: `block.json` declaring `acf/team-grid` as the block name, `team-grid.php` as the render template, `groups` icon, `common` category. `regACFBlocks()` in `functions.php` will pick this up automatically. + +- [ ] **Step 1: Create the block.json file** + +Create the file `views/blocks/team-grid/block.json` with the following exact content: + +```json +{ + "name": "acf/team-grid", + "title": "Team Grid", + "description": "A responsive grid of team members. Click a card to expand the bio.", + "category": "common", + "icon": "groups", + "keywords": ["team", "people", "staff", "bio"], + "acf": { + "mode": "preview", + "renderTemplate": "team-grid.php" + }, + "supports": { + "anchor": true, + "align": false + } +} +``` + +- [ ] **Step 2: Verify the JSON parses** + +Run: `node -e "JSON.parse(require('fs').readFileSync('views/blocks/team-grid/block.json','utf8')); console.log('OK')"` +Expected: prints `OK` and exits 0. + +- [ ] **Step 3: Commit** + +```bash +cd "C:/Users/ksolo/Herd/community-works-collaborative/wp-content/themes/community-works-collaborative" +git add views/blocks/team-grid/block.json +git commit -m "✨ feat: Add team-grid block.json registration" +``` + +--- + +## Task 3: Block CSS + +**Files:** +- Create: `views/blocks/team-grid/team-grid.css` + +**Interfaces:** +- Produces: BEM-styled CSS for `.team-grid`, `__inner`, `__card`, `__details`, `__summary`, `__photo`, `__name`, `__title`, `__bio-wrap`, `__bio-inner`, `__bio`. Reuses `text-cwc-blue-01` for the name. Implements the `grid-template-rows: 0fr → 1fr` animation for the bio. + +- [ ] **Step 1: Create the block CSS file** + +Create the file `views/blocks/team-grid/team-grid.css` with the following exact content: + +```css +/* Team Grid block */ + +.team-grid { + &__card { + @apply flex flex-col; + } + + &__details { + @apply flex flex-col; + } + + &__summary { + @apply flex flex-col items-center text-center cursor-pointer list-none; + + &::-webkit-details-marker { + display: none; + } + + &::marker { + display: none; + content: ''; + } + } + + &__photo { + @apply w-32 h-32 sm:w-40 sm:h-40 lg:w-48 lg:h-48 object-cover rounded-md mb-4; + } + + &__name { + @apply text-cwc-blue-01 text-22px font-bold leading-tight mb-1; + } + + &__title { + @apply text-gray-600 text-16px font-light italic mb-3; + } + + &__bio-wrap { + display: grid; + grid-template-rows: 0fr; + transition: grid-template-rows 300ms ease; + } + + &__details[open] &__bio-wrap { + grid-template-rows: 1fr; + } + + &__bio-inner { + overflow: hidden; + min-height: 0; + } + + &__bio { + @apply text-gray-700 text-16px font-light leading-snug pt-2; + } +} +``` + +- [ ] **Step 2: Verify the BEM children are present** + +Run: `grep -cE "&__(card|details|summary|photo|name|title|bio-wrap|bio-inner|bio)\b" views/blocks/team-grid/team-grid.css` +Expected: `9` (one match per BEM child). + +- [ ] **Step 3: Commit** + +```bash +cd "C:/Users/ksolo/Herd/community-works-collaborative/wp-content/themes/community-works-collaborative" +git add views/blocks/team-grid/team-grid.css +git commit -m "✨ feat: Add team-grid block CSS" +``` + +--- + +## Task 4: Block Template (PHP) + +**Files:** +- Create: `views/blocks/team-grid/team-grid.php` + +**Interfaces:** +- Consumes: `get_field( 'members' )` → array of members each with `image` (array: `url`, `alt`), `name` (string), `title` (string), `bio` (wysiwyg HTML). +- Produces: A `
` element with a `__inner` grid div, and an `
` per member. Each card contains a `
` with a `` (photo + name + title) and a `__bio-wrap` containing the bio. + +- [ ] **Step 1: Create the block PHP template** + +Create the file `views/blocks/team-grid/team-grid.php` with the following exact content (tabs for indentation): + +```php + + * element so the bio expands when the photo, name, or title is clicked. + * + * @package CWC + */ + +namespace CWC; + +$members = get_field( 'members' ); + +if ( empty( $members ) ) { + return; +} + +$classes = 'team-grid container my-section mx-auto'; +$wrapper = blockWrapperAttributes( $classes, $is_preview ); +?> + +
> +
+ +
+
+ + + <?php echo esc_attr( $member['image']['alt'] ?? '' ); ?> + + + +

+ +

+ + + +

+ +

+ +
+ +
+
+ +
+ +
+ +
+
+
+
+ +
+
+``` + +- [ ] **Step 2: Lint the PHP file with PHPCS** + +Run: `composer exec phpcs -- views/blocks/team-grid/team-grid.php` +Expected: exits 0 with no errors. If PHPCS reports alignment warnings on the assignment block (similar to the Task 5 services-list fix), run `composer exec phpcbf -- views/blocks/team-grid/team-grid.php` to auto-fix, then re-run PHPCS to confirm exit 0. Whitespace-only changes are acceptable; do not modify logic. + +- [ ] **Step 3: Verify the dynamic utility classes are in the source** + +Run: `grep -E "grid-cols-1|sm:grid-cols-2|lg:grid-cols-3|w-32|w-40|w-48" views/blocks/team-grid/team-grid.php` +Expected: at least 3 matches (Tailwind v4 must see the literal class names in the source to emit the rules). + +- [ ] **Step 4: Commit** + +```bash +cd "C:/Users/ksolo/Herd/community-works-collaborative/wp-content/themes/community-works-collaborative" +git add views/blocks/team-grid/team-grid.php +git commit -m "✨ feat: Add team-grid block PHP template" +``` + +--- + +## Task 5: CSS Import Wiring + +**Files:** +- Modify: `styles/blocks/index.css` + +**Interfaces:** +- Consumes: `views/blocks/team-grid/team-grid.css` (from Task 3). +- Produces: A new `@import` line at the end of `styles/blocks/index.css` so Tailwind's build picks up the block's CSS. + +- [ ] **Step 1: Add the import to the block index CSS** + +Append the following line at the end of `styles/blocks/index.css`: + +```css +@import '../../views/blocks/team-grid/team-grid.css'; +``` + +After the edit, the file should end with: + +``` +@import '../../views/blocks/services-list/services-list.css'; +@import '../../views/blocks/team-grid/team-grid.css'; +``` + +- [ ] **Step 2: Verify the import is present** + +Run: `grep -cF "team-grid/team-grid.css" styles/blocks/index.css` +Expected: `1`. + +- [ ] **Step 3: Commit** + +```bash +cd "C:/Users/ksolo/Herd/community-works-collaborative/wp-content/themes/community-works-collaborative" +git add styles/blocks/index.css +git commit -m "🔧 chore: Import team-grid block CSS into theme" +``` + +--- + +## Task 6: Build the CSS + +**Files:** +- Modify: `static/dist/theme.css` + +**Interfaces:** +- Consumes: The new import in `styles/blocks/index.css` (from Task 5) and the block CSS (from Task 3). +- Produces: A rebuilt `static/dist/theme.css` that includes the `.team-grid*` rules and the responsive `grid-cols-1 sm:grid-cols-2 lg:grid-cols-3` utilities. + +- [ ] **Step 1: Run the production Tailwind build** + +Run: `npm run build` +Expected: completes with no errors. + +- [ ] **Step 2: Verify the team-grid CSS classes are in the built output** + +Tailwind v4 unwraps CSS-nesting `&__name` into `__name.team-grid`. Use fixed-string search (Windows-safe): + +Run: `grep -cF "__name.team-grid" static/dist/theme.css` +Expected: `1` or more (the rule is in the dist). + +- [ ] **Step 3: Verify the responsive grid utilities are in the built output** + +Run: `grep -cF "sm\:grid-cols-2" static/dist/theme.css` +Expected: `1` or more. + +Run: `grep -cF "lg\:grid-cols-3" static/dist/theme.css` +Expected: `1` or more. + +- [ ] **Step 4: Commit the rebuilt dist** + +```bash +cd "C:/Users/ksolo/Herd/community-works-collaborative/wp-content/themes/community-works-collaborative" +git add -f static/dist/theme.css +git commit -m "🎨 build: Rebuild dist with team-grid block styles" +``` + +--- + +## Task 7: Playwright Tests + +**Files:** +- Create: `tests/team-page.spec.js` + +**Interfaces:** +- Consumes: The rendered `views/blocks/team-grid/team-grid.php` template (Tasks 3 + 4) on the WordPress page `/team/`. +- Produces: Three tests: + 1. Photo, name, and title visible for the first card. + 2. Bio is closed by default; clicking the summary opens the details and reveals the bio; clicking again closes it. + 3. No axe-core a11y violations scoped to `.team-grid`. + +> **Prerequisite:** the test depends on the Team page being composed in Gutenberg with at least one `Team Grid` block containing one or more members. That composition is the editor's responsibility and is not part of this plan. The tests should be authored against the intended state, not a placeholder state. + +- [ ] **Step 1: Create the test spec file** + +Create the file `tests/team-page.spec.js` with the following exact content: + +```js +import { test, expect } from "@playwright/test"; +import AxeBuilder from "@axe-core/playwright"; + +test.describe("Team Grid block on /team/", () => { + test("renders the photo, name, and title for each member", async ({ page }) => { + await page.goto("/team/"); + + const cards = page.locator(".team-grid__card"); + await expect(cards.first()).toBeVisible(); + + const firstCard = cards.first(); + await expect(firstCard.locator(".team-grid__photo")).toBeVisible(); + await expect(firstCard.locator(".team-grid__name")).toBeVisible(); + await expect(firstCard.locator(".team-grid__title")).toBeVisible(); + }); + + test("bio is hidden by default and expands on summary click", async ({ page }) => { + await page.goto("/team/"); + + const firstDetails = page.locator(".team-grid__details").first(); + // Bio is closed by default. + await expect(firstDetails).not.toHaveAttribute("open", ""); + + // Click the summary; the details should open and the bio should be visible. + await firstDetails.locator(".team-grid__summary").click(); + await expect(firstDetails).toHaveAttribute("open", ""); + await expect(firstDetails.locator(".team-grid__bio")).toBeVisible(); + + // Click again; the details should close. + await firstDetails.locator(".team-grid__summary").click(); + await expect(firstDetails).not.toHaveAttribute("open", ""); + }); + + test("no axe violations on the team-grid block", async ({ page }) => { + await page.goto("/team/"); + const results = await new AxeBuilder({ page }) + .include(".team-grid") + .analyze(); + expect(results.violations).toEqual([]); + }); +}); +``` + +- [ ] **Step 2: Run the tests** + +Run: `npx playwright test tests/team-page.spec.js` +Expected: all 3 tests pass. If tests fail because the Team page has not yet been composed in the editor, that's an expected prerequisite — note the prerequisite in the final report and skip the test run. + +- [ ] **Step 3: Commit** + +```bash +cd "C:/Users/ksolo/Herd/community-works-collaborative/wp-content/themes/community-works-collaborative" +git add tests/team-page.spec.js +git commit -m "✅ test: Add Playwright tests for team-grid block" +``` + +--- + +## Self-Review Notes + +- **Spec coverage:** + - One new ACF block (`team-grid`) — covered by Tasks 1, 2, 3, 4. + - Repeater of `members` with `image` / `name` / `title` / `bio` — covered by Task 1. + - Responsive grid `grid-cols-1 sm:grid-cols-2 lg:grid-cols-3` — covered by Task 4 (PHP) and Task 6 (Tailwind emits the rules). + - Rounded-rectangle headshots via `rounded-md` — covered by Task 3 (CSS). + - Native `
`/`` click-to-expand — covered by Task 4 (PHP) and Task 3 (CSS). + - `grid-template-rows: 0fr → 1fr` animation — covered by Task 3 (CSS). + - No built-in background — explicitly out of scope (Section block wrapper, editor's responsibility). + - Page template not modified — confirmed; `page.php` is not in any file list. + - `header.php` / `footer.php` / page hero not touched — confirmed. + - Reuse existing design tokens (`text-cwc-blue-01`, `rounded-md`) — covered by Tasks 1, 3, 4. + - Playwright tests covering photo + name + title, summary click toggles, and a11y — covered by Task 7. + - Dist rebuild — covered by Task 6. +- **Placeholder scan:** No TBDs. Every step has a concrete file path, exact content, exact command, and expected output. The only conditional is in Task 7 Step 2 — explicitly called out as a prerequisite (editor must compose the page in Gutenberg), not a placeholder. +- **Type consistency:** ACF field `name: "members"` is used in Task 4 (`get_field( 'members' )`). Sub-field names `image`, `name`, `title`, `bio` are used in Task 1 (defined) and Task 4 (consumed). Class names `team-grid`, `__inner`, `__card`, `__details`, `__summary`, `__photo`, `__name`, `__title`, `__bio-wrap`, `__bio-inner`, `__bio` are used in Task 3 (defined in CSS) and Task 4 (rendered in markup) and Task 7 (asserted in tests) — consistent. +- **Ambiguity check:** "Rounded rectangle" is concretely specified as `rounded-md`. "Opens vertically" is concretely specified as `grid-template-rows: 0fr → 1fr` with 300ms transition. "Responsive grid" is concretely specified as `grid-cols-1 sm:grid-cols-2 lg:grid-cols-3`. "No built-in background" is concretely scoped to the Section block wrapper (editor's responsibility, not this block's). All are explicit.