iPhone 17 Pro. 375px viewport. I pulled the page sideways and the content slid with it.
jimchristian.net — my personal site, the one that’s supposed to look professional — had horizontal overflow. A monospace code block set to max-width: 70ch was rendering at roughly 630px. The iPhone viewport is 375px. The math doesn’t work and the CSS didn’t care.
One site broken. But I have eight Astro sites deployed. If this pattern exists on one, it probably exists on others.
It existed on seven of them.
The Parallel Audit
Rather than checking each site manually, I launched eight subagents simultaneously — one per site. Each one scanned for the same class of issues: minmax() grids without mobile clamping, fixed-width elements inside responsive containers, nav bars that don’t wrap, prose blocks with hardcoded max-widths.
Eight agents. Eight reports. Seven failures. They came back within seconds of each other, and watching the results land was like opening browser tabs on a site you built drunk — the same mistake, over and over, in slightly different fonts.
| Site Type | Status | Primary Issue |
|---|---|---|
| Personal site | Broken | 70ch prose container, grid minmax(300px) |
| Newsletter site | Broken | Card grid minmax(280px), button row no wrap |
| Blog (this one) | Broken | Code blocks exceeding viewport |
| 4 other project sites | Broken | Various grid and padding issues |
| Parody site | Clean | Intentionally simple layout |
The only clean site is the joke. There’s probably a lesson in that.
Seven sites with the same class of bug. The parody site — the one I spent the least time on — was the only one that passed. Complexity creates surface area. Simplicity survives the viewport.
The One-Line Fix
Every grid problem had the same solution:
/* Before: breaks on any viewport narrower than 300px */
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
/* After: clamps to viewport width */
grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
min(Npx, 100%) makes the browser pick whichever value is smaller — the fixed pixel width or the full container width. On desktop, 100% is larger than 300px, so you get your intended column size. On mobile, 100% is 375px minus padding, so the grid clamps to whatever fits. No media query. No breakpoint. One function call and desktop behaviour is identical while mobile stops overflowing.
What I expected: Each site would need its own responsive fix — different layouts, different breakpoints.
What actually happened: Every single grid bug was the same pattern. Wrap the pixel value in min(). Done.
The Four Bug Categories
Across seven sites, every overflow grouped into four categories:
-
CSS Grid
minmax()without clamping — The most common. Card grids, feature grids, pricing grids. All using fixed minimum widths that exceeded mobile viewports. Fix: wrap inmin(). -
Prose containers with
max-width: 70ch— Fine for body text in a proportional font. Broken for monospace code blocks inside that container, because70chin monospace (where every character is the same width) renders wider than70chin the body font the container was designed for. -
Nav and button groups without
flex-wrap— Horizontal button rows that ran off-screen on narrow viewports. Fix: addflex-wrap: wrapand appropriate gap. -
Container padding too generous —
2rempadding on a 375px viewport eats 128px, leaving 247px for content. Fix:padding: clamp(1rem, 4vw, 2rem).
Damage Report
| Metric | Value |
|---|---|
| Sites audited | 8 |
| Sites broken | 7 |
| Total CSS changes | ~30 lines across 7 repos |
| Deploys to VPS | 6 (one site has no live domain) |
| Time elapsed | ~2 hours, audit to deploy |
Thirty lines of CSS. Seven repositories. That’s the cost of not knowing about min() when I wrote the first grid, and then copy-pasting the pattern into every project after.
Preventing Recurrence
Fixing seven sites is fine. Fixing seven sites and then building an eighth with the same bugs is not.
I added mandatory mobile overflow prevention rules to the web-architect agent — the agent that scaffolds new Astro sites:
Every
minmax()grid MUST usemin(Npx, 100%)for the minimum value. Every flex container with horizontal children MUST haveflex-wrap: wrap. Container padding MUST useclamp()or viewport-relative units.
The rules are in the agent now, not in my memory where I’ll forget them. Next time I build a site, the agent will refuse to write a grid without the mobile clamp. The lesson is in the tool, not in my head.
Each site got its fixes committed, pushed, built, and deployed to the VPS. Six deploys via rsync, one skipped (rrhub-site doesn’t have a live domain yet).
The Verification Gap
Here’s what I didn’t do: pull out the iPhone and actually check.
I fixed the CSS. I confirmed the builds succeeded. I deployed. I moved on. The logical next step — load each site on the actual device that revealed the problem — didn’t happen.
There’s a version of this story where min() introduces a different layout problem at some specific viewport width between 375px and the desktop breakpoint. A card grid that collapses to single-column too early, maybe, or a prose container that’s uncomfortably narrow at 414px (iPhone Plus). There’s another version where the clamp() padding fix looks fine on the phones I tested but leaves text jammed against the edge on a Galaxy Fold at 280px. I won’t find out until someone opens the site on that device — and they won’t tell me, they’ll just leave.
The gap between “deployed the fix” and “verified the fix” is the same gap that appeared during the SoN rebrand with unsourced hyperlinks. The pattern: the most satisfying moment (it builds! it deploys!) is exactly where I stop checking. Shipping feels like finishing. It isn’t.