Repository navigation
Conversation
This was referenced May 12, 2026
ffroliva
added a commit
that referenced
this pull request
May 21, 2026
`generate_images_batch` now makes one transport call with `count`
baked into the request, instead of fan-out via `asyncio.gather`. The
UI transport clicks the matching x{N} tab so Flow produces N images
in one submission round-trip. Simpler, faster, matches user intent.
Extracted `_drive_images_generation` (plural, returns list) so both
the single-image and multi-image paths share one token-mint +
transport-call site. The singular `_drive_image_generation` is now a
thin delegator that **forces `count=1`** on the request before
delegating (via `_dc_replace(req, count=1)`) — guards against
callers passing a `GenerateImageRequest(count>1)` to the single-image
API and getting images silently discarded.
Preserved during the tree-replay:
- `download_video` method on `FlowApiClient` (added by PR #36 video
download work — untouched by this commit's count-selector scope).
- `generate_images_batch.seeds` parameter — currently a no-op under
the active UI transport (which ignores per-shot seeds). Length
validation preserved so callers passing mismatched `seeds` still
get a loud failure. The `seed` / `batch_id` surface across
`generate_image` and `_drive_image_generation` is also preserved
for commit #1b to remove cleanly.
Tests carry forward PR #35's single-call assertions and add two
back-compat pins (`test_batch_seeds_back_compat_accepted_and_ignored`
+ parametrized `test_batch_seeds_length_mismatch_raises`). All 37
scoped tests pass.
Codex code review at this SHA returned MAJOR-REVISION; both findings
addressed in this amended commit:
- restored `download_video` (BLOCKER: tree-replay from e8f932a
predated PR #36 and wiped the method).
- forced `count=1` in singular delegator (MAJOR: silent-discard bug).
- updated the `_ = seed, batch_id` rationale comment so it no longer
mistakenly claims the values "pass through to HTTP transports".
Independence: this commit has no dependency on commit #1b (seed
removal) or commit #2 (`gflow image batch` subcommand), so it is
independently cherry-pickable to a `release/*` branch.
Refs: design spec §5 commit #1, plan §Task 1.1.
Closes #14 part 1.
ffroliva
added a commit
that referenced
this pull request
May 21, 2026
Empirically verified dead under the active UI transport since v0.7.0. The user-supplied seed was discarded by the `_ = seed, batch_id` shim in `_drive_image_generation` before reaching any transport. The active UI transport clicks buttons and never reads the seed; the experimental HTTP transports mint locally and never received the user's seed either. Cleanup landed in this commit: CLI (src/gflow_cli/cli_image.py): - `--seed` option deleted from `gflow image t2i` and `gflow image i2i`. - Help-text examples and cross-flag UsageError validations removed. - `seed=seed` kwarg dropped from the two `client.generate_image` call sites. Public client (src/gflow_cli/api/client.py): - `seed=` / `batch_id=` removed from `generate_image` signature. - `seeds=` removed from `generate_images_batch` signature. - `seed`/`batch_id` kwargs + `_ = seed, batch_id` shim removed from the private `_drive_image_generation` (now a clean count=1 delegator). - The `secrets.randbelow(2**31)` and `_new_batch_id()` fallbacks inside `generate_image` deleted (no caller path needs them). - `_new_batch_id()` helper deleted — no remaining callers in src/. - `import secrets`, `from collections.abc import Sequence`, and `import uuid` removed (no remaining references). Tests: - 5 RED tests authored by Task 2.0 turn GREEN: * test_generate_image_has_no_seed_kwarg * test_generate_images_batch_has_no_seeds_kwarg * test_drive_image_generation_private_has_no_seed_kwarg * test_t2i_rejects_seed_flag * test_i2i_rejects_seed_flag - Two stale CLI tests deleted (testing now-removed cross-flag validations that were passing accidentally on Click's "no such option: --seed" message): * tests/cli/test_cli_image.py::test_t2i_seed_with_n_gt_1_errors * tests/cli/test_t2i_multi_prompt.py::test_t2i_rejects_seed_in_multi_prompt_before_profile_resolution - All `seed=N` kwargs removed from `client.generate_image(...)` call sites in test_client_image.py. - Back-compat tests added in commit #1 deleted along with the `seeds=` parameter they pinned. User-facing docs aligned with the code in this commit (per the project rule: every commit affecting user-facing behaviour updates docs in the same commit): - docs/USAGE.md: removed `--seed` flag entries from `gflow image t2i` and `gflow image i2i` reference tables, the "Seed-requires-count==1 invariant" paragraph, and the "Reproducible single shot" example. - README.md: removed `--seed` from both `gflow image t2i` flag-list references. - docs/USER_GUIDE.md unchanged — its `--seed` references are in the `gflow video t2v` example (out of scope for this PR per spec §3). - docs/USAGE.md retains `--seed` entries for `gflow video t2v` and `gflow video i2v` (also out of scope). The wire-format body builder `src/gflow_cli/api/image.py::_build_batch_generate_images_body(seed, batch_id, ...)` is UNCHANGED — those parameters live at the wire-protocol layer used by the experimental HTTP transports' internal mint. The `GeneratedImage` and `GenerateImageRequest` / `GenerateVideoRequest` DTOs retain their `seed` fields (they record the seed Flow used; not a user-controlled input). Council G1 verdicts (3-reviewer council on this commit's pre-amend SHA 3108c55): - Codex code-quality: MAJOR-REVISION (docs still advertised --seed; addressed in this amend). - Claude TDD/security: APPROVE. - Gemini spec-conformance: UNAVAILABLE (gemini-2.5-pro rate-limited with status 429 across 6 retries; reduced to 2 reviewers per orchestration plan §4.7 fallback policy). Refs: design spec §1, §3, §5 commit #1b, §10 AC16, §12 D8. BREAKING CHANGE: --seed flag removed from `gflow image t2i` and `gflow image i2i`. `FlowApiClient.generate_image` no longer accepts `seed=` or `batch_id=` kwargs; `FlowApiClient.generate_images_batch` no longer accepts `seeds=`. Callers passing these will get a TypeError (library) or "no such option" (CLI). If reproducibility via user-controlled seed becomes possible again — either through Flow UI exposing a seed control or via HTTP transport revival — the surface will be re-introduced at that layer.
ffroliva
added a commit
that referenced
this pull request
May 23, 2026
ui_automation.py had no equivalent of ui_automation_video.py's _switch_to_video_mode. When the Flow account was last in Video mode, "image" prompts were silently routed to the video endpoint: no batchGenerateImages response observed, and the response listener timed out after 3 minutes. Also explains the historical "first-attempt listener-miss flake" recorded in phase-b-followups memory item #1. Adds UiAutomationTransport._switch_to_image_mode mirroring the video side: opens the 2-step mode dropdown via the shared MODE_SWITCH_TRIGGER_SELECTORS (imported from ui_automation_video), clicks the Image tab via the new IMAGE_TAB_IN_MENU_SELECTORS cascade (aria-controls*='IMAGE' first for locale-stability, then Portuguese "Imagem" / English "Image" / icon-ligature fallbacks), presses Escape to close the menu so _configure_generation_settings can open it fresh, and emits ui_automation.image_mode_entered. Wired into both call sites: generate_images (single t2i) calls it after _dismiss_blocking_overlays and before _configure_generation_settings; _generate_images_batch_locked calls it once per batch in the same orphaned_project_warning try/except wrapper used for overlay dismissal (so a mode-switch failure on a freshly-created project surfaces the project_id for user lookup). Tests: - New tests/api/transports/test_ui_automation_image_mode.py with three unit tests (happy path, trigger missing, image-tab missing) mirroring tests/api/transports/test_ui_automation_video.py's TestSwitchToVideoMode. - Autouse fixture in tests/api/transports/test_ui_automation.py TestGenerateImages and at the module level of tests/api/transports/test_ui_automation_batch.py stubs _switch_to_image_mode for orchestration tests that don't exercise the mode switch directly (mirror of the video side's monkeypatch.setattr(VideoGenerationMixin, "_switch_to_video_mode", AsyncMock())). Live-verified on profile ffroliva: 1 t2i shot (1 JPEG @ 768x1376, 9:16 within 0.79%) and full batch e2e (4 JPEGs at 9:16, 16:9, 16:9, 1:1, shared project_id). User confirmed gallery state.
ffroliva
added a commit
that referenced
this pull request
May 23, 2026
LIVE_VERIFICATION_image_batch.md gains a "Post-mode-switch-fix verification" section recording today's full-pass results on profile ffroliva (1 t2i smoke + 4-image batch e2e, all eight test assertions green, user-confirmed gallery). Retracts an earlier session's "Phase 7e produced 4 unique images" claim that was based on file-size differences without magic-byte verification — inspection of the surviving pytest tmp dirs (pytest-825/826) shows zero prompt PNGs were ever written in those runs; the only PNGs present were count-tab diagnostic screenshots. CHANGELOG.md [Unreleased] § Fixed gains an entry for the image-mode-selection bug, noting it also resolves the historical "first-attempt listener-miss flake" from phase-b-followups memory item #1. Notes the denon82 WAF/reCAPTCHA 403 is unrelated to this fix and remains open as a separate investigation; ffroliva's success today demonstrates WAF can be passed by a sufficiently fresh chrome-strategy profile.
This was referenced May 23, 2026
ffroliva
added a commit
that referenced
this pull request
May 23, 2026
…40) * docs(spec): plan for multi-image-prompt e2e + jitter investigation Design doc for landing PR #35 (issue #14) as a production-ready, e2e-tested feature on a properly-named feature/multi-image-prompt branch. Captures strategy A (close + reopen PR), atomic commit split, parameterized live e2e, and the systematic jitter-necessity investigation (R0/R1/R2 matrix on profile ui_automation). Will be consumed by the writing-plans skill to produce the implementation plan. * docs(spec): harden multi-image-prompt design from council review Four-reviewer parallel review (Python craft / SOLID, e2e test design, methodology, project conventions) returned high-signal findings. Integrated as v2 of the design doc. Key changes: - Dropped shim-removal from in-scope (verified: would force a public API change to generate_image; net-zero readability). D8. - Deferred cosmetic _drive_image[s]_generation rename to follow-up. D9. - Aligned e2e env vars to canonical GFLOW_CLI_E2E_* prefix. D12. - Switched jitter override mechanism to dependency injection via the existing run_manifest_image_batch(jitter_range=...) parameter (no monkeypatch, no production-code env-var branch). D11. - Replaced ad-hoc OUT_DIR timestamping with pytest tmp_path. D10. - Restructured jitter matrix: R1/R2/R3 (added same_project=0 + jitter=0 cell to isolate the IV from same-project rapid-fire). D14. - N=3 per cell across 2 sessions with conservative-default-on- inconclusive (keep jitter). D13/D14. - Operationally pinned the pass/fail definition for UI transport (no HTTP 429 to read; uses listener-miss + overlay + timeout signals). - Added application-layer structlog events for post-drop regression detection (image_batch.submission_attempt/result/row_completed/ inter_submission_latency_ms). D4. - Added stale-test grep AC + pre-commit AC + INDEX-reference AC (AC13/14/15). - Mandated origin/claude/plan-next-issue-Stegy deletion + PR body "Supersedes #35" prefix. D7. - Tightened tree-replay diff scope to image-batch paths only. - Added malformed-row negative fixture (unit-only, never live). - Bumped budget estimate to ~72 generations worst case. * docs(spec): v3 — full --seed cleanup + dead seed/batch_id removal Code verification revealed --seed has been a no-op user-facing feature since v0.7.0: the user's seed is discarded by the `_ = seed, batch_id` shim in _drive_image_generation before reaching any transport. The active UI transport doesn't honour seeds at all. User authorised Option A (full removal) over Option B (plumb through the request DTO for transports nobody uses). Spec changes: - §1 problem statement adds the --seed dishonesty. - §2 goal adds "honest API surface." - §3 in-scope: detailed cascade — CLI flag, public generate_image, private _drive_image_generation kwargs, generate_images_batch seeds= param, 9 test call sites, USAGE.md mentions. - §3 out-of-scope: replaced "shim deferral" with "seed-controlled reproducibility is a future Flow-side concern." - §5 commit split: #1 (count selector) and #1b (refactor — remove --seed) are now separate commits. #1 stays cherry-pickable to release/* independently. - §9 CHANGELOG: ### Removed entries for --seed flag and for the public-API kwargs removal (TypeError on caller misuse). - §10 AC16: explicit grep-based completeness criteria for the seed cleanup (cli_image, api/client, tests, --help output). - §11 risk register: 4 new rows for breaking-change-with-no-real- regression, stale CLI tests, and library caller TypeError. - §12 D8: flipped from "preserve shim" to "delete completely." - §12 D2: commit count updated to 7 (1, 1b, 2, 3, 4, 5a, 5b). Body builder seed/batch_id is preserved — wire-format level, used internally by the experimental HTTP transports which mint locally and do not propagate user-supplied seeds anyway. * docs(plan): implementation plan for multi-image-prompt feature Bite-sized, TDD-flavoured task breakdown of the v3 design spec into 7 phases (bootstrap, count-selector fix #1, --seed removal #1b, image batch + observability #2, live e2e #3, docs #4, jitter matrix + verdict #5a/#5b, PR cycle). Each task includes: - exact file paths and line markers - complete code snippets (no placeholders, no "TODO") - exact PowerShell commands with expected output - scoped /gflow:check after each commit - conventional-commit message templates Self-review table at the end maps spec §10 acceptance criteria 1-16 to satisfying tasks; all covered. Saved to docs/superpowers/plans/ per writing-plans skill convention. * docs(plan): v2 — apply all 25 council findings Four-reviewer council audit returned 6 BLOCKERs + 12 MAJORs + 7 lower-priority items. All addressed: BLOCKERs fixed: - Drop src/gflow_cli/api/image.py from Task 0.2 path list (verified: not in PR #35 file list). - read_manifest_file -> parse_manifest_file plan-wide (verified: actual function at image_batch.py:545). - Task 4.1 e2e uses e2e_profile_dir pytest fixture parameter (verified at tests/e2e/conftest.py:25), not a function call. - Task 4.1 Step 0 adds `uv add --dev pillow` prerequisite. - Task 1.1 Step 2 promotes "edit directly" as primary path — git add -p cannot separate count-selector from --seed hunks. - Task 7.1 step 5: removed `git rebase --exec --reset-author` (violated spec §6.B / D6). Replaced with hard-reset + redo Phase 0 if author leakage detected. MAJORs fixed: - Task 7.2: gh pr create via --body-file (avoids PowerShell here-string newline/backtick mangling). - Task 7.1 step 3: AC16 grep tightened with ^\s*# exclusion. - Task 4.2: smoke-run dropped (was burning credits before commit); first credit-spending run is now R3 rep 1 in Phase 6. - Task 2.4: commit message uses `refactor(image)!:` with proper BREAKING CHANGE footer. - New Task 7.0: rebase on develop before push (matrix takes ≥2h, develop may move). - New Phase 8: branch deletion deferred to post-merge. - New Task 2.0: TDD red tests for #1b (inspect.signature + CliRunner --seed UsageError) — 5 tests pinning the cleanup. - Task 3.2 step 3: project_id field added to submission_attempt event; test assertion updated. - Task 3.2 step 1: log_capture fixture uses yield + reset_defaults on teardown. - Task 4.1: WebP magic bytes accepted; aspect tolerance ±2%. - Task 6.2 Step 2.5: abort gate if R1 session-1 fails non-listener-miss. Lower-priority addressed: - AsyncMock __aenter__/__aexit__ wiring on fake_client_factory. - _invalid fixture guard in e2e _resolve_manifest_path. - try/finally last_events.json dump on e2e failure. - Per-rep manifest variants tmp/sample_batch_repN.tsv to defeat cache. - import hashlib/time moved to image_batch.py module top. - Stale-test grep duplicated after Task 2.4 (not only at end). - Malformed-row test parametrized over 3 rows. Council compliance summary table appended to Self-review section. * docs(plan): orchestration plan for multi-model execution Coordinator-led orchestration modeled on the prior canonical pattern in 2026-05-14-shell-multi-prompt/. Defines: - Roles + model assignments: - Coordinator: Claude Opus 4.7 (this session) - Implementer A: Claude Sonnet/Opus via Agent tool (judgment work) - Implementer B: Codex CLI v0.131.0 via `codex exec` (mechanical pattern work — Phase 2 --seed deletion, Phase 3 TDD events, Phase 5 doc templating) - Implementer C: Gemini 2.5 Pro via `gemini -p` (long-context Phase 4 e2e drafting + cross-model spec-conformance review) - Reviewers: codex review / gemini -p / Claude subagent - Task matrix mapping all 33 plan tasks to a wave + implementer + reviewer + live-credit flag + dependencies. - 7 parallel execution waves with explicit serial/parallel boundaries. - Per-task dispatch protocol with concrete PowerShell + prompt templates for codex exec, gemini -p, and Agent tool. - Three council gates: G1 (pre-#1b BREAKING), G2 (Phase 6 verdict), G3 (pre-PR final). Each runs 3 reviewers in parallel; Coordinator consolidates. - Quality gates between phases (static checks + stale-grep + reviewer verdict + state log entry). - 8 escalation rules (regression in --help, BREAKING outside #1b, matrix abort, reviewer disagreement, credit-cap, pyright drift, author leak, gflow:check regression). - State tracking via tmp/orchestration-state.md (gitignored) and per-dispatch artefacts under tmp/dispatches/. - DoD aligned with spec §10 AC1-16 plus optional receipts archive. - Model selection rationale + quality-first priority justification. Companion: also patches the impl plan's Task 0.1 step 3 to use `git checkout feature/multi-image-prompt` (the branch already exists on this branch as of the planning commits). * docs(plan): harden orchestration plan against Gemini rate-limit Probed CLI model availability before pushing: - Codex CLI 0.131.0: GPT-5 Codex default; headless `codex exec` works. - Gemini CLI 0.42.0: OAuth-personal tier rate-limited on cloudcode-pa.googleapis.com: - `gemini-2.5-pro` → 429 (intermittent capacity) - `gemini-3-pro` / `gemini-3-flash` → 404 (not exposed to this account) - Auto-fallback to `gemini-3.1-flash-lite` is also 429-prone Plan changes: - §0: new "Model availability — verified 2026-05-21" table with the probe results, the OAuth-personal tier note, and the optional user mitigation (paid API key). - §4.7: new Fallback policy — every Gemini dispatch has a retry-once + Claude-Opus-subagent fallback path. Quota-conscious dispatch order: Gemini first in a wave, Codex/Claude in parallel as backup. - §5 G1/G2/G3 councils: documented graceful degradation when Gemini is unavailable (skip + note in consolidation report). - §12: setup confirmations table replaces open-questions section; Wave 6 ~72-generation budget confirmed approved. * fix(image): use native xN count selector for -n N (#14 part 1) `generate_images_batch` now makes one transport call with `count` baked into the request, instead of fan-out via `asyncio.gather`. The UI transport clicks the matching x{N} tab so Flow produces N images in one submission round-trip. Simpler, faster, matches user intent. Extracted `_drive_images_generation` (plural, returns list) so both the single-image and multi-image paths share one token-mint + transport-call site. The singular `_drive_image_generation` is now a thin delegator that **forces `count=1`** on the request before delegating (via `_dc_replace(req, count=1)`) — guards against callers passing a `GenerateImageRequest(count>1)` to the single-image API and getting images silently discarded. Preserved during the tree-replay: - `download_video` method on `FlowApiClient` (added by PR #36 video download work — untouched by this commit's count-selector scope). - `generate_images_batch.seeds` parameter — currently a no-op under the active UI transport (which ignores per-shot seeds). Length validation preserved so callers passing mismatched `seeds` still get a loud failure. The `seed` / `batch_id` surface across `generate_image` and `_drive_image_generation` is also preserved for commit #1b to remove cleanly. Tests carry forward PR #35's single-call assertions and add two back-compat pins (`test_batch_seeds_back_compat_accepted_and_ignored` + parametrized `test_batch_seeds_length_mismatch_raises`). All 37 scoped tests pass. Codex code review at this SHA returned MAJOR-REVISION; both findings addressed in this amended commit: - restored `download_video` (BLOCKER: tree-replay from e8f932a predated PR #36 and wiped the method). - forced `count=1` in singular delegator (MAJOR: silent-discard bug). - updated the `_ = seed, batch_id` rationale comment so it no longer mistakenly claims the values "pass through to HTTP transports". Independence: this commit has no dependency on commit #1b (seed removal) or commit #2 (`gflow image batch` subcommand), so it is independently cherry-pickable to a `release/*` branch. Refs: design spec §5 commit #1, plan §Task 1.1. Closes #14 part 1. * refactor(image)!: remove no-op --seed flag and dead seed/batch_id params Empirically verified dead under the active UI transport since v0.7.0. The user-supplied seed was discarded by the `_ = seed, batch_id` shim in `_drive_image_generation` before reaching any transport. The active UI transport clicks buttons and never reads the seed; the experimental HTTP transports mint locally and never received the user's seed either. Cleanup landed in this commit: CLI (src/gflow_cli/cli_image.py): - `--seed` option deleted from `gflow image t2i` and `gflow image i2i`. - Help-text examples and cross-flag UsageError validations removed. - `seed=seed` kwarg dropped from the two `client.generate_image` call sites. Public client (src/gflow_cli/api/client.py): - `seed=` / `batch_id=` removed from `generate_image` signature. - `seeds=` removed from `generate_images_batch` signature. - `seed`/`batch_id` kwargs + `_ = seed, batch_id` shim removed from the private `_drive_image_generation` (now a clean count=1 delegator). - The `secrets.randbelow(2**31)` and `_new_batch_id()` fallbacks inside `generate_image` deleted (no caller path needs them). - `_new_batch_id()` helper deleted — no remaining callers in src/. - `import secrets`, `from collections.abc import Sequence`, and `import uuid` removed (no remaining references). Tests: - 5 RED tests authored by Task 2.0 turn GREEN: * test_generate_image_has_no_seed_kwarg * test_generate_images_batch_has_no_seeds_kwarg * test_drive_image_generation_private_has_no_seed_kwarg * test_t2i_rejects_seed_flag * test_i2i_rejects_seed_flag - Two stale CLI tests deleted (testing now-removed cross-flag validations that were passing accidentally on Click's "no such option: --seed" message): * tests/cli/test_cli_image.py::test_t2i_seed_with_n_gt_1_errors * tests/cli/test_t2i_multi_prompt.py::test_t2i_rejects_seed_in_multi_prompt_before_profile_resolution - All `seed=N` kwargs removed from `client.generate_image(...)` call sites in test_client_image.py. - Back-compat tests added in commit #1 deleted along with the `seeds=` parameter they pinned. User-facing docs aligned with the code in this commit (per the project rule: every commit affecting user-facing behaviour updates docs in the same commit): - docs/USAGE.md: removed `--seed` flag entries from `gflow image t2i` and `gflow image i2i` reference tables, the "Seed-requires-count==1 invariant" paragraph, and the "Reproducible single shot" example. - README.md: removed `--seed` from both `gflow image t2i` flag-list references. - docs/USER_GUIDE.md unchanged — its `--seed` references are in the `gflow video t2v` example (out of scope for this PR per spec §3). - docs/USAGE.md retains `--seed` entries for `gflow video t2v` and `gflow video i2v` (also out of scope). The wire-format body builder `src/gflow_cli/api/image.py::_build_batch_generate_images_body(seed, batch_id, ...)` is UNCHANGED — those parameters live at the wire-protocol layer used by the experimental HTTP transports' internal mint. The `GeneratedImage` and `GenerateImageRequest` / `GenerateVideoRequest` DTOs retain their `seed` fields (they record the seed Flow used; not a user-controlled input). Council G1 verdicts (3-reviewer council on this commit's pre-amend SHA 3108c55): - Codex code-quality: MAJOR-REVISION (docs still advertised --seed; addressed in this amend). - Claude TDD/security: APPROVE. - Gemini spec-conformance: UNAVAILABLE (gemini-2.5-pro rate-limited with status 429 across 6 retries; reduced to 2 reviewers per orchestration plan §4.7 fallback policy). Refs: design spec §1, §3, §5 commit #1b, §10 AC16, §12 D8. BREAKING CHANGE: --seed flag removed from `gflow image t2i` and `gflow image i2i`. `FlowApiClient.generate_image` no longer accepts `seed=` or `batch_id=` kwargs; `FlowApiClient.generate_images_batch` no longer accepts `seeds=`. Callers passing these will get a TypeError (library) or "no such option" (CLI). If reproducibility via user-controlled seed becomes possible again — either through Flow UI exposing a seed control or via HTTP transport revival — the surface will be re-introduced at that layer. * feat(image): add `gflow image batch` subcommand with --same-project (#14 part 2) - Accepts JSON or TSV manifests (dispatched by file extension). - TSV: prompt[<TAB>count[<TAB>aspect_ratio[<TAB>model]]] — optional columns fall back to CLI defaults. - JSON: [{"text": "...", "count": 2, "aspect_ratio": "16:9", "model": "nano2"}, ...]. - MAX_BATCH_PROMPTS = 5 constant in image_batch.py. - --same-project flag: all prompts share one Flow project with a 3-7s random jitter between submissions (anti-bot-detection, will be empirically verified in commit #5b). Default (no flag): each prompt creates its own project. - New structlog events for throttling-regression debugging: - image_batch.submission_attempt - image_batch.submission_result - image_batch.row_completed - image_batch.inter_submission_latency_ms Useful post-merge if a user reports Flow throttling. Sample fixtures under test_assets/: - sample_batch.tsv (happy path; 3 rows / 4 images) - sample_batch.json (same, JSON form) - sample_batch_invalid.tsv (malformed; unit-test only) Tests: 35+ carried-forward unit tests from PR #35 (parsing, dispatcher, runner), 4 new observability-event tests, 4 new malformed-fixture tests (parametrized + dispatcher). Closes #14 part 2. * test(e2e): live image batch e2e parameterized by same-project + DI jitter Spec §7. Mirrors tests/e2e/test_video_t2v_e2e.py conventions: - pytestmark = pytest.mark.e2e - Skipped unless GFLOW_CLI_E2E_PROFILE is set (canonical e2e gate) - Env-var parameterization for manifest, same-project, jitter - pytest tmp_path for output dir (auto-cleanup, no Windows `:` issues) - e2e_profile_dir pytest fixture (from tests/e2e/conftest.py:25) Assertions: - Exit code 0 (via outcome status) - File cardinality matches sum of row counts - PNG / JPEG / WebP magic bytes (fail loud on anything else) - Pillow dimensions match aspect ratio +/- 2% - ui_automation.batch_response_seen count == manifest row count - image_batch.row_completed count == total image count - image_batch.submission_attempt event present per row - --same-project=1: single project ID across rows - --same-project=0: distinct project ID per row (from submission_result events) Jitter override uses DI (jitter_range=(0.0, 0.0) via the existing run_manifest_image_batch parameter), not monkeypatch and not production-code env-var branch. _resolve_manifest_path refuses any _invalid fixture defensively so a misconfigured env never burns Flow credits on a malformed manifest. try/except around run_manifest_image_batch dumps the last 10 observability events to tmp_path/last_events.json on failure, per spec §11 risk-register "partial e2e cleanup" mitigation. Adds Pillow as dev dependency for the aspect dimension assertion. This commit does NOT run the e2e live. The first credit-spending run happens in Phase 6 (jitter matrix) as R3 cell, rep 1. * docs: `gflow image batch` in USAGE, CHANGELOG, INDEX - docs/USAGE.md: new "Batch image generation" subsection with TSV + JSON formats, `--same-project` flag (anti-detection jitter, live-verified outcome pending Phase 6 matrix), MAX_BATCH_PROMPTS=5, exit codes, the four observability event schemas, and pointers to `test_assets/sample_batch.{tsv,json}` for copy-paste. - CHANGELOG.md [Unreleased]: - ### Added — `gflow image batch` subcommand and the four `image_batch.*` structlog events for throttling-regression debugging. - ### Fixed — native xN count selector for `gflow image t2i -n N`. - ### Removed (BREAKING) — `--seed` flag from `gflow image t2i` and `i2i`; public-API kwargs `seed=` / `batch_id=` on FlowApiClient.generate_image and `seeds=` on generate_images_batch. Callers get TypeError / "no such option". - docs/INDEX.md: row linking to docs/LIVE_VERIFICATION_image_batch.md (the evidence file lands in commit #5a after the Phase 6 matrix). The jitter rationale paragraph in USAGE.md currently reads "live-verified" with the verdict pending the Phase 6 matrix run. If the matrix verdict is DROP (jitter unnecessary), commit #5b will refine that paragraph; if KEEP, no further docs update is needed. * docs(image): jitter live-verification skeleton — matrix pending Empty skeleton for the Phase 6 jitter matrix evidence file. Contains: - The full 3-cell × 2-session × N=3 matrix structure - Pass/fail criteria from spec §8 (exit code, listener events, overlay events, row timeout) - Decision rule (drop / keep / inconclusive-keep) with conservative default - Reproduce block with per-rep manifest variants to defeat Flow caching - Pre-flight + session-1 + session-2 commands - Empty matrix table and "Tested / Correlation IDs / Outputs / Aborted runs / Post-#5b verification" sections to be filled by the matrix run Pre-commits docs/INDEX.md's link to this file (added in commit `e6589e0` Wave 5 docs) so the cross-reference is satisfied. Matrix runs are user-driven and credit-spending (~72 generations worst case, approved). Per orchestration plan §1 "live-credit-spending phases stay on Claude/user pair", the actual matrix runs and the verdict-driven #5b commit happen in a later session — see tmp/orchestration-state.md (gitignored) for the resumption protocol. * chore: ignore worktrees directory * docs: add PR #38 review design, implementation plan, and monitor script * docs(plan,spec): record v3 plan + v4 spec amendments and stay-mounted session design v4 of the multi-image-prompt spec adds a revision banner superseding §8 jitter matrix and --same-project surface after mid-matrix discovery (2026-05-22) that ui_automation.generate_images discards project_id. v3 of the plan adds a matching banner with a seven-commit chain (v3-1..v3-7). New stay-mounted-batch-session design + plan land alongside. Spec is 5-reviewer council hardened and user-approved. * test(image): relax batch_response_seen assertion to lower bound Playwright's page.on("response", ...) listener fires for every matching HTTP response from Flow (initial submit, progress polls, final result), so the floor is one response per generated image — not one response per row. Equality breaks when a manifest row has count>1 or when same-project mode multiplexes events. Lower-bound assertion preserves the regression signal while tolerating Flow's actual response shape. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §8.3. * docs(image): retract jitter-matrix verdict — same-project transport defect The 2026-05-22 matrix run on profile denon82 invalidated its own premise: the ui_automation transport explicitly discards the project_id it's given, so every prompt of --same-project=1 lands in its own Flow project. The matrix was measuring rapid-fire across separate projects, not rapid-fire within one project — the question it was scoped to answer cannot be reached from the data collected. Updates the evidence file to record the two R1 runs (one over-strict assertion failure, one missing-file failure), retract the premature KEEP verdict, and point at the v3 plan for the actual fix path. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §1. * refactor(image)!: stay-mounted editor session in ui_automation transport Fixes the --same-project=1 no-op defect: ui_automation.generate_images discarded the project_id it was given ("accepted for Protocol parity; UI creates its own project"), so every prompt of a batch landed in its own Flow project. The stay-mounted refactor opens the editor once per batch, submits N prompts with jitter between, captures each prompt's responses via a per-prompt listener (now returning a detach handle), awaits in submission order with cleanup invariant on listener detach, and parses images into per-prompt BatchSubmissionResult records. BREAKING: BatchOutcome's downstream-error string format may change for failed prompts (now derives from BatchSubmissionResult.error which is a typed GFlowError). The public list[BatchOutcome] return type and the image_batch.submission_attempt / row_completed structlog events are preserved. The --same-project flag is dropped in the follow-up commit v3-4; this commit makes always-same-project the only behaviour at the transport+orchestrator layer. New types: BatchSubmissionResult (api.dto), BatchPartialError + BatchIntegrityError (errors). Extended: _attach_batch_response_listener returns (captured, detach_fn). Extended: _send_prompt clears the editor field (Ctrl+A + Delete) before typing so consecutive prompts in a stay-mounted editor do not concatenate. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md v3 (5-reviewer council hardened). Plan: docs/superpowers/plans/2026-05-22-stay-mounted-batch-session-plan.md. * test(image): fill Phase 3 spec gaps — submission_attempt project_id, missing tests, pyright nits - image_batch.submission_attempt event now emits the real project_id from results[idx], not the "<pending-transport>" placeholder. Event emission moves to after the transport returns so the project_id is real (spec §5.4). - Finding 2 (missing latency/submission_result events) verified non-issue: inter_submission_latency_ms and submission_result were present in the pre-Phase-3 orchestrator and are preserved in the Phase 3 refactor. - tests/api/transports/test_ui_automation_batch.py adds 4 spec-§8.1 unit tests: settings-fail-after-attach detach-before-continue, _await_captured partial-list timeout, batch-setup failure propagation, project_id-identical assertion. - Fixes mock signature in test_settings_fail_after_attach: cfg mock used keyword-only params (*, aspect, model, count) but real _configure_generation_settings takes positional args (page, aspect_cli, count) — caused TypeError on all calls. Corrected to positional signature; asserts updated to match configure-before-attach transport order (listener not attached when configure raises, noop-detach fires). - Adds type: ignore[arg-type] on _FakePage call sites (lines 58, 76, 119, 123) where the duck-typed mock fails pyright's strict Page protocol check. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §5.4 + §8.1. Follow-up to f6199db (Phase 3). * refactor(image)!: drop --same-project flag from gflow image batch BREAKING CLI. The flag had two states: --same-project=1 (intended to put all prompts in one Flow project; was a no-op at the transport layer until the Phase 3 refactor) and --same-project=0 (each prompt in its own project; equivalent to looping gflow image t2i externally). Per user design intent (memory batch-submission-cadence), batch = always one project; for different-project results, loop t2i instead. Removes the Click option, the same_project parameter, and any test invocations that pass it. Updates --help text to describe always-same-project semantics and the submission-cadence (anti-bot) jitter rationale. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §5.5. * test(e2e): update image batch e2e for always-same-project semantics Drops GFLOW_CLI_E2E_BATCH_SAME_PROJECT env var (no longer a knob) and the same_project=same_project argument that no longer matches the orchestrator signature. Adds the shared-project-id assertion that is the structural verification of the v3-3 fix: all image_batch.submission_attempt events must share one project_id. Keeps GFLOW_CLI_E2E_BATCH_JITTER — that env var lets tests opt into zero-jitter for faster/cheaper runs without affecting production defaults. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §8.3. * docs(image): always-same-project semantics + jitter rationale USAGE.md rewrites the gflow image batch section to describe always-same- project as the only mode and the submission-cadence (anti-bot) rationale for the 3-7s jitter. CHANGELOG.md [Unreleased] records the fix, the removed flag, the new exception types (BatchPartialError, BatchIntegrityError), and the new structlog event (ui_automation.orphaned_project_warning). The run_manifest_image_batch docstring is updated to match. INDEX.md cleaned of --same-project references where present. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §3 + §5.5. * fix(image): batch-transport race in await loop short-circuit The await-phase short-circuit at line 1181 used `not captured and submit_error is None` to detect "this entry was recorded from the continue-on-error send-fail path". It also matched successfully-submitted prompts whose responses had not yet landed in the listener's captured list — a race between the synchronous submit loop completing and Flow's async responses arriving. With continue_on_error=False, the resulting fail outcomes never set submit_error, so the post-loop fail-fast raise was bypassed and the method silently returned all-fail outcomes. Switches the sentinel to `detach is _noop_detach` — send-fail-recorded entries always carry the noop detach by construction (line 1149), and successful submissions carry the real detach callable returned by _attach_batch_response_listener. The new condition is timing-independent. Also moves _noop_detach out of the per-prompt for-loop body to just before it, so pyright can prove the name is always bound when the await loop references it (reportPossiblyUnboundVariable). Adds tests/api/transports/test_ui_automation_batch.py:: test_await_loop_does_not_short_circuit_on_in_flight_submission that reproduces the race scenario by mocking an in-flight prompt with empty initial captured and asserts _await_captured is invoked rather than short-circuited. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §5.6 (listener detach + per-prompt await invariant). Discovered during Phase 7 live verification 2026-05-22. * fix(image): count selector sticks across stay-mounted prompts The xN selector in Flow's editor persists between submissions in the stay-mounted batch flow. If prompt N sets count=2 and prompt N+1 wants count=1, the prior x2 selection wasn't reset and prompt N+1 generated 2 images instead of 1 — discovered live in Phase 7 (pytest-793). _configure_generation_settings now explicitly clicks x1 before clicking the target xN (Pattern A: force-reset), matching the CG Worker's proven pattern in flow_logic._quantity_option_texts. Both label variants are tried for each step (x{N} and {N}x) so older/localised Flow builds that render "1x" instead of "x1" are handled — also mirrors the Worker. The old _COUNT_TAB dict is replaced by _count_tab_candidates(n) which returns both variants, and _SUPPORTED_COUNTS replaces the dict key-check. The new _set_count() static method encapsulates the two-step reset logic and is called from _configure_generation_settings for both the single-prompt and stay-mounted batch paths. Unit test added: two-prompt sequence with counts 2 → 1 asserts the x1 click fires for the second prompt regardless of the first prompt's state. Three additional unit tests cover the x1-only case, the {N}x fallback, and the basic x1-then-x2 sequence. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §9 Risk row 4 (editor state drift between prompts). Discovered during Phase 7 live verification 2026-05-22. * fix(image): count-setter read-back verify + retry for stay-mounted editor The 2026-05-22 first count fix (401aaf5) used unconditional clicks (Pattern A — click x1, then xN). Live e2e showed it didn't actually update Flow's UI state for stay-mounted prompts: row 2 with count=1 still produced 2 images after row 1 set count=2. This commit rewrites _set_count to a read-back-verify-with-retry pattern: open the settings panel only if not already visible, read the currently-displayed count, click the desired tab (trying both x{N} and {N}x variants), then read back and retry up to 3 times. Raises a clear RuntimeError if the UI doesn't converge after 3 tries. Adds four structlog events for diagnosability: ui_automation.count_setter_entered, ui_automation.count_click_attempted, ui_automation.count_click_result, ui_automation.count_setter_completed. Adds per-prompt before/after screenshots saved to out_dir so future drift can be diagnosed without re-instrumenting. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §9 Risk row 4 (editor state drift between prompts). Follow-up to 401aaf5. * docs(image): Phase 7 partial pass — race fix verified, count-tab selector blocked by #24 Records the live verification effort on profile denon82 (2026-05-22). Race-condition fix (c759c90) is verified: stay-mounted batch flow no longer returns all-fail outcomes from an empty-captured short-circuit; submissions wait for responses correctly; the batch shares one Flow project across all prompts as designed. The follow-up count-selector work (401aaf5, 18d184b) ran into a pre-existing locale issue: the user's browser renders Flow in Portuguese, so count tabs read as "1 imagem"/"2 imagens" rather than the x1/x2 English patterns the code matches. Tracked by issue #24 (open since 2026-05-19, "Make UiAutomationTransport selectors language-agnostic (structural-first)"). A Phase 7b rerun against #24's fix will close the loop. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §9 Risk row 4 (editor state drift — partial coverage), spec §8.3 verification deferred behind #24. * fix(image): locale-invariant count tab — position-first selectors (#24 partial) Replaces the x{N}/{N}x text matching in _set_count and _read_displayed_count with a position-in-tablist structural selector and aria-selected="true" read-back. Falls back to leading-digit regex extraction so any locale's displayed text ("1", "x1", "1 imagem", "1 image") parses to the same count value, or returns None gracefully when no digit is found (deterministic position-based click still proceeds). Live evidence: Phase 7 retry 3 on profile denon82 (Portuguese locale) showed _read_displayed_count returning "imageImagem" — neither x1 nor 1x. The icon ligature + localized label rendering meant the existing text-match retry exhausted on the very first prompt. This change makes the path locale-agnostic per #24's structural-first pattern. Remaining work on #24 (NOT in this commit, tracked in the issue): - ONBOARDING_SELECTORS — purely localized text, no structural anchor - NEW_PROJECT_SELECTORS — localized fallbacks - SUBMIT_BUTTON_SELECTORS tail — localized fallbacks This commit closes the count-tab portion of #24 but leaves the onboarding / new-project / submit-button-tail items open. Phase 7b live verification on denon82 should now run cleanly through the batch path. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §9 Risk row 4 (editor state drift between prompts) + issue #24. * chore(image): add DOM dump diagnostic for count-tab discovery (#24) Phase 7b iteration 4 (commit 2a6de4d) failed to match Flow's count-tab DOM on the user's Portuguese-locale profile — the position-based [role="tablist"][role="tab"] and digit-leading-button strategies returned None. We can't fix selectors without seeing what Flow actually renders. Adds _dump_count_panel_dom to UiAutomationTransport — a diagnostic helper that page.evaluates a structured snapshot of all role="tab"/tablist/radiogroup/radio elements, buttons-with-leading-digit, and google-symbols ligatures present in the document, then writes to out_dir as JSON. Called inside _set_count immediately after the settings panel is open, before any tab click attempt. Safe-by-default: no-op without out_dir or prompt_idx; failures swallowed. The next Phase 7b run will produce count_panel_dom_prompt_{idx}.json files for each prompt. The DOM evidence informs the final locale-invariant selector design that closes the count-tab portion of #24. Not a fix — diagnostic only. Follow-up commit will use the dump output to rewrite _set_count and _read_displayed_count against the real DOM. * fix(image): thread output_dir to client constructor so transport _out_dir is set The orchestrator's factory call (run_manifest_image_batch) omitted out_dir, so client._out_dir stayed None, transport._out_dir stayed None, and _dump_count_panel_dom returned early at its `if out_dir is None` guard — no DOM evidence was ever captured during Phase 7b runs. One-line fix: pass out_dir=output_dir to the factory call. Now the diagnostic dump in 6181499 actually writes to disk. No production behaviour change for the download path (that uses output_dir directly, not via transport). Follow-up to 6181499 (DOM dump). Spec #24. * fix(image): filter count-tab selectors by 1x/xN text pattern (DOM-evidence-driven, #24) The 2a6de4d structural-first rewrite returned None because `[role="tablist"] [role="tab"]` matched all 11 tabs across all 3 tablists (Mode/Aspect/Count), not just the 4 count ones. The previous text-match rewrite returned "imageImagem" because `[aria-selected="true"]` matched the Mode tab first. DOM dump from Phase 7b on profile denon82 (tmp/dom_dump.json) shows count tabs are uniquely identifiable by their text pattern: 1x / x2 / x3 / x4 (purely locale-invariant — digit + literal "x", no localized words). _count_tabs_locator now filters role="tab" by text matching `^(1x|x[2-4])$`. _read_displayed_count filters [aria-selected="true"] by the same pattern, so it returns the COUNT tab's value, not the Mode or Aspect tab's. _set_count clicks by position within the filtered locator (count - 1). Removes dead _extract_count_digit helper (no longer called from src/; inline re.search used instead). _is_settings_panel_open now delegates to _count_tabs_locator.first.is_visible(). Closes the count-tab portion of #24. Remaining #24 work: ONBOARDING_SELECTORS, NEW_PROJECT_SELECTORS, SUBMIT_BUTTON_SELECTORS tail (not exercised by the t2i/batch path). Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §9 Risk row 4. Follow-up to 6181499 (DOM dump) + a6d702f (out_dir threading). * fix(image): listener cross-contamination — post-submit-time filter + straggler window Phase 7c live e2e on profile denon82 produced 5 files for a 3-prompt manifest but only 2 unique image sizes — prompt N's listener captured stragglers from prompt N-1 that arrived between attach and submit. _await_captured(expected_count=N) returned immediately when len(captured) >= N, so each prompt's await consumed STALE captures instead of waiting for its own response. Adopts the Worker's proven pattern (compile-growth-monorepo/python/workers/ google-flow-worker/flow_images.py _wait_for_n_new_images): each captured entry now stores a `ts = time.monotonic()` at append time; generate_images_batch and _generate_images_locked record `submit_time` right before _send_prompt; and _await_captured filters by `entry["ts"] >= submit_time` before counting toward expected_count. Adds a 2.5s "straggler window" after the threshold is reached for any late same-submission arrivals (also from the Worker pattern). Defense-in-depth early-detach on expected_count was considered but dropped — it requires plumbing expected_count into the listener closure, complicating the per-listener attach surface. The timestamp filter + straggler window are sufficient for correctness; the bakery prompt should now generate its own image. Tests: promoted xfail multi-listener test to a real assertion (attach-time isolation), added test_await_captured_post_submit_time_filter_rejects_stale (direct regression for the contamination scenario: 2 stale + 1 fresh, only 1 returned), and test_await_captured_all_entries_pass_when_submit_time_zero (backwards-compat default). 271 tests pass. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §9 Risk row 1. Phase 7c follow-up to 38d391a (count-tab fix). * refactor(image): serial submission per prompt — Worker pattern (drop fire-and-forget) Live Phase 7 runs on profile denon82 showed cross-contamination across per-prompt listeners: each listener captured stragglers from prior prompts AND its own responses, producing 5 files for a 3-prompt manifest where only 2 image bodies were unique. The fundamental issue: Flow's response payload carries no per-submission identifier, and timing-based filters can't reliably disambiguate overlapping responses (prior-prompt stragglers and current-prompt first response can arrive in the same window). The Worker (compile-growth-monorepo/python/workers/google-flow-worker/flow_images.py _wait_for_n_new_images) avoids this by being strictly SERIAL: submit, await, detach, jitter, submit. The editor still stays mounted across the batch (same-project invariant intact), but only one listener is active at a time, so cross-contamination is structurally impossible. This commit rewrites _generate_images_batch_locked to the serial pattern. Each prompt's lifecycle: _configure_generation_settings -> _attach_batch_response_listener -> _send_prompt -> _await_captured -> detach -> parse -> append result -> jitter (if not last). The pending-tuple/await-loop split is gone. The post-submit-time filter in _await_captured stays as defense-in-depth. The fire-and-forget design intent (spec §4) is preserved as a future optimization once Flow provides per-submission identifiers. Until then, serial is the working pattern. Performance impact: each batch takes roughly N * (gen_time + jitter) instead of max(gen_time) + (N-1) * jitter — slower for large batches but correct. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §9 Risk row 1 (multi-listener concurrency). test(image): replace multi-listener-concurrency test with serial-invariant assertions Drop test_multi_listener_no_cross_contamination (not applicable: serial pattern means only one listener is ever active). Replace with test_no_two_listeners_simultaneously_active which asserts the attach → detach → attach sequence structurally. Update happy-path test to record event_log and assert strict attach_0/detach_0/attach_1/detach_1/attach_2/detach_2 ordering. Rename in-flight-submission regression test to match serial semantics. Also fix pre-existing ruff violations in test_observability_events.py (unused import, two E501 lines) that were already present on this branch. * fix(image): move count-setter diagnostic dumps to out_dir/_diagnostics/ subdir Phase 7e live e2e on profile denon82 produced the correct result (4 unique images, one per prompt, all counts right) but the test's file-count assertion failed because it globbed all *.png in out_dir and picked up 6 diagnostic screenshots (count_before/after_prompt_*.png). The diagnostics shouldn't pollute the user's output_dir at all. Move them to a _diagnostics/ subdirectory created on-demand. Production images (from client.download_image) keep landing at out_dir/<prompt_filename>.png as before. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md (no spec change — internal artefact placement only). * fix(image): select Image mode in editor before submitting ui_automation.py had no equivalent of ui_automation_video.py's _switch_to_video_mode. When the Flow account was last in Video mode, "image" prompts were silently routed to the video endpoint: no batchGenerateImages response observed, and the response listener timed out after 3 minutes. Also explains the historical "first-attempt listener-miss flake" recorded in phase-b-followups memory item #1. Adds UiAutomationTransport._switch_to_image_mode mirroring the video side: opens the 2-step mode dropdown via the shared MODE_SWITCH_TRIGGER_SELECTORS (imported from ui_automation_video), clicks the Image tab via the new IMAGE_TAB_IN_MENU_SELECTORS cascade (aria-controls*='IMAGE' first for locale-stability, then Portuguese "Imagem" / English "Image" / icon-ligature fallbacks), presses Escape to close the menu so _configure_generation_settings can open it fresh, and emits ui_automation.image_mode_entered. Wired into both call sites: generate_images (single t2i) calls it after _dismiss_blocking_overlays and before _configure_generation_settings; _generate_images_batch_locked calls it once per batch in the same orphaned_project_warning try/except wrapper used for overlay dismissal (so a mode-switch failure on a freshly-created project surfaces the project_id for user lookup). Tests: - New tests/api/transports/test_ui_automation_image_mode.py with three unit tests (happy path, trigger missing, image-tab missing) mirroring tests/api/transports/test_ui_automation_video.py's TestSwitchToVideoMode. - Autouse fixture in tests/api/transports/test_ui_automation.py TestGenerateImages and at the module level of tests/api/transports/test_ui_automation_batch.py stubs _switch_to_image_mode for orchestration tests that don't exercise the mode switch directly (mirror of the video side's monkeypatch.setattr(VideoGenerationMixin, "_switch_to_video_mode", AsyncMock())). Live-verified on profile ffroliva: 1 t2i shot (1 JPEG @ 768x1376, 9:16 within 0.79%) and full batch e2e (4 JPEGs at 9:16, 16:9, 16:9, 1:1, shared project_id). User confirmed gallery state. * test(e2e): exclude _diagnostics from image-batch file-count Commit c5c8d4a relocated _set_count diagnostic screenshots into out/_diagnostics/ to avoid polluting the batch output directory. The e2e test's file-cardinality assertion at tests/e2e/test_image_batch_e2e.py:142 uses out.rglob("*") which recurses into the new subdir, so a 3-prompt manifest (count=1+2+1=4 expected) now counted 10 files (4 outputs + 6 diagnostic PNGs in _diagnostics/). Filter the assertion to skip paths containing "_diagnostics" in their parts. Production behaviour unchanged. Live-verified on profile ffroliva (pytest-837, 1 passed in 127s). * docs(image): record post-fix live verification on ffroliva LIVE_VERIFICATION_image_batch.md gains a "Post-mode-switch-fix verification" section recording today's full-pass results on profile ffroliva (1 t2i smoke + 4-image batch e2e, all eight test assertions green, user-confirmed gallery). Retracts an earlier session's "Phase 7e produced 4 unique images" claim that was based on file-size differences without magic-byte verification — inspection of the surviving pytest tmp dirs (pytest-825/826) shows zero prompt PNGs were ever written in those runs; the only PNGs present were count-tab diagnostic screenshots. CHANGELOG.md [Unreleased] § Fixed gains an entry for the image-mode-selection bug, noting it also resolves the historical "first-attempt listener-miss flake" from phase-b-followups memory item #1. Notes the denon82 WAF/reCAPTCHA 403 is unrelated to this fix and remains open as a separate investigation; ffroliva's success today demonstrates WAF can be passed by a sufficiently fresh chrome-strategy profile. * fix(tests): unblock CI — drop stale seed= stub param, disable structlog logger caching Two paired fixes that together restore CI green on Linux: 1. tests/features/test_image_steps.py — remove `seed: int | None` from the `_fake_t2i` stub signature. The real `_run_t2i` dropped the `seed=` kwarg in commit e83c609 (refactor!: remove no-op --seed flag), so the committed stub raised `TypeError: _fake_t2i() missing 1 required keyword-only argument: 'seed'` when the CLI called it without seed=. The error was masked behind the privacy-preserving `observability.emit_unhandled_event` hash filter (message_hash 13fdd58c..., stack_hash bbeacfb5...) which is why earlier debug passes only saw the hash, not the message. Affected: test_t2i_single_image, test_multiimage_fanout. 2. src/gflow_cli/observability.py — switch `cache_logger_on_first_use=True → False`. With caching on, the first structlog.get_logger() call (during module import) freezes the processor chain to the production renderer; per-test `structlog.configure(processors=[LogCapture(), ...])` in fixtures then has no effect because the cached logger ignores the new chain. With caching off, each get_logger() re-resolves the chain, and the test's LogCapture sees image_batch.submission_attempt / image_batch.row_completed events. Affected: test_emits_submission_attempt_per_row, test_emits_row_completed_per_row. Both fixes were present in the working tree at session start but were never committed (likely paused mid-fix by a prior session); the mode-switch work on this branch surfaced the resulting CI red on the multi-image-prompt PR, which is why they're being landed here. Verified locally: pytest -q -m "not e2e and not live" --cov=gflow_cli on tests/features tests/image_batch tests/api/transports → 292 passed, 1 skipped. * docs+test(image): polish — record mode-switch in debugging, surface WAF / #24 in KNOWN_ISSUES, add call-site regression test docs/DEBUGGING.md - Adds `ui_automation.image_mode_entered` and `ui_automation.batch_403_body` to the listener event-keys table. - Rewrites the "no batch_response_seen after prompt_submitted" paragraph to point at editor-mode confusion as the most likely cause (resolved by PR #40); the UI-flake path is now the secondary hypothesis. - Lists the two new debug-screenshot names produced by _switch_to_image_mode failures. KNOWN_ISSUES.md (new Open entries): - `batchGenerateImages` HTTP 403 (WAF / reCAPTCHA `PUBLIC_ERROR_UNUSUAL_ACTIVITY`) — first observed 2026-05-23 on profile `denon82`; not blocked by code, fix is a fresh/cooled profile. - `UiAutomationTransport` selectors still partially localized (issue #24 partial) — count tabs done, ONBOARDING_SELECTORS still unanchored. Cross-linked to issue #24 (commented separately). tests/api/transports/test_ui_automation_image_mode.py (new class): - `TestModeSwitchCallSite` regression-guards the integration between the generate paths and the mode switch. Two tests: 1. `test_generate_images_switches_to_image_mode_before_send_prompt` — asserts call order: enter_editor → dismiss_overlays → switch_to_image_mode → ... → send_prompt. 2. `test_generate_images_batch_switches_to_image_mode_once_before_any_prompt` — asserts exactly one mode-switch per batch and before the first submit. - Uses a sentinel exception (_StopHere) raised from _await_captured to halt the chain after _send_prompt; the test only cares about the preceding call order. Single-shot test uses pytest.raises(_StopHere); batch test catches BatchPartialError (the production wrap when continue_on_error=False). * style(tests): rename _StopHere → _StopHereError (ruff N818) CI lint rule N818 requires exception classes to end with `Error`. Caught only on the Linux ruff run. * refactor: resolve all SonarCloud issues on PR #40 (7 fixes) Closes the 7 open SonarCloud issues raised on the multi-image-prompt PR. Each entry below is annotated with the rule key and the mechanism used to clear it; no behaviour change. S5655 — _drive_images_generation argument type (client.py:679, 757) `dataclasses.replace(req, count=...)` returns `T` via the stdlib's generic protocol — pyright narrows it back to GenerateImageRequest but SonarCloud's analyser does not. Pinning the local with an explicit `req_one: GenerateImageRequest = _dc_replace(...)` annotation gives both analysers the same hint. S1192 — literal duplication (ui_automation.py:1048, 1069, 1119, 1134) `"ui_automation.count_setter_completed"` was repeated four times inside `_set_count` at the four exit-path log lines. Extracted to a module-level `_EVT_COUNT_SETTER_COMPLETED` constant. S1172 — unused parameter (image_batch.py:683) `run_manifest_image_batch` accepted `project_title: str` from the pre-stay-mounted era when the orchestrator created the Flow project itself (see commit f6199db). Today the transport creates the project as part of `_enter_editor`, so the parameter is dead weight. Removed from the signature. Updated all five call sites: cli_image.py, tests/e2e/test_image_batch_e2e.py, tests/image_batch/test_observability_events.py (×2), tests/image_batch/test_image_manifest.py (×3). No behaviour change — the parameter was already ignored end-to-end. S3776 — cognitive complexity refactors - `_set_count` (16 → 15): merged the inner `if displayed is None or displayed == count` into the outer `if clicked` condition. Single combined boolean. Same semantics: success only when the click landed AND read-back digit matches (or read-back returned None — unrecognised locale, position click is deterministic so we trust it). - `parse_tsv_manifest` (31 → ~10): extracted three column parsers `_tsv_parse_count`, `_tsv_parse_aspect`, `_tsv_parse_model`. Each owns the empty→default fallback, validation, and raise-with-line-context for its column. The main loop is now a flat sequence of three helper calls per row. - `_generate_images_batch_locked` (55 → ~6): extracted `_run_one_prompt_in_batch` that owns ONE prompt's full lifecycle (configure → attach listener → submit → await → detach → parse). Returns `(BatchSubmissionResult, fatal_error)`. The outer function is now a flat loop over prompts that appends results, breaks on fail-fast, and sleeps for jitter between iterations. Detach invariant preserved: every code path inside the helper detaches exactly once. Live behaviour unchanged (4 callsites that previously had near-duplicate fail-handling collapsed into one). Quality gates: PYTHONUTF8=1 uv run python scripts/ci/check_repo_hygiene.py ✓ uv run ruff check src tests ✓ uv run ruff format --check src tests ✓ uv run pyright src 0 errors uv run python -m pytest -q -m "not e2e and not live" tests/image_batch tests/api/transports tests/features 294 passed * fix(sonar): use cast() with pyright pragma — annotation form didn't satisfy S5655/S5890 The previous annotation form (`req_one: GenerateImageRequest = _dc_replace(...)`) triggered SonarCloud S5890 ("Assign a value of type GenerateImageRequest instead of Dataclass") plus the original S5655 didn't clear either. Switching back to `cast()` with a per-line `# pyright: ignore[reportUnnecessaryCast]` makes both Sonar and pyright agree: Sonar sees a forced type, pyright sees the silenced "unnecessary" diagnostic. Also ships a previously-uncommitted ruff format pass on test_ui_automation_image_mode.py that was applied locally but never pushed — caught by `ruff format --check` on the Linux runner.
ffroliva
pushed a commit
that referenced
this pull request
May 25, 2026
…estAttachFrameSlotSelection Finding #1 (HIGH): replace is_visible(timeout=3000) with wait_for(state="visible", timeout=3000) in the text-label fallback path of _attach_frame. Playwright's Locator.is_visible() ignores its timeout param ("deprecated and does nothing") — the intended 3-second grace window was silently absent, causing immediate false-negatives on non-English profiles. Finding #2 (MEDIUM): replace bare VideoGenerationMixin._upload_via_open_dialog = AsyncMock() class mutations with monkeypatch.setattr() in both TestAttachFrameSlotSelection tests that needed patching. Bare class assignments are never rolled back, contaminating tests that run later in the session. Finding #3 (LOW): remove the dead `with MagicMock() as mock_upload:` context manager from test_structural_slot_used_when_available. The block provided no cleanup and mock_upload was never used; the real patch was the class assignment inside it, now replaced by monkeypatch.setattr(). Also bundles Finding #4 (structs.count() race): capture count once into a local before the branch so the error f-string reuses it rather than issuing a second async DOM query. All 148 unit tests pass; pyright and ruff clean. https://claude.ai/code/session_017p481rvFbk2i1CNfdsxocD
This was referenced May 26, 2026
5 tasks done
5 tasks done
kittinan
referenced
this pull request
in kittinan/gflow-cli
Jun 6, 2026
Adds FlowApiClient.generate_character_image() (Task 5, issue ffroliva#145): - Routes exclusively through transport.generate_character_images() — never a direct REST POST (scenario #1 guardrail) - Guards transport=None before any call (scenario ffroliva#21: clear error without navigation when session is not set up / headless) - Validates workflows[0].parentEntityId == entity_id before returning; raises WireFormatError on mismatch or absent key (scenario ffroliva#5: never bind a foreign workflow) - Logs character.image_generated at INFO (credit-spending op, no prompt text) - Fixes stale _raise_for_non_retryable docstring: 403 maps to WafRejectionError, not AuthExpiredError Test: tests/api/test_client_generate_character.py (8 tests, LIVE fixture) pyright src: 0 errors; ruff check + format: clean Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
kittinan
referenced
this pull request
in kittinan/gflow-cli
Jun 6, 2026
Adds Phase-2 Task 10 tests for the gflow character create feature (issue ffroliva#145): - tests/features/character_create.feature + test_character_create_steps.py: three BDD scenarios mirroring the character_read harness (scenarios() bind, CliRunner, six-seam monkeypatch infra-bypass). Happy create + foreign-workflow error driven at the CLI entrypoint; resume/no-re-spend (#3/ffroliva#4) driven at the saga layer (real saga, mocked client+recorder) because routing it through the fully-mocked CLI would assert nothing about resume. - tests/services/test_character_gen_no_direct_post.py: scenario #1 guardrail. Runtime spy (real saga + real generate_character_image, _post_json and batch_generate_images_url patched to explode) proves character gen routes through the UI transport, plus a source-scan invariant over the saga and generate_character_image method body. Option B / 403 WAF wall rationale. Phase-2 sweep: 942 passed, 1 skipped (tests/api tests/data tests/services tests/cli tests/features). pyright src: 0 errors. ruff check + format: clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kittinan
referenced
this pull request
in kittinan/gflow-cli
Jun 6, 2026
…e DoD tests) Codify the Character-creation Definition-of-Done as repeatable live e2e tests. Default-OFF (opt-in via GFLOW_CLI_E2E_RUN_CHARACTER=1 + GFLOW_CLI_E2E_PROFILE) so CI / `gflow:check` never spend Imagen credits. - register `e2e_character` cost sub-marker in pyproject.toml + marker-registry invariant (_COST_SUB_MARKERS) - tests/e2e/test_character_create_e2e.py — 3 named live tests mirroring the subprocess + e2e_env pattern of test_data_layer_e2e.py: * test_character_create_binds_parent_entity (#1/ffroliva#5): create --json then show --json read-back; asserts non-empty workflow_ids + thumbnail_media_id bound, project_id matches the existing project (no new project) * test_character_create_partial_saga_recoverable (#3/ffroliva#4): same-name re-run resumes (same entity_id, workflow set does not double, exactly one character-mode op row in the isolated DB) — no double-spend * test_character_personality_utf8 (ffroliva#18): accented name + personality survive create -> show byte-for-byte (PYTHONUTF8=1) Live happy path first verified manually 2026-06-02 (entity 951d2ce4 "Marina Cortes"); these make it repeatable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
11 tasks done
tranzmatt
pushed a commit
to tranzmatt/gflow-cli-unofficial
that referenced
this pull request
Jul 2, 2026
Design spec for the gflow Tools framework: TOML-defined tools, --tool/-t surface (no -e/--expand), creative-director as tool ffroliva#1 with banana-claude alignment (5-component formula, banned-keyword filter, image+video domains), broaden across all generation commands, MCP parity, data recording, docs, packaged as 3 PRs. Companion reference doc captures the 5-lens (owl) evaluation and the deferred S3 dispatch-framework design for future pickup.
tranzmatt
pushed a commit
to tranzmatt/gflow-cli-unofficial
that referenced
this pull request
Jul 2, 2026
…ge guard (ffroliva#219) Co-lands PR ffroliva#219 (Chrome profile + T2I extra-image bugs) with the council's required must-fixes folded in. Changes: - browser_manager: add `_is_playwright_chrome_channel_available()` probing only the exact Google-Chrome paths Playwright hardcodes; `channel_for_profile()` gates `channel="chrome"` on it instead of `is_chrome_available()` (which also accepts Chromium). Fixes the misleading "Chromium distribution 'chrome' is not found at /opt/google/chrome/chrome" on Chromium-only hosts. Linux probe is limited to `/opt/google/chrome/chrome` only (must-fix ffroliva#3 — the over-broad `/usr/bin/google-chrome*` paths would re-trigger the very error fixed here). - ui_automation: `GEN_SETTINGS_BUTTON_SELECTORS` aliased to `MODE_SWITCH_TRIGGER_SELECTORS` (must-fix ffroliva#2 — single source of truth); both require `button[aria-haspopup='menu']`, preventing a mis-click that skipped the count panel and left Flow's default count (≈2) billing extra generations. - client: `generate_image()` logs `client.generate_image_extra_returned` when the transport returns more images than the requested count=1. - CHANGELOG: add [Unreleased] ### Fixed block (must-fix ffroliva#1). - tests: extra-image warning fires on 2 images (+ negative case); `_is_playwright_chrome_channel_available()` Chromium-only → False (must-fix ffroliva#4). Co-Authored-By: C1ph3r404 <C1ph3r404@users.noreply.github.com>
5 tasks done
15990166686ll-crypto
pushed a commit
to 15990166686ll-crypto/gflow-cli
that referenced
this pull request
Aug 20, 2026
HIGH ffroliva#1: wrap setup() body in try/except BaseException so that partial-setup failures (e.g. ctx.new_page raises) call teardown() before re-raising, preventing playwright/context handle leaks. HIGH ffroliva#2: replace bare `TimeoutError` catch on asyncio.wait_for with builtin `TimeoutError` (project target py311; ruff UP041 prefers builtin over asyncio alias). MEDIUM ffroliva#10: replace non-deterministic `__hash__()` seed with stable `int(sha256(name.encode()).hexdigest(), 16) % 2**31`; import hashlib. MEDIUM ffroliva#11: add class-level docstring warning about single-flight profile_dir exclusivity (spec § 5.4.4). MEDIUM ffroliva#12: extract _is_retry out of the public generate_images() signature into private _generate_images_inner(); public method matches Protocol exactly. LOW (bonus): remove dead `if TYPE_CHECKING: pass` block. Tests: 10 → 12 (added T8 partial-setup leak guard, T9 deterministic seed). pyright: 0 errors. ruff: 0 warnings.
15990166686ll-crypto
pushed a commit
to 15990166686ll-crypto/gflow-cli
that referenced
this pull request
Aug 24, 2026
HIGH ffroliva#1: wrap setup() body in try/except BaseException so that partial-setup failures (e.g. ctx.new_page raises) call teardown() before re-raising, preventing playwright/context handle leaks. HIGH ffroliva#2: replace bare `TimeoutError` catch on asyncio.wait_for with builtin `TimeoutError` (project target py311; ruff UP041 prefers builtin over asyncio alias). MEDIUM ffroliva#10: replace non-deterministic `__hash__()` seed with stable `int(sha256(name.encode()).hexdigest(), 16) % 2**31`; import hashlib. MEDIUM ffroliva#11: add class-level docstring warning about single-flight profile_dir exclusivity (spec § 5.4.4). MEDIUM ffroliva#12: extract _is_retry out of the public generate_images() signature into private _generate_images_inner(); public method matches Protocol exactly. LOW (bonus): remove dead `if TYPE_CHECKING: pass` block. Tests: 10 → 12 (added T8 partial-setup leak guard, T9 deterministic seed). pyright: 0 errors. ruff: 0 warnings.
15990166686ll-crypto
pushed a commit
to 15990166686ll-crypto/gflow-cli
that referenced
this pull request
Aug 24, 2026
…froliva#40) * docs(spec): plan for multi-image-prompt e2e + jitter investigation Design doc for landing PR #35 (issue #14) as a production-ready, e2e-tested feature on a properly-named feature/multi-image-prompt branch. Captures strategy A (close + reopen PR), atomic commit split, parameterized live e2e, and the systematic jitter-necessity investigation (R0/R1/R2 matrix on profile ui_automation). Will be consumed by the writing-plans skill to produce the implementation plan. * docs(spec): harden multi-image-prompt design from council review Four-reviewer parallel review (Python craft / SOLID, e2e test design, methodology, project conventions) returned high-signal findings. Integrated as v2 of the design doc. Key changes: - Dropped shim-removal from in-scope (verified: would force a public API change to generate_image; net-zero readability). D8. - Deferred cosmetic _drive_image[s]_generation rename to follow-up. D9. - Aligned e2e env vars to canonical GFLOW_CLI_E2E_* prefix. D12. - Switched jitter override mechanism to dependency injection via the existing run_manifest_image_batch(jitter_range=...) parameter (no monkeypatch, no production-code env-var branch). D11. - Replaced ad-hoc OUT_DIR timestamping with pytest tmp_path. D10. - Restructured jitter matrix: R1/R2/R3 (added same_project=0 + jitter=0 cell to isolate the IV from same-project rapid-fire). D14. - N=3 per cell across 2 sessions with conservative-default-on- inconclusive (keep jitter). D13/D14. - Operationally pinned the pass/fail definition for UI transport (no HTTP 429 to read; uses listener-miss + overlay + timeout signals). - Added application-layer structlog events for post-drop regression detection (image_batch.submission_attempt/result/row_completed/ inter_submission_latency_ms). D4. - Added stale-test grep AC + pre-commit AC + INDEX-reference AC (AC13/14/15). - Mandated origin/claude/plan-next-issue-Stegy deletion + PR body "Supersedes #35" prefix. D7. - Tightened tree-replay diff scope to image-batch paths only. - Added malformed-row negative fixture (unit-only, never live). - Bumped budget estimate to ~72 generations worst case. * docs(spec): v3 — full --seed cleanup + dead seed/batch_id removal Code verification revealed --seed has been a no-op user-facing feature since v0.7.0: the user's seed is discarded by the `_ = seed, batch_id` shim in _drive_image_generation before reaching any transport. The active UI transport doesn't honour seeds at all. User authorised Option A (full removal) over Option B (plumb through the request DTO for transports nobody uses). Spec changes: - §1 problem statement adds the --seed dishonesty. - §2 goal adds "honest API surface." - §3 in-scope: detailed cascade — CLI flag, public generate_image, private _drive_image_generation kwargs, generate_images_batch seeds= param, 9 test call sites, USAGE.md mentions. - §3 out-of-scope: replaced "shim deferral" with "seed-controlled reproducibility is a future Flow-side concern." - §5 commit split: #1 (count selector) and #1b (refactor — remove --seed) are now separate commits. #1 stays cherry-pickable to release/* independently. - §9 CHANGELOG: ### Removed entries for --seed flag and for the public-API kwargs removal (TypeError on caller misuse). - §10 AC16: explicit grep-based completeness criteria for the seed cleanup (cli_image, api/client, tests, --help output). - §11 risk register: 4 new rows for breaking-change-with-no-real- regression, stale CLI tests, and library caller TypeError. - §12 D8: flipped from "preserve shim" to "delete completely." - §12 D2: commit count updated to 7 (1, 1b, 2, 3, 4, 5a, 5b). Body builder seed/batch_id is preserved — wire-format level, used internally by the experimental HTTP transports which mint locally and do not propagate user-supplied seeds anyway. * docs(plan): implementation plan for multi-image-prompt feature Bite-sized, TDD-flavoured task breakdown of the v3 design spec into 7 phases (bootstrap, count-selector fix #1, --seed removal #1b, image batch + observability #2, live e2e #3, docs #4, jitter matrix + verdict #5a/#5b, PR cycle). Each task includes: - exact file paths and line markers - complete code snippets (no placeholders, no "TODO") - exact PowerShell commands with expected output - scoped /gflow:check after each commit - conventional-commit message templates Self-review table at the end maps spec §10 acceptance criteria 1-16 to satisfying tasks; all covered. Saved to docs/superpowers/plans/ per writing-plans skill convention. * docs(plan): v2 — apply all 25 council findings Four-reviewer council audit returned 6 BLOCKERs + 12 MAJORs + 7 lower-priority items. All addressed: BLOCKERs fixed: - Drop src/gflow_cli/api/image.py from Task 0.2 path list (verified: not in PR #35 file list). - read_manifest_file -> parse_manifest_file plan-wide (verified: actual function at image_batch.py:545). - Task 4.1 e2e uses e2e_profile_dir pytest fixture parameter (verified at tests/e2e/conftest.py:25), not a function call. - Task 4.1 Step 0 adds `uv add --dev pillow` prerequisite. - Task 1.1 Step 2 promotes "edit directly" as primary path — git add -p cannot separate count-selector from --seed hunks. - Task 7.1 step 5: removed `git rebase --exec --reset-author` (violated spec §6.B / D6). Replaced with hard-reset + redo Phase 0 if author leakage detected. MAJORs fixed: - Task 7.2: gh pr create via --body-file (avoids PowerShell here-string newline/backtick mangling). - Task 7.1 step 3: AC16 grep tightened with ^\s*# exclusion. - Task 4.2: smoke-run dropped (was burning credits before commit); first credit-spending run is now R3 rep 1 in Phase 6. - Task 2.4: commit message uses `refactor(image)!:` with proper BREAKING CHANGE footer. - New Task 7.0: rebase on develop before push (matrix takes ≥2h, develop may move). - New Phase 8: branch deletion deferred to post-merge. - New Task 2.0: TDD red tests for #1b (inspect.signature + CliRunner --seed UsageError) — 5 tests pinning the cleanup. - Task 3.2 step 3: project_id field added to submission_attempt event; test assertion updated. - Task 3.2 step 1: log_capture fixture uses yield + reset_defaults on teardown. - Task 4.1: WebP magic bytes accepted; aspect tolerance ±2%. - Task 6.2 Step 2.5: abort gate if R1 session-1 fails non-listener-miss. Lower-priority addressed: - AsyncMock __aenter__/__aexit__ wiring on fake_client_factory. - _invalid fixture guard in e2e _resolve_manifest_path. - try/finally last_events.json dump on e2e failure. - Per-rep manifest variants tmp/sample_batch_repN.tsv to defeat cache. - import hashlib/time moved to image_batch.py module top. - Stale-test grep duplicated after Task 2.4 (not only at end). - Malformed-row test parametrized over 3 rows. Council compliance summary table appended to Self-review section. * docs(plan): orchestration plan for multi-model execution Coordinator-led orchestration modeled on the prior canonical pattern in 2026-05-14-shell-multi-prompt/. Defines: - Roles + model assignments: - Coordinator: Claude Opus 4.7 (this session) - Implementer A: Claude Sonnet/Opus via Agent tool (judgment work) - Implementer B: Codex CLI v0.131.0 via `codex exec` (mechanical pattern work — Phase 2 --seed deletion, Phase 3 TDD events, Phase 5 doc templating) - Implementer C: Gemini 2.5 Pro via `gemini -p` (long-context Phase 4 e2e drafting + cross-model spec-conformance review) - Reviewers: codex review / gemini -p / Claude subagent - Task matrix mapping all 33 plan tasks to a wave + implementer + reviewer + live-credit flag + dependencies. - 7 parallel execution waves with explicit serial/parallel boundaries. - Per-task dispatch protocol with concrete PowerShell + prompt templates for codex exec, gemini -p, and Agent tool. - Three council gates: G1 (pre-#1b BREAKING), G2 (Phase 6 verdict), G3 (pre-PR final). Each runs 3 reviewers in parallel; Coordinator consolidates. - Quality gates between phases (static checks + stale-grep + reviewer verdict + state log entry). - 8 escalation rules (regression in --help, BREAKING outside #1b, matrix abort, reviewer disagreement, credit-cap, pyright drift, author leak, gflow:check regression). - State tracking via tmp/orchestration-state.md (gitignored) and per-dispatch artefacts under tmp/dispatches/. - DoD aligned with spec §10 AC1-16 plus optional receipts archive. - Model selection rationale + quality-first priority justification. Companion: also patches the impl plan's Task 0.1 step 3 to use `git checkout feature/multi-image-prompt` (the branch already exists on this branch as of the planning commits). * docs(plan): harden orchestration plan against Gemini rate-limit Probed CLI model availability before pushing: - Codex CLI 0.131.0: GPT-5 Codex default; headless `codex exec` works. - Gemini CLI 0.42.0: OAuth-personal tier rate-limited on cloudcode-pa.googleapis.com: - `gemini-2.5-pro` → 429 (intermittent capacity) - `gemini-3-pro` / `gemini-3-flash` → 404 (not exposed to this account) - Auto-fallback to `gemini-3.1-flash-lite` is also 429-prone Plan changes: - §0: new "Model availability — verified 2026-05-21" table with the probe results, the OAuth-personal tier note, and the optional user mitigation (paid API key). - §4.7: new Fallback policy — every Gemini dispatch has a retry-once + Claude-Opus-subagent fallback path. Quota-conscious dispatch order: Gemini first in a wave, Codex/Claude in parallel as backup. - §5 G1/G2/G3 councils: documented graceful degradation when Gemini is unavailable (skip + note in consolidation report). - §12: setup confirmations table replaces open-questions section; Wave 6 ~72-generation budget confirmed approved. * fix(image): use native xN count selector for -n N (#14 part 1) `generate_images_batch` now makes one transport call with `count` baked into the request, instead of fan-out via `asyncio.gather`. The UI transport clicks the matching x{N} tab so Flow produces N images in one submission round-trip. Simpler, faster, matches user intent. Extracted `_drive_images_generation` (plural, returns list) so both the single-image and multi-image paths share one token-mint + transport-call site. The singular `_drive_image_generation` is now a thin delegator that **forces `count=1`** on the request before delegating (via `_dc_replace(req, count=1)`) — guards against callers passing a `GenerateImageRequest(count>1)` to the single-image API and getting images silently discarded. Preserved during the tree-replay: - `download_video` method on `FlowApiClient` (added by PR #36 video download work — untouched by this commit's count-selector scope). - `generate_images_batch.seeds` parameter — currently a no-op under the active UI transport (which ignores per-shot seeds). Length validation preserved so callers passing mismatched `seeds` still get a loud failure. The `seed` / `batch_id` surface across `generate_image` and `_drive_image_generation` is also preserved for commit #1b to remove cleanly. Tests carry forward PR #35's single-call assertions and add two back-compat pins (`test_batch_seeds_back_compat_accepted_and_ignored` + parametrized `test_batch_seeds_length_mismatch_raises`). All 37 scoped tests pass. Codex code review at this SHA returned MAJOR-REVISION; both findings addressed in this amended commit: - restored `download_video` (BLOCKER: tree-replay from f9918d0 predated PR #36 and wiped the method). - forced `count=1` in singular delegator (MAJOR: silent-discard bug). - updated the `_ = seed, batch_id` rationale comment so it no longer mistakenly claims the values "pass through to HTTP transports". Independence: this commit has no dependency on commit #1b (seed removal) or commit #2 (`gflow image batch` subcommand), so it is independently cherry-pickable to a `release/*` branch. Refs: design spec §5 commit #1, plan §Task 1.1. Closes #14 part 1. * refactor(image)!: remove no-op --seed flag and dead seed/batch_id params Empirically verified dead under the active UI transport since v0.7.0. The user-supplied seed was discarded by the `_ = seed, batch_id` shim in `_drive_image_generation` before reaching any transport. The active UI transport clicks buttons and never reads the seed; the experimental HTTP transports mint locally and never received the user's seed either. Cleanup landed in this commit: CLI (src/gflow_cli/cli_image.py): - `--seed` option deleted from `gflow image t2i` and `gflow image i2i`. - Help-text examples and cross-flag UsageError validations removed. - `seed=seed` kwarg dropped from the two `client.generate_image` call sites. Public client (src/gflow_cli/api/client.py): - `seed=` / `batch_id=` removed from `generate_image` signature. - `seeds=` removed from `generate_images_batch` signature. - `seed`/`batch_id` kwargs + `_ = seed, batch_id` shim removed from the private `_drive_image_generation` (now a clean count=1 delegator). - The `secrets.randbelow(2**31)` and `_new_batch_id()` fallbacks inside `generate_image` deleted (no caller path needs them). - `_new_batch_id()` helper deleted — no remaining callers in src/. - `import secrets`, `from collections.abc import Sequence`, and `import uuid` removed (no remaining references). Tests: - 5 RED tests authored by Task 2.0 turn GREEN: * test_generate_image_has_no_seed_kwarg * test_generate_images_batch_has_no_seeds_kwarg * test_drive_image_generation_private_has_no_seed_kwarg * test_t2i_rejects_seed_flag * test_i2i_rejects_seed_flag - Two stale CLI tests deleted (testing now-removed cross-flag validations that were passing accidentally on Click's "no such option: --seed" message): * tests/cli/test_cli_image.py::test_t2i_seed_with_n_gt_1_errors * tests/cli/test_t2i_multi_prompt.py::test_t2i_rejects_seed_in_multi_prompt_before_profile_resolution - All `seed=N` kwargs removed from `client.generate_image(...)` call sites in test_client_image.py. - Back-compat tests added in commit #1 deleted along with the `seeds=` parameter they pinned. User-facing docs aligned with the code in this commit (per the project rule: every commit affecting user-facing behaviour updates docs in the same commit): - docs/USAGE.md: removed `--seed` flag entries from `gflow image t2i` and `gflow image i2i` reference tables, the "Seed-requires-count==1 invariant" paragraph, and the "Reproducible single shot" example. - README.md: removed `--seed` from both `gflow image t2i` flag-list references. - docs/USER_GUIDE.md unchanged — its `--seed` references are in the `gflow video t2v` example (out of scope for this PR per spec §3). - docs/USAGE.md retains `--seed` entries for `gflow video t2v` and `gflow video i2v` (also out of scope). The wire-format body builder `src/gflow_cli/api/image.py::_build_batch_generate_images_body(seed, batch_id, ...)` is UNCHANGED — those parameters live at the wire-protocol layer used by the experimental HTTP transports' internal mint. The `GeneratedImage` and `GenerateImageRequest` / `GenerateVideoRequest` DTOs retain their `seed` fields (they record the seed Flow used; not a user-controlled input). Council G1 verdicts (3-reviewer council on this commit's pre-amend SHA 3108c55): - Codex code-quality: MAJOR-REVISION (docs still advertised --seed; addressed in this amend). - Claude TDD/security: APPROVE. - Gemini spec-conformance: UNAVAILABLE (gemini-2.5-pro rate-limited with status 429 across 6 retries; reduced to 2 reviewers per orchestration plan §4.7 fallback policy). Refs: design spec §1, §3, §5 commit #1b, §10 AC16, §12 D8. BREAKING CHANGE: --seed flag removed from `gflow image t2i` and `gflow image i2i`. `FlowApiClient.generate_image` no longer accepts `seed=` or `batch_id=` kwargs; `FlowApiClient.generate_images_batch` no longer accepts `seeds=`. Callers passing these will get a TypeError (library) or "no such option" (CLI). If reproducibility via user-controlled seed becomes possible again — either through Flow UI exposing a seed control or via HTTP transport revival — the surface will be re-introduced at that layer. * feat(image): add `gflow image batch` subcommand with --same-project (#14 part 2) - Accepts JSON or TSV manifests (dispatched by file extension). - TSV: prompt[<TAB>count[<TAB>aspect_ratio[<TAB>model]]] — optional columns fall back to CLI defaults. - JSON: [{"text": "...", "count": 2, "aspect_ratio": "16:9", "model": "nano2"}, ...]. - MAX_BATCH_PROMPTS = 5 constant in image_batch.py. - --same-project flag: all prompts share one Flow project with a 3-7s random jitter between submissions (anti-bot-detection, will be empirically verified in commit #5b). Default (no flag): each prompt creates its own project. - New structlog events for throttling-regression debugging: - image_batch.submission_attempt - image_batch.submission_result - image_batch.row_completed - image_batch.inter_submission_latency_ms Useful post-merge if a user reports Flow throttling. Sample fixtures under test_assets/: - sample_batch.tsv (happy path; 3 rows / 4 images) - sample_batch.json (same, JSON form) - sample_batch_invalid.tsv (malformed; unit-test only) Tests: 35+ carried-forward unit tests from PR #35 (parsing, dispatcher, runner), 4 new observability-event tests, 4 new malformed-fixture tests (parametrized + dispatcher). Closes #14 part 2. * test(e2e): live image batch e2e parameterized by same-project + DI jitter Spec §7. Mirrors tests/e2e/test_video_t2v_e2e.py conventions: - pytestmark = pytest.mark.e2e - Skipped unless GFLOW_CLI_E2E_PROFILE is set (canonical e2e gate) - Env-var parameterization for manifest, same-project, jitter - pytest tmp_path for output dir (auto-cleanup, no Windows `:` issues) - e2e_profile_dir pytest fixture (from tests/e2e/conftest.py:25) Assertions: - Exit code 0 (via outcome status) - File cardinality matches sum of row counts - PNG / JPEG / WebP magic bytes (fail loud on anything else) - Pillow dimensions match aspect ratio +/- 2% - ui_automation.batch_response_seen count == manifest row count - image_batch.row_completed count == total image count - image_batch.submission_attempt event present per row - --same-project=1: single project ID across rows - --same-project=0: distinct project ID per row (from submission_result events) Jitter override uses DI (jitter_range=(0.0, 0.0) via the existing run_manifest_image_batch parameter), not monkeypatch and not production-code env-var branch. _resolve_manifest_path refuses any _invalid fixture defensively so a misconfigured env never burns Flow credits on a malformed manifest. try/except around run_manifest_image_batch dumps the last 10 observability events to tmp_path/last_events.json on failure, per spec §11 risk-register "partial e2e cleanup" mitigation. Adds Pillow as dev dependency for the aspect dimension assertion. This commit does NOT run the e2e live. The first credit-spending run happens in Phase 6 (jitter matrix) as R3 cell, rep 1. * docs: `gflow image batch` in USAGE, CHANGELOG, INDEX - docs/USAGE.md: new "Batch image generation" subsection with TSV + JSON formats, `--same-project` flag (anti-detection jitter, live-verified outcome pending Phase 6 matrix), MAX_BATCH_PROMPTS=5, exit codes, the four observability event schemas, and pointers to `test_assets/sample_batch.{tsv,json}` for copy-paste. - CHANGELOG.md [Unreleased]: - ### Added — `gflow image batch` subcommand and the four `image_batch.*` structlog events for throttling-regression debugging. - ### Fixed — native xN count selector for `gflow image t2i -n N`. - ### Removed (BREAKING) — `--seed` flag from `gflow image t2i` and `i2i`; public-API kwargs `seed=` / `batch_id=` on FlowApiClient.generate_image and `seeds=` on generate_images_batch. Callers get TypeError / "no such option". - docs/INDEX.md: row linking to docs/LIVE_VERIFICATION_image_batch.md (the evidence file lands in commit #5a after the Phase 6 matrix). The jitter rationale paragraph in USAGE.md currently reads "live-verified" with the verdict pending the Phase 6 matrix run. If the matrix verdict is DROP (jitter unnecessary), commit #5b will refine that paragraph; if KEEP, no further docs update is needed. * docs(image): jitter live-verification skeleton — matrix pending Empty skeleton for the Phase 6 jitter matrix evidence file. Contains: - The full 3-cell × 2-session × N=3 matrix structure - Pass/fail criteria from spec §8 (exit code, listener events, overlay events, row timeout) - Decision rule (drop / keep / inconclusive-keep) with conservative default - Reproduce block with per-rep manifest variants to defeat Flow caching - Pre-flight + session-1 + session-2 commands - Empty matrix table and "Tested / Correlation IDs / Outputs / Aborted runs / Post-#5b verification" sections to be filled by the matrix run Pre-commits docs/INDEX.md's link to this file (added in commit `8f11b39` Wave 5 docs) so the cross-reference is satisfied. Matrix runs are user-driven and credit-spending (~72 generations worst case, approved). Per orchestration plan §1 "live-credit-spending phases stay on Claude/user pair", the actual matrix runs and the verdict-driven #5b commit happen in a later session — see tmp/orchestration-state.md (gitignored) for the resumption protocol. * chore: ignore worktrees directory * docs: add PR #38 review design, implementation plan, and monitor script * docs(plan,spec): record v3 plan + v4 spec amendments and stay-mounted session design v4 of the multi-image-prompt spec adds a revision banner superseding §8 jitter matrix and --same-project surface after mid-matrix discovery (2026-05-22) that ui_automation.generate_images discards project_id. v3 of the plan adds a matching banner with a seven-commit chain (v3-1..v3-7). New stay-mounted-batch-session design + plan land alongside. Spec is 5-reviewer council hardened and user-approved. * test(image): relax batch_response_seen assertion to lower bound Playwright's page.on("response", ...) listener fires for every matching HTTP response from Flow (initial submit, progress polls, final result), so the floor is one response per generated image — not one response per row. Equality breaks when a manifest row has count>1 or when same-project mode multiplexes events. Lower-bound assertion preserves the regression signal while tolerating Flow's actual response shape. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §8.3. * docs(image): retract jitter-matrix verdict — same-project transport defect The 2026-05-22 matrix run on profile denon82 invalidated its own premise: the ui_automation transport explicitly discards the project_id it's given, so every prompt of --same-project=1 lands in its own Flow project. The matrix was measuring rapid-fire across separate projects, not rapid-fire within one project — the question it was scoped to answer cannot be reached from the data collected. Updates the evidence file to record the two R1 runs (one over-strict assertion failure, one missing-file failure), retract the premature KEEP verdict, and point at the v3 plan for the actual fix path. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §1. * refactor(image)!: stay-mounted editor session in ui_automation transport Fixes the --same-project=1 no-op defect: ui_automation.generate_images discarded the project_id it was given ("accepted for Protocol parity; UI creates its own project"), so every prompt of a batch landed in its own Flow project. The stay-mounted refactor opens the editor once per batch, submits N prompts with jitter between, captures each prompt's responses via a per-prompt listener (now returning a detach handle), awaits in submission order with cleanup invariant on listener detach, and parses images into per-prompt BatchSubmissionResult records. BREAKING: BatchOutcome's downstream-error string format may change for failed prompts (now derives from BatchSubmissionResult.error which is a typed GFlowError). The public list[BatchOutcome] return type and the image_batch.submission_attempt / row_completed structlog events are preserved. The --same-project flag is dropped in the follow-up commit v3-4; this commit makes always-same-project the only behaviour at the transport+orchestrator layer. New types: BatchSubmissionResult (api.dto), BatchPartialError + BatchIntegrityError (errors). Extended: _attach_batch_response_listener returns (captured, detach_fn). Extended: _send_prompt clears the editor field (Ctrl+A + Delete) before typing so consecutive prompts in a stay-mounted editor do not concatenate. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md v3 (5-reviewer council hardened). Plan: docs/superpowers/plans/2026-05-22-stay-mounted-batch-session-plan.md. * test(image): fill Phase 3 spec gaps — submission_attempt project_id, missing tests, pyright nits - image_batch.submission_attempt event now emits the real project_id from results[idx], not the "<pending-transport>" placeholder. Event emission moves to after the transport returns so the project_id is real (spec §5.4). - Finding 2 (missing latency/submission_result events) verified non-issue: inter_submission_latency_ms and submission_result were present in the pre-Phase-3 orchestrator and are preserved in the Phase 3 refactor. - tests/api/transports/test_ui_automation_batch.py adds 4 spec-§8.1 unit tests: settings-fail-after-attach detach-before-continue, _await_captured partial-list timeout, batch-setup failure propagation, project_id-identical assertion. - Fixes mock signature in test_settings_fail_after_attach: cfg mock used keyword-only params (*, aspect, model, count) but real _configure_generation_settings takes positional args (page, aspect_cli, count) — caused TypeError on all calls. Corrected to positional signature; asserts updated to match configure-before-attach transport order (listener not attached when configure raises, noop-detach fires). - Adds type: ignore[arg-type] on _FakePage call sites (lines 58, 76, 119, 123) where the duck-typed mock fails pyright's strict Page protocol check. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §5.4 + §8.1. Follow-up to 2062eee (Phase 3). * refactor(image)!: drop --same-project flag from gflow image batch BREAKING CLI. The flag had two states: --same-project=1 (intended to put all prompts in one Flow project; was a no-op at the transport layer until the Phase 3 refactor) and --same-project=0 (each prompt in its own project; equivalent to looping gflow image t2i externally). Per user design intent (memory batch-submission-cadence), batch = always one project; for different-project results, loop t2i instead. Removes the Click option, the same_project parameter, and any test invocations that pass it. Updates --help text to describe always-same-project semantics and the submission-cadence (anti-bot) jitter rationale. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §5.5. * test(e2e): update image batch e2e for always-same-project semantics Drops GFLOW_CLI_E2E_BATCH_SAME_PROJECT env var (no longer a knob) and the same_project=same_project argument that no longer matches the orchestrator signature. Adds the shared-project-id assertion that is the structural verification of the v3-3 fix: all image_batch.submission_attempt events must share one project_id. Keeps GFLOW_CLI_E2E_BATCH_JITTER — that env var lets tests opt into zero-jitter for faster/cheaper runs without affecting production defaults. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §8.3. * docs(image): always-same-project semantics + jitter rationale USAGE.md rewrites the gflow image batch section to describe always-same- project as the only mode and the submission-cadence (anti-bot) rationale for the 3-7s jitter. CHANGELOG.md [Unreleased] records the fix, the removed flag, the new exception types (BatchPartialError, BatchIntegrityError), and the new structlog event (ui_automation.orphaned_project_warning). The run_manifest_image_batch docstring is updated to match. INDEX.md cleaned of --same-project references where present. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §3 + §5.5. * fix(image): batch-transport race in await loop short-circuit The await-phase short-circuit at line 1181 used `not captured and submit_error is None` to detect "this entry was recorded from the continue-on-error send-fail path". It also matched successfully-submitted prompts whose responses had not yet landed in the listener's captured list — a race between the synchronous submit loop completing and Flow's async responses arriving. With continue_on_error=False, the resulting fail outcomes never set submit_error, so the post-loop fail-fast raise was bypassed and the method silently returned all-fail outcomes. Switches the sentinel to `detach is _noop_detach` — send-fail-recorded entries always carry the noop detach by construction (line 1149), and successful submissions carry the real detach callable returned by _attach_batch_response_listener. The new condition is timing-independent. Also moves _noop_detach out of the per-prompt for-loop body to just before it, so pyright can prove the name is always bound when the await loop references it (reportPossiblyUnboundVariable). Adds tests/api/transports/test_ui_automation_batch.py:: test_await_loop_does_not_short_circuit_on_in_flight_submission that reproduces the race scenario by mocking an in-flight prompt with empty initial captured and asserts _await_captured is invoked rather than short-circuited. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §5.6 (listener detach + per-prompt await invariant). Discovered during Phase 7 live verification 2026-05-22. * fix(image): count selector sticks across stay-mounted prompts The xN selector in Flow's editor persists between submissions in the stay-mounted batch flow. If prompt N sets count=2 and prompt N+1 wants count=1, the prior x2 selection wasn't reset and prompt N+1 generated 2 images instead of 1 — discovered live in Phase 7 (pytest-793). _configure_generation_settings now explicitly clicks x1 before clicking the target xN (Pattern A: force-reset), matching the CG Worker's proven pattern in flow_logic._quantity_option_texts. Both label variants are tried for each step (x{N} and {N}x) so older/localised Flow builds that render "1x" instead of "x1" are handled — also mirrors the Worker. The old _COUNT_TAB dict is replaced by _count_tab_candidates(n) which returns both variants, and _SUPPORTED_COUNTS replaces the dict key-check. The new _set_count() static method encapsulates the two-step reset logic and is called from _configure_generation_settings for both the single-prompt and stay-mounted batch paths. Unit test added: two-prompt sequence with counts 2 → 1 asserts the x1 click fires for the second prompt regardless of the first prompt's state. Three additional unit tests cover the x1-only case, the {N}x fallback, and the basic x1-then-x2 sequence. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §9 Risk row 4 (editor state drift between prompts). Discovered during Phase 7 live verification 2026-05-22. * fix(image): count-setter read-back verify + retry for stay-mounted editor The 2026-05-22 first count fix (bd2eb02) used unconditional clicks (Pattern A — click x1, then xN). Live e2e showed it didn't actually update Flow's UI state for stay-mounted prompts: row 2 with count=1 still produced 2 images after row 1 set count=2. This commit rewrites _set_count to a read-back-verify-with-retry pattern: open the settings panel only if not already visible, read the currently-displayed count, click the desired tab (trying both x{N} and {N}x variants), then read back and retry up to 3 times. Raises a clear RuntimeError if the UI doesn't converge after 3 tries. Adds four structlog events for diagnosability: ui_automation.count_setter_entered, ui_automation.count_click_attempted, ui_automation.count_click_result, ui_automation.count_setter_completed. Adds per-prompt before/after screenshots saved to out_dir so future drift can be diagnosed without re-instrumenting. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §9 Risk row 4 (editor state drift between prompts). Follow-up to bd2eb02. * docs(image): Phase 7 partial pass — race fix verified, count-tab selector blocked by #24 Records the live verification effort on profile denon82 (2026-05-22). Race-condition fix (23431e1) is verified: stay-mounted batch flow no longer returns all-fail outcomes from an empty-captured short-circuit; submissions wait for responses correctly; the batch shares one Flow project across all prompts as designed. The follow-up count-selector work (bd2eb02, 96ea7e2) ran into a pre-existing locale issue: the user's browser renders Flow in Portuguese, so count tabs read as "1 imagem"/"2 imagens" rather than the x1/x2 English patterns the code matches. Tracked by issue #24 (open since 2026-05-19, "Make UiAutomationTransport selectors language-agnostic (structural-first)"). A Phase 7b rerun against #24's fix will close the loop. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §9 Risk row 4 (editor state drift — partial coverage), spec §8.3 verification deferred behind #24. * fix(image): locale-invariant count tab — position-first selectors (#24 partial) Replaces the x{N}/{N}x text matching in _set_count and _read_displayed_count with a position-in-tablist structural selector and aria-selected="true" read-back. Falls back to leading-digit regex extraction so any locale's displayed text ("1", "x1", "1 imagem", "1 image") parses to the same count value, or returns None gracefully when no digit is found (deterministic position-based click still proceeds). Live evidence: Phase 7 retry 3 on profile denon82 (Portuguese locale) showed _read_displayed_count returning "imageImagem" — neither x1 nor 1x. The icon ligature + localized label rendering meant the existing text-match retry exhausted on the very first prompt. This change makes the path locale-agnostic per #24's structural-first pattern. Remaining work on #24 (NOT in this commit, tracked in the issue): - ONBOARDING_SELECTORS — purely localized text, no structural anchor - NEW_PROJECT_SELECTORS — localized fallbacks - SUBMIT_BUTTON_SELECTORS tail — localized fallbacks This commit closes the count-tab portion of #24 but leaves the onboarding / new-project / submit-button-tail items open. Phase 7b live verification on denon82 should now run cleanly through the batch path. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §9 Risk row 4 (editor state drift between prompts) + issue #24. * chore(image): add DOM dump diagnostic for count-tab discovery (#24) Phase 7b iteration 4 (commit 736cd35) failed to match Flow's count-tab DOM on the user's Portuguese-locale profile — the position-based [role="tablist"][role="tab"] and digit-leading-button strategies returned None. We can't fix selectors without seeing what Flow actually renders. Adds _dump_count_panel_dom to UiAutomationTransport — a diagnostic helper that page.evaluates a structured snapshot of all role="tab"/tablist/radiogroup/radio elements, buttons-with-leading-digit, and google-symbols ligatures present in the document, then writes to out_dir as JSON. Called inside _set_count immediately after the settings panel is open, before any tab click attempt. Safe-by-default: no-op without out_dir or prompt_idx; failures swallowed. The next Phase 7b run will produce count_panel_dom_prompt_{idx}.json files for each prompt. The DOM evidence informs the final locale-invariant selector design that closes the count-tab portion of #24. Not a fix — diagnostic only. Follow-up commit will use the dump output to rewrite _set_count and _read_displayed_count against the real DOM. * fix(image): thread output_dir to client constructor so transport _out_dir is set The orchestrator's factory call (run_manifest_image_batch) omitted out_dir, so client._out_dir stayed None, transport._out_dir stayed None, and _dump_count_panel_dom returned early at its `if out_dir is None` guard — no DOM evidence was ever captured during Phase 7b runs. One-line fix: pass out_dir=output_dir to the factory call. Now the diagnostic dump in fc058ac actually writes to disk. No production behaviour change for the download path (that uses output_dir directly, not via transport). Follow-up to fc058ac (DOM dump). Spec #24. * fix(image): filter count-tab selectors by 1x/xN text pattern (DOM-evidence-driven, #24) The 736cd35 structural-first rewrite returned None because `[role="tablist"] [role="tab"]` matched all 11 tabs across all 3 tablists (Mode/Aspect/Count), not just the 4 count ones. The previous text-match rewrite returned "imageImagem" because `[aria-selected="true"]` matched the Mode tab first. DOM dump from Phase 7b on profile denon82 (tmp/dom_dump.json) shows count tabs are uniquely identifiable by their text pattern: 1x / x2 / x3 / x4 (purely locale-invariant — digit + literal "x", no localized words). _count_tabs_locator now filters role="tab" by text matching `^(1x|x[2-4])$`. _read_displayed_count filters [aria-selected="true"] by the same pattern, so it returns the COUNT tab's value, not the Mode or Aspect tab's. _set_count clicks by position within the filtered locator (count - 1). Removes dead _extract_count_digit helper (no longer called from src/; inline re.search used instead). _is_settings_panel_open now delegates to _count_tabs_locator.first.is_visible(). Closes the count-tab portion of #24. Remaining #24 work: ONBOARDING_SELECTORS, NEW_PROJECT_SELECTORS, SUBMIT_BUTTON_SELECTORS tail (not exercised by the t2i/batch path). Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §9 Risk row 4. Follow-up to fc058ac (DOM dump) + db90478 (out_dir threading). * fix(image): listener cross-contamination — post-submit-time filter + straggler window Phase 7c live e2e on profile denon82 produced 5 files for a 3-prompt manifest but only 2 unique image sizes — prompt N's listener captured stragglers from prompt N-1 that arrived between attach and submit. _await_captured(expected_count=N) returned immediately when len(captured) >= N, so each prompt's await consumed STALE captures instead of waiting for its own response. Adopts the Worker's proven pattern (compile-growth-monorepo/python/workers/ google-flow-worker/flow_images.py _wait_for_n_new_images): each captured entry now stores a `ts = time.monotonic()` at append time; generate_images_batch and _generate_images_locked record `submit_time` right before _send_prompt; and _await_captured filters by `entry["ts"] >= submit_time` before counting toward expected_count. Adds a 2.5s "straggler window" after the threshold is reached for any late same-submission arrivals (also from the Worker pattern). Defense-in-depth early-detach on expected_count was considered but dropped — it requires plumbing expected_count into the listener closure, complicating the per-listener attach surface. The timestamp filter + straggler window are sufficient for correctness; the bakery prompt should now generate its own image. Tests: promoted xfail multi-listener test to a real assertion (attach-time isolation), added test_await_captured_post_submit_time_filter_rejects_stale (direct regression for the contamination scenario: 2 stale + 1 fresh, only 1 returned), and test_await_captured_all_entries_pass_when_submit_time_zero (backwards-compat default). 271 tests pass. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §9 Risk row 1. Phase 7c follow-up to c4a62cc (count-tab fix). * refactor(image): serial submission per prompt — Worker pattern (drop fire-and-forget) Live Phase 7 runs on profile denon82 showed cross-contamination across per-prompt listeners: each listener captured stragglers from prior prompts AND its own responses, producing 5 files for a 3-prompt manifest where only 2 image bodies were unique. The fundamental issue: Flow's response payload carries no per-submission identifier, and timing-based filters can't reliably disambiguate overlapping responses (prior-prompt stragglers and current-prompt first response can arrive in the same window). The Worker (compile-growth-monorepo/python/workers/google-flow-worker/flow_images.py _wait_for_n_new_images) avoids this by being strictly SERIAL: submit, await, detach, jitter, submit. The editor still stays mounted across the batch (same-project invariant intact), but only one listener is active at a time, so cross-contamination is structurally impossible. This commit rewrites _generate_images_batch_locked to the serial pattern. Each prompt's lifecycle: _configure_generation_settings -> _attach_batch_response_listener -> _send_prompt -> _await_captured -> detach -> parse -> append result -> jitter (if not last). The pending-tuple/await-loop split is gone. The post-submit-time filter in _await_captured stays as defense-in-depth. The fire-and-forget design intent (spec §4) is preserved as a future optimization once Flow provides per-submission identifiers. Until then, serial is the working pattern. Performance impact: each batch takes roughly N * (gen_time + jitter) instead of max(gen_time) + (N-1) * jitter — slower for large batches but correct. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md §9 Risk row 1 (multi-listener concurrency). test(image): replace multi-listener-concurrency test with serial-invariant assertions Drop test_multi_listener_no_cross_contamination (not applicable: serial pattern means only one listener is ever active). Replace with test_no_two_listeners_simultaneously_active which asserts the attach → detach → attach sequence structurally. Update happy-path test to record event_log and assert strict attach_0/detach_0/attach_1/detach_1/attach_2/detach_2 ordering. Rename in-flight-submission regression test to match serial semantics. Also fix pre-existing ruff violations in test_observability_events.py (unused import, two E501 lines) that were already present on this branch. * fix(image): move count-setter diagnostic dumps to out_dir/_diagnostics/ subdir Phase 7e live e2e on profile denon82 produced the correct result (4 unique images, one per prompt, all counts right) but the test's file-count assertion failed because it globbed all *.png in out_dir and picked up 6 diagnostic screenshots (count_before/after_prompt_*.png). The diagnostics shouldn't pollute the user's output_dir at all. Move them to a _diagnostics/ subdirectory created on-demand. Production images (from client.download_image) keep landing at out_dir/<prompt_filename>.png as before. Spec: docs/superpowers/specs/2026-05-22-stay-mounted-batch-session-design.md (no spec change — internal artefact placement only). * fix(image): select Image mode in editor before submitting ui_automation.py had no equivalent of ui_automation_video.py's _switch_to_video_mode. When the Flow account was last in Video mode, "image" prompts were silently routed to the video endpoint: no batchGenerateImages response observed, and the response listener timed out after 3 minutes. Also explains the historical "first-attempt listener-miss flake" recorded in phase-b-followups memory item #1. Adds UiAutomationTransport._switch_to_image_mode mirroring the video side: opens the 2-step mode dropdown via the shared MODE_SWITCH_TRIGGER_SELECTORS (imported from ui_automation_video), clicks the Image tab via the new IMAGE_TAB_IN_MENU_SELECTORS cascade (aria-controls*='IMAGE' first for locale-stability, then Portuguese "Imagem" / English "Image" / icon-ligature fallbacks), presses Escape to close the menu so _configure_generation_settings can open it fresh, and emits ui_automation.image_mode_entered. Wired into both call sites: generate_images (single t2i) calls it after _dismiss_blocking_overlays and before _configure_generation_settings; _generate_images_batch_locked calls it once per batch in the same orphaned_project_warning try/except wrapper used for overlay dismissal (so a mode-switch failure on a freshly-created project surfaces the project_id for user lookup). Tests: - New tests/api/transports/test_ui_automation_image_mode.py with three unit tests (happy path, trigger missing, image-tab missing) mirroring tests/api/transports/test_ui_automation_video.py's TestSwitchToVideoMode. - Autouse fixture in tests/api/transports/test_ui_automation.py TestGenerateImages and at the module level of tests/api/transports/test_ui_automation_batch.py stubs _switch_to_image_mode for orchestration tests that don't exercise the mode switch directly (mirror of the video side's monkeypatch.setattr(VideoGenerationMixin, "_switch_to_video_mode", AsyncMock())). Live-verified on profile ffroliva: 1 t2i shot (1 JPEG @ 768x1376, 9:16 within 0.79%) and full batch e2e (4 JPEGs at 9:16, 16:9, 16:9, 1:1, shared project_id). User confirmed gallery state. * test(e2e): exclude _diagnostics from image-batch file-count Commit 4ea84f8 relocated _set_count diagnostic screenshots into out/_diagnostics/ to avoid polluting the batch output directory. The e2e test's file-cardinality assertion at tests/e2e/test_image_batch_e2e.py:142 uses out.rglob("*") which recurses into the new subdir, so a 3-prompt manifest (count=1+2+1=4 expected) now counted 10 files (4 outputs + 6 diagnostic PNGs in _diagnostics/). Filter the assertion to skip paths containing "_diagnostics" in their parts. Production behaviour unchanged. Live-verified on profile ffroliva (pytest-837, 1 passed in 127s). * docs(image): record post-fix live verification on ffroliva LIVE_VERIFICATION_image_batch.md gains a "Post-mode-switch-fix verification" section recording today's full-pass results on profile ffroliva (1 t2i smoke + 4-image batch e2e, all eight test assertions green, user-confirmed gallery). Retracts an earlier session's "Phase 7e produced 4 unique images" claim that was based on file-size differences without magic-byte verification — inspection of the surviving pytest tmp dirs (pytest-825/826) shows zero prompt PNGs were ever written in those runs; the only PNGs present were count-tab diagnostic screenshots. CHANGELOG.md [Unreleased] § Fixed gains an entry for the image-mode-selection bug, noting it also resolves the historical "first-attempt listener-miss flake" from phase-b-followups memory item #1. Notes the denon82 WAF/reCAPTCHA 403 is unrelated to this fix and remains open as a separate investigation; ffroliva's success today demonstrates WAF can be passed by a sufficiently fresh chrome-strategy profile. * fix(tests): unblock CI — drop stale seed= stub param, disable structlog logger caching Two paired fixes that together restore CI green on Linux: 1. tests/features/test_image_steps.py — remove `seed: int | None` from the `_fake_t2i` stub signature. The real `_run_t2i` dropped the `seed=` kwarg in commit ac143e4 (refactor!: remove no-op --seed flag), so the committed stub raised `TypeError: _fake_t2i() missing 1 required keyword-only argument: 'seed'` when the CLI called it without seed=. The error was masked behind the privacy-preserving `observability.emit_unhandled_event` hash filter (message_hash 13fdd58c..., stack_hash bbeacfb5...) which is why earlier debug passes only saw the hash, not the message. Affected: test_t2i_single_image, test_multiimage_fanout. 2. src/gflow_cli/observability.py — switch `cache_logger_on_first_use=True → False`. With caching on, the first structlog.get_logger() call (during module import) freezes the processor chain to the production renderer; per-test `structlog.configure(processors=[LogCapture(), ...])` in fixtures then has no effect because the cached logger ignores the new chain. With caching off, each get_logger() re-resolves the chain, and the test's LogCapture sees image_batch.submission_attempt / image_batch.row_completed events. Affected: test_emits_submission_attempt_per_row, test_emits_row_completed_per_row. Both fixes were present in the working tree at session start but were never committed (likely paused mid-fix by a prior session); the mode-switch work on this branch surfaced the resulting CI red on the multi-image-prompt PR, which is why they're being landed here. Verified locally: pytest -q -m "not e2e and not live" --cov=gflow_cli on tests/features tests/image_batch tests/api/transports → 292 passed, 1 skipped. * docs+test(image): polish — record mode-switch in debugging, surface WAF / #24 in KNOWN_ISSUES, add call-site regression test docs/DEBUGGING.md - Adds `ui_automation.image_mode_entered` and `ui_automation.batch_403_body` to the listener event-keys table. - Rewrites the "no batch_response_seen after prompt_submitted" paragraph to point at editor-mode confusion as the most likely cause (resolved by PR #40); the UI-flake path is now the secondary hypothesis. - Lists the two new debug-screenshot names produced by _switch_to_image_mode failures. KNOWN_ISSUES.md (new Open entries): - `batchGenerateImages` HTTP 403 (WAF / reCAPTCHA `PUBLIC_ERROR_UNUSUAL_ACTIVITY`) — first observed 2026-05-23 on profile `denon82`; not blocked by code, fix is a fresh/cooled profile. - `UiAutomationTransport` selectors still partially localized (issue #24 partial) — count tabs done, ONBOARDING_SELECTORS still unanchored. Cross-linked to issue #24 (commented separately). tests/api/transports/test_ui_automation_image_mode.py (new class): - `TestModeSwitchCallSite` regression-guards the integration between the generate paths and the mode switch. Two tests: 1. `test_generate_images_switches_to_image_mode_before_send_prompt` — asserts call order: enter_editor → dismiss_overlays → switch_to_image_mode → ... → send_prompt. 2. `test_generate_images_batch_switches_to_image_mode_once_before_any_prompt` — asserts exactly one mode-switch per batch and before the first submit. - Uses a sentinel exception (_StopHere) raised from _await_captured to halt the chain after _send_prompt; the test only cares about the preceding call order. Single-shot test uses pytest.raises(_StopHere); batch test catches BatchPartialError (the production wrap when continue_on_error=False). * style(tests): rename _StopHere → _StopHereError (ruff N818) CI lint rule N818 requires exception classes to end with `Error`. Caught only on the Linux ruff run. * refactor: resolve all SonarCloud issues on PR #40 (7 fixes) Closes the 7 open SonarCloud issues raised on the multi-image-prompt PR. Each entry below is annotated with the rule key and the mechanism used to clear it; no behaviour change. S5655 — _drive_images_generation argument type (client.py:679, 757) `dataclasses.replace(req, count=...)` returns `T` via the stdlib's generic protocol — pyright narrows it back to GenerateImageRequest but SonarCloud's analyser does not. Pinning the local with an explicit `req_one: GenerateImageRequest = _dc_replace(...)` annotation gives both analysers the same hint. S1192 — literal duplication (ui_automation.py:1048, 1069, 1119, 1134) `"ui_automation.count_setter_completed"` was repeated four times inside `_set_count` at the four exit-path log lines. Extracted to a module-level `_EVT_COUNT_SETTER_COMPLETED` constant. S1172 — unused parameter (image_batch.py:683) `run_manifest_image_batch` accepted `project_title: str` from the pre-stay-mounted era when the orchestrator created the Flow project itself (see commit 2062eee). Today the transport creates the project as part of `_enter_editor`, so the parameter is dead weight. Removed from the signature. Updated all five call sites: cli_image.py, tests/e2e/test_image_batch_e2e.py, tests/image_batch/test_observability_events.py (×2), tests/image_batch/test_image_manifest.py (×3). No behaviour change — the parameter was already ignored end-to-end. S3776 — cognitive complexity refactors - `_set_count` (16 → 15): merged the inner `if displayed is None or displayed == count` into the outer `if clicked` condition. Single combined boolean. Same semantics: success only when the click landed AND read-back digit matches (or read-back returned None — unrecognised locale, position click is deterministic so we trust it). - `parse_tsv_manifest` (31 → ~10): extracted three column parsers `_tsv_parse_count`, `_tsv_parse_aspect`, `_tsv_parse_model`. Each owns the empty→default fallback, validation, and raise-with-line-context for its column. The main loop is now a flat sequence of three helper calls per row. - `_generate_images_batch_locked` (55 → ~6): extracted `_run_one_prompt_in_batch` that owns ONE prompt's full lifecycle (configure → attach listener → submit → await → detach → parse). Returns `(BatchSubmissionResult, fatal_error)`. The outer function is now a flat loop over prompts that appends results, breaks on fail-fast, and sleeps for jitter between iterations. Detach invariant preserved: every code path inside the helper detaches exactly once. Live behaviour unchanged (4 callsites that previously had near-duplicate fail-handling collapsed into one). Quality gates: PYTHONUTF8=1 uv run python scripts/ci/check_repo_hygiene.py ✓ uv run ruff check src tests ✓ uv run ruff format --check src tests ✓ uv run pyright src 0 errors uv run python -m pytest -q -m "not e2e and not live" tests/image_batch tests/api/transports tests/features 294 passed * fix(sonar): use cast() with pyright pragma — annotation form didn't satisfy S5655/S5890 The previous annotation form (`req_one: GenerateImageRequest = _dc_replace(...)`) triggered SonarCloud S5890 ("Assign a value of type GenerateImageRequest instead of Dataclass") plus the original S5655 didn't clear either. Switching back to `cast()` with a per-line `# pyright: ignore[reportUnnecessaryCast]` makes both Sonar and pyright agree: Sonar sees a forced type, pyright sees the silenced "unnecessary" diagnostic. Also ships a previously-uncommitted ruff format pass on test_ui_automation_image_mode.py that was applied locally but never pushed — caught by `ruff format --check` on the Linux runner.
15990166686ll-crypto
pushed a commit
to 15990166686ll-crypto/gflow-cli
that referenced
this pull request
Aug 24, 2026
…estAttachFrameSlotSelection Finding ffroliva#1 (HIGH): replace is_visible(timeout=3000) with wait_for(state="visible", timeout=3000) in the text-label fallback path of _attach_frame. Playwright's Locator.is_visible() ignores its timeout param ("deprecated and does nothing") — the intended 3-second grace window was silently absent, causing immediate false-negatives on non-English profiles. Finding ffroliva#2 (MEDIUM): replace bare VideoGenerationMixin._upload_via_open_dialog = AsyncMock() class mutations with monkeypatch.setattr() in both TestAttachFrameSlotSelection tests that needed patching. Bare class assignments are never rolled back, contaminating tests that run later in the session. Finding ffroliva#3 (LOW): remove the dead `with MagicMock() as mock_upload:` context manager from test_structural_slot_used_when_available. The block provided no cleanup and mock_upload was never used; the real patch was the class assignment inside it, now replaced by monkeypatch.setattr(). Also bundles Finding ffroliva#4 (structs.count() race): capture count once into a local before the branch so the error f-string reuses it rather than issuing a second async DOM query. All 148 unit tests pass; pyright and ruff clean. https://claude.ai/code/session_017p481rvFbk2i1CNfdsxocD
15990166686ll-crypto
pushed a commit
to 15990166686ll-crypto/gflow-cli
that referenced
this pull request
Aug 24, 2026
Adds FlowApiClient.generate_character_image() (Task 5, issue ffroliva#145): - Routes exclusively through transport.generate_character_images() — never a direct REST POST (scenario ffroliva#1 guardrail) - Guards transport=None before any call (scenario ffroliva#21: clear error without navigation when session is not set up / headless) - Validates workflows[0].parentEntityId == entity_id before returning; raises WireFormatError on mismatch or absent key (scenario ffroliva#5: never bind a foreign workflow) - Logs character.image_generated at INFO (credit-spending op, no prompt text) - Fixes stale _raise_for_non_retryable docstring: 403 maps to WafRejectionError, not AuthExpiredError Test: tests/api/test_client_generate_character.py (8 tests, LIVE fixture) pyright src: 0 errors; ruff check + format: clean Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
15990166686ll-crypto
pushed a commit
to 15990166686ll-crypto/gflow-cli
that referenced
this pull request
Aug 24, 2026
…rdrail Adds Phase-2 Task 10 tests for the gflow character create feature (issue ffroliva#145): - tests/features/character_create.feature + test_character_create_steps.py: three BDD scenarios mirroring the character_read harness (scenarios() bind, CliRunner, six-seam monkeypatch infra-bypass). Happy create + foreign-workflow error driven at the CLI entrypoint; resume/no-re-spend (ffroliva#3/ffroliva#4) driven at the saga layer (real saga, mocked client+recorder) because routing it through the fully-mocked CLI would assert nothing about resume. - tests/services/test_character_gen_no_direct_post.py: scenario ffroliva#1 guardrail. Runtime spy (real saga + real generate_character_image, _post_json and batch_generate_images_url patched to explode) proves character gen routes through the UI transport, plus a source-scan invariant over the saga and generate_character_image method body. Option B / 403 WAF wall rationale. Phase-2 sweep: 942 passed, 1 skipped (tests/api tests/data tests/services tests/cli tests/features). pyright src: 0 errors. ruff check + format: clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
15990166686ll-crypto
pushed a commit
to 15990166686ll-crypto/gflow-cli
that referenced
this pull request
Aug 24, 2026
…e DoD tests) Codify the Character-creation Definition-of-Done as repeatable live e2e tests. Default-OFF (opt-in via GFLOW_CLI_E2E_RUN_CHARACTER=1 + GFLOW_CLI_E2E_PROFILE) so CI / `gflow:check` never spend Imagen credits. - register `e2e_character` cost sub-marker in pyproject.toml + marker-registry invariant (_COST_SUB_MARKERS) - tests/e2e/test_character_create_e2e.py — 3 named live tests mirroring the subprocess + e2e_env pattern of test_data_layer_e2e.py: * test_character_create_binds_parent_entity (ffroliva#1/ffroliva#5): create --json then show --json read-back; asserts non-empty workflow_ids + thumbnail_media_id bound, project_id matches the existing project (no new project) * test_character_create_partial_saga_recoverable (ffroliva#3/ffroliva#4): same-name re-run resumes (same entity_id, workflow set does not double, exactly one character-mode op row in the isolated DB) — no double-spend * test_character_personality_utf8 (ffroliva#18): accented name + personality survive create -> show byte-for-byte (PYTHONUTF8=1) Live happy path first verified manually 2026-06-02 (entity 951d2ce4 "Marina Cortes"); these make it repeatable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
15990166686ll-crypto
pushed a commit
to 15990166686ll-crypto/gflow-cli
that referenced
this pull request
Aug 24, 2026
Design spec for the gflow Tools framework: TOML-defined tools, --tool/-t surface (no -e/--expand), creative-director as tool ffroliva#1 with banana-claude alignment (5-component formula, banned-keyword filter, image+video domains), broaden across all generation commands, MCP parity, data recording, docs, packaged as 3 PRs. Companion reference doc captures the 5-lens (owl) evaluation and the deferred S3 dispatch-framework design for future pickup.
15990166686ll-crypto
pushed a commit
to 15990166686ll-crypto/gflow-cli
that referenced
this pull request
Aug 24, 2026
…ge guard (ffroliva#219) Co-lands PR ffroliva#219 (Chrome profile + T2I extra-image bugs) with the council's required must-fixes folded in. Changes: - browser_manager: add `_is_playwright_chrome_channel_available()` probing only the exact Google-Chrome paths Playwright hardcodes; `channel_for_profile()` gates `channel="chrome"` on it instead of `is_chrome_available()` (which also accepts Chromium). Fixes the misleading "Chromium distribution 'chrome' is not found at /opt/google/chrome/chrome" on Chromium-only hosts. Linux probe is limited to `/opt/google/chrome/chrome` only (must-fix ffroliva#3 — the over-broad `/usr/bin/google-chrome*` paths would re-trigger the very error fixed here). - ui_automation: `GEN_SETTINGS_BUTTON_SELECTORS` aliased to `MODE_SWITCH_TRIGGER_SELECTORS` (must-fix ffroliva#2 — single source of truth); both require `button[aria-haspopup='menu']`, preventing a mis-click that skipped the count panel and left Flow's default count (≈2) billing extra generations. - client: `generate_image()` logs `client.generate_image_extra_returned` when the transport returns more images than the requested count=1. - CHANGELOG: add [Unreleased] ### Fixed block (must-fix ffroliva#1). - tests: extra-image warning fires on 2 images (+ negative case); `_is_playwright_chrome_channel_available()` Chromium-only → False (must-fix ffroliva#4). Co-Authored-By: C1ph3r404 <C1ph3r404@users.noreply.github.com>
This was referenced Sep 2, 2026
7 tasks done
ffroliva
added a commit
that referenced
this pull request
Sep 14, 2026
…not a channel Every survey in this series bound page.on(...). A service worker's fetches are reported on the BrowserContext instead, so a page-scoped zero cannot tell "no such traffic" from "traffic on a surface I never bound" -- which is exactly what the no-push claim rested on. Rather than argue it, bind both and diff them: whatever the context saw and the page did not IS the blind spot. Two $0 image runs, identical results. 3 responses per run reach the context and never the page: ~1.4s play.google.com/log?format=json&hasfast=true&authuser=0 ~3.9s www.google.com/recaptcha/enterprise/reload?k=... ~4.0s www.google.com/recaptcha/enterprise/clr?k=... application/binary Plus one dedicated worker, recaptcha/enterprise/webworker.js -- the carrier the council named, confirmed present rather than hypothesised. So surveys #1 and #2 and this note's own video run each missed three responses, and their "0 WebSocket" figures were narrower than they read. Both sibling notes now say so. What does NOT change: none of the three is a channel (two reCAPTCHA endpoints and a logging sink, all short request/response), and with the wider listener plus Network.webTransport* registered for the first time in this series, the count is still zero WebSocket, SSE and WebTransport. The conclusion stands on a wider surface than it was argued on. Target.setAutoAttach was deliberately NOT used: flattened child sessions arrive with a sessionId Playwright's connection does not route to a CDPSession, so those events would be dropped silently -- a detector that looks armed and is not, which is the one failure mode this series keeps guarding against. Traffic strictly inside a worker's scope stays unmeasured, and is named as such. Also names the third-party host census once, because ogads-pa has now been re-derived as "not Flow" in three separate notes. None of these should ever be blocked or route-aborted: gflow has no such machinery -- no host registry, no block list, the only context.route() being a narrow generation-payload interception -- and that absence is correct. This project's premise is that Google's stack rejects browsers advertising automation, so suppressing a beacon every real Chrome session sends is itself the anomaly. The census was page-scoped on its first run and therefore reproduced the very bug context_only exists to expose: it omitted play.google.com, the host whose identity was the open question. Fixed to count both, and re-run at $0 to prove it -- the host is absent from the census in run A and present in run B.
ffroliva
added a commit
that referenced
this pull request
Sep 14, 2026
…s client timer (#808) Closes the last open question from the 2026-09-14 protocol spikes, and the only one that cost credits. Spike #2 settled the image path (ogiZ0b held open ~19.7 s, one response, no push and no poll) and then corrected its own premise: video uses a poll loop, a different mechanism, so an absence on images was never an absence on video. This measures video directly. One t2v generation on ffroliva (veo_3_1_lite, x1, ~10 credits), full submit->completion lifecycle instrumented. THE ANSWER. It is a fixed client timer and Flow's page owns it. Seven consecutive jwpduf inter-DISPATCH gaps span 10 ms end to end (5.002 5.009 5.011 5.005 5.008 5.001 5.004, mean 5.006, stdev 3.5 ms). The same eight polls' ARRIVAL gaps have stdev 178 ms and spread 641 ms -- fifty times wider, pure network jitter. That ratio is what excludes the alternative: a cadence sent in response to anything would inherit that thing's jitter, so dispatch spacing would be at least as ragged as arrival spacing. A clock the network cannot perturb is a local clock. No collapse at completion (last inter-dispatch gap 5.004 == first 5.002). The job finished during the poll dispatched at 53.554; that poll's own reply carried status 3, and as29s went out 34 ms later -- the client reacting to the answer it asked for. Nothing inbound between polls: the only non-batchexecute responses are four GA4 /g/collect beacons (204 No Content) and one play.google.com/log, both outbound telemetry, the latter sitting between two dispatches 5.011 s apart. Nothing is held open either -- every poll answered in under 0.81 s, against ogiZ0b's 19.7 s on the image path. So push is now measured absent on BOTH generation paths rather than generalised from one, and "we could subscribe instead of polling" is refuted by measurement for video as it already was for images. The 5 s interval is a floor we read, not a latency knob we hold. Recorded in docs/ARCHITECTURE.md and the migrated-host memory so a future proposal meets it. PRE-REGISTRATION. The video arm and its five readings were committed and pushed BEFORE the run; spike #1 could not prove that ordering and had to say so. The note applies the reading to its own data outcome by outcome -- including the arm that fired while its consequent was refuted ("a client timer WE OWN; latency is OURS to tune"), which is recorded as a correction rather than reworded away. THE DETECTOR'S OWN BLIND SPOT, measured rather than argued. Every survey in this series bound page.on(...), but a service worker's fetches are reported on the BrowserContext, so a page-scoped zero cannot tell "no such traffic" from "traffic on a surface I never bound". Binding both and diffing them found 3 responses per run that the page never sees, identical across two $0 image runs: play.google.com/log, and two recaptcha/enterprise/* (one application/binary), plus one dedicated worker, recaptcha/enterprise/webworker.js. Surveys #1 and #2 and this PR's own video run each missed those three, and both sibling notes now say so. None is a channel, and with the wider listener plus Network.webTransport* registered for the first time here, the count is still zero WebSocket, SSE and WebTransport -- the conclusion stands on a wider surface than it was argued on. Target.setAutoAttach was deliberately not used: flattened child sessions arrive with a sessionId Playwright does not route to a CDPSession, so those events would drop silently, which is a detector that looks armed and is not. THREE INSTRUMENT BUGS, which are the recurring failure here rather than the Flow surface. Keying dispatch times by url[:200] would have destroyed the result -- all 8 jwpduf URLs are byte-identical at that truncation, measured -- and was fixed before the run. protocols keyed at [:300] against events at [:200] left 62 responses including every poll with no HTTP version; fixed, and no h2/h3 claim is made from the video capture (spike_two_domain_protocol_survey.py is unaffected, it truncates both sides at 300). str(record)[:120] truncated the terminal fields, so "done=True, 1,273,409 bytes" reached stdout and never the evidence file the note cited them to; sourcing corrected and those fields are now recorded by name, which also keeps the signed video_url out. COST SAFETY. --runs defaulted to 2 in both modes, so appending --mode video to the committed image invocation silently doubled a real bill while the banner computed that total and printed it. Now 1 for video, 2 for image. Page check-in moved into finally: except Exception does not catch Ctrl-C and the video arm holds a pooled page for up to poll_timeout_s. ALSO. check_doc_links.py now audits docs/superpowers/spikes/ whole instead of by hand-maintained allowlist -- every spike note ever written was outside the gate; all 34 pass, so nothing is grandfathered (29 -> 63 files). The third-party host census is named once, because ogads-pa has been re-derived as "not Flow" in three separate notes; none of those hosts should ever be blocked or route-aborted, and gflow has no machinery that would, which is correct for a project whose premise is that Google's stack rejects browsers advertising automation. Reviewed by an 8-dimension council (D0/D2/D3/D5/D14 green; D1/D4/D9/D13 yellow); every must-fix applied. All local gates green; CI 16/16 including SonarCloud.
5 of 7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
tasks/lessons.mdafter any user correction and review at session start.Test plan
Reasoning style for an agentsection