Commit Graph
1319 Commits
Author SHA1 Message Date
digital-garbage b28071bca2 fix(ui): node shelf fills its row; light preview stage isn't black
The shelf capped tiles at 168px, so a pane wide enough for three but not four
left a dead strip down the right. Tiles now take 1fr and stretch to fill the
row — safe because the art is a fixed height, where an aspect-ratio would have
made a wide column a tall one too.

The preview letterbox stays black behind real video in both themes, but an
empty or generating stage is just a panel: in the light theme it was a black
rectangle with slate text and a vignette.
2026-08-13 14:13:28 +03:00
digital-garbage db70062bb7 feat(ui): one start screen, and chrome in the system face
The welcome card is gone. The full-screen splash is the start screen for both
frontends, shown whenever nothing is open — Begin, the last project, the full
project list (with import), and the maintenance actions. "Load an existing
project" now opens a picker inside that surface instead of handing off.

Chrome type follows Apple's conventions: group headers, field labels, tabs,
segmented controls, lane names and status chips are sentence-case system sans,
sized on the scale, with no letter-spacing. Monospace is now reserved for data —
timecodes, counts, paths, prompts, node classes — which is what it was doing in
the timeline ruler all along. Zone padding moves onto the 4pt grid.

Scene length/fps modes read "Project default" / "Timeline trim" instead of
labels too long for their control.
2026-08-12 19:56:10 +03:00
digital-garbage 457136ca68 feat: t2v mode, a type scale, Inter, and 4.0.0
t2v: projects carry generation_mode ("i2v" default). In t2v nothing checks or
reports a missing anchor, and a new scene starts empty rather than from an
image. Images stay wireable either way — the mode decides what is expected, not
what is allowed. Switchable any time in Project settings; the wizard writes it.

