Skip to content
Merged
47 changes: 47 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- **`gflow video`/`image` can no longer get stuck behind Flow's expanded chat
sidebar (#493).** Expanding the sidebar removes the classic composer
**entirely** — no `crop_*` settings trigger *and* no Agent pill — which is
exactly the fingerprint reported in #493 ("no `crop_*` settings button", "the
Agente pill matches neither selector"). It also explains the exit code: with
no agentic indicator on screen either, the cohort detector matches nothing, so
the run dies as `UiSelectorDriftError` (exit 23) instead of the retryable
agentic error (25). Recovery hinged on a single selector scoped to the
sidebar's `edit_square` affordance; a cohort whose sidebar lacks that ligature
never found the X, so the sidebar never closed and the composer never came
back. `ensure_media_mode` now falls back to an unscoped close **only** from
the demonstrably stuck state (no `crop_*` **and** no pill), where the classic
composer is gone and there is nothing else a close button could belong to.
Reproduced live and A/B-proven: with the scoped selector neutered the fallback
recovers; with both neutered it does not.

- **`--duration` now fails fast instead of masquerading as UI drift (#451, #288).**
Flow's video settings popover is **model-conditional**: only `omni-flash` renders
a `4s/6s/8s/10s` row, and the Veo 3.1 models render **no duration control at all**
— verified live on two accounts and two locales. `api/video.py` had claimed "the
four `VEO_3_1_*` models cap at 8s", which presumed a control that is never drawn,
so `_select_video_duration` hunted a missing element and died with
`UiSelectorDriftError` (exit 23) after ~30 s. That is why the bug reproduced
identically on playwright 1.59 and 1.61 (the version bound was correctly
exonerated) and why the locale hypothesis was refuted — it was never either.
`--duration` with a Veo model now exits **2 before any browser work**, naming the
model and the fix. New `VideoModel.supports_duration()`; the DTO guards it too, for
API callers.
- **`--reference-entity` no longer advertised on `video i2v`, where it always failed.**
The flag was registered on `t2v`/`i2v`/`r2v`, but `_validate_i2v_symmetry` rejects
reference entities on i2v — so the i2v form raised for every caller who believed
the help text. It is now applied to `t2v`/`r2v` only. The reverse error was in the
docs: `REFERENCE_STRATEGIES.md`, `USAGE.md` and the `t2v` help text all stated "the
video path has no `--reference-entity` flag" while `cli_video.py` registered it —
corrected in all four places.
- **A named remote reference that isn't in the picker now raises a typed error.**
`--ref-name` searches Flow's picker, which indexes Flow's own short auto-caption —
not the generation prompt — so a prompt passed as `--ref-name` surfaced as a bare
Playwright `TimeoutError` after 8 s, with no exit code to branch on and no hint
that the *name* was the problem. Now `ReferenceNotFoundError` (**exit 32**), listing
what the picker actually offered.
- `reference_cap_for` now records the live-verified ingredient rule it already
encoded: `veo-quality` refuses image ingredients (Flow: "You cannot use image
ingredients with this model") while Omni Flash / Fast / Lite accept them, and a cap
of 0 *is* the answer to "does this model take ingredients?". Deliberately no second
predicate — one was written, found to have no production caller, and deleted.

- **MCP `gflow_generate_image` `ui_mode` now matches the video tool's contract.**
The image tool rejected an unknown `ui_mode` with a flat `error` **string**, so a
client reading `error["title"]` — the RFC 9457 shape every other 400 from these
Expand Down
68 changes: 45 additions & 23 deletions KNOWN_ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,31 +181,53 @@ accepted. Two corrections fixed it (verified end-to-end on macOS Apple Silicon):

Evidence: [LIVE_VERIFICATION_v0.23.0](docs/LIVE_VERIFICATION_v0.23.0.md).

### New Flow editor variant (composer frame slots + Agent toggle) is not recognized
### Expanded chat sidebar left the composer unrecoverable (exit 23)

- **Status:** **Open** — awaiting a DOM signature from an affected account;
- **Status:** **Resolved** in the unreleased line — fixed and A/B-verified live;
tracked in [#493](https://github.com/ffroliva/gflow-cli/issues/493)
- **Severity:** High · **Affects:** `gflow image` / `gflow video` generation on
accounts that received the new editor, any locale

A third Flow editor layout was reported on 2026-08-13 (macOS, v0.55.0; also
v0.53.1): frame-slot buttons ("Initial"/"Final") sit directly on the composer
next to an "Agent" pill, and there is **no** classic `crop_*` aspect/settings
button for gflow to drive. The layout matches **none** of the known cohort
indicators (agentic chat composer, full-page media library), so the mode-switch
step falls through to `UiSelectorDriftError` (**exit 23**) instead of the
retryable exit-25 cohort classification. The selector cascade itself is
locale-invariant — the reporter's Portuguese UI is unrelated.

The error detail now names this hypothesis and the run writes a PII-safe DOM
signature to `diag_mode_switch_miss.json` when it can be captured (structural
allowlist: ligature names, tag counts — no cookies, tokens, prompts, or page
text; if the file is absent, attach the incident bundle instead — its
`ui.json` carries the same DOM signature). **If you hit this, attach that JSON file to
[#493](https://github.com/ffroliva/gflow-cli/issues/493)** — recognizing the
variant (and restoring a clean retryable classification, or full support) needs
exactly that DOM signature. Re-running later may land the classic editor if
the rollout is still an A/B arm.
- **Severity:** High · **Affected:** `gflow image` / `gflow video` generation on
accounts whose chat sidebar lacks the `edit_square` affordance, any locale

Reported 2026-08-13 (macOS, v0.53.1 and v0.55.0) as an unrecognized "third
editor variant": frame-slot buttons ("Inicial"/"Final") on the composer next to
an "Agente" pill, and **no** classic `crop_*` settings button.

**Root cause (2026-08-14, reproduced live at zero credits):** there is no third
variant. Those are stock classic-composer features — "Inicial"/"Final" are the
**Frames** sub-mode's Start/End slots, and the Agent pill sits alongside the
classic popover. The real trigger is Flow's **expanded chat sidebar**, which
removes the classic composer *entirely*:

```
in sidebar state -> crop_* triggers = 0 Agent pill = 0
```

One state, both reported symptoms. It also explains the exit code: with no
agentic indicator on screen either, the cohort detector matches nothing, so the
run fails as `UiSelectorDriftError` (**exit 23**) rather than the retryable
exit-25 classification. The Portuguese UI was unrelated — the cascade is
locale-invariant (ligature-keyed), re-confirmed on a pt-BR account.

Recovery depended on a single selector scoped to the sidebar's `edit_square`
("new session") affordance. On a cohort whose sidebar lacks that ligature the
close button was never found, so the sidebar never closed, the composer never
returned, and **every** run failed. `mode_control.ensure_media_mode` now falls
back to an unscoped close, reached **only** from the demonstrably stuck state
(no `crop_*` **and** no Agent pill) — safe there because the classic composer is
gone, so nothing else a close button could belong to remains.

Verified by an A/B on a real editor: with the scoped selector neutered the
fallback recovers; with both neutered it does not. Two earlier hypotheses were
tested and refuted first (a `crop_free` sub-mode trigger, and a composer
hydration race) — see
[the spike evidence](docs/superpowers/spikes/2026-08-14-video-model-capability-matrix.md).

If you still hit exit 23 after upgrading, the run writes a PII-safe DOM
signature to `diag_mode_switch_miss.json` (structural allowlist: ligature names
and tag counts — no cookies, tokens, prompts, or page text; if absent, the
incident bundle's `ui.json` carries the same data). Attach it to
[#493](https://github.com/ffroliva/gflow-cli/issues/493) — that would indicate a
state genuinely different from the sidebar one.

### Flow's new full-page media-library UI breaks entity attach (A/B rollout)

Expand Down
14 changes: 8 additions & 6 deletions docs/REFERENCE_STRATEGIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ transports.

2. **HOW you author it** (only matters once you've chosen "by identity")
- Inline, readable, agent-friendly → `@Name` in the prompt.
- Explicit, scriptable, name-ambiguous → `--reference-entity <entityId>` (image path only — see
- Explicit, scriptable, name-ambiguous → `--reference-entity <entityId>` (all paths except `i2v` — see
the matrix below).

## Decision table

| I want to… | Use | Why |
|---|---|---|
| Reference a **saved Character** by name, inline | `@Name` in the prompt | Resolves to `referenceEntities`; the model anchors on that entity. Works on every generation path. |
| Reference a Character by **explicit id** in a script | `--reference-entity <entityId>` (`t2i`/`i2i`) | Same wire as `@Name`, no name-resolution ambiguity. **Not available on `t2v`/`r2v`** — use `@Name` there. |
| Reference a Character by **explicit id** in a script | `--reference-entity <entityId>` (`t2i`/`i2i`/`t2v`/`r2v`) | Same wire as `@Name`, no name-resolution ambiguity. **Not available on `i2v`** (its DTO rejects reference entities). |
| Reference a **saved media asset** (generated/uploaded) by name | `@Name` in the prompt | Resolves to the asset's UUID → `referenceImages`, zero re-upload. |
| Reference a **one-off / arbitrary image** or a look | `--ref <path-or-uuid>` (`i2i`, `r2v`) | Stages `referenceImages` directly; no saved identity needed. |
| Reuse the **same subject** across many generations | `gflow character` once → then `@Name` everywhere | A Character is the durable, name-addressable identity. See [CHARACTER.md](CHARACTER.md). |
Expand All @@ -52,12 +52,14 @@ Which reference methods each path accepts (design spec §2, verified against `cl
|---|---|---|
| `image t2i` | `@Name` · `--reference-entity <id>` | `@Name` (in-project UUID → `referenceImages`) |
| `image i2i` | `@Name` · `--reference-entity <id>` | `@Name` · `--ref <path-or-uuid>` |
| `video t2v` | `@Name` **only** (no `--reference-entity` flag on the video path) | ❌ — media mentions on the video path are Phase 3 |
| `video r2v` | `@Name` **only** (no `--reference-entity` flag on the video path) | `--ref <path>` (local ingredients); `@media` is Phase 3 |
| `video t2v` | `@Name` · `--reference-entity <id>` | ❌ — media mentions on the video path are Phase 3 |
| `video r2v` | `@Name` · `--reference-entity <id>` | `--ref <path>` (local ingredients); `@media` is Phase 3 |

Notes:
- **The video path has no `--reference-entity` flag.** To attach a saved Character on `t2v`/`r2v`,
`@Name` is the only name-based route. On the image path both forms exist.
- **`--reference-entity` IS available on `t2v` and `r2v`** (both carry `referenceEntities` on the
wire). It is deliberately absent from `i2v`, whose DTO rejects reference entities — an i2v
request carries start/end frames instead. Corrected 2026-08-14: this section previously denied
the flag on the whole video path while `cli_video.py` registered it. On the image path both forms exist.
- A mention whose resolved kind is unsupported on the invoked path (e.g. `@someMedia` on `t2v`)
fails fast with a clear exit-11 message naming the Phase-3 limitation — no silent drop, no wasted
credit.
Expand Down
18 changes: 14 additions & 4 deletions docs/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -423,9 +423,10 @@ Rules:

`@Name` and `--reference-entity <id>` resolve to the **same** wire
(`referenceEntities`) and dedupe against each other: reach for `@Name` for the
inline, by-name path — it's also the **only** name-based option on video, which
has no `--reference-entity` flag — and `--reference-entity` when you'd rather pin
an explicit entity id in a script (image paths only). `--ref` is different: it
inline, by-name path — and `--reference-entity` when you'd rather pin
an explicit entity id in a script. Both are available on `t2i`/`i2i` and on
`video t2v`/`r2v`; `video i2v` takes neither, because its DTO rejects reference
entities (it carries start/end frames instead). `--ref` is different: it
attaches an arbitrary **image** (`referenceImages`), not a saved identity, and can
be combined with a `@Name` for "this identity, in this look".

Expand Down Expand Up @@ -493,9 +494,17 @@ All prompts in a batch share one Flow project. The editor is opened once and sta

> **Shared video flags** (`t2v` / `i2v` / `r2v`):
> `--model [omni-flash|veo-lite|veo-fast|veo-quality|veo-lite-lp]` (omit → Flow's
> current UI default), `--duration [4|6|8|10]` (10 requires `--model omni-flash`),
> current UI default), `--duration [4|6|8|10]` (**requires `--model omni-flash`** —
> see below),
> `--count INTEGER` (1–4; >1 multiplies credit cost), `--aspect [9:16|16:9]`,
> `--profile NAME`, `--out-dir DIR` (default `tmp/`).
> **`--duration` only works on `omni-flash`** (issues #451/#288). Flow's settings
> popover is model-conditional: `omni-flash` renders a `4s/6s/8s/10s` row, and the
> Veo 3.1 models render **no duration control at all** — verified live on two
> accounts and two locales. Passing `--duration` with a Veo model now fails fast
> with a message naming the model, before any browser work; it used to burn ~30 s
> of selector timeouts and die with exit 23 as if Flow's UI had drifted. Omit
> `--duration` to accept Flow's default length for those models.
> `--count` is enforced **fail-closed**: if Flow's count control cannot be
> located (selector drift), the run refuses with exit 23 *before* submitting
> instead of proceeding on Flow's sticky default (typically x2) and silently
Expand Down Expand Up @@ -1367,6 +1376,7 @@ shell scripts can branch on the failure mode without parsing stderr.
| `29` | `MentionIndexUnavailableError` | An `@mention` was present but the catalog source needed to resolve it (character entities or media assets) failed to load — distinct from an empty index, which is not an error | Check network connectivity (character source) or `GFLOW_CLI_DB_PATH` / filesystem permissions (media source), then retry |
| `30` | `QueueSchemaError` | A `gflow serve`/MCP worker-queue task payload has an unrecognized `schema_version` or fails validation against the typed request DTOs | Usually means gflow-cli was downgraded after a newer version enqueued the task, or the payload was hand-edited; re-enqueue with a compatible version |
| `31` | `FlowAppError` | Flow's web app hit a client-side exception (its error-boundary page rendered instead of the editor) — a transient Flow crash, not a gflow bug | Retry shortly; if it persists, Flow itself is degraded — wait and retry later |
| `32` | `ReferenceNotFoundError` | A referenced media NAME is not in this project's picker. Flow indexes a short auto-caption, not the generation prompt, so a prompt used as a reference name never matches | Reference the asset by its media UUID, pass a local file with `--ref`, or check what exists with `gflow data list images` |
| `130`| SIGINT | User-interrupted (Ctrl-C) | — |

**Exit code 16 — data store / migration error.** Fires when:
Expand Down
Loading