Chapter 28. 2026-07-17 to 07-18: The Twelve-Hour Estate-Wide Pass
On 2026-07-17 I ran a Lighthouse pass across the estate with parallel agents: 22 live web surfaces audited, 18 PRs merged across nine repos in one day (one of them reverted and redone), and most of the estate confirmed at 90-plus. My own notes had claimed the estate held “perfect Lighthouse” scores, and the pass confirmed that line was aspirational before anyone opened a terminal. The pass record supersedes it.
The structure was the one that lets a single operator cover that much ground: parallel background agents, one git worktree per lane, each agent merging and deploying under standing autonomy while I made the judgment calls. The same structure supplied two of the day’s worst problems. The machine running the measurements also ran every other concurrent session on this Mac, and one of those sessions broke production in the middle of my pass.
The per-surface scores, and the count I can prove
The clean wins first: jcig-site (jcinvestmentgroup.ventures) reached
a perfect 100/100/100/100 through PR #13, which fixed color contrast,
malformed
<dl>/<dt>/<dd>
markup, and an invalid robots.txt directive. acp.suedeai.ai
live-verified at 97/100/100/100. studio.suedeai.ai went from Performance
0.55 to 0.97, LCP 5.1s to 2.0s, and TBT 590ms to 70ms, verified on my
machine rather than against the live deploy. ip.suedeai.ai and
social.suedeai.ai closed the pass still sitting around Performance
56-57, with real bundle-weight work remaining.
The pass file is the primary tally: at least 15 of 22 audited surfaces were confirmed in the top range, with all four categories at 90 or better. A later index summarized the result as 18 to 20, but it did not preserve the surface-level evidence needed to support that higher count, so this book does not use it. The stricter, traceable number wins.
What landed: 18 PRs across nine repos
| Repo | PR(s) | What landed |
|---|---|---|
| Suede-AI-App frontend + landing-site | #653-#663 | static rendering, wallet-SDK scoping, hydration fixes,
NO_LCP root cause, one revert and redo |
| suede-cosmos | #6 | perpetual requestAnimationFrame canvas drift loop,
unscoped zustand store subscriptions, sub-12px fonts |
| suede-empire | #5 | a --dim color token too light against 3 backgrounds,
aria-label mismatches on the logo and the split-flap board |
| fretpulse | #29 | two setInterval polls for the Pulse-by-Anthony-J
metronome running forever regardless of active tab, favicon/analytics
404s, missing <select> labels |
| jcig-site | #13 | contrast, definition-list markup, robots.txt |
| suede-geo | #10 | contrast, link-in-text-block, missing favicon |
| suedeai-org | #29 | render-blocking Google Fonts stylesheet, link-in-text-block |
| suede-acp-x402-business | #5 | exempting an animated H1 from its entrance stagger |
Two notes on that table. First, the per-repo arithmetic in my own pass file does not add up: it labels the frontend lane “×6” while enumerating eight PR numbers, counts PR #663 under both frontend and landing-site, and describes PR #657 both as a landing-site PR shared with suede-home and as suede-home’s own. The totals (18 PRs, nine repos) are self-consistent; the per-repo split is not, and I am not going to pretend otherwise. Second, look at what the small-repo rows contain. A Lighthouse pass sells itself as performance work, and then half of what it surfaces is contrast failures, missing labels, broken markup, and timers nobody cleared. fretpulse had two polls driving a metronome whether or not the tab was active. suede-cosmos had a canvas loop that never stopped. Those are functional bugs wearing a performance costume.
One
headers() call held eight hosts dynamic
The opening move was PR #653. The shared root layout in
Suede-AI-App/frontend wraps eight hosts: app, social,
studio, play, agents, ip, fretpulse, distro. That layout called
headers() with no condition, to answer one question (“is
this app.suedeai.ai?”) that is true for one host and false for the other
seven, each of which already injects its own JSON-LD. One dynamic API
call in shared chrome forced dynamic rendering estate-wide and pinned
Cache-Control: private, no-cache, no-store on every
request.
PR #653 moved the app-host-exclusive routes into an
(app-host) route group with their own layout and deleted
the headers() call from the shared root. studio, social,
play, and distro now render as static pages and serve
Cache-Control: public, max-age=0, must-revalidate,
confirmed live. An earlier attempt to fix this from middleware (PR #612)
had been proven dead by live curl (Vercel’s origin-function
Cache-Control wins over anything middleware sets on a
rewrite() response; they don’t merge) and reverted in PR
#614 rather than left as dead code under a confident comment.
That single merge did more for the estate than any other PR in the pass, and it triggered the next bug family the same day.
The wallet SDK loaded on every route, marketing pages included
Marketing pages that never touch a wallet were loading wagmi plus
WalletConnect/Reown AppKit plus the Solana wallet-adapter, with
autoConnect, on every route, and two repos that share no
code had the same dominant cost. On Suede-AI-App/frontend
that stack dominated TBT and bundle size across the studio, play,
distro, fretpulse, and acp routes; on ip.suedeai.ai it did the same
across six marketing routes. The mechanism was identical in both repos:
the provider stack sat in the root layout, mounted for all comers.
landing-site went one further. Its Header.tsx carried its
own Connect Wallet button, so the cost lived in shared chrome, not in
any page you could audit route by route.
The fix shape: extract the provider stack into its own component and
mount it through route-group layouts only where pages call wallet hooks.
PR #655 scoped the SDK away from studio/play/distro/fretpulse/acp. PR
#656 did the same for the landing-site homepage and went further,
migrating the homepage’s single on-chain read to a viem
createPublicClient call instead of keeping the whole SDK
for one read.
The lesson generalizes past wallets. When you scope a heavy
dependency to the routes that need it, grep transitively for hook usage,
not just src/app: the shared nav or header is the usual
hidden culprit. And when the same anti-pattern shows up in two unrelated
repos on the same day, stop treating it as a bug. It is a house pattern,
and it will be waiting in the next repo too.
PR
#660: ssr:false skips server rendering and nothing
else
PR #660 lazy-loaded Header.tsx’s wallet-connect button
as part of the same scoping effort. Build passed, tsc passed, lint
passed. It merged and deployed under the same standing autonomy as
everything else, and then the live Connect Wallet button threw a
client-side exception on click across six marketing routes in
production. The mechanism: ssr:false skips server rendering
and nothing else. It does not make wagmi hooks provider-optional. Once
the layout dropped WalletProviders and the component
mounted client-side with zero WagmiProvider ancestor,
wagmi’s useConfig() threw.
A manual click-through on an isolated browser tab caught it about 10
minutes in, and PR #661 reverted it. The redo, PR #663, inverted the
ownership model: HeaderWalletConnect now brings its own
scoped EvmProvider by default, and routes that already have
an ancestor provider (the dashboard, ip/[id]) opt out
through an explicit walletProvidersMounted prop instead of
any ancestor detection. The v2 got click-tested across all eight
wallet-touching routes before and after merge, by the implementing agent
against a local build and then against the live deployed site.
Build success said the change was fine, and build success was wrong. If a change touches something a user clicks, someone has to click it, on the deployment users get.
React hydration error #418 after switching routes to static
React error #418, the hydration mismatch, showed up the same day in
three places: suede-home, and the studio and social surfaces of
Suede-AI-App/frontend. My pass record counts those as three
repos. Nothing new had been written. PR #653 had changed the ground
under old code. Components in shared chrome (a mobile nav button, a
page-padding wrapper, FounderSignature.tsx,
MusicianCreateFooterCta.tsx) branched on
usePathname() or typeof window. While every
route rendered per request, those checks resolved against a real request
and matched. Once the routes were prerendered at build time, the build
baked in one value, the client computed another, and React noticed.
The fix is a mounted flag built on useSyncExternalStore,
so the first client render always matches server output and corrects
itself after mount. PR #658 fixed page-padding.tsx and
mobile-menu-button.tsx; PR #657 fixed
FounderSignature.tsx and
MusicianCreateFooterCta.tsx in suede-home. The obvious
alternative, a plain useEffect plus useState
mount flag, trips this codebase’s
react-hooks/set-state-in-effect lint rule, so
useSyncExternalStore is the required shape here.
The portable rule: flipping a route from dynamic to static changes the correctness of every runtime conditional in its shared chrome. Audit them as part of the same change, in every repo that shares the pattern, or the hydration errors will file in one repo at a time until you do.
Two
animation causes of a NO_LCP error under Lantern
Lighthouse’s default mode, Lantern, simulates throttling rather than applying it. Two animation patterns broke its LCP detection during the pass, with the same symptom and different causes, and the difference cost me most of a day.
studio.suedeai.ai returned a NO_LCP error and a hard 0
Performance score under Lantern while a real devtools-throttled run
measured normal. The cause was an “ember-breathe” live-indicator dot
animating box-shadow, a non-composited property, on
infinite iteration. Continuous non-compositor repaint left Lantern
unable to model a valid LCP candidate graph. The diagnostic recipe that
isolated it: bisect by page (/studio failed,
/studio/about did not), then
lighthouse --only-audits=non-composited-animationsthen confirm with a raw Chrome trace (--save-assets,
looking for largestContentfulPaint::Candidate events) or a
PerformanceObserver check in a real browser. PR #662
root-caused it and cleaned up font preload and subsetting on studio plus
an LCP-image and render-blocking-CSS fix on social. The fix that held
came the next day: PR #669 exempted the LCP element from the animation
outright.
Then acp.suedeai.ai produced the identical NO_LCP, and
its non-composited-animations audit came back clean. I
applied the fix that had worked on ip.suedeai.ai and studio.suedeai.ai,
tightening the entrance-animation timing 3x, and NO_LCP
persisted. The cause was different. The LCP element itself, the H1, was
opacity:0-animated inside an entrance stagger, and an
animated LCP candidate defeats Lantern no matter how fast the animation
runs. The fix exempted the H1 and kept the staggered reveal on secondary
hero elements, shipped as PR #5 in
JasonColapietro/suede-acp-x402-business (that repo lives
under my personal account, not the Suede-AI org, which matters when you
go looking for it). Shipping it surfaced a second problem: that Vercel
project does not auto-deploy on push, and a 6-day-stale deploy was live.
I shipped by hand with vercel --prod plus an alias pin,
then live-verified 97/100/100/100.
Two rules came out of this pair. Animate transform and
opacity, never box-shadow or
width, and treat infinite iteration as its own red flag.
And do not delete entrance animations to buy LCP: scale durations and
delays down, divided by about 2 to 3, which preserved the visual stagger
on ip’s .doc-rise and studio’s .luxe-rise
tiers (5 delay tiers each). When NO_LCP survives the
tightening, exempt the LCP element alone rather than tightening again.
Same symptom does not mean same cause, and the fix that worked on the
last surface does not transfer on its own authority.
Check
uptime before trusting a Lighthouse number
One agent ran the identical local production build through Lantern
three times: clean pass, clean pass, then NO_LCP once
machine load rose, and that finding reframed the whole pass. The code
had not changed between runs; the machine had. This Mac carries
concurrent Claude Code sessions and Codex CLI processes belonging to
other work, and that contention corrupts
--throttling-method=devtools in proportion to host load.
Above a threshold it corrupts even Lantern’s simulated mode, which I had
assumed was contention-proof. Load average ran 8-16 for hours during the
pass and oscillated between 5 and 838 through the following night. The
fallback I reached for was no fallback at all: the keyless default quota
on pagespeedonline.googleapis.com is a shared per-project
daily quota, already exhausted before the pass began, and it never reset
within the session.
The rule that survives: run
uptimebefore treating any Lighthouse number from this machine as ground truth. Above a load average of about 5, trust only before/after deltas taken on the same machine state, or get a real PageSpeed Insights or real-device reading. I enforced the rule against my own work the same night. PR #675, a framer-motion code-split for studio, stayed open and unverified because load hit 600-838 mid-verification. At that load, “fixed” and “still broken” are both unprovable, and merging on an unprovable claim is how a performance pass ships a regression.
The shared machine sent one more reminder. Mid-pass, an unrelated
session landed a bad rebase on Suede-AI-App main. PRs #672
and #673 repaired a duplicate articlePath declaration that
caused production builds to fail. The more specific deployment record
says the live site remained on its prior deployment because Vercel did
not promote the failed build, so I classify this as an 18-minute
production-pipeline failure, not a confirmed live outage. On an estate
where parallel agents push to one default branch, failures arrive from
outside your own change set. Attribute the incident to the branch, not
to whatever you happened to be doing when the alarm went off.
What I deferred, and the uncommitted worktree work I lost
A pass this wide ends with three lists: fixed, deferred, lost.
Four items stayed broken by decision. The Twitter ads pixel fired on
every page of play and studio, dragging third-party-cookie and inspector
flags into the reports; that is a marketing attribution call, not a perf
bug, and on 2026-07-18 PR #674 host-scoped the pixel to app.suedeai.ai,
picked from three options: gate it behind interaction, scope it by host,
or accept the score. scan.suedeai.ai’s Vercel Web Analytics toggle sat
disabled at project-settings level; account-level and project-settings
toggles are not something I let agents flip, so it stayed put pending my
own sign-off. social.suedeai.ai’s Reown/WalletConnect allowed-origins
gap, found while verifying PR #658 as a console error plus two 403s from
the relay, needs a dashboard session at cloud.reown.com, not a code
change; nothing in the repo, the build, or the test suite can represent
it. And acp.suedeai.ai’s .dashed-bar::after still animates
width where transform:scaleX() belongs,
flagged and left.
The lost list is the one that stings. ip.suedeai.ai and
social.suedeai.ai were the two hardest remaining surfaces, both still
around Performance 56-57 with real bundle-weight gaps. The in-progress
work on both lived as uncommitted state in worktrees, and a process
restart orphaned those worktrees with nothing salvageable. The work
carried forward as a written handoff
(05_handoffs/2026-07-18-claude-suede-lighthouse-pass-handoff.md
in the vault) rather than as code. Uncommitted worktree state is one
restart away from zero. On a long multi-surface pass, commit early and
keep committing; a handoff document is a consolation prize.
The order to fix in: shared chrome, then per-surface
The order the day imposed is the order I would choose again. Shared
chrome first: PR #653 and the wallet scoping in PRs #655 and #656 each
moved multiple surfaces per merge, and they flushed out the downstream
failures (the hydration mismatches, the broken header button) while the
pass still had hours left to absorb them. Per-surface cleanup second:
the small-repo PRs held audit-level items, cheap to fix in parallel
lanes because each lane owned its own worktree and merged without
waiting on the others. Verification ran throughout: click-tests for
anything interactive, live curls for anything cached, and
uptime next to every score.
The denominator matters more than the wins. 22 surfaces went in. Between 15 and 20 came out confirmed at 90-plus, two closed with honest open gaps and a handoff, four items were deferred with the reasons written down, and one production incident passed through from outside the pass. The old “perfect Lighthouse” line died on 2026-07-17, and what replaced it is worth more, because every number in it comes with the machine state it was measured under.