# Diary — 2026-06-04 (Adi)

User: adi (Adi Pramono), lead developer. Session log for the page-build automation POC.

## Goal of the session

Test one question before committing the team to a build method: **can we automate page-building
with custom Gutenberg blocks + scripted import, instead of hand-building every page in Kadence
Rows?** I built a working proof-of-concept on the Homepage, end to end, into the `brentwooddev`
migration target.

## The strategic call (and the data behind it)

- My first instinct was Kadence-native (Rows, Advanced Gallery) to avoid custom code.
- The reframe: **Kadence-native = manual; a thin custom-block layer = automatable.** You can't
  reliably script Kadence's markup for ~1,000 elements, but you *can* emit your own block markup
  from the Laravel DB — the same way the 6,016 blogs were imported.
- Element distribution across all 236 pages (swept via the rendered mirror):
  - photo-block **519 (51%)**, text-block **195 (19%)**, slideshow 67, staff-list 37,
    embed-video 36, course-list 30, youtube 27, quote 25, publication-list 14, livestream-list 10,
    calendar 6, timetable 5, embed-code 4, inquiry 2, social 1.
  - **2 types = 70% of all page content; ~6 types = ~88%.** The genuinely bespoke tail is ~2%.
- Conclusion (on paper): **hybrid, leaning automated.** Custom blocks for the workhorses (emit from
  DB), core-block conversion for text, Query Loop for the CPT lists, hand-build the ~2%. The human
  pass shrinks to review + hero selection + the handful of feature pages — not building 236 by hand.
- **Caveat (see "human-experience caveat" below):** the automation path is the right *strategy*, but
  in practice the custom-block work is a poor fit for *me* to hand-drive. For the pages I build,
  Kadence Rows is faster and safer; the block/import lane is Rian's.

## What I built — Homepage POC

Pipeline, proven on one page:

```
Laravel DB  ->  clean JSON (exact published stack)  ->  generator  ->  Gutenberg blocks  ->  WP page
```

- **Target:** `brentwooddev` — page "POC Home (Adi)", slug `poc-home-adi`, ID 21651.
  View: https://brentwooddev.demoing.info/poc-home-adi/ (gate pw `showmethesite`).
- **Extractor (docker-free):** a temporary read-only route on the Laravel mirror
  (`/_poc_dump_8842/{id}`). It runs *inside* the app container, so it has DB access without
  `docker exec`. Pulls the exact published element stack via the model's `$page->content`
  accessor and returns clean JSON. **Removed after use** (mirror verified healthy).
- **Generator:** `tmp/poc/gen_home.py` — maps each element to a block.
- **Custom blocks, registered in the `kadence-child` theme** (Adi's call — child theme, not a plugin):
  - `brentwood/media-grid` — photo grids (columns/height/layout/spans/links/text-panel), dynamic
    render, server-side editor preview.
  - `brentwood/video-hero` — full-bleed video + **editable** text card via InnerBlocks.

## Key findings / gotchas (the valuable part to remember)

1. **Version/sort-order trap.** Filtering content by `pages.published_version_id` gives the WRONG
   stack (5 elements with sort gaps) vs the model's `$page->content` accessor (correct 7 with the
   hero). Content elements are versioned individually. Use the model accessor / rendered truth, not
   a raw version join. (Same lesson as the page-hero work in `notes/MIGRATION-DECISIONS.md`.)
2. **Photo order = `sort_order`, not array/DB order.** The CMS renders each block's photos by
   `sort_order`. Hit this live — "Why Brentwood" had its two photos swapped and the 6 accreditation
   logos came out `3,4,5,1,2,6`. Generator now sorts by `sort_order`.
3. **`wp_slash` is mandatory for block imports.** WP runs `wp_unslash` on save, which strips the
   backslash-escaping inside block-comment JSON attributes — so any block whose body contains quotes
   (`class=""`) silently loses ALL its attributes. Must `wp_slash()` the content before
   `wp_insert_post`/`wp_update_post`. **This will bite the bulk page importer** — page bodies are
   full of quoted HTML.
4. **`bg-highlight` = white.** The hero "card" is a white box (Tailwind `bg-highlight` -> `bg-white`),
   shadow, `max-w-2xl`, overlapping a **full-bleed 2:1 video** pulled up under the nav (the `huge`
   embed-video layout). Don't guess hero styling — read the blade + tailwind config.
5. **embed-video has ~6 layout variants** (huge/full/left/right/split-left/split-right). The
   generator must branch per-layout, like photo-block's `layout` field.
6. **Hero video URL.** Self-hosted mp4 at `/storage/videos/{file_upload_id}/{slug}.mp4` (range/206
   support). Currently hardcoded for the one hero; **production extractor must emit
   `EmbedVideo->url` / `->url_hls`** per element (the model already computes them).
