Room under the homepage titles, and the site stops flashing on every click #12

Merged
austin merged 5 commits from home-titles-and-page-transitions into main 2026-07-26 22:21:13 -05:00
Owner

Started as "the homepage titles need more room" and turned into one bug written seven times.

The bug

<div class="mb-12" th:replace="~{fragments/opening :: section-head('Visit us','')}"></div>

th:replace swaps the whole host element out for the fragment. The fragment's own class is what renders; anything on the calling tag is dropped — silently. Thymeleaf doesn't warn, the page still renders, and it looks nearly right. Confirmed against the running container before changing anything: mb-12 appeared zero times in the served HTML.

Every place it had happened:

Where Dropped What you saw
home.html x2 mb-12 Both section titles flush against the grid below
contact.html x2 mt-4 "Our hours" and "Find us" headings flush against their content
catering.html mt-10 Divider sat on the paragraph above it
footer.html mt-4 No gap between address and hours
home.html text-bakery-800 "Find us" address a shade darker (bakery-900) than "Our hours" beside it

Fixed where fragments/visit.html already said it should be — "both fragments take the classes that vary by where they sit". hours and address now take the element's own class as well as the row's or the link's; divider takes its spacing the same way. On the homepage the section-head gap moves to the element below, matching how catering's price grids already do it, at mt-14 md:mt-18.

Also on the homepage, as asked: card captions py-6 -> px-4 py-8, Visit us panel headings mb-6 -> mb-8.

The site flashed white on every click

body carried opacity: 0 and a 0.5s fadeIn. That's the SPA's — there it ran once, at React boot, and every navigation after happened inside an already-faded-in document. Server-rendered, every nav link and every filter chip is a new document, so a once-per-session animation became once-per-click: blank, then a 10px slide and a fade, on a navigation that was already fast.

Replaced with the cross-document view transition it was reaching for. The browser holds the old page up until the new one is ready and cross-fades, so nothing is ever blank. Unsupported browsers get a plain immediate navigation. Reduced motion keeps the navigation, drops the cross-fade.

A test, because nothing warns

TemplateHygieneTest walks the templates and fails on any tag carrying both a class and a th:replace, naming the file, line and tag. No Spring context, no database — five milliseconds. th:insert is deliberately not checked; it keeps the host tag, so a class there is fine.

Verified by mutation: put the catering divider's mt-10 back and the test failed on that exact line, then removed it again.

Checked

  • mvn verify63 tests, 0 failures, against main as merged in (platform v0.1.10)
  • Tailwind 4.3.3: @view-transition{navigation:auto} survives minification, mt-14 / md:mt-18 generate, fadeIn gone
  • Diagnosed against the live container, not just read off the source

Generated with Claude Code

Started as "the homepage titles need more room" and turned into one bug written seven times. ### The bug ```html <div class="mb-12" th:replace="~{fragments/opening :: section-head('Visit us','')}"></div> ``` `th:replace` swaps the whole host element out for the fragment. The fragment's own `class` is what renders; anything on the calling tag is dropped — silently. Thymeleaf doesn't warn, the page still renders, and it looks nearly right. Confirmed against the running container before changing anything: `mb-12` appeared **zero** times in the served HTML. Every place it had happened: | Where | Dropped | What you saw | |---|---|---| | `home.html` x2 | `mb-12` | Both section titles flush against the grid below | | `contact.html` x2 | `mt-4` | "Our hours" and "Find us" headings flush against their content | | `catering.html` | `mt-10` | Divider sat on the paragraph above it | | `footer.html` | `mt-4` | No gap between address and hours | | `home.html` | `text-bakery-800` | "Find us" address a shade darker (`bakery-900`) than "Our hours" beside it | Fixed where `fragments/visit.html` already said it should be — *"both fragments take the classes that vary by where they sit"*. `hours` and `address` now take the element's own class as well as the row's or the link's; `divider` takes its spacing the same way. On the homepage the section-head gap moves to the element below, matching how catering's price grids already do it, at `mt-14 md:mt-18`. Also on the homepage, as asked: card captions `py-6` -> `px-4 py-8`, Visit us panel headings `mb-6` -> `mb-8`. ### The site flashed white on every click `body` carried `opacity: 0` and a 0.5s `fadeIn`. That's the SPA's — there it ran **once**, at React boot, and every navigation after happened inside an already-faded-in document. Server-rendered, every nav link and every filter chip is a new document, so a once-per-session animation became once-per-click: blank, then a 10px slide and a fade, on a navigation that was already fast. Replaced with the cross-document view transition it was reaching for. The browser holds the old page up until the new one is ready and cross-fades, so nothing is ever blank. Unsupported browsers get a plain immediate navigation. Reduced motion keeps the navigation, drops the cross-fade. ### A test, because nothing warns `TemplateHygieneTest` walks the templates and fails on any tag carrying both a `class` and a `th:replace`, naming the file, line and tag. No Spring context, no database — five milliseconds. `th:insert` is deliberately not checked; it keeps the host tag, so a class there is fine. Verified by mutation: put the catering divider's `mt-10` back and the test failed on that exact line, then removed it again. ### Checked - `mvn verify` — **63 tests, 0 failures**, against `main` as merged in (platform v0.1.10) - Tailwind 4.3.3: `@view-transition{navigation:auto}` survives minification, `mt-14` / `md:mt-18` generate, `fadeIn` gone - Diagnosed against the live container, not just read off the source Generated with [Claude Code](https://claude.com/claude-code)
austin added 2 commits 2026-07-26 22:08:19 -05:00
"What people come in for" and "Visit us" were written with an mb-12 and rendered
with nothing: th:replace swaps the whole host element out for the fragment, so
the class attribute on that tag never reaches the page. Both titles sat flush on
the grid below them. Confirmed against the running site — mb-12 appears nowhere
in the served HTML.

