# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## What this is `ComfyUI-Hand-Tie-Clips` chains multiple MiniMax H3 Reference-to-Video generates into one longer clip. Each hop after the first is guided by the previous hop's last overlap frames + audio via stock `MiniMaxH3AddGuide` (requires a ComfyUI build with `MiniMaxH3AddGuide`, ComfyUI PR #15439). It is explicitly **not** the H3-Multishot/airlock pack; do not merge that pack's syntax in here. Two nodes ship: `HandTieClips` (**H3 Ref2VA Chain**, the whole pipeline) and `HTCContinuityState` (legacy continuity text, superseded — see below). It lives inside a full ComfyUI checkout (`D:\ComfyUI\custom_nodes\ComfyUI-Hand-Tie-Clips`) and imports directly from ComfyUI internals (`comfy.model_management`, `comfy.samplers`, `comfy.utils`, `comfy_extras.nodes_minimax_h3`, `comfy_extras.nodes_custom_sampler`, `comfy_extras.nodes_audio`, `nodes`, `folder_paths`). It only runs as a loaded custom node inside that ComfyUI instance — there is no standalone entrypoint, package manager, or test harness. ## The target sampling regime **Turbo LoRA at 4-8 steps.** That is what the node is built for and what the shipped workflow uses (8 steps, `minimax_h3_ref2v_turbo_4step`). High-step runs (14+) are diagnostic instruments to check whether an effect exists at all; **any change must be re-validated at 8 steps before it counts as working**, because at low step counts seed structure dominates and can swamp conditioning differences. Never report a 14-step result as validation on its own. ## Development workflow No build step, linter config, or test suite (`pyproject.toml` declares zero dependencies beyond ComfyUI). The dev loop is: - **Python changes**: restart ComfyUI to reload the node. - **JS changes** (anything under `js/`): hard-refresh the ComfyUI browser tab. - Validate by loading `workflows/HandTieClips_Starter.json` and queuing a run. Two signals: - the `[HandTieClips]`-prefixed console lines (hop count, shot plan table, reference register table, model-patch log, cache hits, overlap drop, final frame/duration summary); - the node's third output, **`info`**, which carries the fully assembled per-hop prompts. Wire it to a Preview Text node to read exactly what each hop sent to the text encoder. The editor shows what you wrote; `info` shows what the compiler made of it, which is not the same thing. ### Pushing, when the change touches `docs/img/` `origin` carries **two push URLs** (GitHub and HuggingFace) so one `git push origin main` mirrors both. Git LFS does not follow that: it uploads objects to the first URL only, so GitHub gets a pointer in the tree with no object behind it and rejects the push with `GH008: Your push referenced at least 2 unknown Git LFS objects`. Push the objects first: ``` git lfs push https://github.com/dntpi/ComfyUI-Hand-Tie-Clips.git main git push origin main ``` Two further traps behind that one, both silent: - **`raw.githubusercontent.com` does not resolve an LFS pointer.** It serves the 131-byte pointer file as `200 OK` / `text/plain`, so every `` breaks with no error anywhere. Use `media.githubusercontent.com/media////`, which is what the README and the `Icon`/`Banner` fields point at. `github.com///raw/` behaves like `raw.`, not like `media.`. - **HuggingFace refuses plain binaries regardless of size** -- it rejected PNGs of 262-545 KB, not just the >10 MB the old `.gitattributes` comment assumed. Anything binary under `docs/img/` must be LFS or the mirror push fails. ## Architecture Python modules feed four nodes, plus a DOM editor in `js/`. `h3_ref_chain.py` holds `HandTieClips.run()` and the prompt-assembly helpers; `plan.py`, `directives.py`, `refs.py`, `store.py` and `tone.py` are parsers, compilers and estimators with no ComfyUI imports in their math, which is what makes them testable outside a running server. `routes.py` serves the editor its vocabulary. `preview_node.py` and `tone.py` each register a standalone side node. ### Two independent reference channels — do not conflate them The core invariant, and the thing most likely to be broken by a careless change: 1. **References** (files in `/h3_refs`, named by each ref's `file`) → bound into the tokenizer as `` *and* into the DiT as `minimax_refs`. Identity/place stills. On `hop_script=next`, they ride **hop 1** unless a ref's `shots` lists later hops. They are never the join pin. Riding a face/outfit plate of a different room on hop 2+ opens a new Ref2VA generate (chain_00030..00034: commercial-kitchen face+outfit beat the residential pin; hop 2 dropped the apron). 2. **The AddGuide pin** (`MiniMaxH3AddGuide.execute`, once per hop after 0) → DiT `minimax_keyframes` at t=0 only. **The text encoder never sees this pin.** The master throws the overlapped frames away after decode. `pin_to_qwen` (`_attach_pin_to_qwen`) optionally makes the *text encoder* aware of the incoming state, since channel 2 is otherwise invisible to it: `last frame` is **``** of the previous hop's last frame (identity stills that still ride shift to Picture 2+), `pin clip` appends the overlap frames as an extra `