7. **Editable-text pattern:** text humans will edit -> **InnerBlocks** (core heading/paragraph);
   fixed media/layout -> block attributes. `video-hero` does this — the card is InnerBlocks,
   `render.php` wraps them with the video chrome. Editor preview now plays the real video so admins
   see the front-end composition.

## The human-experience caveat (important — surfaced late in the session)

The element-distribution math says "automate with custom blocks." But the *lived* experience of
actually building this POC pointed the other way **for me specifically**:

- Iterating custom blocks on the server (mosiah) is **trickier and slower for me than it looks**.
  I can't write to `brentwooddev/wp-content` on disk directly (not in its unix group), so every
  block edit goes through `wp eval` + `file_put_contents` as www-data — no normal editor loop, no
  fast feedback, fiddly to debug. The friction is real and it's mine, not the tooling's fault.
- Building the same page in a **Kadence Row layout** is the opposite: in-browser, visual, no server
  access needed, nothing to register or deploy. For a human doing one page at a time, it's faster
  and far less error-prone than hand-tuning block PHP on the server.
- So the honest read: **the custom-block / automation path is powerful but it's not the path *I*
  should be hand-driving.** For the pages I build, **Kadence Rows is the right call for me.**

**Revised division of labour:**
- **Adi (me):** build pages in **Kadence Row layout**. This is where my hands are fastest and the
  feedback loop is tightest.
- **Custom blocks / scripted import:** *leave blank for Rian.* The block-authoring + extractor +
  bulk-import path needs the server-side access (docker DB read, direct `wp-content` writes) that I
  don't have anyway — it's naturally Rian's lane. I'll hand him the POC artifacts and findings.

This doesn't kill the automation thesis — the distribution data still says it pays off at scale.
It just reassigns *who* drives it, based on what actually felt workable in practice today.

## My access constraints (as user `adi`)

- CAN: `srv-gw wp` on `brentwooddev` (wp-cli); write to the child theme via `wp eval` +
  `file_put_contents` (runs as www-data); read Laravel source + the mirror at `172.17.0.1:3082`.
- CANNOT: `docker exec brentwood-mysql` (Laravel DB); write to `brentwooddev/wp-content` on disk
  directly (not in its unix group).
- So the **production** extractor's DB read is Rian's existing `docker exec` pattern; the mirror-route
  was my docker-free workaround for the POC.

## What changed in `brentwooddev` (flagging — it's the migration target)

Additive and reversible:
- `kadence-child` theme: new `blocks/media-grid/` and `blocks/video-hero/`
  (each: block.json, render.php, style.css, editor.js), new `inc/brentwood-blocks.php`, and one
  `require_once` line appended to `functions.php`. Both `render.php` files `ABSPATH`-guarded.
- One new published page: `poc-home-adi` (ID 21651).
- Laravel mirror `routes/web.php`: temp dump route added then **removed** (clean).

## Status

**Proven:** full Laravel->WP page automation works; layout is faithful; marginal cost of the next
page is ~0. Hero video + all 9 photo images match the live site exactly and in order; hero text
matches live *and* is editable in Gutenberg; editor preview mirrors the front end.

**Still stubbed:** news list (-> Query Loop), social feed (-> embed), image **sideloading**
(currently hotlinked from brentwood.ca), production extractor (docker DB read + video URLs),
photo-block text-panel editability.

## Next steps

**Pause here and confirm the direction with Rian before going further.** This POC is the proof:
building the Homepage as user `adi` *required* all of the above — the docker-free mirror route, the
`wp eval` write path, the version/sort-order traps, the `wp_slash` gotcha. That's a lot of moving
parts for one page, and most of it leans on access I don't have. The artifacts in `tmp/poc/` are the
evidence of what the custom-block path actually costs at my access level.

So rather than push on, I'm parking it for a decision:

- **Me + the team's junior devs: pause on the custom-block / automation track.** Don't start wiring
  the Query Loop, sideloading, or extractor work until Rian gives direction. If we build pages in the
  meantime, do them in **Kadence Rows** (no server access needed, fast feedback).
- **For Rian to decide:** is the automated block/import pipeline worth Rian owning end-to-end (he has
  the docker DB read + `wp-content` write access it needs), or do we go Kadence-Rows-by-hand for the
  bulk and reserve automation only for the courses/blogs already proven via DB import?

Hand-off when Rian's ready: POC artifacts (`tmp/poc/`), the gotchas list above, and the
human-experience caveat. Everything additive in `brentwooddev` is reversible (see below).

POC artifacts: `tmp/poc/` (`gen_home.py`, `theme-block/`, `home.json`, `home-content.html`).
