Skip to content

feat: character-create flow recorder + #153 locale fix - #158

Merged
ffroliva merged 8 commits into
developfrom
chore/character-create-recording
Jun 4, 2026
Merged

ffroliva merged 8 commits into
developfrom
chore/character-create-recording

Conversation

@ffroliva

@ffroliva ffroliva commented Jun 4, 2026

Copy link
Copy Markdown
Owner

Summary

Makes gflow's real character create flow recordable as a browser-only video (for the gflow-cli-remotion character promo), with the recording concern kept entirely out of core. Bundles a real production bug fix found along the way.

Production changes (src/)

  • fix character create: editor URL 404s on non-English locales (default --locale en-US is not a valid Flow URL segment) #153 — routes.character_editor_url interpolated the locale verbatim, so the genuine CLI default en-US produced /fx/en-US/… which 404s the Flow character editor. Now normalizes a BCP-47 tag to its short Flow URL segment (en-US → en, pt-BR → pt), with an en fallback for degenerate input. Language-agnostic.
  • refactor(client) — extract the inline launch_persistent_context(**kwargs) call into an overridable _persistent_context_kwargs() seam (value-for-value behavior-preserving), so dev tooling can augment the launch without any recording concern in core. channel_for_profile promoted to a module-scope import.

Dev tooling (scripts/dev/, NOT in the package)

  • RecordingFlowApiClient — subclasses the client and overrides only the seam to add Playwright record_video_dir (size tracks the viewport).
  • record_flow_capture.py — drives the genuine character_create saga through the recording client, then transcodes the .webm to .mp4. Isolates the DB and the image output dir to throwaway temp dirs, forces concurrency=1 (exactly one webm), guards image-not-video via character_create structlog events, and types the name into the editor for on-screen display (display-only; the editor title is decoupled from displayName per fix(character): editor title shows "Untitled" — name box is decoupled from entityInfo.displayName #151).

Process

Full grounded plan-council (verdict GO-WITH-FIXES — all MUST-FIX applied) → TDD per task → grounded branch-review council. Image generation is free (only video spends credits).

Live verification (promo-denon82, free)

Real headed run produced a 36.7 s / 1280×720 mp4 showing the genuine character editor: "Marina" typed in the name field + the generated face in the hero canvas. character list shows the created character with refs=1 (a face was generated). #153 confirmed live: --locale en-US → /fx/en/ loaded with no 404.

Test plan

Refs #153. Feeds the gflow-cli-remotion character promo (PR #1).

🤖 Generated with Claude Code

ffroliva and others added 8 commits June 4, 2026 15:00
…TH-FIXES)

Full grounded LLM council reviewed the plan against real source. Verdict
GO-WITH-FIXES: architecture sound (single client-owned context; seam faithful;
concurrency=1 -> one webm). Adds a council-revision addendum (authoritative) and
a new Task 0 (fix #153 locale normalization, land-first). MUST-FIX baked in:
Settings import path, orphaned channel_for_profile import, locale/#153.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
character_editor_url interpolated the locale verbatim, so the genuine CLI
default "en-US" produced /fx/en-US/... which 404s the Flow character editor
(only the short primary subtag is a valid segment). Reduce the tag to its
lower-cased primary subtag (en-US -> en, pt-BR -> pt); short inputs pass through
unchanged. Lets the tool stay language-agnostic with the real default locale.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-preserving)

Extract the inline launch_persistent_context(**kwargs) call in
FlowApiClient._enter_setup into an overridable _persistent_context_kwargs()
method, so a dev-scoped recording subclass can augment the launch (add
record_video_dir) without any recording concern living in core. The returned
dict is value-for-value identical to the previous inline call.

Promote `from gflow_cli.browser_manager import channel_for_profile` to module
scope (browser_manager imports only gflow_cli.errors -> no cycle) and drop the
now-orphaned function-local import (would have tripped ruff F401).

