Skip to content

sitetile: give .st-faq a container, and let the cta's caption= be seen - #6

Merged
cverorg merged 1 commit into
mainfrom
fix/faq-inset-and-cta-caption
Aug 28, 2026
Merged

sitetile: give .st-faq a container, and let the cta's caption= be seen#6
cverorg merged 1 commit into
mainfrom
fix/faq-inset-and-cta-caption

Conversation

@cverorg

@cverorg cverorg commented Aug 28, 2026

Copy link
Copy Markdown
Member

Two renderer defects found on a built site. Neither failed anything: the build is green, the HTML is valid, and in both cases the detector was a person looking at the page.

1 — .st-faq shipped no layout rule (tile-lab#7)

Thirty lines of site.css style .st-faq-head, -list, -item, -q and -a. Every one of them is a descendant of .st-faq, which had no rule at all — so the stylesheet reads like a package that thought hard about the faq, while the container it all hangs from has no width and no padding. probe_render on a built site showed .st-faq matching exactly one rule: * { box-sizing }. The FAQ ran edge to edge, directly under a prose section inset 48px.

A missing rule is invisible against a block of present ones. Same defect class as .st-notfound and .st-cta-arrow (2026-08-08): we emit a class and never paint it.

The fix takes the shared wrapper declarations — the ones .st-prose, .st-grid, .st-cta, .st-people already use — so the faq joins the other sections rather than acquiring a second opinion about page width. It sits in :where() (zero specificity, a floor any theme overrides by naming the class) and is deliberately not gated on data-theme-custom, unlike the faq paint below it. That gate is correct for a look a hand-rolled theme already chose; on the container it would exempt precisely the sites that have the bug.

2 — caption= on a cta was reported inert while it reordered the DOM (tile-lab#8)

save_page / verify_site told authors, at write time:

caption= … read by nothing — it parses, it is accepted, and the renderer never looks at it (no element, no data- attribute, so CSS cannot reach it either)

Every clause was false. The real reader is ctaCaptionFirst() in site-core.js, called from Cta.astro, and it moves the caption paragraph ahead of the button row in the real DOM (not a CSS order illusion — the reading order is right for screen readers too).

The checker was not making it up. The table of "which params each coral reads" is derived from this package, on purpose — a hand-kept list is wrong the day the renderer moves and nobody downstream can tell. The derivation scans a section component for pm.<name>. Cta.astro read the param as ctaCaptionFirst(pm): it handed the whole bag to a helper and the helper reached inside. Real code, real effect, and no pm.caption anywhere to find.

So the fix is on this side of the seam. ctaCaptionFirst now takes the value, and the read happens at the call site in the component, where the thing deriving the table can see it. A checker that is wrong is worse than no checker — it does not merely fail to help, it talks authors out of markup that works.

One more instance of the same defect, not fixed here: hero reads pm['logo-pos'], which cannot be written pm.logo-pos — it is not an identifier. A dot-only derivation calls that one inert too. Making the downstream scan match the bracket form is the consumer's half; this PR asserts ours.

Guards

Both were run with the fix reverted and both go red — a guard nobody has watched fail is a guard you only know is quiet.

  • base-paint.test.mjs.st-faq is styled as a container (matched on its own selector, not on a descendant), is ungated, and carries the shared wrapper's declarations verbatim. Control: the faq paint is still gated, so the test can tell box model from look.
  • coral-params.test.mjs (new) — the test this issue asked for. It first measures that caption= reorders the reference renderer (one variable between the two arms), then requires a scan of Cta.astro to see it: a param proved to be read, but reported inert, fails here. It also pins the general shape — no section component may hand pm to a helper — with controls for the detector itself, for the comment stripper (this file's own prose contains the word pm, and a scanner that reads its own explanation reports the fix as the bug), and for bracket reads.

Verification

  • bash scripts/test.sh → exit 0, ✅ ALL GREEN.
  • The astro smoke is skipped by default (no packages/sitetile/astro/node_modules) and CI does not install them either, so the changed component would otherwise be exercised by nothing. Installed them and ran it: ✅ sitetile-astro smoke PASS (59/59).
  • Then built the real Astro renderer against a temporary fixture (a faq section and a cta caption=before) and read the output back: the built CSS carries :where(.st-faq){max-width:var(--gd-maxw);margin-inline:auto;padding:2.5rem clamp(1rem,4vw,3rem)}, and in the caption=before section the blurb precedes st-cta-btns while the page's other, plain cta keeps buttons-first. Fixture reverted.

Refs tile-lab#7, tile-lab#8.

Two defects found on a built site, both invisible to every gate this repo has.

.st-faq had NO rule. Thirty lines of the stylesheet style .st-faq-head, -list,
-item, -q and -a — every one a DESCENDANT — so the file reads like a package that
thought about the faq, while the container it all hangs from had no width and no
padding. probe_render on a built site showed `.st-faq` matching one rule:
`* { box-sizing }`. The FAQ ran edge to edge under a prose section inset 48px.
Third instance of the .st-notfound / .st-cta-arrow defect class: we emit a class
and never paint it, and the only detector is a person looking at the page.

The rule takes the SHARED wrapper's declarations (.st-prose, .st-grid, .st-cta,
.st-people) — no second opinion about page width — and sits in :where() so any
theme that names .st-faq wins. It is NOT gated on data-theme-custom, unlike the
faq PAINT below it: that gate is right for a look a hand-rolled theme already
chose, and wrong here, because it would exempt exactly the sites with the bug.

`caption=` on a cta was reported to authors as "read by nothing … the renderer
never looks at it", at save time, while it reordered the section's DOM. The list
of params each coral reads is derived from this package by scanning a section
component for `pm.<name>`; Cta.astro called `ctaCaptionFirst(pm)` — it handed the
whole bag to a helper that reached inside. Real code, real effect, nothing for the
derivation to find. A checker that is wrong is worse than none: it talks authors
out of markup that works. So ctaCaptionFirst now takes the VALUE and the read
happens at the call site, where a scan can see it.

Guards, both shown to go red with the fix reverted:

  base-paint.test.mjs   .st-faq is styled as a CONTAINER (its own selector, not a
                        descendant), ungated, and carries the shared wrapper's
                        declarations verbatim. Control: the faq paint IS still
                        gated, so the test can tell box model from look.

  coral-params.test.mjs measures that caption= reorders the reference renderer,
                        then requires the scan to see it — a read param reported
                        inert fails here. Plus the general shape: no component may
                        hand `pm` to a helper. Controls for the detector, for the
                        comment stripper (this file's own prose says "pm"), and
                        for bracket reads.

The bracket half is not hypothetical. hero reads `pm['logo-pos']`, which cannot be
written `pm.logo-pos`, so a dot-only derivation calls that inert too — same defect,
a different door. Fixing the consumer's scan is the other repo's half; this one
asserts ours: every param this renderer reads is read where a scan can find it.

Refs tile-lab#7, tile-lab#8.
@cverorg
cverorg merged commit 54b460b into main Aug 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant