Chapter 36. How to Run an Estate-Wide Pass

A pass is one question asked of every surface inside a bounded window, with the answers written down beside the date they were taken. I have run three: Lighthouse scores across the estate starting 2026-07-17, a 2026-07-26 sweep asking whether each filename in every Vercel repo’s bare api/ directory resolves to a live public route, and a rollout checking every public repo for the committed marketplace registration in .claude/settings.json.

The shape holds across all three. Define one check that produces a value. Enumerate the surfaces from the platform, not from memory. Sweep. Record per-surface results with dates. Fix the worst first. Then write the sweep date into the standing rule so the next operator knows how stale the answer is.

Define one check that produces a value

“Is this page fast” is not a check. “What does Lighthouse report for Performance, and under which throttling method” is a check, because it lands in a cell. The api/ sweep’s check is a command:

curl -s -o /dev/null -w '%{http_code}' https://<domain>/api/<basename>

Expect 404 for anything that is not a real endpoint. That check has a property the file tree does not: it interrogates production. On 2026-07-26 the file tree in suede-geo looked reasonable and the deployed surface was not. GET https://scan.suedeai.ai/api/check.test returned 200 with a plain-text body of partial, which was a fixture from a mock server inside a test file. Requesting the URL executed the test suite inside a production function, booted that test’s HTTP server, and hung past 25 seconds against the 300-second function ceiling. Anyone could have looped it. The remediation went in as site/.vercelignore, chosen over moving the file because it keeps the test in git and runnable on my machine.

Verify against production rather than the repo, on every check where production is the thing you care about. A liveness sweep uses the same discipline. hub.suedeai.ai returns a final HTTP 200 in 0.221s with no redirect, measured with curl -L --max-time 20, and that number means something because it came off the wire.

Enumerate surfaces from the platform, not from your repo docs

The repo list is not the surface list. On 2026-07-16 I audited every app in the jasoncola1@gmail.com App Store Connect account for a stale-name bug and found Suede Cinematic: AI Video, ASC app id 6780266335, bundle xyz.suedeai.cinematic. Its only submission, v1.0, had been sitting REJECTED since 2026-06-14 with no resubmission. No project note, map, or repo doc tracked it. It corresponded to an untracked Suede-AI-App/ios-cinematic/ directory with no README, AGENTS, or CLAUDE.md inside. My call on 2026-07-16 was to leave it alone and not resubmit, which is a decision I could only make once the sweep put it in front of me.

Repo documentation is the least reliable inventory I own, because it gets written once and never re-checked against the platform. On 2026-07-10 I confirmed that suede-map (map.suedeai.ai) does not auto-deploy on push while its own repo CLAUDE.md claimed push went to production. It ships with npx vercel --prod --yes from ~/code/suede-map followed by a curl. At least four surfaces in this estate are manual-deploy while the rest auto-deploy, and the deploy mechanism is a per-project fact you verify rather than inherit.

The same trap sits one layer up, at the account level. On 2026-07-17 suedeai.xyz did not appear in Search Console for jasoncola1@gmail.com. Filtering the property switcher showed suedeai.ai, suedeai.org, and unrelated suedeai*.com domains, with no .xyz anywhere. A second Google account under the Johnny Suede identity holds verified ownership of that domain and is also a listed owner on suedeai.ai, where Settings then Users and permissions shows both JasonCola1 and Johnny Suede. The Change of Address from .xyz to .ai had been submitted from that second account. On a solo estate accumulated over years, a property that does not exist usually means you are signed in as the wrong identity. Check the second account before you re-verify anything from scratch.

The same check across unrelated repos finds house patterns

Running the same check across unrelated repos on the same day is what turns a bug into a pattern. The performance pass produced four families, and three of them appeared in more than one repo.

Family Where it surfaced Fix
Wallet/Web3 SDK mounted globally Suede-AI-App/frontend studio/play/distro/fretpulse/acp routes; 6 marketing routes on ip.suedeai.ai PR #655 and PR #656
React #418 hydration mismatch after static rendering suede-home; frontend studio and social PR #657 and PR #658
NO_LCP from a non-composited or animated LCP element studio.suedeai.ai; acp.suedeai.ai PR #662, PR #669, and PR #5 in JasonColapietro/suede-acp-x402-business
Merged work not reaching production muse.suedeai.ai; acp.suedeai.ai Manual vercel --prod plus alias pin

The wallet family is the clearest case of a house pattern. Pure 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 that was the dominant TBT and bundle-size cost on both sets of routes. In both repos the provider stack was mounted unconditionally in the root layout. landing-site also had its own Connect Wallet button inside Header.tsx, doing the same thing a second time from shared chrome rather than from a page. Route-level scoping alone would have missed that. Grep transitively for the hooks, not for src/app. PR #656 also migrated the homepage’s single on-chain read to a viem createPublicClient rather than keeping the whole SDK around to serve one call.

The hydration family arrived out of a change I made on purpose. PR #653 removed a root-layout headers() call that had been forcing dynamic rendering across the estate, which flipped studio, social, play, and distro to static. Every usePathname() or typeof window conditional living in shared chrome had been correct under per-request rendering and became wrong under prerendering, because the check now bakes in a build-time value that disagrees with the client at runtime. React #418 showed up independently in three repos the same day. The fix is a mounted flag backed by useSyncExternalStore, so the first client render matches server output and self-corrects after mount. A plain useEffect plus useState mount flag trips this codebase’s react-hooks/set-state-in-effect lint rule, which takes the obvious shape off the table. PR #658 handled page-padding.tsx and mobile-menu-button.tsx; PR #657 handled FounderSignature.tsx and MusicianCreateFooterCta.tsx in suede-home.

One symptom hitting three repos inside a single day is the signal that pays for the whole pass.

Same symptom does not mean same cause

studio.suedeai.ai returned a NO_LCP error and a hard 0 Performance score under Lighthouse’s default Lantern simulated-throttling mode, while real devtools-throttling measurement gave a normal number. The cause was an “ember-breathe” live-indicator dot animating box-shadow, a non-composited property, with infinite iteration. Continuous non-compositor repaint left Lantern’s simulator unable to model a valid LCP candidate graph. The diagnostic recipe that got me there: bisect by page, since /studio failed and /studio/about did not, then run lighthouse --only-audits=non-composited-animations, then confirm against a raw Chrome trace with --save-assets looking for largestContentfulPaint::Candidate events, or a PerformanceObserver check in a real browser. That root cause took most of a day. PR #662 fixed it alongside font preload and subsetting on studio and a real LCP-image plus render-blocking-CSS fix on social, verified locally at Performance 0.55 to 0.97, LCP 5.1s to 2.0s, TBT 590ms to 70ms. PR #669 finished the job the next day by exempting the LCP element from the animation outright.

Then acp.suedeai.ai produced the identical NO_LCP symptom with a clean non-composited-animations audit. Tightening the animation timing by 3x, the move that had worked on ip.suedeai.ai and studio.suedeai.ai, left NO_LCP in place. The LCP element itself, the H1, was opacity:0-animated with an entrance stagger, and an animated LCP candidate defeats Lantern no matter how fast the animation runs. Full exemption of the H1, staggered reveal kept on secondary hero elements, shipped as PR #5 in JasonColapietro/suede-acp-x402-business, which is on my personal account and not the Suede-AI org. That surface also turned out not to auto-deploy on push, with a 6-day-stale build live at the time. I deployed manually with vercel --prod plus an alias pin and live-verified 97/100/100/100.

Carrying a fix forward across surfaces is how a pass gets fast. Carrying an assumption forward is how it produces false negatives.

Check load average before you trust a Lighthouse number

The measurement instrument in the performance pass was this Mac, and the Mac was under load from work that had nothing to do with the pass. Multiple parallel Claude Code sessions and Codex CLI processes belonging to other sessions were running throughout. Host CPU contention corrupts --throttling-method=devtools in proportion to the contention, since that mode applies a real CPU multiplier. Above a threshold it corrupts default Lantern mode too.

I have the proof rather than the theory. An agent reproduced the identical local production build passing Lantern cleanly twice, then failing NO_LCP on the third run once machine load rose. Nothing about the build changed. Load average sat at 8 to 16 for hours during the pass, confirmed with uptime, and oscillated between 5 and 838 through the following night.

the standing rule is a command before a number:

uptime

Above load average roughly 5, do not trust an absolute Lighthouse score. Trust before-and-after deltas taken on the same machine state, or get a real PageSpeed Insights reading or a real-device reading. The keyless default quota on pagespeedonline.googleapis.com does not qualify as a fallback channel: it is a shared per-project daily quota, it was already exhausted before the pass started, and it never reset inside the session. A real API key is required for that path to be worth anything.

The rule cost me a merge and I let it. PR #675, a framer-motion code-split on studio, was left open and unverified because the machine hit load 600 to 838 during verification. At that load “still broken” and “fixed” are both unprovable, and merging on either claim would have been guessing.

Resource contention from parallel work shows up in other costumes. On 2026-07-23 three separate agents, on fretpulse, Suede Social, and the music-generator TWA task, all hit 99 to 100 percent full disk mid-build on the same day. Each self-recovered, so I flagged it for a df -h check rather than treating it as blocking. The root condition is the same one that corrupted the Lighthouse numbers a week earlier, and it presents as unrelated per-repo build failures.

A green build is not a result

PR #660 lazy-loaded the wallet-connect button in Header.tsx. It passed build, tsc, and lint, merged, deployed, and then threw a client-side exception on click across 6 marketing routes in production. ssr:false skips server rendering and does nothing to make wagmi hooks provider-optional, so once the layout dropped WalletProviders and the component mounted client-side with no WagmiProvider ancestor, useConfig() threw synchronously. Manual click-through on an isolated browser tab caught it, and PR #661 reverted inside about 10 minutes. PR #663 redid it: HeaderWalletConnect brings its own scoped EvmProvider and is standalone by default, and routes that already have an ancestor provider opt out through a walletProvidersMounted prop instead of relying on ancestor detection. I click-tested all 8 wallet-touching routes before and after that merge.

Merged is not deployed either. On 2026-07-16 muse.suedeai.ai was still serving the pre-change build over an hour and two pushes after the manifest work merged to origin/main at commit f3f3558, and https://app.suedeai.ai/.well-known/assetlinks.json was still 404 after 14 minutes of polling despite commit bcde5f2f being on main. The Vercel CLI on this machine had {} in ~/Library/Application Support/com.vercel.cli/auth.json, zero stored credentials, so every vercel command blocked on an interactive OAuth device flow that the agent declined to complete unprompted. Only a live curl surfaced any of it.

Two more things a long pass will take from you. Production broke for roughly 18 minutes mid-pass on 2026-07-17 because an unrelated session landed a bad rebase on Suede-AI-App main, fixed in PRs #672 and #673, with no connection to the performance work at all. And on 2026-07-18 the worktrees holding in-progress work for the two hardest remaining surfaces, ip.suedeai.ai and social.suedeai.ai at Performance around 56 to 57, were orphaned by a process restart with nothing salvageable. That work carried forward through the vault handoff at 05_handoffs/2026-07-18-claude-suede-lighthouse-pass-handoff.md. Commit early on a multi-surface pass, because uncommitted worktree state is one restart from zero.

Write the sweep date into the rule

A pass produces two artifacts. The first is the per-surface record with dates attached, which for the 2026-07-17 pass lives in the vault handoff above , which recorded 18 to 20 of 22 surfaces confirmed at 90 or better. The second is a standing rule in CLAUDE.md carrying the date it was last enforced, so the next operator reads “Swept 2026-07-26” and knows exactly how much of the estate that claim covers and how stale it has become.

Rate limits set the pace, not your throughput. The 19-domain Search Console sweep on 2026-07-10 hit “Quota Exceeded, try tomorrow” after 11 indexing requests, leaving 8 batch-2 domains unindexed, and a scheduled retry the same day hit the quota on the first domain and completed 0 of 8. That is a per-account daily limit and it makes a 19-domain sweep a multi-day operation by design. Plan the pass around the platform’s ceiling.

Two smaller disciplines close the loop. Before you delete anything a sweep marks as dead, prove nothing references it: when I pruned the music skill cluster from 14 skills to 3 on 2026-07-12, I ran lint_skill_estate.py with --external-root pointed at the public pack to confirm no surviving skill referenced the 11 deletions. And treat verification leftovers as access-control debt. On 2026-07-18 suedeai.ai showed 1 stale “Domain name provider” ownership token owned by johnny@suedeai.ai, a leftover DNS TXT record that would have let anyone controlling that mailbox re-verify ownership and take back Search Console access. I removed the record from GoDaddy DNS and confirmed through Search Console’s Verify Removal flow, which now reports 0 unused tokens.