* refactor(loop-audio): card reads only multilines + Koolook Director (#177)
The audio-lipsync card is now bounded to two source families: the five
tracked Text Multiline nodes and the LTX Director (Koolook v1.3.2)
node's own widgets + input wiring. SAMPLER block (BasicScheduler /
KSamplerSelect / RandomNoise / CFGGuider scrapes), FORK STATE block
(_dev_build.json + git status), and INERT warning are gone — they were
either widget scrapes outside the two source families or duplicates of
the Director's own state.
New card layout (top to bottom):
HEADER run NNN — {name} + date + workflow filename
KNOB STATE relay_overrides (the per-render knob)
BASE · NOTES overlay - info verbatim, blank lines preserved
BASE · LOCKED epsilon · Audio src · Working folder (path-wrapped)
BASE · SCENE Segments (N) parent, indented `1) 0 to 5 seconds`
child list, flat Prompt/Audio/Keyframe coverage
POST-RENDER overlay - feedback + parsed motion/sync/sharp
Audio src now derived structurally from a 4-state machine
(audio_vae link × use_custom_audio × audioSegments count) that mirrors
ltx_director.py:610-622 — never from substring matches on prompt text.
Per-segment Audio coverage checks audioSegments[] overlap, not the
maintainer's [Audio]: prompt formatting.
Also fixed: upstream-LTXDirector widget index bug (saved workflow keeps
legacy order even after Koolook fork reordered the schema; DIRECTOR_WIDX
matches the serialised order); loop_audio.py's .env discovery now falls
back to the main repo when run from a worktree (same pattern as
make_card.py); notes.md drops the widget-scraped Mechanical interp
section; log.md columns realigned (Audio src + Segments replace Phase 1
/ Phase 2 / Custom audio).
Three new docs alongside the module:
reading-graph.html visual flow (dark theme, color-coded bands)
reading-graph.schema.yaml machine-readable mirror of the same graph
CHEATSHEET.md refreshed against the new design
All 231 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(lint): split semicolon statements + drop unused symbols in make_card_audio
Ruff (project's Lint & Smoke gate) flagged 10 issues in the refactored
card renderer:
- F401 unused imports — `typing.Optional` at the top of the file and
`loop_audio.REPO_ROOT` in the standalone CLI's import block.
- F841 unused locals — `dur_f` and `dur_s` were left over from the
dropped "Duration" row; segment time ranges convey the same info now.
Kept in the state dict for notes.md; not read in the renderer.
- E702 six semicolon-joined statements in the HEADER and POST-RENDER
sections (`draw.text(...); y += N`). Split into separate lines.
Rendering is unchanged — verified by re-running the standalone CLI
against run-002. All 249 tests still pass; ruff now clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(loop-audio): resolve PR #185 multi-agent review findings
Anchored by the verified multi-agent review on PR #185 — every fix
traces to a confirmed finding (false-positive rate was 0/11).
HIGH:
- derive_audio_state now distinguishes "(no director)" from "off (no
VAE)". Pre-fix, is_input_wired(None, ...) returned None, the
truthiness check collapsed director-missing into VAE-unwired, and
the card / log silently agreed on the wrong story. Added an early
return when director_node is None. Also documented that vae_wired
can be None (input socket absent in older schemas) and that we
intentionally collapse it with False at the unwired branch — after
the missing-director case has been split off.
- extract_multilines now iterates needles longest-first. Pre-fix the
per-node `break` was order-dependent on config insertion: a future
short substring (e.g. "relay") in the same tracked_multilines list
would have silently shadowed "relay_overrides". Sorting by length
descending removes that footgun without breaking the current 5-needle
layout.
- parse_timeline now coerces per-segment numeric fields (start /
length / trimStart) to int via int(float(...)) with try/except → 0
fallback. The upstream Director itself does this defensively at
ltx_director.py:232-234; without coercion a string-valued field
silently breaks video_segment_has_audio's `<` comparisons
(lexical) and crashes the renderer at `start / fps`. Also drops
non-dict entries from segments / audioSegments arrays so a
malformed JSON can't slip past json.loads + isinstance checks.
- Duplicated wrap_path + video_segment_has_audio bodies in
make_card_audio.py are gone. Renderer now imports them at module
level from loop_audio so the two scripts agree byte-for-byte on
path-wrapping rules and audio-overlap geometry.
MEDIUM:
- DIRECTOR_WIDX now carries a MIRROR: comment pointing at the YAML
schema's `widget_order:` block (the cross-link the review asked for
as the minimum drift-prevention fix).
- duration_frames / duration_seconds removed from the state dict and
from render_audio_card's docstring. The renderer dropped the
Duration row earlier in the PR; notes.md reads them straight from
director_node via director_widget, so keeping them in state was
documenting a contract the renderer didn't honour. YAML schema now
splits them out under a notes_only section to match.
- Log-row score formatting now uses an explicit `is not None` check
(`_score()` helper) instead of `or '?'` — 0 was a legitimate score
the maintainer could type that pre-fix collapsed to '?' because 0
is falsy. Matches the card's existing `_s()` helper for parity.
- Card already surfaces the missing-Director state distinctly thanks
to derive_audio_state returning "(no director)" — the BASE · LOCKED
Audio src row shows that label loudly when applicable. No
additional row needed.
LOW:
- Renderer falls back to "1) frames 0-120 (no fps)" when frame_rate
is None instead of computing "0 to 0 seconds" silently.
Test coverage:
- New tests/scripts/test_loop_audio.py — 41 tests covering all five
audio_src states, multiline ordering edge cases, parse_timeline
coercion + malformed inputs, video_segment_has_audio overlap
boundaries, parse_feedback (with explicit zero-score regression
test), is_input_wired None vs False semantics, wrap_path
separator-only wrapping, pick_existing_path filesystem fallback,
autogen_label slug derivation.
Docs:
- reading-graph.html lists five audio states (was four).
- reading-graph.schema.yaml audio_state_machine adds "(no director)"
state + director_present input; state_dict moves duration_frames /
duration_seconds into a notes_only section; invariants block grows
with the missing-director split, type-coercion guarantee, longest-
needle-wins guarantee, and zero-score preservation.
- CHEATSHEET.md Audio src table grows from 4 to 5 rows.
Validation:
- 290 tests pass (was 249 — 41 new + 0 regressions).
- ruff clean on scripts/loop_audio.py, scripts/make_card_audio.py,
tests/scripts/test_loop_audio.py.
- Standalone CLI re-renders run-002 card identically (same workflow,
same pixels).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(investigations): scaffold #177 LTX23 audio-file lip-sync investigation
Reorganize docs/investigations/ into numbered topic folders, each
self-contained with narrative + upstream pin + patches + per-run snapshots.
Add a generic sync helper that mirrors the dev-sync pattern for any
investigation's third-party node modifications.
Structure (new):
docs/investigations/
00_LTX23-base-1-step/README.md (rename of ltx-director-4k-transitions.md)
01_LTX23-audio-file-lipsync/
README.md -- problem, mechanism, patches, reversion, promotion
upstream.json -- repo + version + pinned commit + sync target env + files
patches/ -- source of truth for modified upstream files
ltx_director.py -- adds `relay_overrides` multiline JSON input
prompt_relay.py -- per-segment sigma per Prompt-Relay paper formula
runs/
LOOP.md -- iteration protocol
log.md -- rolling table (empty header for now)
Sync helper:
scripts/sync_investigation_patches.py <NN>_<topic> [--dry-run]
- Reads upstream.json for target env var + file list
- Backs up the target's current state once per day (<file>.bak.<YYYYMMDD>)
- Worktree-aware: falls back to MAIN's .env via git rev-parse --git-common-dir
- Prints a two-line report matching scripts/sync_to_dev.py shape
- Per-investigation trigger phrase via `trigger:` key (sync-audio for #177)
Env:
.env.example -- documents new KOLOOK_WHATDREAMSCOST_PATH
.env -- local path set
Glossary updates (docs/reference/glossary.md):
+ Investigation folder
+ JSON file folder
+ Loop
+ Run
+ Investigation patches
+ sync_investigation_patches.py
Closes the workflow gap from #177: MAIN is now the source of truth for
investigation patches; the live install is a sync destination only. No
manual file copying between iterations — `sync-audio` does it.
* chore(ruff): exclude docs/investigations from lint
The investigation patches under docs/investigations/*/patches/ are
vendored upstream source (WhatDreamsCost-ComfyUI v1.3.2) carried so
sync_investigation_patches.py can mirror them into the live install
and so iterations can be diff'd against the pinned upstream commit.
Linting them would force divergence from upstream and defeat that
purpose. Mirrors the existing forks/ exclude precedent.
Fixes the CI Lint & Smoke failure on PR #178.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: per-task automation modules + WhatDreamsCost Koolook fork
Pivot docs/automations/ from "one folder per model" to "one folder per
automation module" so the same diffusion model can host several
independent tasks side by side. Replace the parallel docs/investigations/
tree with a fork of WhatDreamsCost-ComfyUI under forks/.
Automation modules (new layout)
- docs/automations/LTX-2.3/base-1step/ the existing single-stage
automation, moved from
docs/automations/LTX-2.3/
with its README, findings,
and handoff-checklist
rescoped to the task. The
4k-transitions backstory
that produced the module
moves here as
backstory/4k-transitions.md.
The earlier
docs/investigations/experiments/
card PNGs/HTML land in
example-cards/.
- docs/automations/LTX-2.3/audio-lipsync/ the audio-file lip-sync
module previously living
under docs/investigations/01_…/.
README, handoff, findings
stub, backstory/, and
runs/{LOOP,log}.md, all
referencing the new fork.
- docs/automations/README.md + CHEATSHEET.md updated to describe the
module-per-task pivot and
the new tree shape.
- docs/automations/CONVENTIONS.md unchanged (cross-cutting
loop contract still holds).
WhatDreamsCost Koolook fork (new)
- forks/whatdreamscost_koolook/versions/v1_3_2/
UPSTREAM_PIN.yaml pinned to upstream e81223a
(v1.3.2), GPL-3.0
verified 2026-05-28.
ltx_director.py MODIFIED — adds the
relay_overrides multiline
JSON widget on LTXDirector.
Moved from
docs/investigations/01_…/patches/.
prompt_relay.py MODIFIED — per-segment σ per
the Prompt-Relay paper
formula σ = (L − w_eff) /
(2 · √ln(1/ε)). Moved from
docs/investigations/01_…/patches/.
patches.py UNMODIFIED — vendored
verbatim from upstream
because ltx_director.py
imports detect_model_type
and apply_patches from it
(same precedent as
radiance_koolook's
color_helpers.py).
- Registered as LTXDirector__koolook_v1_3_2 ("LTX Director (Koolook v1.3.2)")
so it coexists with an installed copy of upstream WhatDreamsCost-ComfyUI.
Wired into the root __init__.py NODE_CLASS_MAPPINGS merge.
- forks/forks_manifest.yaml + forks/THIRD_PARTY.md updated with the new
entry (attribution, change log, license verified date, exposed node IDs).
Removed
- docs/investigations/ entire tree retired; its
contents either moved to
automation modules or to
forks/.
- scripts/sync_investigation_patches.py no longer needed — fork
code lives in the Koolook
tree and is synced via the
standard dev-sync flow.
- KOLOOK_WHATDREAMSCOST_PATH in .env.example same reason.
- docs/investigations in pyproject.toml's ruff
extend-exclude added in 96bf8ad to silence
lint on vendored patches;
no longer needed now that
vendored code is under
forks/ (already excluded).
Docs
- docs/reference/glossary.md Investigation folder,
Investigation patches,
JSON file folder, and the
sync_investigation_patches.py
entries replaced by
Automation module, Backstory,
Working folder, and Card.
Loop and Run rewritten to
point at the new structure.
- CHANGELOG.md [Unreleased] describes the restructure +
the new fork.
Verification
- ruff check . → all checks passed
- tools/preflight_release.py --skip manager-meta → 42 files parsed; 11
unique node IDs collected including LTXDirector; VAE dispatch branches
all OK; fixture workflows still reference all registered Koolook IDs.
- pytest → 173 passed, 0 failed.
Workflow JSON heads-up
The active audio-lipsync workflow LTX-23-audio_tests_v01.json continues to
reference the upstream LTXDirector ID. To pick up the new fork's behaviour,
swap that node to "LTX Director (Koolook v1.3.2)" (registered ID
LTXDirector__koolook_v1_3_2) — sockets and wiring are preserved. The
relay_overrides widget appears at the bottom of the new node. Documented
in the audio-lipsync README's "Workflow JSON — Koolook node ID" section.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(scripts): add dev-sync-audio — scoped sync for audio-lipsync iteration
`dev-sync-audio` is a scoped wrapper around `dev-sync` for the
[`docs/automations/LTX-2.3/audio-lipsync/`](docs/automations/LTX-2.3/audio-lipsync/)
iteration loop. It copies *only* the paths that module touches — the
`forks/whatdreamscost_koolook/` subtree and the root `__init__.py` —
instead of `rmtree`-ing the whole dest `forks/` and re-copying every
Koolook fork on every render. Same `KOLOOK_COMFYUI_DEV_PATH` target,
same ComfyUI-Manager restart, same two-line chat-report shape.
Changes
- `scripts/sync_to_dev_audio.py` (new). Thin wrapper that reuses
`sync_to_dev`'s env loader, target validator, restart helper, and
chat-report builder, supplying its own `AUDIO_PATHS` tuple. Defaults
the `--scope` tag to `"audio-lipsync fork edit"` so the build
identifier names this module even when invoked without an explicit
scope.
- `scripts/sync_to_dev.py` — `sync()` gains an optional `paths` kwarg
(defaults to the existing `RUNTIME_PATHS`). Backwards-compatible with
every existing call site.
- `docs/automations/LTX-2.3/audio-lipsync/README.md` and
`runs/LOOP.md` updated to point at `dev-sync-audio` instead of plain
`dev-sync` for the iteration step.
- Project `CLAUDE.md` — new "Per-module scoped variants" subsection
under the existing `dev-sync` section documents the
`dev-sync-<module>` pattern, the trigger-name → script-name mapping,
and reiterates that the user-initiated-only rule applies verbatim to
every `dev-sync-*` variant.
Verification
- ruff check . → all passed
- python scripts/sync_to_dev_audio.py --dry-run -v →
would copy: __init__.py
would copy: forks/whatdreamscost_koolook
e7f3ab4 - beautiful-mahavira-ae6da6
would sync 2 entries -> <KOLOOK_COMFYUI_DEV_PATH> (dev-sync-audio)
(Confirms scope is correct and the existing dev-sync chat-report
shape is preserved.)
User-initiated only — never automatic. Trigger phrase: `dev-sync-audio`
in chat (also: "copy audio fork", "sync audio fork"). Same rule as
`dev-sync` — see project CLAUDE.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(audio-lipsync): only snapshot runs when explicitly kept
Every render adds a row to runs/log.md (always). The heavier
run-NNN_<label>/ snapshot folders (workflow JSON copy + relay_overrides
+ patch state + notes) are now created only when the maintainer says
"keep", "save run", "save this", or "snapshot" in chat. Without that
trigger, the render is just a log row.
Why: an active knob sweep can do 10+ renders/hour. Saving a folder
per render bloats runs/ fast and most of those folders are noise the
moment the next render lands. Cheap log row everywhere, expensive
snapshot only for runs worth pinning (knob landed, surprising failure,
something heading for findings.md).
Changes
- docs/automations/LTX-2.3/audio-lipsync/runs/LOOP.md
Step 5 split into "always log" + "snapshot only on request".
Trigger-detection table updated. Run-labels section reframed
(NNN matches log.md row number). Added a one-paragraph "why this
retention policy" rationale.
- docs/automations/LTX-2.3/audio-lipsync/README.md
Folder-map flags run-NNN_<label>/ as conditional. Iteration loop
step 5 names the keep / save-run / save-this triggers explicitly.
Not a code change. No script edits — the retention rule lives in the
agent protocol, not in dev-sync-audio (which stays scoped to code
sync, not run management).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(review): harden relay_overrides parser + polish (PR #178 review)
Addresses verified findings from the /review-pr pass on PR #178.
H1 — relay_overrides values are now type-checked
- Allowlist of recognised knobs (`video_strength`, `video_window_scale`,
`audio_strength`, `audio_window_scale`, `audio_epsilon`) with explicit
float-coercion. Numeric strings ("10") coerce silently; non-numeric
values (e.g. "ten") now raise a ValueError naming the offending knob
and showing a minimal correct example — instead of a TypeError deep
inside `build_segments` arithmetic, or a SUCCESS render at upstream
defaults that polluted the A/B iteration log.
H2 — malformed JSON no longer silently falls back to defaults
- `json.JSONDecodeError`, non-dict JSON (arrays, strings, bare numbers,
null), and non-numeric values for recognised knobs all now raise
`ValueError` with operator-actionable messages. The only silent
fallback is the explicit empty/whitespace case — "use upstream
defaults" is the operator's intent there.
- Typo'd keys (e.g. `viedo_strength`) log a warning naming the unknown
keys + the recognised set, then drop the typo. Previously they were
silently retained in the cleaned dict but never queried by
`build_segments`, indistinguishable from a default render.
Architectural side-effect — shrinks the diff vs vendored upstream
- `_parse_relay_overrides` extracted into a stdlib-only sibling module
`_relay_overrides.py`. `ltx_director.py` shrinks closer to upstream
baseline (one import + one alias instead of a 30-line function body)
and is easier to diff against `e81223a`. The parser is also now
unit-testable without ComfyUI's `comfy_api` runtime.
L5 — tests added (31 cases, all passing)
- New `tests/forks/__init__.py` + `tests/forks/test_whatdreamscost_v1_3_2_relay_overrides.py`.
- Covers every confirmed failure mode (H1 + H2), the happy path, the
comment-key convention, the unknown-key warning, and an allowlist-
consistency assertion that fails loudly if `build_segments` ever
grows a knob without the parser being updated.
- Test module uses `importlib.util.spec_from_file_location` so it can
load the parser without walking through the package hierarchy (which
would trigger the `comfy_api` import inside `ltx_director.py`).
L3 — `_build_line` promoted to public `build_line`
- `scripts/sync_to_dev.py` renames the private helper. The underscore
alias is kept for one release cycle as a deprecation slot.
- `scripts/sync_to_dev_audio.py` switches to `_dev.build_line()`. No
behaviour change; chat-report shape is bit-identical to before.
L2 — `source_ref` clarified in pin metadata
- `forks/whatdreamscost_koolook/versions/v1_3_2/UPSTREAM_PIN.yaml` and
`forks/forks_manifest.yaml` both now spell out that `e81223a` is the
post-v1.3.2 README-only commit on upstream's `main`, not the v1.3.2
release tag itself. Code is byte-identical to the tagged release; the
pin differs only in trailing docs.
L1, L4 are explicitly left alone (L1 is dead upstream cruft, kept for
minimum-diff; L4 is helper duplication tracked for a separate follow-up
issue when a third fork lands).
Verification
- ruff check . → clean
- pytest → 204 passed (173 prior + 31 new parser tests)
- tools/preflight_release.py --skip manager-meta → all PASS (static AST
extraction still picks up LTXDirector from the fork)
- scripts/sync_to_dev_audio.py --dry-run → two-line chat report still
emits via the renamed `build_line` helper
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(audio-lipsync): config-driven loop-audio script + module cheat sheet
End-to-end automation for one render cycle of the audio-lipsync
iteration loop. The chat phrase `loop-audio` invokes a single script
that does the whole chain — no manual artifact-shuffling.
Scripts (cross-cutting infrastructure at the repo root, like make_card
and sync_to_dev; they get lifted into the module folder in the
`docs/automations/` → `automations/` move queued for the follow-up PR)
- scripts/loop_audio.py
Reads its settings from loop_audio.config.json (alongside),
auto-discovers the most recent workflow JSON in the ComfyUI user
workflows dir (derived from KOLOOK_COMFYUI_DEV_PATH or overridden
via KOLOOK_COMFYUI_WORKFLOWS_DIR), extracts the five tracked Text
Multilines + LTX Director state + sampler chain, captures
fork-state (HEAD SHA + last dev-sync-audio sync info + fork-dir
git status), writes the run-NNN_<auto-label>/ snapshot, appends a
row to runs/log.md, and chains the card renderer in. Flags
mirror sync_to_dev: --dry-run, --label, --no-log, --no-card,
--workflow, --config.
- scripts/loop_audio.config.json
Single source of truth for the customisable settings (job_name,
module_path, workflow_pattern, comfyui_workflows_subpath,
skip_filename_substring, tracked_multilines, fork_to_track,
render_card). Underscore-prefixed keys are inline documentation
and ignored by the loader. When the convention shifts — new
multiline title, new fork path, different filename pattern —
edit the JSON, not the Python.
- scripts/make_card_audio.py
Audio-lipsync-specific card renderer. Five sections (KNOB STATE,
FORK STATE, SAMPLER, BASE notes, OUTCOME) plus an INERT warning
band when the workflow's Director is upstream `LTXDirector` with
a non-empty relay_overrides. Same palette + font fallback chain
as scripts/make_card.py so the base-1step and audio-lipsync card
families read as a set. PIL/Pillow added to the `test` optional
deps so CI doesn't hit ImportError if any future test touches
this script.
Docs
- docs/automations/LTX-2.3/audio-lipsync/CHEATSHEET.md
Module-scoped operating instructions per the cheat-sheet pattern:
what the module is for, the chat triggers, the required workflow
shape (5 multilines + the Koolook Director variant), the render
cycle steps, what lands in each run-NNN_ folder, findings-
promotion rule, where every customisable setting lives, and a
card-section legend.
- docs/reference/glossary.md
`Card` rewritten to describe the per-module renderer fan-out
(`make_card.py` for base-1step, `make_card_audio.py` for
audio-lipsync) — cards no longer live at a single stable
`_AI/card.png` path. New `Loop config (per-module)` entry
documenting the `<script>.config.json` convention + the
required-keys list.
- docs/automations/LTX-2.3/audio-lipsync/runs/log.md
Header rewritten with column set matching the actual log-row
output (Director / relay_overrides / Custom audio / Phase 1 /
Phase 2 / Scores / Notes).
First real run captured
- docs/automations/LTX-2.3/audio-lipsync/runs/run-001_bear_3x-fr_w.audio_upstream_audio-off_vstr10.0/
Baseline reference render — `Bear_3x-FR_w.Audio`, upstream
`LTXDirector` still wired into the workflow, use_custom_audio
off. Motion 4/5 · Sync 5/5 · Sharp 4/5 per maintainer feedback.
The relay_overrides `{"video_strength": 10.0}` is set but inert
(Director not yet swapped to the Koolook variant); the card
surfaces this with the amber INERT band at the bottom. Captures:
workflow.json (185KB), relay_overrides.txt (with INERT note),
patch_state.txt (3ffb7b7 / 15d5688 / clean), notes.md (feedback
+ scores + mechanical interp), card.png (61KB).
Verification
- ruff check . → clean
- pytest -q → 204 passed, 0 failed
- tools/preflight_release.py --skip manager-meta → all PASS
- scripts/loop_audio.py --dry-run → identifies the latest workflow,
emits the two-line chat-report header, doesn't touch disk
- scripts/loop_audio.py (real) → wrote the run-001 folder + card +
log row in a single invocation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(audio-lipsync card): match base-1step visual style 1:1
The first cut of the audio-lipsync card used solid colored header bands
and block section panels — visually heavier than base-1step's renderer.
Refactored to copy make_card.py's primitives directly:
- small uppercase accent-colored section labels (no header bars)
- subtle BG_SECTION panel outlines instead of full-width fills
- left-accented note boxes (4px coloured bar + dark BG + label inside)
for Base · notes, Feedback (video), and the conditional Director
note warning
- same font fallback chain + sizes as make_card.py (F_SECTION 17pt,
F_MONO 19pt, F_NOTE italic 18pt)
- oversized canvas + post-render crop matches make_card.py's pattern
so cards size to actual content
- palette imported in lockstep (BG_OUTER/CARD/SECTION, BORDER, the
three accent triples plus the new ACCENT_FORK violet)
Two tuning fixes spotted during re-render:
- _wrap_text gained a keep_blank_lines flag so prose blocks can
collapse the empty separator lines maintainer canvases often have
- max_lines bumped on the prose blocks: Base · notes 18 (was 6,
truncated the OVERLAY - INFO body), Feedback 6 (was 5), Director
note 8 (was 4, truncated mid-sentence)
- make_card_audio.py's standalone CLI now loads .env via
loop_audio.load_dotenv before rebuilding state, so re-renders show
the same FORK STATE lines (MAIN sha + sync sha + scope) that the
live loop_audio run captures
Visual reference: docs/automations/LTX-2.3/base-1step/example-cards/
run-001-card.png is the layout target. The audio-lipsync card now
reads as a sibling of that family — KNOB STATE in amber, FORK STATE in
violet, SAMPLER + Base · notes in sky, FEEDBACK + OUTCOME in green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(fork): ship LTX Director timeline-editor JS to the Koolook variant
The Koolook variant of LTXDirector was loading as a bare schema — no
image strip, no audio strip, no canvas timeline editor — because the
upstream timeline-editor JS bundle never came across with the fork.
The widget upstream paints onto the node lives entirely in
`js/ltx_director.js`; the Python class only declares the inputs.
Compounding it, the JS's binding check is hard-coded
``if (nodeData.name === "LTXDirector")``, so even if the bundle had
been loaded, it would have skipped our suffixed registry ID
(`LTXDirector__koolook_v1_3_2`).
Fix: vendor the JS into the fork and rebind it.
- web/whatdreamscost_koolook_v1_3_2/ltx_director.js
Verbatim copy of upstream's
custom_nodes/WhatDreamsCost-ComfyUI/js/ltx_director.js at the
pinned commit (e81223a). Two string changes vs upstream:
* `name: "LTXDirector"` -> `"LTXDirector_Koolook_v1_3_2"`
so the registered ComfyUI extension doesn't collide with the
upstream extension (both load in parallel).
* `if (nodeData.name === "LTXDirector")` ->
`if (nodeData.name === "LTXDirector__koolook_v1_3_2")`
so the canvas widget mounts on the Koolook variant only;
the upstream extension keeps owning the upstream node.
A short comment block at the top documents both deltas.
- scripts/sync_to_dev_audio.py
AUDIO_PATHS gains `web/whatdreamscost_koolook_v1_3_2` so
dev-sync-audio carries the JS into the live install alongside the
fork's Python files + root __init__.py.
After dev-sync-audio + ComfyUI restart, the Koolook variant mounts the
same timeline editor as upstream: image/audio strips, segment dragging,
the visual preview row, the [Audio] tag handling, the
img_compression/divisible_by widgets. The `relay_overrides` field
declared in the Python schema renders at the bottom of the node
alongside everything else.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>