Found by walking ONE site through the pipeline by hand: MoMA, chosen because if the muse system has spirit it should show it there. Two findings, one of them network-wide.
MoMA's muse was Playfair Display with an electric-cyan accent - the default AI answer for anything. The system prompt is not at fault; it is a genuinely good art-direction brief that insists on grounding the palette in what the business actually IS. The fault was the subject it was given:
Name: The Museum of Modern Art
Google types: tourist_attraction, art_gallery, art_museum, museum, ...
museAutoAttachForBusiness() selected six columns and forwarded five, while _autoMuseUserMessage() had always been able to use the address, rating, review count, website, price level and scraped copy - all in the row it had just read. Every bespoke muse on the network was designed from a name and a type list.
Now the brief carries all of it, plus review text, which had never been used and is the richest signal we hold: reviews name what a place is known for, in the words of people who went. A MoMA reviewer writes "whether it be Starry Night or models of schools". No list of Google types carries that.
Reviews are passed as voice and subject evidence, explicitly not as fact - they are strangers' words and some are wrong. The design brief may learn what a place is famous for; the copy pipeline still takes facts from the record.
Result on the same model and prompt: cyan/Playfair/editorial-magazine became Bauhaus primaries (#dc3a2a / #2a3adc / #f0c83a) on bone white, bold sans, poster archetype, swiss-modular grid, "circle square triangle". The lineage MoMA canonized, from a brief that finally described the subject.
The rule: pass everything free before blaming the model. A generic brief gets a generic answer, and it looks like a taste problem rather than a data one.
museBodyAttrs() expects a MUSE ROW and internally calls museComputeTokens(museReadingWithDefaults($muse)). Its only caller, museEmitV2BodyAttrs(), passes an already-computed TOKEN ARRAY. museReadingWithDefaults() sees no reading_json key and returns pure defaults, silently - correct behaviour for a v1 muse, catastrophic here.
passed tokens (what shipped): gallery=grid paper=smooth btn=flat version="1"
passed the muse row (correct): grid=swiss-modular hero-comp=layered-blocks
motion=fade-up hover=invert header=minimal version="2"
data-muse-version gates every v2 rule (body[data-muse-version="2"]). So type_pair, type_scale, grid_system, hero_composition, section_tones, image_crop, image_overlay, motion_vocabulary, hover_style, header/footer style and section_transition were all computed, written into the stylesheet, and matched nothing. MoMA asked for deco-display-geo (Limelight) and rendered Inter.
This is why bespoke sites look alike however good the reading is. The readings were never the problem.
Fixing the signature enabled v2 on 653 live sites at once - a layer that, because of this very bug, had never been looked at in a browser. It degraded them instantly. Reverted in minutes.
museV2RenderEnabled() now gates it: ?musev2=1 or the muse_v2_enabled platform setting, off by default. Opting out reproduces the previous output exactly (the default token set), not just a zeroed version number - otherwise data-muse-grid / -motion / -hover would still advertise themselves to any rule keyed on them alone.
Fixed before rollout (v2.2.569): the section reveal. Nine variants each shipped their own fade-in, and three faults ran through them - scripts that queried at PARSE time (so they never saw sections below themselves), two variants sharing one idempotency flag, and CSS that hid unconditionally so no-JS meant no reveal. Now one shared muse-section-reveal.php, armed by a JS-added class, collected at DOM-ready, with an uncancellable 4s watchdog.
A measurement caution earned here. I first read this as "live pages are blank". It was not: sections DO reveal on genuine scroll. The blank captures came from screenshotting mid-transition and from a synthetic scroll that jumped faster than IntersectionObserver samples. When instrumenting a time-based effect, the observation method is part of the experiment - wait out the transition, scroll the way a person scrolls, and prefer the DOM's own numbers to a screenshot.
dz muse do=tokens biz=<id> prints every stage and the body attribute string computed both ways. Built because from outside the browser you can only see the result, and every failure in this pipeline is silent by design.
A feature that has never once executed in production is not a feature, it is a hypothesis - and a well-tested unit can still be wired to nothing. Ask what proves a layer is live, not merely that its code is correct. Here the proof was one attribute on one tag, and nobody had ever looked at it.
The page-level .doozer-reveal section system is scroll-driven with no watchdog. Googlebot executes JavaScript but does not scroll, so measured on one live site 7 of 10 sections were at opacity: 0 for a rendering crawler. Indexing beats any effect: both reveals now show everything unconditionally after 4s.
The rule for any effect that hides content: it must prove it will un-hide it. Gate the hiding on a JS-added class so no-JS never hides; collect targets at call time, never at parse time; and keep a watchdog you never cancel.
muse_v2_enabled = 1, 660 sites. dz muse do=v2 on=0 is the instant rollback, no deploy. It was used once during the release and worked in seconds.
muse-motion.js gates on data-muse-version="2", so it had never executed in production either. Switching v2 on ran it for the first time and exposed three defects, all the same shape as the section-reveal bugs:
initFadeUp wrote INLINE opacity: 0. Inline beats every stylesheet rule, sosections carrying both reveal classes still sat at zero. It also matched NESTED [data-muse-section] nodes, which can never intersect inside a hidden parent. Now it defers completely when the page-level reveal exists — one reveal per page — and targets top-level sections only, with an uncancellable watchdog.
initTypewriter destroyed the H1. textContent = '' then retyped it over ~3s.Googlebot executes JS, so a crawler could snapshot an empty or partial headline; a stall left it truncated for good. Now the full text stays in the DOM and is revealed with a clip.
initTiltWake hid gallery cards with no watchdog.Twice during this release a page read as "10 of 11 sections hidden" and then healed the moment I ran JavaScript against it. Cause: a backgrounded browser tab throttles timers and does not fire IntersectionObserver — and running JS wakes it.
Check document.visibilityState and document.hasFocus() before believing any measurement of a scroll- or timer-driven effect. Foreground the tab first. Screenshots of a throttled tab are stale for the same reason.
Together with the earlier mistake (screenshotting mid-transition, scrolling faster than IO samples), that is three separate occasions where the observation method was the bug. Prefer the DOM's own state — is the reveal class present? — over a rendered pixel, and when the two disagree, suspect the harness.