Skip to content

feat(ui-automation): robust aria-pressed agentic→classic mode control - #332

Merged
ffroliva merged 1 commit into
developfrom
feature/robust-mode-controller
Jul 17, 2026
Merged

ffroliva merged 1 commit into
developfrom
feature/robust-mode-controller

Conversation

@ffroliva

Copy link
Copy Markdown
Owner

Summary

Fixes the root cause behind the spurious "forced agentic — classic media panel not recoverable" aborts (blocking #299/#314/#183/#174): the recovery clicked the Agent pill once, blindly, and treated apps_spark_2 (the always-present Tools nav icon) as proof of an unrecoverable agentic cohort.

New leaf component mode_control — read_mode + ensure_media_mode. Flow's Agent toggle is button[aria-pressed]:has(span.content); its aria-pressed is the mode (false=classic, true=agent) — locale-invariant. ensure_media_mode closes the expanded chat sidebar (X) first, then toggles OFF only when actually on — never a blind click, idempotent, bounded. _dismiss_agent_affordances delegates to it, so every caller (image + video mode-switch, driver detection) gets robust recovery. The classic→agentic direction already has the robust _force_agent_mode.

Evidence (owner-provided live DOM + validation)

The mechanism was confirmed from owner-provided agentic-mode DOM/screenshots (PT locale) and then live-validated end-to-end by scripts/dev/spike_mode_roundtrip.py, which drove a full classic → agent-on → sidebar → close → toggle-off → classic cycle on real Flow, asserting aria-pressed and crop_* at every step:

step aria-pressed crop_* sidebar-X
initial false ✅ –
toggle on true – –
expand (gone) – ✅
close X true – –
toggle off false ✅ –

Test plan

  • mode_control unit tests (state-machine fake: noop / agent→media / sidebar→agent→media / stuck).
  • Updated _exit_agent_mode integration tests (fake now models aria-pressed; the stuck case still escalates to FlowAgentUiError).
  • /gflow:check: ruff clean, pyright src 0/0/0, hygiene + doc-links clean; 257 transport/driver tests pass.
  • Independent code-review: clean (no correctness issues). Ponytail-review: dropped the redundant ensure_agent_mode (duplicated the superior _force_agent_mode); kept the cohesive read_mode+ensure_media_mode pair.

Follow-up (documented, not in this PR)

Adopt read_mode in factory.detect_ui_mode and drop apps_spark_2/tune from AGENTIC_UI_INDICATORS (still used as a last-resort escalation signal today; the robust recovery now handles the common case first).

🤖 Generated with Claude Code

Replace the blind "click the Agent pill once and give up" recovery with a
clean, reusable, state-aware component. Flow's Agent toggle is a
button[aria-pressed]:has(span.content) whose aria-pressed IS the mode
(false=classic media, true=agent) — locale-invariant, unlike the button text
or the apps_spark_2 "Tools" icon (present in BOTH modes; a false forced-agentic
signal that caused spurious "not recoverable" aborts).

New leaf module `mode_control` (read_mode + ensure_media_mode): reads
aria-pressed, closes the expanded chat sidebar (X) first, then toggles OFF only
when actually on — never a blind click, idempotent, bounded loop.
`_dismiss_agent_affordances` now delegates to `ensure_media_mode`, so every
caller (image + video mode-switch, driver detection) gets the robust recovery.
(The classic→agentic direction already has the robust `_force_agent_mode`.)

Live-validated: `scripts/dev/spike_mode_roundtrip.py` drove a full
classic→agent→sidebar→close→toggle-off→classic cycle on real Flow, asserting
aria-pressed + crop_* at every step. Unit tests for the component + updated
_exit_agent_mode integration tests. Removed the now-dead _AGENT_* loop constants.

Recon: `scripts/dev/spike_mode_switch_agentic_to_classic.py` (apps_spark_2=Tools).
Unblocks #299/#314/#183/#174. Follow-up: adopt read_mode + drop apps_spark_2/tune
from AGENTIC_UI_INDICATORS (still a last-resort escalation signal today).
@ffroliva
ffroliva merged commit 3159f09 into develop Jul 17, 2026
8 checks passed
@ffroliva
ffroliva deleted the feature/robust-mode-controller branch July 17, 2026 14:26
15990166686ll-crypto pushed a commit to 15990166686ll-crypto/gflow-cli that referenced this pull request Aug 24, 2026
…ffroliva#332)

Replace the blind "click the Agent pill once and give up" recovery with a
clean, reusable, state-aware component. Flow's Agent toggle is a
button[aria-pressed]:has(span.content) whose aria-pressed IS the mode
(false=classic media, true=agent) — locale-invariant, unlike the button text
or the apps_spark_2 "Tools" icon (present in BOTH modes; a false forced-agentic
signal that caused spurious "not recoverable" aborts).

New leaf module `mode_control` (read_mode + ensure_media_mode): reads
aria-pressed, closes the expanded chat sidebar (X) first, then toggles OFF only
when actually on — never a blind click, idempotent, bounded loop.
`_dismiss_agent_affordances` now delegates to `ensure_media_mode`, so every
caller (image + video mode-switch, driver detection) gets the robust recovery.
(The classic→agentic direction already has the robust `_force_agent_mode`.)

Live-validated: `scripts/dev/spike_mode_roundtrip.py` drove a full
classic→agent→sidebar→close→toggle-off→classic cycle on real Flow, asserting
aria-pressed + crop_* at every step. Unit tests for the component + updated
_exit_agent_mode integration tests. Removed the now-dead _AGENT_* loop constants.

Recon: `scripts/dev/spike_mode_switch_agentic_to_classic.py` (apps_spark_2=Tools).
Unblocks ffroliva#299/ffroliva#314/ffroliva#183/ffroliva#174. Follow-up: adopt read_mode + drop apps_spark_2/tune
from AGENTIC_UI_INDICATORS (still a last-resort escalation signal today).
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