feat(surfaces): emit toilville/* Gutenberg blocks from ForgeBlock (layer 2 to 3) #104

Merged
peterswimm merged 2 commits from feat/forgeblock-toilville-gutenberg-emitter into main 2026-09-09 21:02:58 +00:00
Owner

Closes the layer Peter flagged on #102. Targets main.

The pipeline stopped one step short. ForgeBlock was rendered straight to core
Gutenberg blocks, so the patterns were a flat content skeleton — while the blocks
that actually carry the Toilville design (toilville/* in
Toilville_Rituals/wordpress/rituals-wp/) sat unused, even though
toilville-fse/templates/front-page.html already composes them by hand.

The adapter layer

forgeblock_toilville.py — each adapter recognises a run of ForgeBlocks and
emits the matching toilville/* block with attributes filled from the design
copy. Unrecognised runs still fall through to core blocks, so output stays valid
while coverage grows. Adapters are surface-aware: a surface with no hero
block (rituals/news/contact) falls through rather than borrowing another page's
hero.

Surface Emits
landing toilville/hero-home, toilville/joinband-cta
spelwork toilville/spelwork-hero, toilville/spelwork-cta
rituals / news / contact core fallback — no matching block exists yet

The Home hero now round-trips the design exactly:

{"eyebrow":"Toilville LLC · software for governed work",
 "headline1":"Governance should be a","emphasis":"primitive",
 "headline2":", not a plugin.","body":"We build software for organizations…",
 "cta1Label":"See Rituals","cta1Url":"#rituals",
 "cta2Label":"Build on Spelwork","cta2Url":"#spelwork"}

Two converter fixes this depended on

1. _collect_text() lost document order. handle_data() concatenated every
text run of a node into .text regardless of position, so
<h1>a <span>b</span> c</h1> flattened to "a c b" — that's the mangled
"Governance should be a , not a plugin. primitive" in the #102 renders. Node
now keeps parts, an ordered interleave of text runs and children. Joining is
punctuation-aware, so <span>primitive</span>, not… no longer yields
"primitive , not…".

2. Headings record emphasis — the inline <em>/<strong>/recoloured
<span>. A flat string can't say which words carry the accent, which is
exactly what hero-home's headline1/emphasis/headline2 split needs.

And a fix to my own previous pass

Annotating CTAs in #101 promoted the nearest enclosing <div> to
data-block="actions", which swallowed sibling headings and body copy — the
Home closing CTA "Let's build it." vanished entirely. Buttons are now wrapped
in their own button-only container. Landing goes 47 → 49 blocks.

Supporting changes

  • theme.json gains six palette slugs so layer 3 has presets for the tokens
    added in #100: accent-spelwork, accent-rituals,
    neutral-warm-{ink,body,muted}, brand-surface. Each verified against
    dist/toilville/tokens.dtcg.json.
  • align_check.py gains a layer-3 audit — raw hex in the toilville/* blocks,
    with the SPELWork-skin values (#7394c8/#587c6d/#c66c5a) reported
    separately since those are correct-but-untokenised, not drift.

Correction to something I said on #102

I reported "9× stale #cc5c37, 0× canonical" in the blocks. That was a plain
grep that didn't distinguish fallbacks — they are all
var(--wp--preset--color--primary, #cc5c37), so the blocks do reference the
preset and already render the corrected #c05734 in production. Only the
fallback literal is stale. The genuine raw hex is 13 values (#fff, #E1B659
in JS/PHP, #1a1a1a, #5a5a5a, #f4f4f2), which the new audit lists.

Verification

  • just check — fully green
  • pytest generator/tests/ tools/wp_theme_ingestor/test_*.py — 267 passed
  • Other brands: only benign change is spelwork gaining a correct
    "emphasis": "legible." on its H1

Still open

No block yet for the Rituals hero, the Wish/Steward/Ritual triad, the
ritual-in-progress demo, the CLI terminal, the intent-registry console, or "Who
we are". products-grid/product-card/wish-form adapters need column_set
and form detection first. Tracked on #102.

https://claude.ai/code/session_01Kbzfscp4w5enUrZxMnDt78

Closes the layer Peter flagged on #102. Targets `main`. The pipeline stopped one step short. ForgeBlock was rendered straight to **core** Gutenberg blocks, so the patterns were a flat content skeleton — while the blocks that actually carry the Toilville design (`toilville/*` in `Toilville_Rituals/wordpress/rituals-wp/`) sat unused, even though `toilville-fse/templates/front-page.html` already composes them by hand. ## The adapter layer `forgeblock_toilville.py` — each adapter recognises a run of ForgeBlocks and emits the matching `toilville/*` block with attributes filled from the design copy. Unrecognised runs still fall through to core blocks, so output stays valid while coverage grows. Adapters are **surface-aware**: a surface with no hero block (rituals/news/contact) falls through rather than borrowing another page's hero. | Surface | Emits | |---|---| | landing | `toilville/hero-home`, `toilville/joinband-cta` | | spelwork | `toilville/spelwork-hero`, `toilville/spelwork-cta` | | rituals / news / contact | core fallback — no matching block exists yet | The Home hero now round-trips the design exactly: ```json {"eyebrow":"Toilville LLC · software for governed work", "headline1":"Governance should be a","emphasis":"primitive", "headline2":", not a plugin.","body":"We build software for organizations…", "cta1Label":"See Rituals","cta1Url":"#rituals", "cta2Label":"Build on Spelwork","cta2Url":"#spelwork"} ``` ## Two converter fixes this depended on **1. `_collect_text()` lost document order.** `handle_data()` concatenated every text run of a node into `.text` regardless of position, so `<h1>a <span>b</span> c</h1>` flattened to `"a c b"` — that's the mangled *"Governance should be a , not a plugin. primitive"* in the #102 renders. `Node` now keeps `parts`, an ordered interleave of text runs and children. Joining is punctuation-aware, so `<span>primitive</span>, not…` no longer yields `"primitive , not…"`. **2. Headings record `emphasis`** — the inline `<em>`/`<strong>`/recoloured `<span>`. A flat string can't say *which* words carry the accent, which is exactly what `hero-home`'s `headline1`/`emphasis`/`headline2` split needs. ## And a fix to my own previous pass Annotating CTAs in #101 promoted the nearest enclosing `<div>` to `data-block="actions"`, which **swallowed sibling headings and body copy** — the Home closing CTA *"Let's build it."* vanished entirely. Buttons are now wrapped in their own button-only container. Landing goes 47 → 49 blocks. ## Supporting changes - `theme.json` gains six palette slugs so layer 3 has presets for the tokens added in #100: `accent-spelwork`, `accent-rituals`, `neutral-warm-{ink,body,muted}`, `brand-surface`. Each verified against `dist/toilville/tokens.dtcg.json`. - `align_check.py` gains a layer-3 audit — raw hex in the `toilville/*` blocks, with the SPELWork-skin values (`#7394c8`/`#587c6d`/`#c66c5a`) reported separately since those are correct-but-untokenised, not drift. ## Correction to something I said on #102 I reported "9× stale `#cc5c37`, 0× canonical" in the blocks. That was a plain grep that didn't distinguish fallbacks — they are all `var(--wp--preset--color--primary, #cc5c37)`, so the blocks **do** reference the preset and already render the corrected `#c05734` in production. Only the fallback literal is stale. The genuine raw hex is 13 values (`#fff`, `#E1B659` in JS/PHP, `#1a1a1a`, `#5a5a5a`, `#f4f4f2`), which the new audit lists. ## Verification - `just check` — fully green - `pytest generator/tests/ tools/wp_theme_ingestor/test_*.py` — 267 passed - Other brands: only benign change is `spelwork` gaining a correct `"emphasis": "legible."` on its H1 ## Still open No block yet for the Rituals hero, the Wish/Steward/Ritual triad, the ritual-in-progress demo, the CLI terminal, the intent-registry console, or "Who we are". `products-grid`/`product-card`/`wish-form` adapters need `column_set` and form detection first. Tracked on #102. https://claude.ai/code/session_01Kbzfscp4w5enUrZxMnDt78
docs(review): side-by-side fidelity evidence for the toilville deck pipeline
Some checks failed
PR policy check / policy-gate (pull_request) Failing after 0s
Security Scans / Trivy filesystem scan (pull_request) Successful in 41s
Security Scans / Gitleaks secret scan (pull_request) Successful in 41s
Test Generator / lint-workflows (pull_request) Successful in 30s
UDG dist drift gate / dist/ matches dtcg + udg sources (pull_request) Successful in 22s
Test Generator / lint-prose (pull_request) Successful in 33s
Test Generator / test (pull_request) Successful in 3m32s
15f5902d96
Renders the five toilville.com pages design-vs-generated so the gaps flagged in
PR #101 are visible rather than described: displaced inline <em> in headings,
feature bullets reduced to en-dashes, two- and three-column card grids
flattened to a single linear column, and the News/Contact structures lost.

Includes the render harness so the comparison is reproducible.

Claude-Session: https://claude.ai/code/session_01Kbzfscp4w5enUrZxMnDt78
feat(surfaces): emit toilville/* Gutenberg blocks from ForgeBlock (layer 2 -> 3)
Some checks failed
PR policy check / policy-gate (pull_request) Failing after 0s
Security Scans / Trivy filesystem scan (pull_request) Successful in 3m9s
Security Scans / Gitleaks secret scan (pull_request) Successful in 3m7s
Test Generator / lint-workflows (pull_request) Successful in 1m15s
Test Generator / lint-prose (pull_request) Successful in 1m25s
UDG dist drift gate / dist/ matches dtcg + udg sources (pull_request) Successful in 1m17s
Test Generator / test (pull_request) Successful in 7m19s
a6dd67df72
The pipeline stopped one layer short. ForgeBlock was being rendered straight to
CORE Gutenberg blocks (paragraph/heading/group/buttons), so the generated
patterns were a flat content skeleton — while the blocks that actually carry the
Toilville design (toilville/* in Toilville_Rituals/wordpress/rituals-wp/) sat
unused, even though toilville-fse/templates/front-page.html already composes
them by hand.

forgeblock_toilville.py adds an adapter layer: each adapter recognises a run of
ForgeBlocks and emits the matching toilville/* block with attributes filled from
the design copy. Unrecognised runs still fall through to core blocks, so output
stays valid while coverage grows. Adapters are surface-aware — a surface with no
hero block (rituals/news/contact) falls through rather than borrowing another
page's hero.

Covered so far:
  landing   -> toilville/hero-home + toilville/joinband-cta
  spelwork  -> toilville/spelwork-hero + toilville/spelwork-cta

Two converter fixes this depended on:

1. _collect_text() lost document order. handle_data() concatenated every text
   run of a node into .text regardless of position, so
   `<h1>a <span>b</span> c</h1>` flattened to "a c b" — the mangled
   "Governance should be a , not a plugin. primitive" in the review renders.
   Node now keeps `parts`, an ordered interleave of text runs and children, and
   _collect_text walks that. Joining is punctuation-aware so
   `<span>primitive</span>, not…` no longer yields "primitive , not…".

2. Headings now record `emphasis` — the inline <em>/<strong>/recoloured <span>.
   A flat string cannot say WHICH words carry the accent, which is exactly what
   hero-home's headline1/emphasis/headline2 split needs.

Also fixes an error in the previous pass: annotating CTAs promoted the nearest
enclosing <div> to data-block="actions", which swallowed sibling headings and
body copy (the Home closing CTA "Let's build it." vanished entirely). Buttons
are now wrapped in their own button-only container. Landing goes 47 -> 49 blocks.

theme.json gains six palette slugs so layer 3 has presets for the tokens added
in #100 — accent-spelwork, accent-rituals, neutral-warm-{ink,body,muted},
brand-surface. Each verified against dist/toilville/tokens.dtcg.json.

align_check.py gains a layer-3 audit: raw hex in the toilville/* blocks, with
the SPELWork-skin values (#7394c8/#587c6d/#c66c5a) reported separately since
those are correct-but-untokenised, not drift.

Claude-Session: https://claude.ai/code/session_01Kbzfscp4w5enUrZxMnDt78
peterswimm deleted branch feat/forgeblock-toilville-gutenberg-emitter 2026-09-09 21:02:58 +00:00
Sign in to join this conversation.
No description provided.