Both replace what a block allocates, which crashes aimdo's compiler (seen on a
rental with v4's shadow negative). Attached to the model, so any sampler.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
H3's latent upscaler loads through the H3 module (ComfyUI's loader has no
branch for it); an upscale resizes H3's anchor pin so pass 2 stays pinned.
FunPack Sampler takes an optional wired schedule, run exactly as written.
New preset: H3 Reference to Video + second pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The H3 module offers anchor_pin, so ALG blurs the pin's latent without
learning H3's payload layout.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
core.patching.strip now also removes our object patches, so a feature switched
off does not keep its patch on a model that goes round again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v4 read the prompt length from transformer_options['c_crossattn'], which
ComfyUI never sets, so every block fell back to stock. It also stood down on
any i2v scene (per-token mod rows). Both fixed; tested on a real tiny H3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Block repeat's video-only mode now really keeps audio single-pass: H3 blocks
write in place, so v4 restored audio from an already-repeated stream.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The port plan calls for every module under modules/ to declare a
CATEGORY so Settings/Engine panels can group by it. Only alg, sharpen,
appearance, and full_control had one; the other 12 (loaders, empty
latent, media load, minimax_h3, output decode, sampling modifiers/
sampler, system pipeline) are plain infrastructure with no user-facing
settings dial, so they land in "system" except output_decode (post,
matching its STAGE) and the two sampling modules (sampling). Verified
against core/tests (548 passed) -- category validation and the
registry's field-mapping catch a typo here, per the earlier fixture-gap
fix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two pieces of the "port v4's exact UI onto v5's backend" plan:
- core/contract.py: new optional ModuleSpec.category field (closed set:
continuity, guidance, conditioning, sampling, post, system), validated in
core/schema.py, threaded through core/registry.py's FIELDS and
to_manifest(). Presentation grouping, independent of STAGE (execution
order) -- a module can be stage="sampling" and category="guidance" at
once. Only the 4 existing modules that actually render a settings panel
(alg, sharpen, appearance, full_control) got a category assigned; the
other 12 have none to categorize (no SETTINGS block) and stay
uncategorised, which is valid.
- v4's frontend (movie_editor/frontend/*, ~29k lines, 61 files) copied
verbatim into app/legacy/. core/routes.py's "/" route now serves
legacy/index.html instead of the composer entry point; a <base> tag in
that file resolves its own relative script/link tags against
/funpack/app/legacy/ regardless of what URL it's served at, so every
file moved as a unit with zero per-file path edits. Verified live: all
61 assets load 200 OK, the real v4 onboarding screen renders pixel-for-
pixel (it's the actual DOM/CSS, not a reimplementation), and several v4
API calls (/api/media, /api/projects, /api/git/status) already succeed
against v5's real backend by coincidence of shared route naming -- the
rest 404/405 as expected, since api.js's ~90 functions haven't been
rewired to v5's actual routes yet (next piece of this plan).
The old composer entry point (app/index.html, app/composer/**) stays in
the tree, unreferenced, until a later cleanup pass once nothing imports it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Every module panel was decoration. The values lived in the browser and
nothing sent them, so a modifier switched on was one the run never heard
about. They now travel with the pipeline on every check, and core places
them wherever a module says they belong -- announced, not known, so the
node that carries them is not privileged.
Settings with nowhere to go are said beside Generate rather than
swallowed, and the warning survives the run it is about: the status line
is redrawn by every message, so a warning put there was gone the moment
the run said Queued.
Also: a settings row in a docked panel squeezed its label to one word per
line, because the control keeps a 140px floor and took it out of the
label. Below the width where they fit side by side, they stack.
The wording fix a commit ago was written by hand in two files and the same
mislabel was still sitting in four others -- traits, chain, modifier install and
node schema all reported a thing that loaded, recognised its work and then
raised as one that "did not load", sending a reader after an import error that
never happened. Three hand-written strings would have drifted, so log.broke()
now says it once and every site calls it.
Two of the new tests were vacuous when first written -- "nothing said did not
load" is also true where nothing ran at all, and picking the first provider
found lands on one whose traits exclude the test's model. Both assert the line
is present before asserting the wrong line is not.
One finding, confirmed, plus four sites it did not name.
Detail declared no traits, which read as "works on everything" and meant "is
offered on everything and drops itself on half of them". Core's unsharp mask
pads in two dimensions and raises on anything with more, so on a model with a
time axis the toggle was present, switchable, and inert -- a knob that does
nothing, which is the fault this project has shipped once already. It now
declares spatial_latent and is absent where it cannot work.
The guard's regression case survives, because the guard is not about a module
being wrong about itself: a hook meets the tensor it was handed. The stub model
now announces a latent rank, without which every trait-declaring modifier was
filtered out and those tests passed by installing nothing at all.
Round seven, one finding, confirmed in part -- the maths is deliberately not
being fixed, the advertisement was the defect.
Full control did half of what it said and never said which half. It disabled
the mid-run crash guard and left every value refusal in place, so the stated
purpose -- a one-step schedule, a width the model cannot use, let them and
warn -- did not work. Worse, a rejected value was quietly replaced by the
default, so asking for 999 gave 0.4 with a warning about 999.
Now one switch turns off both: the value is passed through, every problem is
said as a warning naming that the consequences are the user's, and a run made
without the guards is identifiable as one afterwards. A key NO module
declares is still never kept, whatever the setting -- nothing reads it, so
keeping it cannot honour the request, only disguise the typo.
core/graph.py was thoroughly tested and called from nowhere: the claim that
a built-in node can be replaced or removed was a property of a library
nothing invoked. It has routes now, and tests that drive them over a real
socket -- the modifiers node removed and the sampler rewired, the VAE loader
swapped for core's, and both refusals coming back as reasons.
Those routes separate two things the old shape conflated. "refused" means
the edit did not happen. "incomplete" means it did and the pipeline still is
not ready -- an unset file picker on a fresh install is the normal case, not
a failed edit, and showing them together would say the wrong thing about both.
And build() could not see a missing REQUIRED input at all, because the
schema lookup flattened required and optional into one dict. A slot with no
model file built clean and failed only once queued.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Stopping a generation is a normal thing to do -- a bad seed, a wrong prompt,
a rental minute -- so it is not an error path that can be left approximate.
A leak here is invisible: the run AFTER a cancel just behaves slightly wrong.
The sampler says a run was cancelled and re-raises. It is not ours to
absorb; ComfyUI ends the queue item. But a run that simply stops with
nothing said is indistinguishable from one that broke.
Verified against the real engine: a 40-step generation interrupted mid-way
reports execution_interrupted, and the next one completes as run 2. Tests
cover what must be left behind, which is nothing -- no wrappers on the shared
model after five cancelled runs, the sharp anchor put back on a denoiser that
outlives the run, and no modifier marked as dropped, because a cancelled
modifier did not fail and disabling it next time would punish the user for
stopping.
Temp previews needed no change: core's PreviewImage already writes to the
temp directory under a unique prefix, so where the app SHOWS them is a UI
decision and is recorded as one rather than built here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>