The gap moves to the element below, which is where catering already keeps it
(mt-8 on its price grids) and which is the only place th:replace cannot eat it.
A little wider than the twelve that was intended, since the point is the breath:
14, and 18 from md.

The card captions get the same treatment — py-8 rather than py-6, and px-4 so a
longer name than "Cakes" doesn't run to the edges — and the two panel headings
in Visit us go mb-6 to mb-8.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The site stops flashing white on every click
build-and-publish / build (pull_request) Successful in 2m15s
5aa2e4589e
Clicking a nav link or a filter chip made the whole site look like it reloaded,
because it did: body carried `opacity: 0` and a half-second fadeIn, so every
navigation went blank, then slid 10px and faded back.

Those two lines are the SPA's. There they ran once, when React booted, and every
navigation afterwards happened inside a document that had already faded in.
Server-rendered, every link is a new document, so a once-per-session animation
became a once-per-click one — and the thing it was decorating was already fast.

Replaced with the cross-document view transition it was reaching for: the
browser holds the old page up until the new one is ready and cross-fades, so
nothing is ever blank. Where it isn't supported the navigation is simply
immediate, which is the right fallback and still better than a blank page.
Reduced motion keeps the navigation and drops the cross-fade; the print rule no
longer has to undo an opacity that isn't set any more.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
austin marked the pull request as work in progress 2026-07-26 22:08:43 -05:00
austin added 3 commits 2026-07-26 22:17:07 -05:00
A sweep for the bug behind the homepage titles found five more of it, because
the mistake is easy and silent: put a class on the tag you write a th:replace
on, and Thymeleaf hands you back the fragment with its own class attribute and
drops yours. Nothing warns, and the page looks nearly right.

  catering  the divider's mt-10, so the branch mark sat on the paragraph above it
  contact   mt-4 under both "Our hours" and "Find us" — the same flush headings
            the homepage had
  footer    mt-4 between the address and the hours under it
  home      text-bakery-800 on the address, which is why "Find us" rendered a
            shade darker (bakery-900, inherited from body) than "Our hours" in
            the panel beside it

Fixed where the fragments already said it should be: "both fragments take the
classes that vary by where they sit". They now take two — the element's own
class as well as the row's or the link's — so the caller has somewhere to put
this that survives. `divider` takes its spacing the same way.

A class on a th:replace tag now has no reason to exist anywhere in the
templates, and there are none left.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
A test for the class that gets thrown away
build-and-publish / build (pull_request) Successful in 1m57s
7449744682
The bug this guards has now been written seven times, and the reason is that
nothing tells you: Thymeleaf drops the class, renders the page, and the result
looks nearly right. A reviewer has to know the rule and then spot it.

So the rule is a test. It walks the templates, finds any tag carrying both a
class and a th:replace, and fails with the file, the line and the tag. No Spring
context and no database — it reads files, and costs five milliseconds.

th:insert is deliberately not checked: that one keeps the host tag, so a class
on it is fine.

The second test guards the first. A file-walking assertion that quietly stopped
finding files would pass forever, so it asserts the templates are actually there
to be read.

Verified by putting the catering divider's dropped mt-10 back and watching it
fail with that exact line, then removing it again.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
austin marked the pull request as ready for review 2026-07-26 22:21:06 -05:00
austin merged commit 412037d3e4 into main 2026-07-26 22:21:13 -05:00
austin deleted branch home-titles-and-page-transitions 2026-07-26 22:21:13 -05:00
This repo is archived. You cannot comment on pull requests.
No Reviewers
No labels
1 Participants
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: TheVine/itsthevine#12