Repository navigation
fix(selectors): de-blind the diagnostics, and fix the two selectors hiding behind them - #735
Conversation
…her half Phase 0 for #730, $0, denon82 migrated host. Two surfaces, controls recorded before any count, hard-error only after the dump is written. CARRIER RESULT - unambiguous. i.google-symbols matched ZERO for every ligature on both surfaces; mat-icon matched; and .google-symbols matched exactly the same count as mat-icon every time. The class sits on both carriers, so a class-only anchor covers labs AND migrated with no extra cascade entry: the fix for the 24 <i>-only constants is one character each, not 24 duplicated entries. Two selectors confirmed broken-but-masked: - SUBMIT_BUTTON_SELECTORS: both <i> entries miss; only has-text('arrow_forward') fires, matching the mat-icon's TEXT rather than its tag. Independently seen live the same day. It works by luck. - IMAGE_MODEL_PICKER_TRIGGER: arrow_drop_down i=0/mat=1, no twin, no fallback. THE FINDING A CARRIER-ONLY FIX WOULD HAVE SHIPPED PAST: the migrated host renders `add`, and renders `add_2` NOWHERE (composer add=1/add_2=0, editor add=2/add_2=0). That is a ligature NAME change, not a carrier change. NEW_PROJECT_SELECTORS, ADD_MEDIA_BUTTON and _CHARACTER_SLOT_ADD_SELECTOR all anchor on add_2 - and the last one ALREADY covers both carriers, so #703 swept it and left it broken. Changing the carrier on those three would have produced a green sweep and three selectors that still match nothing. It also corrects #730's own number: 55 counts literals and double-counts cascades that alternate carriers. 24 constants is the actionable figure. The probe found the name drift only because it counted whether the ligature was present AT ALL, alongside the carrier comparison - a control column added so a zero could not be misread as absence. Added for epistemic hygiene; it is what turned up the second defect class. Also recorded: the first run of this spike died at `Could not find 'New project' CTA`, because it entered via _enter_editor, which clicks NEW_PROJECT_SELECTORS - one of the constants under test. A probe must not route through its own subject. Now navigates straight to the project URL, with the reason in the code. NOT measured, and stated in the doc: labs (no unmoved account here), ligatures absent from both surfaces are not proven absent from the product, and whether broadening to .google-symbols changes .first picks where document order matters.
…iding behind them Scoped down from #730's "sweep 24 constants" after /gflow:predict returned CAUTION 7.3/10 from three personas, unanimous: right change, wrong blast radius. Four changes ship, every one backed by a measurement. THE ONE THAT MATTERS. Our incident-bundle DOM dump queried 'i.google-symbols, span.google-symbols'; diagnostics.py's STRUCTURAL_DOM_JS queried 'i.google-symbols'. On flow.google.com, where every ligature rides a <mat-icon>, BOTH RETURNED ZERO. The instrument we reach for to diagnose selector drift was blind to the host the drift lives on - which is why #727 and #731 stayed invisible, and why a bundle from a migrated user would have named no ligatures at all. Found by the Devil's Advocate persona, not by me; it is worth more than the other 23 constants. TWO SELECTORS WERE BROKEN BEHIND IT: - SUBMIT_BUTTON_SELECTORS was working BY LUCK. Entries 0 and 1 were tag-qualified and matched nothing on migrated; the submit only landed because entry 2 matches the mat-icon's TEXT. Live: prompt_submitted via="button:has-text('arrow_forward')", after paying two misses at ~2s each on every submit. - IMAGE_MODEL_PICKER_TRIGGER had no twin and no fallback, so the miss was total and silent: best-effort, so generation proceeded on whatever tier the editor opened at. Both now anchor on the `google-symbols` CLASS, which sits on the labs <i> and the migrated <mat-icon> alike. That was CSS semantics asserted, never observed - the whole proposal rested on it - so tests/api/transports/test_ligature_carrier.py now proves it against a real CSS engine with a two-carrier fixture. No account, no network, no credits. If it ever fails, the premise is wrong and this must revert. THE FIXTURE CAUGHT A DEFECT IN MY OWN FIX: `matched 3 of 2`. SUBMIT_BUTTON_SELECTORS used `:text`, a SUBSTRING match, which also accepts `arrow_forward_ios` - a real Material Symbol. The <i> qualifier had been containing that; dropping it for the class-only carrier made the over-match reachable on the load-bearing submit path. Now `:text-is`. So the "one character each" framing I used was wrong: dropping a tag qualifier can expose a latent substring over-match, and each site needs its exactness checked too. The dominated `button:has(i:text(...))` entry is deleted rather than broadened. DELIBERATELY NOT TOUCHED, each for a measured reason: - The negation sites, `button:not(:has(i.google-symbols))` and the `has_not` filter. Broadening INVERTS them: they would exclude more buttons, and on a dialog whose buttons carry mat-icons the iconless set collapses to zero -> no include click -> a 120s hang. P1 also found they are ALREADY broken the other way on migrated. - The 10 mode-detection selectors in drivers/factory.py and mode_control.py. They feed a classifier that calls raise_if_migrated every tick, so they never execute on the host this fixes. Zero upside, nonzero risk on labs - which is unmeasured. - The add_2 constants. That is a ligature NAME drift, not a carrier one, and _CHARACTER_SLOT_ADD_LIGATURE feeds a JS exact-match heuristic where a string swap is not the fix. Verified: 4043 passed, pyright 0 errors, ruff clean, hygiene 1030 files, doc links, website mirror, memory round trip, PII scan. Refs #730
…n control The Iron Law: done means verified by a run. The PR previously said the offline fixture was what backed it and that a live run was owed. Here it is. E2E: tests/e2e/test_character_create_e2e.py::test_character_create_format_prompt_ clicks_format_button -> 1 passed in 219.19s on denon82. That exercises _click_submit with the changed SUBMIT_BUTTON_SELECTORS, end to end, on a real generation. But pytest swallows stderr on a pass, so it cannot show WHICH cascade entry fired - and "still works" is not the claim; "works via the fixed selector rather than the accidental text fallback" is. So the sweep spike now counts the shipped constant and the form it replaced, side by side, on both surfaces: composer submit shipped=1 pre_730=0 composer image_model_picker shipped=0 pre_730=0 composer STRUCTURAL_DOM_JS 47 ligatures, 17 distinct character_editor submit shipped=1 pre_730=0 character_editor image_model_picker shipped=1 pre_730=0 character_editor STRUCTURAL_DOM_JS 14 ligatures, 13 distinct pre_730=0 everywhere is the point: the old anchors matched NOTHING on this host, and the new ones resolve. The diagnostics went from blind to 47 ligatures - that is the incident-bundle artifact a migrated user would have sent us, previously empty. AND THE PROBE CAUGHT ITSELF. The first verification run reported zero ligatures of any kind on the composer, where an earlier run on the same URL saw 8. The page had not rendered its icons at the fixed 3s mark. I had given the character editor a control and left the composer without one, so its zeros were unreadable and I nearly reported them as findings - the exact failure this week has been about, in my own instrument. One control on one surface certifies only that surface. Fixed twice over: every surface now carries its own control and hard-errors after the dump, and the composer waits for `.google-symbols` to attach rather than sleeping a fixed interval - the thing being counted is the thing to wait for. Same URL now yields 17 hits. That also makes image_model_picker=0 on the composer a MEANINGFUL zero rather than an unreadable one: the control passed, so that button genuinely is not on that surface, consistent with the migrated composer having no image mode. Not measured: labs, still - no unmoved account here.
Live verification — the E2E gap is closedE2E: But pytest swallows stderr on a pass, so it cannot show which cascade entry fired — and "still works" was never the claim. "Works via the fixed selector rather than the accidental text fallback" is. So the sweep spike now counts the shipped constant against the form it replaced, on both surfaces:
The probe caught itselfThe first verification run reported zero ligatures of any kind on the composer, where an earlier run on the same URL saw 8. The page had not rendered its icons at the fixed 3s mark. I had given the character editor a control and left the composer without one — so those zeros were unreadable, and I nearly reported them as findings. That is precisely the failure this week has been about, reproduced in my own instrument. One control on one surface certifies only that surface. Fixed twice over: every surface now carries its own control and hard-errors after the dump, and the composer waits for That also promotes Still not measuredlabs. No unmoved account exists here. The |
Summary
Scoped down from #730's "sweep 24 constants" after
/gflow:predictreturned CAUTION 7.3/10 from three personas — unanimous: right change, wrong blast radius. Four changes ship, every one backed by a measurement.Refs #730 (the full sweep stays open; see "Not touched" below)
The one that matters: our diagnostics were blind
On
flow.google.com, where every Material Symbols ligature rides a<mat-icon>, both returned zero.The instrument we reach for to diagnose selector drift was blind to the host the drift lives on. That is why #727 and #731 stayed invisible for weeks, and why an incident bundle from a migrated user would have reported no ligatures at all. Both queries are now class-only.
Two selectors were broken behind it
SUBMIT_BUTTON_SELECTORS<mat-icon>'s text. Live:prompt_submitted via="button:has-text('arrow_forward')"— after paying two misses per submitIMAGE_MODEL_PICKER_TRIGGERBoth now anchor on the
google-symbolsclass, which sits on the labs<i>and the migrated<mat-icon>alike.The premise is now observed, not asserted
The whole proposal rested on "
.google-symbolsmatches both carriers" — CSS semantics, never checked.tests/api/transports/test_ligature_carrier.pyproves it against a real CSS engine with a two-carrier fixture: no account, no network, no credits. If it ever fails, the premise is wrong and this reverts.It immediately caught a defect in my own fix:
matched 3 of 2.SUBMIT_BUTTON_SELECTORSused:text, a substring match, which also acceptsarrow_forward_ios— a real Material Symbol. The<i>qualifier had been containing that; dropping it for the class-only carrier made the over-match reachable on the load-bearing submit path. Now:text-is.So my own "one character each" framing was wrong: dropping a tag qualifier can expose a latent substring over-match, and each site needs its exactness checked too.
Not touched, each for a measured reason
button:not(:has(i.google-symbols)), and ahas_notfilter). Broadening inverts them — they would exclude more buttons, and on a dialog whose buttons carry mat-icons the iconless set collapses to zero → no include click → a 120s hang. They are also already broken the other way on migrated.raise_if_migratedevery tick, so they never execute on the host this fixes. Zero upside, nonzero risk on labs — which is unmeasured.add_2constants. A ligature name drift, not a carrier one, and_CHARACTER_SLOT_ADD_LIGATUREfeeds a JS exact-match heuristic where a string swap is not the fix.Validation
pytest -q --cov— 4043 passed, 7 skippedpyright src— 0 errors ·ruff check/format --checkcleanE2E: the changed selectors sit on the submit and model-picker paths, exercised by the character-create e2e. Not re-run here — the previous live run on this branch's parent predates these selector edits, so I am not claiming live coverage for them. The offline two-carrier fixture is what backs this PR; a live run belongs with whoever takes the remaining sweep.