Tests: a pin test fixes the seam's contract, and a call-site routing test
asserts __aenter__ consults the seam exactly once (a re-inlined dict would drop
it to 0 -- the pin test alone can't catch that). pyright src 0, 32 client tests
green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…seam

scripts/dev/_recording_client.py subclasses FlowApiClient and overrides ONLY
_persistent_context_kwargs() to inject Playwright record_video_dir/record_video_size.
No recording concern lives in core; the subclass is never imported by the package.
Test asserts the recording kwargs are injected and the base kwargs are preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scripts/dev/record_flow_capture.py drives the genuine character_create saga
through RecordingFlowApiClient and transcodes the captured .webm to .mp4.
Free: character/image generation costs no credits.

Council hardening folded in:
- Settings imported from gflow_cli.config (MUST-FIX); built after the env vars.
- Isolates BOTH the data store (GFLOW_CLI_DB_PATH) and the image output dir
  (GFLOW_CLI_OUTPUT_DIR) to throwaway temp dirs; forces GFLOW_CLI_CONCURRENCY=1.
- Asserts EXACTLY ONE .webm is produced (refuses to guess otherwise).
- Image-not-video guard: captures character_create structlog events and requires
  completed + a face event, fails on a failure event (refs=1 alone is insufficient).
- recorder.close() in finally; ffmpeg-absent transcode falls back to the raw webm.

Wiring tests cover the guard, the ffmpeg fallback, and argparse dispatch (no
browser). Live verification (refs=1 + real footage) is a separate gated run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…play

The recorder now types the name into the editor's name input (after the real
character_create completes, still recording) so the promo video shows the name.
Display-only: the editor title is decoupled from displayName (#151) and won't
persist, which is irrelevant to the video. Located language-agnostically (tag the
topmost editable text input structurally, proven by the #151 recon spikes) with a
React native-setter fallback; degrades gracefully if the input isn't found.
Adds a --name flag (default "Marina").

Live-verified on promo-denon82: the 36.7s capture shows "Marina" in the name
field AND the generated face in the hero canvas (image/free).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ustness)

Council branch-review follow-ups (all SHOULD-FIX / cheap NITs):
- test_client.py: the routing test now asserts launch_persistent_context was
  invoked WITH the seam's exact dict (call_args.kwargs == seam output), not just
  that the seam was consulted — closing the behavior-preservation guard gap.
- record_flow_capture.py _run: restore os.environ + rmtree the throwaway
  isolation dir in a finally (reentrant, no leaked catalog.db); pre-bind `cap`;
  type into client._pages[0] instead of the deprecated _page alias.
- _recording_client.py: derive record_video_size from the base viewport (drop
  the parallel _VIDEO_SIZE constant so it can't desync).
- routes.character_editor_url: fall back to "en" for a degenerate/empty locale
  (no double-slash URL) + test.
- _FIND_NAME_INPUT_JS: clear any prior data-gflow-title tag (idempotent).

pyright src 0, ruff clean, 46 affected tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t alias

The new _persistent_context_kwargs() seam added a dict[str, Any] annotation that
gave the pre-existing S1192 duplicated-literal smell a new-code location, failing
PR #158's new_maintainability_rating gate. Define a single module-level
`JsonObject = dict[str, Any]` alias (matching the data.models convention) and use
it throughout client.py, so the literal appears exactly once. Annotations only —
behavior unchanged (pyright src 0, 666 tests green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ffroliva
ffroliva merged commit 03d4222 into develop Jun 4, 2026
7 checks passed
kittinan pushed a commit to kittinan/gflow-cli that referenced this pull request Jun 6, 2026
…ased]

The character-editor URL now normalizes BCP-47 locales to the short Flow segment
(en-US -> en), fixing the 404 on the genuine default locale. Shipped in ffroliva#158.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ffroliva
ffroliva deleted the chore/character-create-recording branch June 12, 2026 23:47
15990166686ll-crypto pushed a commit to 15990166686ll-crypto/gflow-cli that referenced this pull request Aug 24, 2026
…t alias

The new _persistent_context_kwargs() seam added a dict[str, Any] annotation that
gave the pre-existing S1192 duplicated-literal smell a new-code location, failing
PR ffroliva#158's new_maintainability_rating gate. Define a single module-level
`JsonObject = dict[str, Any]` alias (matching the data.models convention) and use
it throughout client.py, so the literal appears exactly once. Annotations only —
behavior unchanged (pyright src 0, 666 tests green).

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
…recording

feat: character-create flow recorder + ffroliva#153 locale fix
15990166686ll-crypto pushed a commit to 15990166686ll-crypto/gflow-cli that referenced this pull request Aug 24, 2026
…ased]

The character-editor URL now normalizes BCP-47 locales to the short Flow segment
(en-US -> en), fixing the 404 on the genuine default locale. Shipped in ffroliva#158.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

character create: editor URL 404s on non-English locales (default --locale en-US is not a valid Flow URL segment)

1 participant