Scaling: one type scale (Apple's macOS text styles) replaces ~64 off-grid sizes
like 11.5px, body drops 14 → 13, and buttons/fields/menu items take their height
from shared control tokens instead of per-component padding. Fixes the timeline
zone head overlapping the panel above it: its status block wraps, and a fixed
34px height let it spill both ways.

Node cards carry their name and destination ON the tile instead of captioning it
from below, and no longer print the class twice. The add-node list flips above
the card when it would run off the bottom.

Fonts: Inter (open, Helvetica/SF-alike) with system-ui behind it, Inter Tight
for display, Roboto Mono for mono. Apple ships SF free but licenses it for work
on Apple platforms only, so it cannot be bundled.

Also: "Continue with <project>" on the wizard splash, and About reads
FunPack 4 "Blinding Blackout" 4.0.0.
2026-08-12 19:43:39 +03:00
digital-garbage 218bf5e628 feat(wizard): tour step, one continuous surface, restart-on-abandon
Setup panes no longer hand you off elsewhere. Models, pipeline links, prompt
shortcuts and prompt splits mount as full screens of the wizard itself, with the
section's own toolbar in the header and one Done button back to the step you
came from. Same builders and editors as before — SettingsWindow.mountSection()
and Composer.mountPane() host them without the window around them.

New Tour step before the last screen: Watch tour / Skip the tour, with the tour
starting when setup finishes.

An unfinished wizard now starts over instead of resuming mid-flow. Only the
wizard's own ComfyUI restart (installing node packs) writes a resumable record;
a refresh or a closed tab does not. The project an abandoned run created is
adopted by the next one, so changing your mind leaves nothing behind.

The wizard also stops appearing when it has already been answered: loading a
project from the splash silences it for the session, and it never opens over a
project that is already open. File ▸ Project Setup Wizard (Easy Gen: Setup
Wizard…) runs it again on demand.

Renames the LTX family label to LTX2 / LTX2.3 / LTX2.5.
2026-08-12 19:18:16 +03:00
digital-garbage 5babbdda82 fix(settings): hold autosave until the Settings window closes
Every knob in Settings writes through the store, so the debounce could fire
mid-session: it sent a half-edited value and came back with a server copy that
replaced state.project, putting the knob under the cursor back where it was.

Opening the window now holds scheduled saves; closing it writes once, with the
values on screen. An explicit flush still goes through, so Generate and the
update/switch/restart actions cannot build or reload from an unsaved project.
Leaving the tab flushes too.

Also fixes the same stale-echo race in the models save path (an edit made while
a save was in flight was overwritten by the response), and makes commit() wait
for an in-flight models reload before copying it into project.models.
2026-08-12 19:06:18 +03:00
digital-garbageandClaude Opus 5 2c0b08bdee fix(models): wires onto core ports can be removed again; readable node shelf
A wire onto an open core port is recorded twice — as the slot's wire and as a
core_override — and reconcileOpenPortWiring() rebuilds each from the other. Removing
the wire cleared only the first, so the next reconcile put it straight back. That is
why "Studio · model" could not be unwired without deleting the node. _setWireTarget
now drops the override too, and only when it still names that same wire.
Confirmed both ways: with a stale override alone, reconcile resurrects the wire.

Cards now derive their name from the file the node loads, so five LoRAs read as
add-detail-xl / filmgrain_v2 instead of five nodes called "LoRA". An explicit rename
still wins. Each card also says where it feeds ("→ Studio · model"), using the same
derived names, so the shelf shows how the pipeline is put together.

Tiles are capped rather than 1fr — in a narrow pane two stretched columns made each
card ~270px tall.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 18:47:43 +03:00
digital-garbageandClaude Opus 5 ef9f7d02f7 feat(models): node shelf, and no autosave while a node is open
Nodes are a grid of cards led by a "+ New node" tile; clicking one opens that
node's editor. The sidebar keeps only the two places you can be.

The important half is the saving. Editing a node saved on every change, so a save
landing between two keystrokes wrote whatever was on screen at that instant —
start at 1.0, type 0.5, and 0.58 could reach disk with no way to tell. The editor
now buffers: config is still mutated in place (wiring edits legitimately touch
other slots' wires, so a per-slot draft could not hold them) but persist() is a
no-op until Save. Cancel restores the snapshot taken when the page opened, and
navigating away with unsaved changes asks first.

Verified: five intermediate values including 0.58 typed into a float field leave
the server on 1.0; Save writes 0.5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 18:39:25 +03:00
digital-garbageandClaude Opus 5 9916840cb1 fix(theme): apply the colour scheme on click only
The picker previewed on hover, and preview stamped data-theme-pref as well as
data-theme. That attribute is what get() reads, so after clicking a card, hovering
another and moving away restored the HOVERED theme, not the chosen one — clicking
looked like it did nothing.

Hover preview is gone entirely: the theme now changes on an explicit click and
nothing else, which removes the ambiguity rather than patching it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 14:46:14 +03:00
digital-garbageandClaude Opus 5 03c0b2dcd9 fix(onboarding): setup panes open over the wizard instead of replacing it
The wizard sat at z-index 800, above .modal-overlay (500) and below .fw (4000),
so "Open Models…" opened behind it and did nothing visible, and the extras step
worked around that by closing the wizard first — which dropped you into a
half-finished editor. It now sits at 400: Models, Settings and the Composer layer
over it the way they layer over the editor, and closing one returns to the step.

Prompt splits called Composer.open(), which does not exist; the export is toggle().

Also: MiniMax H3 no longer pulls in ComfyUI-LTXVideo. LTXFloatToInt only converted
the project's frame rate for user slots, and H3's rate is fixed at 24 by the model,
so an LTX pack was a hard requirement of a pipeline with no use for it. Core
producers are now filtered by what the family's graph actually built.

The Cutting Room brand links to the hub, as Easy Gen's already did.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 14:41:02 +03:00
digital-garbageandClaude Opus 5 f28e57fbe3 feat(onboarding): first-run wizard for both frontends
Shape follows the macOS Setup Assistant: full screen, no chrome, one centred
column per step — glyph, headline, one short paragraph, controls — with the
primary button in a bar pinned to the bottom so it never moves between steps,
and Back as a chevron in the top-left. Type follows Apple's macOS text styles;
--font-system resolves to SF Pro on a Mac.

Splash (Welcome to FunPack / Begin / Load existing, Return advances) then:
theme, name + model family, missing node packs, t2v-or-i2v, models, optional
links/shortcuts/splits with Skip for now, ready. Steps a host runs are declared
in one table — Easy Gen has no timeline, so it never offers prompt splits.

Installing node packs restarts ComfyUI, so the wizard saves its position and
resumes there after the reload instead of starting over. Returning users still
get the project picker, not the wizard.

pipeline_setup gains applyFamily/markHandled so the wizard can drive it without
its modal reopening over the finished editor, and no longer assumes the Editor's
store — Easy Gen keeps model config on project.models and has no loadModels.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 14:28:56 +03:00
digital-garbageandClaude Opus 5 c5b2e6e48f feat(splash): Update / Switch branch / Restart on every entry screen
One shared FunPackGit.maintenanceRow(), used by the Editor's welcome screen and
Easy Gen's project menu; the hub gets the same three, with the branch list as a
select since /git/status already carries it. These are the actions you need
before opening a project, so none of them should require opening one.

Also drops the phantom "origin" entry from the branch list — refs/remotes/
origin/HEAD shortens to a bare remote name, which fails on checkout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 14:04:01 +03:00
digital-garbageandClaude Opus 5 65976d0c26 feat(theme): light, dark and auto colour schemes
styles.css and easy.css no longer name a colour, only a role: ~250 hard-coded
literals became semantic tokens, with the dark values kept byte-identical. Light
is a white/grey/daylight-blue palette; auto follows the OS and is resolved in
theme.js, so CSS only ever sees dark|light and there is no first-paint flash.
Media letterboxes, badges over frames and the log body stay dark in both.

--amber* is now --accent*, since the accent is amber only in the dark theme.
Picker in Settings ▸ Appearance for both frontends; the hub follows the same key.

Also fixes the rating picker's rgba(var(--accent), x) rules, which were invalid
and being dropped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 13:55:40 +03:00
digital-garbageandClaude Opus 5 5cbe20e6c9 ui(models): input sources above values, wired outputs at the bottom
Node cards and the add-node panel now read top-down in signal order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 13:42:37 +03:00
digital-garbageandClaude Opus 5 7b84dfeebc docs: trim backstory from changelog, README, tooltips and comments
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 03:21:48 +03:00
digital-garbageandClaude Opus 5 7329cd9680 fix: multi-scene chains work with a resolution-changing second pass
second_pass_op="upscale_2x" hands a scene back at twice the latent size while
every LATER scene is still built from latent_template at the original size.
Everything that crosses a scene boundary — carried overlap frames, the anchor's
continuation, the soft join, the JoyAI memory frame, per-scene guide sources —
is spliced into a chunk on the template's grid, so the second scene died on the
shape mismatch and the combination was simply unavailable.

One choke point, _match_template_resolution, brings the carried material back
before it is used, applied at the three places a finished scene crosses into the
next one. Each scene still samples and OUTPUTS at 2x: only what is carried gets
resampled, and only downwards, which is the direction that survives it — those
frames exist to say "continue from here", which a resample preserves far better
than invented detail would.

Identity no-op when the grids already agree, so any run without a resizing op is
bit-identical. Audio has no spatial axes and is never touched. A stale
noise_mask is dropped rather than reapplied at the wrong scale, and a resample
that throws costs continuity with a console line, not the run.

No grid guard on the chain blend: second_pass and second_pass_op are run-level
settings applied to every scene, so a chain cannot hold two resolutions — every
scene is upscaled or none is. Both tooltips said this combination dropped
multi-scene; they now say how it works.

Also: About reads "FunPack 3" with the codename on its own line, rather than
attaching the major to the codename.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 00:05:52 +03:00
digital-garbageandClaude Opus 5 06c3f75080 release: 3.5.1 "Auspicious Asparagus"
Release codenames, the way Ubuntu does it: an adjective and a vegetable
sharing an initial, advancing alphabetically, one per MAJOR version — so all
of 3.x is "Auspicious Asparagus" and only a new major earns a new letter.

CODENAMES lives beside funpack_version() because the two are read and shipped
together, and rides out on the same git-status payload the About panel already
fetches. A major with no entry renders as absence, not as an empty pair of
quotes. The test pins the naming rule itself (shared initial, ascending) so a
future entry that breaks the pattern fails rather than quietly shipping.

Version 3.5.1 in pyproject; CHANGELOG entry covering the whole cycle (the
Unreleased section only documented the first few commits of it); README gains
a Versions preamble explaining the scheme plus notes for 3.5.0 and 3.5.1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 23:55:53 +03:00
digital-garbageandClaude Opus 5 98d48e78e3 fix: cut_opening_frames cuts decoded pixels, not latents (noisy first frame)
The opening frame came out noisy because the cut was made in latent space. The
LTX video VAE is causal: latent frame 0 is the temporal origin and decodes to a
single pixel frame, while every later latent frame was generated as a
CONTINUATION of the frames before it. Slicing the front off the latent promoted
a continuation frame to position 0, and the decoder then applied the
temporal-origin handling that frame was never generated for. H3 already cut
decoded images for unrelated reasons and so never had the bug; LTX now does the
same thing.

The count was wrong too, quietly. A latent cut could only remove whole latent
frames, and the promoted frame changed from covering 1 pixel to covering
`scale`, so dropping k latent frames shortened the clip by k*scale rather than
the (k-1)*scale+1 it actually spanned. The value was also rounded up to a whole
latent frame on the way in. It is now exact: N means N, matching H3.

Each eligible scene records a (start, count) pixel span instead of cutting; the
spans are removed after decode, later ones first so earlier removals cannot
shift their indices. Mid-chain hard-cut scenes keep working — the span is
computed from the scene's position in the chain, which is exact because anchor
scenes append with zero blend overlap. The latent's audio stream loses the same
stretches of TIME, mapped by proportion on its own axis, or sound would run
ahead of picture with no error. The latent's VIDEO stream is deliberately left
whole (cutting it is the bug), so video comes from the IMAGES output — which the
Editor already wires. A run with IMAGES unconnected now skips with a reason
instead of silently doing nothing.

_cut_opening_latent is removed rather than left dead, since its docstring
documented the broken approach as correct.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 23:47:51 +03:00
digital-garbageandClaude Opus 5 e7b624d684 fix: drop guide keyframes on a resized second pass; unify the two ALG switches
Two unrelated fixes.

**Guide keyframes vs a resolution-changing second_pass_op.** Guide keyframes are
recorded as TOKEN indices into pass 1's latent grid, so upscale_2x — which
doubles each spatial dim and thus quadruples the tokens per latent frame —
leaves them addressing the wrong tokens. Since the LTX-2.5 commit core rejects
that outright ("keyframe_idxs holds N tokens, which is not a whole number of
M-token latent frames"), which is how this surfaced; older cores silently
mis-placed the guides instead, which is worse. Pass 2 now drops them and says
so in the scene report, exactly as segmented detailing already does for its
crop — so detailing's stripper becomes public rather than being duplicated. Only
pass 2 is affected; pass 1 ran at the grid the guides were recorded against and
keeps every one. The pin loss and the guide loss now share one predicate
(_latent_spatial_changed) so they cannot drift apart, and an unreadable shape
answers "no change" so a probe failure never costs a guide.

**One ALG control instead of two.** The Distilled Flow panel had its own
alg_enabled while the chain sampler's alg_anchor did the same guidance on any
sampler and already drove the Distilled Flow one when set — two switches for one
behaviour, with precedence depending on which you had touched. The Editor now
shows only alg_anchor. The node input stays for hand-built graphs; what moved is
the Editor's control. A project still carrying the old switch is migrated once,
strength and threshold included, and told so, rather than left running ALG with
nothing on screen to turn it off.

The migration is deferred out of the render pass and guarded by a module-level
set of project ids: every store write re-renders the pane synchronously, so
migrating inline recursed until the stack blew (caught in the browser, not by
the suite).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 23:39:47 +03:00
digital-garbageandClaude Opus 5 b6fa5b177d feat: About panel reports the host's hardware and software
About showed only version/commit/branch. Add the facts you actually want to
check on a fresh rental: chip, memory, GPU (name, VRAM, compute capability),
free disk, OS, ComfyUI, Python, torch (+CUDA), and which fast-attention
backend is importable — the last one answers "is sage actually installed
here" without reading the launch args.

Everything describes the machine ComfyUI runs on, not the browser. On a
rental those differ, and the host is the one worth looking at, so the panel
says so in a line under the facts.

Every probe is individually guarded and degrades to a dash: About is the one
screen that should still render when the install is broken, so no missing
psutil, torch, or shell binary may raise. The frontend optional-chains the
call, so a frontend sharing settings_window.js without the endpoint keeps the
old version-only panel instead of erroring.

Facts move to a two-column grid; values now range from "3.14" to a full GPU
name, and only a shared column keeps the keys aligned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 23:15:30 +03:00
digital-garbageandClaude Opus 5 03158b4edf fix: two LTX-2.5 assumptions that would have failed silently
LTX 2.5 reuses LTXAVModel/LTXBaseModel behind config flags, so the samplers,
the identity-transfer wrappers and the guide path all bind to it unchanged.
Two places assumed 2.3 specifics, and both would have gone wrong quietly.

The AV channel split was a hardcoded width table. From 2.5 on comfy reads the
video/audio projection widths off the checkpoint rather than assuming them, so
a checkpoint can ship a pair the table has never seen — and an unrecognised
width made ltxav_video_channels return the FULL width, which silently steers
the audio text context along with the video one. The only symptom would have
been degraded audio. The split is now measured off the live model when one is
on hand; the table stays as the fallback.

Decode-time noise only ever reached VideoVAE.decode. 2.5's diffusion decoder
takes no timestep and seeds itself, but it is an nn.Module, so stamping the
attributes onto it succeeded and was then ignored — a knob that reads as live
and does nothing. It is now capability-tested against the real contract
(hasattr decode_timestep, not a class name) and says so when it cannot apply.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 22:36:38 +03:00
digital-garbageandClaude Opus 5 c1f40ec48e fix: the ALG toggle belongs with the sampler, not under Experimental
It shipped in Engine ▸ Experimental, which is not where anyone looks after
picking a sampler — with KSampler selected there was no ALG control in sight.
Moved to its own group in Engine ▸ Sampler algorithm, directly under the
sampler panel, and the Distilled Flow section now says the two ALG switches
are the same guidance so only one of them is needed. Guide/JoyAI blur stays
under Experimental, with a pointer to it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 03:58:41 +03:00
digital-garbageandClaude Opus 5 48ff7f0f48 feat: schedules for every sampler, a usable frames field, ALG on any sampler
Three things that were each locked to one implementation detail:

- steps + schedule were a KSampler-branch feature, though every sampler here
  walks a sigma list handed in from outside. They are now pass-level, so
  Distilled Flow and Hybrid Euler 2S can run a named schedule (and the second
  pass can be computed rather than typed) in both frontends and on the node.
- the project's Frames field snapped to the model's grid on every family. On
  H3 that is required, but its arrows moved by 1 and the snap put the value
  straight back; on LTX the builder rounds up by itself, so the snap only
  fought the user. H3 now steps a whole 17 frames per arrow, LTX keeps what
  you type and walks 8k+1 with its arrows.
- ALG lived inside the Distilled Flow step loop, so it was unreachable with a
  stock KSampler. Its blurred/sharp swap is decided by the step's sigma, and
  sigma is an argument of every model call, so a denoiser proxy drives it from
  outside any sampler. New alg_anchor toggle on the Chain Sampler drives the
  in-loop version too, so there is one control wherever you are.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 03:53:11 +03:00
digital-garbageandClaude Opus 5 75ebbb790b fix: only warn when Studio has NO conditioning source at all
A CONDITIONING wired into positive_conditioning is a deliberate setup —
the prompt is encoded somewhere else before it reaches Studio. Warning
about it (and about a CLIP sitting alongside it) told users their working
pipeline was broken, which it isn't.

The chip now fires for the one case that cannot generate anything: no
text encoder on Studio · clip AND nothing on positive_conditioning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 22:34:04 +03:00
digital-garbageandClaude Opus 5 f7d20b4999 feat: project texts reach nodes that encode on their own, expanded
The empty '"prompt" is empty' warning on a wired MiniMax H3 fl2va/ref2va
node was the whole story: with a CONDITIONING wired into Studio and no
CLIP, Studio takes that encoding as-is, so what generates is the wired
node's OWN prompt field — the project prompt never reaches the model.

Three parts:

* Linked inputs gain the missing project texts: anchor, postfix, and
  prompt+postfix, alongside the prompt and negative prompt they already
  had. The global prompt already carries the anchor; the postfix is the
  only piece outside it.
* Every linked TEXT is now shortcut-expanded and $variable-resolved
  before it reaches the node, in Studio's order (variables last). A node
  outside Studio does neither, so the raw text would encode the literal
  /trigger and $name. The revolver is peeked, not committed — the run's
  own expansion is what advances the cycle. Studio's own port still gets
  the raw text and expands it per scene itself.
* A chip beside Generate when the wiring makes one of the two prompts
  inert: no CLIP means the project prompt is not being encoded at all,
  a CLIP alongside means the wired conditioning is dropped. Both used to
  cost a generation to discover.

Also: an empty widget that is driven by a link no longer warns — blank
is its normal stored state.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 22:25:55 +03:00
digital-garbageandClaude Opus 5 7d3cf61920 fix: an fl2va/ref2va node satisfies H3's AV latent requirement
MiniMaxH3ImageToVideo and MiniMaxH3ReferenceToVideo emit their own AV
latent next to their conditioning, so an Empty MiniMax H3 AV Latent is
not needed alongside one. The requirements panel only counted a slot
filed under the empty_latent ROLE, so the row read as missing for anyone
who added the node as what it mostly is — a conditioning source.

A requirement now counts any slot that genuinely SOURCES the type
(outputs it without consuming one), whatever role it carries. VAE keeps
the role check: it is asked for twice and only the role separates the
video decoder from the audio one. A pass-through patcher (a LoRA on
MODEL/CLIP) still never satisfies a requirement by itself.

The builder needed no change here — it already auto-wires the node's
LATENT into the sampler's latent_template.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 22:13:00 +03:00
digital-garbageandClaude Opus 5 bd9cb3062a fix: a wired positive_conditioning satisfies Studio's CLIP requirement
Studio encodes through clip, but a pre-encoded CONDITIONING on
positive_conditioning replaces that path entirely. The Editor still
treated Studio.clip as unconditionally required, so a project that
brought its own conditioning was blocked — and with two text encoders
installed it was blocked on 'N possible sources, wire it explicitly'
for a port it did not need.

The port is now waived when its alternative is wired, and deliberately
left unwired in that case: auto-wiring a CLIP would take precedence
over the user's own conditioning and silently win.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 22:04:37 +03:00
digital-garbageandClaude Opus 5 4e6c68bf51 feat: the project remembers your Editor settings, so a new rental doesn't reset them
Every Editor preference was per-MACHINE: autocomplete, shortcut ideas, the anchor
toggle and the custom-i2v bypass in browser localStorage, and the shortcut
revolver in a server-side sidecar next to the shortcut DB. A freshly rented box
has neither, so all of them silently revert to their defaults and have to be set
by hand again — the revolver's Random mode every single time.

They now ride along in the project file (Project.editor_settings), and opening a
project puts them back. The revolver is still a server setting shared by every
browser and by generation; the project only restores it, and only POSTs when the
value actually differs, so opening a project that already agrees never restarts a
live cycle.

Two cases that would otherwise leave the feature useless for an existing setup:

* A project saved before this existed, opened on a configured machine, adopts
  the machine's current settings instead of staying blank — including reading the
  server's current revolver mode. Nothing changes in the moment; it just stops
  the project from travelling empty.
* setEditorSetting mirrors into the project BEFORE its unchanged-value return.
  Re-picking a value you already have is a no-op, so without this the settings
  you never touch again — exactly the ones you'd lose — would never be recorded.

An ABSENT key still means "leave this browser alone": loading an old project must
never overwrite a configured setting with a default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 19:41:18 +03:00
digital-garbageandClaude Opus 5 946ea6b23a feat: every Engine setting says what it does, what it costs and what it needs
The one-line hints only covered the settings that already carried a wall of
text; roughly forty rows had nothing at all — Guidance, Decode, JoyAI, ALG,
DynaShift, the whole Continuity pane, and every Studio refiner field, none of
which even passed a hint through to field(). Each of those now has one
sentence in the same shape: what it does, what it costs, and what it needs to
be on before it does anything ("Nudges every step toward what your ratings
say you like. Costs 20-30% more time, and only does something with a
refinement key and enough liked generations to have learned a direction.").

The wording is drawn from the node tooltips in samplers.py / conditioning.py,
so the numbers in them are the measured ones, not invented: the 0.25-0.35
mid-scene band, the 45% mid-scene overhead, CFG 1.0 on distilled models, the
frame_overlap=0 + carry_i2v_guides warning.

Two rows also stopped dropping hints on the floor: renderStudioRefinerBool
never forwarded one, and Continuity's number fields had no way to take one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 19:31:00 +03:00
digital-garbageandClaude Opus 5 c37319fb2a feat: settings say what they do in one line; cut_opening_frames works on H3
Engine settings were unreadable: every experimental knob carried a paragraph
about cost figures, sampler caveats and failure modes, so nobody read any of
it. The hint is now ONE sentence saying what the setting does, and the
paragraph moves behind a collapsed "Details" link — still the only place some
of those numbers exist, just not in the way of the checkbox.

cut_opening_frames also learns MiniMax H3. It could never engage there: H3's
anchor is a keyframe condition row rather than a pinned latent prefix, so the
t2v guard read every H3 scene as anchorless, and its video latent sits on a
5k+2 grid an arbitrary cut cannot land on. On H3 the frames now come off the
head of the DECODED batch instead — exact to the frame — with the latent's
audio stream cropped by the same proportion of time, because audio decodes
from this node's LATENT output and would otherwise start N frames early. The
latent keeps its full video stream (off-grid the moment it is cut) and the
scene report says so.

Fixes a latent bug on the way: extra streams were cropped on dim 2 with the
video stream's frame count. On H3 dim 2 is the stereo channel and time is
last, so that would have cropped the audio to mono and left its duration
untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 19:24:42 +03:00
digital-garbageandClaude Opus 5 e0ab2da505 test: pin the hold-back rule as a fixture instead of a live project
The rule that a project with its own links/core_overrides keeps its
family was decided by looking at a real project on disk. That is the
wrong way to establish it: the finding is not repeatable, and reading
real project content is how that content ends up somewhere it must not
be. The case is now constructed — core_overrides on concat/audiodec,
which mean LTXVConcatAVLatent and LTXVAudioVAEDecode, both dropped from
H3's core — so the rule is verified without any user data.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 13:12:15 +03:00
digital-garbageandClaude Opus 5 b99b530665 fix: the project remembers which model family it is for
Every project on disk had model_family = None while the global default
said minimax_h3. The builder therefore read them as LTXAV, and LTXAV's
wiring routes audio_vae at LTXVAudioVAEDecode — a node H3's core does not
build. That is the reported "adds another Audio VAE output to a
non-existent node": one root cause, two symptoms.

Four things were wrong and all four are fixed:

* Creating a project seeded its pipeline config from the global default
  ONLY when the global had slots or links. With no loader slots — the
  normal state — the seed was skipped entirely and model_family,
  full_control and core_overrides were all silently dropped. The global
  IS the default for new projects, so copy it whole.

* /api/pipeline-deps reported the family of the GLOBAL config, so the
  setup dialog showed "MiniMax H3" as already chosen for a project that
  had no family at all. The user saw the right answer, closed the dialog,
  and nothing was ever written. It now takes an optional ?pid= and
  answers for that project.

* Saving a project's models wrote the body straight to the global
  default, so any save that merely rearranged loaders erased the recorded
  family. It is now preserved when the body omits it.

* PipelineCaps.modelFamily read state.models only. That is {slots: []}
  while the live config loads and after a failed fetch — truthy, but with
  no family — so an H3 project reported LTXAV for that window, taking the
  frame grid, fps and the inert-setting chips with it.

Existing projects: one that has no pipeline of its own now inherits the
default family at read time (nothing is written behind the user's back).
One that HAS its own slots or links keeps its silence — verified against
the real project files, where the single project with core_overrides on
concat/audiodec is wired to LTXVConcatAVLatent and LTXVAudioVAEDecode and
would break if flipped. The dialog now shows those their true family so
it stays the user's call.

Finally, role targets and default wires are filtered against the family's
own core, so no role can offer or auto-wire a port on a node the graph
does not contain. That makes the phantom-node shape impossible rather
than merely fixed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 13:03:39 +03:00
digital-garbageandClaude Opus 5 532361fb49 feat: h3_audio_clock works with stock ComfyUI samplers too
The previous commit claimed the correction could not reach a stock
sampler. That was wrong. It can: the correction moves from the step to
the prediction, because a rectified-flow euler update is
x*r + denoised*(1-r), so scaling the audio part of (x - denoised) by the
same factor lands the audio on its own grid. A proxy denoiser wraps any
KSAMPLER-shaped SAMPLER and does exactly that.

Tying a model call to a step is the real constraint, and it is checked
rather than assumed: the proxy verifies on every call that the sigma it
was handed is this step's scheduled one, and switches itself off with a
console note the moment that stops holding. Samplers that evaluate twice
per step (heun, dpm_2, dpmpp_2s/sde, seeds_2) trip that on their first
corrector call and are left untouched.

Measured against comfy's real samplers with a perfect rectified-flow
predictor, so every number is pure schedule error (audio error as a % of
the stream's range, 4/8/20 steps):

  euler          85/38/14  ->  0/0/0    exact at every step count
  res_multistep  69/18/1   ->  21/23/15 helped at 4 steps, HURT at 20
  dpmpp_2m       69/18/0.4 ->  20/22/14 same
  heun, dpm_2    unchanged (proxy stands down)

The lesson worth keeping: this is a first-order integration error, so a
higher-order sampler already absorbs most of it once the schedule is
fine — which explains why res_multistep and er_sde behaved well on H3 at
20 steps while plain-euler paths did not. At 4 steps nothing absorbs it
(everything sits at 68-85%) and only the clock fixes it, which is the
regime turbo LoRAs put you in.

Not gated on any of this: the user picks the sampler, and the tooltip,
the Editor hint and a per-run console note carry the numbers so the
choice is informed. Also corrects two claims from the previous commit —
"no-op above 20 steps" (it is 14% on euler at 20) and "video is
bit-identical" (the streams share one attention sequence).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 12:52:07 +03:00
digital-garbageandClaude Opus 5 132feed08b feat: H3 audio clock — integrate audio on its own flow schedule
MiniMax H3 denoises video and audio on two different flow schedules
(shift 12 / shift 3) but hands the sampler a single sigma grid, so the
DiT reconciles them by scaling the audio velocity by d(sigma_a)/d(sigma_v)
measured at the START of each step. Exact in the continuous limit; on a
few-step schedule the tangent badly overshoots the chord it stands in
for. On a 4-step shift-12 grid the final step drives the audio ~2.5x past
where its own schedule puts it, which is heard as distortion — the reason
the turbo-LoRA community ships a custom sampler for it.

h3_audio_clock on the Chain Sampler swaps that start-of-step slope for
the chord that actually spans the step, so audio lands on its own grid.
One scalar multiply per step: no extra model call, no extra memory. The
video stream is untouched. Above ~20 steps the two agree and it does
nothing, so it is aimed at turbo / distilled schedules.

Wired into the two FunPack sampler functions H3 can reach (Distilled
Flow, and Hybrid Euler 2S via its CONST rectified-flow path). A stock
ComfyUI sampler cannot be reached from outside its loop and says so
rather than looking active. Off by default, UNVALIDATED LIVE.

Editor: Settings > Engine > Experimental, plus the mirror-image inert
chip — H3-only settings left on against an LTX pipeline now warn beside
Generate the same way H3-dead settings already do.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 12:39:06 +03:00
digital-garbageandClaude Opus 5 fdee1fea1a fix: spare H3 token tags are trimmed, not thrown away
An image prompt comes back one tag long: comfy builds minimax_token_tags from
the tokenizer's INPUT sequence (embeds.shape[1]) while the conditioning is what
the encoder returned, and the vision block's expansion does not survive 1:1.
The reconcile treated any surplus as corruption and dropped the whole vector —
which on exactly those prompts discards the modality-0 marks for the image span
and leaves the DiT modulating the picture as if it were text.

Position i still means position i, so the tail is trimmed instead. Growing
(appended tokens) is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 15:48:25 +03:00
digital-garbageandClaude Opus 5 fdf39bbd03 fix: H3 image conditioning reaches the model
Two independent, silent breaks made an H3 i2v scene generate from the prompt
alone.

1. The Editor always runs split_by_transitions, and the per-scene conditionings
   REPLACE the single entry Studio built — but they were re-encoded with no
   reference_image and no h3_references. Both the frame-0 keyframe pin and the
   Qwen vision presentation were gone before the sampler looked, even for a
   one-scene project. The split now carries the anchor onto scene 0 and the
   references onto every scene. Gated to H3: on LTX the anchor rides in the
   latent, and adding Gemma3 vision to scene 0 would change existing output.

2. A MiniMax H3 Image to Video node hands its first/last frame pins out on its
   CONDITIONING output, which had nowhere to go — the sampler's positive comes
   from Studio. New optional h3_keyframes input on the Chain Sampler salvages
   them: first frame onto the opening scene, last frame re-indexed onto the
   closing scene's own final frame (the source node's frame_count is its own,
   and a stale index would land mid-clip where PackedLayout refuses it). The
   editor auto-wires it for H3 projects.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 15:40:23 +03:00
digital-garbageandClaude Opus 5 cd6639e7a7 feat: guide strengths span the full 0..1 range
mid_scene_guide_strength, the continuity pin/prior/mid-scene strengths, and
the per-guide strength field in the inspector were all clamped to 0.25-0.5 —
the measured audio-safe band, hardcoded as a limit. That band is advice, not
physics: it stays in the tooltips, the clamps go.

Backend clamps in timeline.py, the node input range, the JoyAI memory guide's
0.25 floor, and the three UI spinners all move to 0..1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 15:32:20 +03:00
digital-garbageandClaude Opus 5 68a697d521 fix: drop the H3 v2a_grad_scale chip
v2a_grad_scale does nothing without joyai_audio_memory on any model, and
gating the chip on that toggle still left a warning about a knob the user
never asked about. Removes the chip, its fingerprint inputs, and the docs
line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 15:23:14 +03:00
digital-garbageandClaude Opus 5 05a97ada2d fix: turning JoyAI audio memory off clears the H3 v2a chip
The gate added in f40ae82 was right, but fpActionbar never listed
joyai_audio_memory — so switching it off changed nothing the fingerprint
could see and the actionbar zone skipped its repaint, leaving the chip
stranded next to Generate until some other field forced a redraw.

Adds joyai_audio_memory, plus models.disable_core (every chip is gated on
the Chain Sampler running at all), and reads the models config through the
same state.models || project.models fallback PipelineCaps uses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 11:36:29 +03:00
digital-garbageandClaude Opus 5 7af38e2e55 Merge dev into main - Release 3.5.0
MiniMax H3 as a second model family, alongside LTX-2 / LTXAV. See CHANGELOG.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
v3.5.0
2026-08-04 22:57:25 +03:00
digital-garbageandClaude Opus 5 a407b01e9f release: 3.5.0 — MiniMax H3 as a second model family
Version bump and changelog for everything on dev since 3.4.1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:57:10 +03:00
digital-garbageandClaude Opus 5 f40ae828a5 fix: the H3 v2a hint only fires when JoyAI audio memory is on; guard the inputs too
Two things, both from the same debugging session.

v2a_grad_scale is JoyAI's coupling knob and is never installed without
joyai_audio_memory (see the call site), so a left-over value with the feature
off was being reported as an H3 limitation — blaming the model for a knob that
would be equally inert on LTX. Both the sampler's pre-run notice and the
Editor's inert-setting hint now gate on joyai_audio_memory.

The NaN guard only saw the sampler's OUTPUT, which cannot tell "the model
computed garbage" from "the model was handed garbage and propagated it" — and
those have disjoint suspect lists. _sample_chunk now checks the incoming latent
and the schedule first, and says which it was. The schedule check is there
because it is hand-typed and our solvers divide by sigma (er = sigma_next /
sigma): an interior zero or a repeated value is an instant Inf that poisons
every value downstream. A trailing zero stays legal — it is only ever a step's
target, never a divisor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:48:15 +03:00
digital-garbageandClaude Opus 5 9ea0be0713 feat: a non-finite latent stops the run where it happened
A chunk that samples to completion and returns NaN/Inf currently sails through
everything: the scene blend spreads it into the already-finished scenes, the VAE
decodes it, and the first thing that objects is ffmpeg's AAC encoder — "Input
contains (near) NaN/+-Inf", naming nothing that produced it, after the whole
montage has been paid for. The video half never objects at all: NaN through
astype(np.uint8) is undefined but silent, so it degrades with a warning.

_sample_chunk now checks the returned latent and raises with the run-phase label
("scene 2/3 · pass 1 of 2"), which stream, how many values and what dtype, plus
the causes worth testing in order — mismatched LoRA, a mis-inferred checkpoint
layout, a dtype the model cannot hold. It raises rather than warns because a
non-finite latent is unrecoverable and blending it forward destroys the scenes
that were still good. One isfinite() per chunk against a multi-second sample is
not a cost worth weighing; a latent the helper cannot read is ignored, never
fatal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:41:45 +03:00
digital-garbageandClaude Opus 5 8e8a4a5471 fix: the log panel stops eating your selection mid-copy
Every 1500 ms the poll did `bodyEl.textContent = lines.join("\n")`, and
assigning textContent destroys and rebuilds the text node — which drops
whatever the user had selected. Copying three lines out of a running log was
a race against the next tick.

Now the write is withheld while a non-collapsed selection sits inside the log
body (a small "paused — selection held" note says so), and lands as soon as the
selection is released or moves out. Unchanged text no longer touches the DOM at
all, so an idle log is completely still. Adds an explicit ⏸ Pause toggle for
deliberate freezing, and Copy now copies just the selection when there is one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:33:15 +03:00
digital-garbageandClaude Opus 5 4372988bc2 fix: a core override left behind by a family switch is no longer emitted
core_overrides are saved per core ID, not per node class, so switching model
family leaves the replaced node's input names sitting on its successor:
audiodec.audio_vae (LTXVAudioVAEDecode) survives onto VAEDecodeAudio, whose VAE
input is called `vae`. The Models panel lists the NEW node's inputs, so the
leftover is invisible there — it only surfaces when ComfyUI is handed a kwarg
the node never declared and the run dies inside it:

  VAEDecodeAudio.execute() got an unexpected keyword argument 'audio_vae'

Guided wiring filtered these out as a side effect of its open-port check; full
control applied them.

The builder now refuses to write an input the node's class does not declare and
says so in the report (a leftover is not a reason to refuse the whole run), and
the family switch prunes overrides the new core has no input for, so it stops
being carried in the saved project. A valid override is untouched by both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:18:12 +03:00
digital-garbageandClaude Opus 5 0bf1a80820 feat: the KSampler pass can build its own schedule from steps + a scheduler
A SAMPLER object is only the step function, so picking KSampler used to leave
the pass with no schedule at all unless a sigma list was typed by hand.

steps + schedule now build one from the loaded model's model_sampling, exactly
as ComfyUI's BasicScheduler does. The schedule dropdown is the switch between
the two sources rather than a silent precedence rule: "use_user_sigmas" — the
default, so nothing changes for an existing config — runs the Sigmas field as
typed, and any real scheduler computes instead, leaving the typed schedule
parked in the field to switch back to without retyping it. Studio logs the fact
that the field is being ignored, and steps is only shown while it applies.

Both frontends, same change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:07:55 +03:00
digital-garbageandClaude Opus 5 e001853f7a fix: H3 token tags are reconciled with the conditioning Studio hands on
minimax_h3.py shipped tags_match / token_tags_length / extend_token_tags with a
docstring saying a mismatch is a hard IndexError mid-forward — and nothing ever
called them. Studio edits conditioning in many places, and H3 tokenizes reference
images into the same span (ref_image_size "match" scales them to the output
resolution), so the token count moves with the prompt AND with the resolution.
That is why the crash looked intermittent and why editing either one made it come
and go, reported only as a bare "list index out of range" at step 0.

Studio now reconciles before returning: extend the tags when tokens were appended,
drop them when tokens were removed (absent tags the DiT handles, wrong tags it does
not), and print which happened — losing the tags changes how the text span is
modulated, so it must not be silent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 22:03:24 +03:00
digital-garbageandClaude Opus 5 981798d6d9 fix: the sampler-name list comes from ComfyUI, not a hardcoded eight
Studio's KSampler mode passes the chosen name straight to comfy.samplers.
sampler_object(), so any sampler this ComfyUI has always worked — but both
frontends offered a hardcoded list of eight written before the rest existed.
That silently hid 36 of them, including res_multistep, which is what ComfyUI's
own MiniMax H3 templates use.

Both now read sampler_name from the live KSampler node spec and fall back to the
old list if that fetch fails. A saved name missing from the live list stays
selectable instead of being swapped for the first option.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 21:52:14 +03:00
digital-garbageandClaude Opus 5 3506471906 fix: H3 decodes audio from the sampler latent, matching the reference graph
ComfyUI's official H3 templates (video_minimax_h3_r2v / _i2v) feed the raw sampler
latent to BOTH VAEDecode and VAEDecodeAudio and let each VAE take its own stream.
We unbound it first via LTXVSeparateAVLatent, so vae_decode_audio — which hands
samples["samples"] straight to vae.decode() — received a different object than the
reference graph gives it. Video was unaffected because the Chain Sampler decodes
video itself, which matches the symptom: clean video, corrupted audio.

Separate stays in the H3 graph for the refinement save's video latent. LTX is
untouched. UNVALIDATED live — no H3 hardware here; revert this link if audio is
unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 21:46:12 +03:00
digital-garbageandClaude Opus 5 9eb56e791b feat: reference media is marked in the bin and wired to node inputs
Replaces the Engine Settings 'Reference media' tab. Any image / video / audio in
the Media Bin (Editor) or Gallery (Easy Gen) takes an 'R' mark beside the identity
pin; any number can carry one, and mark ORDER is the numbering the badge shows.
Each marked item then appears as an input source in Models & Pipeline — 'R1 ·
face.png (image)' — for every socket its kind can fill, so references reach a node
the same way the timeline image does.

At generation the marked files are copied into ComfyUI's input folder and the
builder injects one loader per reference, chosen by what the DESTINATION socket
asks for (IMAGE / AUDIO / VIDEO), shared when two sockets take the same one. A
reference whose file is gone, or one no installed node can load into that socket,
is reported against the socket that wanted it instead of silently unwired.

The editor no longer feeds Studio's built-in ref2va list: reference media is
wired now, not configured. Project.h3_references stays readable so old project
files still load, but nothing writes or sends it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 20:44:23 +03:00
digital-garbageandClaude Opus 5 209c5d01f8 fix: the updater survives an upstream history rewrite
git pull --ff-only cannot cross a rewritten history — every local commit has a new
hash, so git reports divergence and the in-app update dead-ends in a git hint.
On that failure the updater now asks git cherry whether any local commit's CONTENT
is missing upstream: if none is, nothing can be lost, so it realigns to origin and
reports that it did; if there is real local work, it refuses and says how many
commits would go. A cherry that fails to run counts as 'not proven safe'.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 20:23:06 +03:00