Skip to content

fix(selectors): anchor the New project CTA on add, and revert a guard I got wrong - #740

Merged
ffroliva merged 1 commit into
developfrom
fix/new-project-cta-add-ligature
Sep 7, 2026
Merged

ffroliva merged 1 commit into
developfrom
fix/new-project-cta-add-ligature

Conversation

@ffroliva

@ffroliva ffroliva commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Summary

Live verification overturned my own fix from an hour earlier. That is the headline, and it is the reason this PR exists.

#739 shipped a guard raising FlowHostMigratedError, asserting flow.google.com "renders no '+ New project' control gflow can drive". A $0 run of _enter_editor with no project id created a project there in one click.

The claim was an unproven negative — generalised from a sweep of two other surfaces (the project composer and the character editor) to a third that was never probed. The gallery is where NEW_PROJECT_SELECTORS actually lives, and I never measured it.

Refs #730

What the gallery actually shows

denon82, control 47 ligature nodes, $0:

add* ligatures            {"mat-icon|add": 1}
[0] add_2 / i.gs          0
[1] add_2 / i             0
[2] add_2 / role=button   0
[3] text-matches regex    ERR Error
[4] has-text('New project')  1     <- the ONLY match

The ligature finding was right — add, never add_2. The conclusion was wrong. The CTA is present and clickable; every structural Tier-1 entry missed, and it survived purely on the English text fallback — the anti-pattern Tier 1 exists to prevent, and one that fails outright on a non-EN migrated profile. My guard would then have told that user the control does not exist: false, and a dead end.

Fixed with a class-only add anchor leading Tier 1, covering both carriers. Re-measured live: entry [0] now matches 1 where every previous entry matched 0.

Also deleted: a selector that never worked

button:text-matches('^\+\s+\S+$', 'i') is not a valid Playwright selector. It raises on every evaluation — observed twice against the live gallery — and except Exception: continue swallowed it. It has never matched anything on any host, while costing a round trip per attempt.

Two guard tests caught this change, and one of them was the problem

  • test_new_project_selectors_icon_leads pinned the add_2 selector exactly. Updated to the measured anchor, with the measurement as its stated reason.
  • test_new_project_selectors_plus_regex_is_anchored asserted the regex was anchored — a property of a selector that never ran. It inspected the string instead of evaluating it, so it passed happily for as long as the selector was broken.

Replaced with the guard that would have caught the real bug: every shipped cascade entry must parse against a real CSS engine (.count() on a blank page — no account, no network, no credits), plus a companion asserting the known-bad selector does raise, so the guard cannot quietly stop discriminating.

Validation

  • pytest -q --cov — 4069 passed, 7 skipped
  • pyright src 0 errors · ruff check / format --check clean
  • doc links · hygiene · website mirror · memory round trip · PII scan
  • Live re-measurement of the add anchor on the migrated gallery

E2E: the CTA path is only reached when a generation runs without --project. The live gallery probe above is the direct evidence for the changed selector, and it is stronger than an e2e for this purpose: it counts what each cascade entry matches, rather than confirming that something in the cascade worked. Not re-running the character-create e2e — that path passes --project and never touches this code.

…rd I got wrong

Live verification overturned my own fix from an hour earlier. That is the headline.

#739 shipped a guard raising FlowHostMigratedError, asserting flow.google.com
"renders no '+ New project' control gflow can drive". A $0 run of _enter_editor
with no project id CREATED A PROJECT there in one click. The claim was an UNPROVEN
NEGATIVE - generalised from a sweep of two other surfaces (composer, character
editor) to a third that was never probed. The gallery is where NEW_PROJECT_SELECTORS
actually lives, and I never measured it.

That is the exact failure this session has been documenting, committed by me hours
after writing the rule into repo memory.

WHAT THE GALLERY ACTUALLY SHOWS (denon82, control 47 ligature nodes, $0):

  add* ligatures            {"mat-icon|add": 1}
  [0] add_2 / i.gs          0
  [1] add_2 / i             0
  [2] add_2 / role=button   0
  [3] text-matches regex    ERR Error
  [4] has-text('New project')  1     <- the ONLY match

So the ligature finding was right - `add`, never `add_2` - and the conclusion was
wrong. The CTA is present and clickable; every structural Tier-1 entry missed and it
survived purely on the ENGLISH TEXT fallback, which is the anti-pattern Tier 1 exists
to prevent and which fails outright on a non-EN migrated profile. My guard would then
have told that user the control does not exist: false, and a dead end.

Fixed with a class-only `add` anchor leading Tier 1, covering both carriers.
Re-measured live: entry [0] now matches 1 where every previous entry matched 0.

ALSO DELETED: button:text-matches('^\+\s+\S+$', 'i'). It is not a valid Playwright
selector and RAISES on every evaluation - observed twice against the live gallery -
with `except Exception: continue` swallowing it. It never matched anything on any
host while costing a round trip per attempt.

TWO GUARD TESTS CAUGHT THIS CHANGE, AND ONE OF THEM WAS THE PROBLEM:
  - test_new_project_selectors_icon_leads pinned the add_2 selector exactly. Updated
    to the measured anchor, with the measurement as its reason.
  - test_new_project_selectors_plus_regex_is_anchored asserted the regex was ANCHORED
    - a property of a selector that never ran. It inspected the string instead of
    evaluating it, so it passed happily for as long as the selector was broken.

Replaced with the guard that would have caught the real bug: every shipped cascade
entry must PARSE against a real CSS engine (.count() on a blank page - no account, no
network, no credits), plus a companion asserting the known-bad selector DOES raise,
so the guard cannot quietly stop discriminating.

Verified: 4069 passed, pyright 0 errors, ruff clean, doc links, hygiene, website
mirror, memory round trip, PII scan - and the `add` anchor re-measured on the live
gallery.

Refs #730
@ffroliva
ffroliva merged commit 11fbc47 into develop Sep 7, 2026
15 checks passed
@ffroliva
ffroliva deleted the fix/new-project-cta-add-ligature branch September 7, 2026 20:34
@ffroliva ffroliva mentioned this pull request Sep 7, 2026
tranzmatt pushed a commit to tranzmatt/gflow-cli-unofficial that referenced this pull request Sep 9, 2026
Version bumped in pyproject.toml, .codex-plugin/plugin.json and
src/gflow_cli/__init__.py. CHANGELOG [Unreleased] migrated to [0.71.0] with
the compare/vPREV...vNEW footer the repo already uses.

Gates on this tree: repo hygiene 1034 files clean, doc links 29 resolved,
website mirror in sync, website PII clean, council memory 49/49, ruff, ruff
format, pyright 0 errors, and the offline suite 4070 passed / 24 skipped at
92% coverage against an 80% floor.

An earlier run of that suite reported two failures and both were mine, not the
release: I bumped __init__.py while it was running, and I started a scoped
pytest against the shared tmp/pytest basetemp alongside it. Each passes in
isolation, and the suite above is a clean re-run with nothing else touching the
tree. Recording it because "two tests failed, then passed on a re-run" is
exactly what a real flake looks like and this was not one.

docs/LIVE_VERIFICATION_v0.71.0.md carries the 5-layer ledger, and docs/INDEX.md
points at it as the latest.

The headline item is that `gflow character create --voice` is verified end to
end for the first time: before this release a repo-wide grep for `--voice`
across tests/e2e/ matched nothing. It also settles a contradiction between two
of our own documents (`sent='Charon' stored='Charon' identical=True`).

This release contains a mistake and its retraction twice over, and the notes
say so rather than presenting only the corrected state:
  - ffroliva#739 asserted the migrated gallery renders no "+ New project" control; a $0
    run created a project there in one click, and ffroliva#740 anchors the CTA on `add`
    instead of on English text.
  - The entity guard was given the reason "the backend rejects it"; entity-bound
    generations are in fact accepted and queued, and ffroliva#742 retracts it. What
    fails is the observer, on a null MZZa6b payload against a 60s budget.

One item is recorded as NOT verified rather than omitted: whether a bound
character's voice reaches rendered audio (ffroliva#738). Attachment is proven,
application is not, and the blocker is no longer credits or account access but
retrieval.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant