Commit Graph
59 Commits
Author SHA1 Message Date
digital-garbage 41a75c3cd6 fix: server messages name the right Settings pages; effect, add-on, shot camera, LoRA, VAE, erase and SLA hints say what they do 2026-10-15 13:08:08 +03:00
digital-garbageandClaude Sonnet 5.5 c83d878f3a feat: spaCy and NVIDIA RTX Super Resolution are opt-in add-ons; upscale gains an RTX engine; "resolution source" is now "aspect ratio source"
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 22:20:35 +03:00
digital-garbageandClaude Haiku 5.5 5419cc883d fix: Help shows Start tour and Exit tour only while a tour runs; the tour starts under the menubar; Welcome's Load offers importing a project file; Update checks and installs missing requirements even when the code cannot move
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-13 03:40:47 +03:00
digital-garbageandClaude Opus 5.5 e1dfdd80d9 feat: the slow-torch chip switches torch to its CUDA 13 build in place: downloads first, installs from those files, proves it imports, else puts the old build back; restarts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 18:41:33 +03:00
digital-garbageandClaude Opus 5.5 02912f1347 feat: added or wired-in model patches join the chain (removing one already rejoins it); dynamic dropdowns draw and send the fields each choice brings
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 01:19:16 +03:00
digital-garbageandClaude Opus 5.5 e877ad08b7 rework: Build tab drafts a scene from your best-rated shortcuts; learns from rated scenes in projects
Drops the run log, edit-replay swap and Generate capture calls (core no longer depends on the feature).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 17:18:15 +03:00
digital-garbage f698b8dab8 Add reviewed prompt composer with rating feedback 2026-10-07 22:34:46 +03:00
digital-garbageandClaude Opus 5.5 64cc73c401 fix: older projects' CreateVideo + SaveVideo become FunPack Save Video on open
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 16:15:46 +03:00
digital-garbageandClaude Opus 5.5 6a4133feb2 feat: "FunPack Timing" log line after every run: seconds per node (ComfyUI only logs the total)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:36:54 +03:00
digital-garbageandClaude Opus 5.5 f9ca895d42 feat: Composer Files tab back (library files + taste keys); Enhance shows each run's final prompt, live
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 06:47:45 +03:00
digital-garbageandClaude Opus 5.5 f4e89d4433 fix: auto-hide opens from its strip, never the title bar; popovers re-place on resize; one-line autocomplete; last frame held at end; saved videos named after their render
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 06:44:27 +03:00
digital-garbageandClaude Opus 5.5 62e3c7fa07 fix: settings are read and written only where the server says they live, so a fresh pipeline mirrors at once and a hidden module's settings survive; a refused pipeline names the server's reason and the next edit replaces it; a refused value is not also called missing
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:09:59 +03:00
digital-garbageandClaude Opus 5.5 6fa6c4c6ee fix: a preset, import or wizard pipeline reopens exactly as saved (roles too); Taste key hides when no learning feature is on; a broken node schema no longer takes the module list down; a failed model probe is retried
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 04:40:32 +03:00
digital-garbageandClaude Sonnet 5.5 c0cdf9efd9 fix: all-enhancements payload valid alone, Generate waits for pending settings, forget is complete and sticks, camera choices listed, Manager lookup case-proof and declared
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-04 15:37:30 +03:00
digital-garbageandClaude Sonnet 5.5 1984dd6c1e feat: a missing node card offers the pack that provides it (ComfyUI-Manager's node map) with an Install button
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-04 15:27:03 +03:00
digital-garbageandClaude Sonnet 5.5 fd5e072826 feat: subject text — a reference picture's meaning is put before the anchor of every scene that uses it (tile menu ▸ Subject text…)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-04 13:55:47 +03:00
digital-garbageandClaude Sonnet 5.5 a3327a37ae feat: 'Ready to generate?' — a machine check (ffmpeg, Pillow, GPU/bf16/xformers, models, missing nodes per starting point, failed modules) in Settings
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-04 12:49:40 +03:00
digital-garbageandClaude Sonnet 5.5 972a5e97b4 fix(import): bypassed/muted subgraphs, primitives through reroutes, one control to every node of a kind, no binding to a lookalike, scoped Set/Get, widget alignment from the export, 400 on bad shape
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-04 12:35:22 +03:00
digital-garbageandClaude Sonnet 5.5 a8f5163822 feat: import a ComfyUI workflow (UI or API export) as the pipeline, with the app's controls bound to the nodes that take them
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-04 12:29:27 +03:00
digital-garbageandClaude Sonnet 5.5 022c254ab5 chore: retire the v4-derived frontend (app/legacy, old shell/composer/catalogue, boot.js, layout specs); fonts: Inter Tight licence, drop unused Archivo/Figtree
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-04 12:22:13 +03:00
digital-garbageandClaude Sonnet 5.5 100ae63dd5 feat: the root route serves the v5 UI (app/ui); the v4 one stays reachable under app/legacy
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-04 02:31:39 +03:00
digital-garbageandClaude Sonnet 5.5 53b0903637 fix: empty values no longer wipe module settings on Generate; liked-only clips can be re-rated either way; Generate waits for in-flight settings saves before freezing
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-03 16:52:49 +03:00
digital-garbageandClaude Sonnet 5.5 1724e94c2d feat: module control — switch a module off per project, and a module that fails stays off across runs until repaired or turned back on (Settings > Modules)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-03 02:59:57 +03:00
digital-garbageandClaude Sonnet 5.5 a9cf49d97f feat: Models & Pipeline edits the pipeline's shape — add a node, swap, remove, wire/unwire (one Primitive can drive many inputs), own groups; shape changes travel with the project
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-03 00:17:58 +03:00
digital-garbageandClaude Sonnet 5.5 1e0f9bc49e feat: render stage (serving, preview segments, export, stitch), post-render upscale, media rename/thumbnails, frontend backport from v4
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-02 15:48:46 +03:00
digital-garbageandClaude Sonnet 5.5 dd825979f6 feat: prompt templates and editor settings persist with the project, shortcut revolver (peek vs commit), shortcut ideas stats
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-02 14:56:23 +03:00
digital-garbageandClaude Sonnet 5.5 d5d2989f29 feat: shortcut categories, JSON export/import (reads v4 files), no inert refinement-key field, no dead Files tab; single-scene prompt no longer writes the anchor twice
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-02 12:10:54 +03:00
digital-garbageandClaude Sonnet 5.5 fe98c5c4fc feat: Composer Story view -- every scene as one text cut by typed marker words; split/join on the server, anchor kept apart
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-02 11:42:50 +03:00
digital-garbageandClaude Opus 5.5 2406923802 feat: taste keys -- rating a clip teaches the key its run captured
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 18:24:53 +03:00
digital-garbageandClaude Sonnet 5 6d496251f9 feat: settings card export -- render the live pipeline as a PNG
MEDIUM-tier v4 feature ("Which model was that?" answered three days
later): a button in Models & Pipeline renders the currently loaded
pipeline as a PNG -- every slot's node class and typed-in values, wired
inputs marked as wired rather than printed as a stale value, and the
host facts (torch/CUDA/GPU/attention) that decide whether the setup
reproduces elsewhere. Downloadable, copyable to clipboard, and the
report is embedded in the PNG's own tEXt chunk for later machine
reading.

core/settings_card.py is a hard cut of v4's version, not a verbatim
port: v4 spent most of its length (_OWNED_BY, _EDITOR_DEFAULTS,
_pass_rows, _sampling_sections) describing FunPackStudio and the
LTX-AV Scene Chain Sampler's own hardcoded knob tables -- neither node
exists in v5's module-driven pipeline, so there is nothing left to
describe there. What's genuinely generic (walking a slot's real
inputs, detecting a wired one via core.graph.is_link, the PNG
renderer) is what's kept, adapted to v5's slot shape ({id, node,
group, inputs}, a wire arriving as [source_id, index] rather than a
separate input_sources/wires lookup) and v5's existing core/sysinfo.py
for host facts (whose field names already matched v4's needs).

New POST /api/settings-card route: stateless like /api/pipeline, the
CLIENT sends its own live pipeline (window.PipelineState.slots()) since
that's the one copy that includes any unsaved edit; the server supplies
only what it alone knows (host facts) and the render.

Frontend: app/legacy/api.js's settingsCard stub (present since the
Phase A port, explicitly marked "not built in v5 yet") replaced with a
real POST+blob call; app/legacy/models.js's Export-settings modal
ported from v4 near-verbatim, reading PS.slots()/project name instead
of a project id the old per-project models config needed. Verified
live: real pipeline, real host facts (this Mac's actual Apple MPS/
Python/ComfyUI versions), wired inputs correctly marked.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 21:29:22 +03:00
digital-garbageandClaude Sonnet 5 29ac44ffcd feat: v4 UI port Phase A start -- module categories, legacy frontend served live
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>
2026-09-14 23:58:51 +03:00
digital-garbage ceac622f2c feat: MiniMax H3 Reference-to-Video pipeline preset, and a real MatchType fix it surfaced
Piece A of the H3 workflow port: the loader/SigmaShift/ImageTransformKJ/
R2V/sampler-at-CFG-1/decode/video-mux chain the H3 user runs by hand today,
offered as a preset (GET /api/pipeline/presets) loadable into the same
editable pipeline core/graph.py already supports -- not a second "default",
which module-load order would make ambiguous.

References are deliberately NOT pre-wired into R2V's ref_images autogrow;
an unwired optional slot is what "not in use" means to that node (absent
from its ref_images dict), not an empty image degrading its <Picture i>
numbering. boot.js wires them per-run based on what's actually picked --
next commit.

Building this against a real ComfyUI+KJNodes registration (not a fake
schema) surfaced a genuine core bug: core/graph.py's from_comfyui() had no
idea what to do with a V3 MatchType socket (ImageTransformKJ's own image
input) -- its bare type name "COMFY_MATCHTYPE_V3" matched nothing, so a
completely legal IMAGE wire into it was refused. Fixed generically in
comfy_types.py (match_type_union()) and graph.py, for any node using
MatchType, not special-cased to this one.
2026-09-11 11:46:48 +03:00
digital-garbage 2e384c48b2 core, app: shortcuts, autocomplete, and prompt-craft expansion
A trigger->replacement library (core/shortcuts.py, a close port of v4's
apply_prompt_shortcuts/resolve_variables), a Settings section to manage it,
autocomplete on the real prompt textarea (composer.autocomplete.default, so
it outranks the Constructor modal it lives inside), and a stateless
/api/prompt/expand that combines anchor + shortcuts + $variables + postfix
at the moment a run is queued -- scene.text itself stays exactly what was
typed. Project gained anchor/postfix/variables editing UI on its own tab.

Along the way: a real layout bug in the Properties column (every composer
stack can flex-shrink below its own content, and once the Project tab grew
past a couple of rows the column started squeezing its own children's
content into each other and, at the outer level, into the sibling Generation
section below) -- fixed with flex-shrink:0 at each level that needs it,
caught by a Playwright regression test.
2026-09-08 11:49:49 +03:00
digital-garbageandClaude Sonnet 5 93bdd27b32 app, core: a node's outputs are visible, and model family detection
Every node's own OUTPUTS were invisible on its own panel in the pipeline
editor -- wiring an input already let you pick a source, but opening the
node that PRODUCES something told you nothing about what it makes or
whether anything reads it. Found live, by a real user just looking at a
node, after two full review rounds that only ever checked the consuming
side. Fixed with a read-only "Outputs" section per node, naming each
output, its type, and who (if anyone) currently wires from it.

Also: model family detection. core/probe.py reads a safetensors header
(tensor names only, no weights) and asks every installed model module,
through the same capability-lookup mechanism already used for
empty_latent/decode, whether the file is its own -- core never names a
family itself. A real, adversarially-found bug in the ported signature:
one of MiniMax H3's three candidate prefixes doesn't correspond to any
prefix ComfyUI's own loader actually resolves to, traced through
comfy/model_detection.py and comfy/sd.py -- the probe could have called a
file H3 that ComfyUI's real loader would refuse. Removed, and pinned with
a dedicated test module. A second finding narrowed the folder search to
only the folders FunPack's own loaders actually read from.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 10:54:28 +03:00
digital-garbageandClaude Sonnet 5 75e9e9f686 core, app: pipeline wiring, and Restart/auto-stash for the updater
Pipeline wiring: core/graph.py gains wire()/unwire(), and the "Models and
pipeline" editor can now actually connect one node's output to another's
input -- sockets and widgets alike, which is what makes "linked inputs"
(several widgets sharing one Primitive node's value) buildable at all,
without a second mechanism. Three adversarial review rounds found and fixed
real bugs along the way: check()'s request builder silently dropped the
wire-specific fields; the widgets loop had no wiring UI at all despite the
backend supporting it, and a widget already holding a link rendered as an
editable box with the wrong (default) value; wiring an input that already
had an unsaved drafted value let the stale value silently win on Save. Also
fixed, found live rather than by review: the Add-node picker stacked two
disagreeing search boxes (an outer server search and filterList's own
redundant client-side one).

Updates window: "Restart ComfyUI" is now a plain, always-available button,
not just something offered when an update already landed. Rolling back or
switching branch on a dirty tree now auto-stashes instead of refusing --
updating still refuses, unchanged, since pulling new commits onto changes
in progress is not something to do without asking. Three review rounds
here too: stashing before reading the rollback target shifted what HEAD@{1}
meant (a real reflog hazard, not just reasoned about); a stash's disclosure
was lost if the step right after it failed; that fix was too narrow and
missed pull()'s own failures inside checkout(pull_after=True) -- the actual
default path -- and rollback()'s own reset failing; and finally, the
broadened fix still only caught GitUpdateError, missing subprocess-level
failures like a hung fetch timing out, which is the realistic failure mode
on a rented GPU box's network.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 19:25:06 +03:00
digital-garbageandClaude Sonnet 5 fd292259e7 core, app: media import -- a library separate from the run-output bin
Files the user brings IN (a reference image, an imported clip) now have a
real store: core/media.py, /api/media routes, and a "Media" tab beside the
bin's "Results" tab in the Assets zone. Upload, list, fetch, delete all
round-trip through the real server; nothing wires into a pipeline input yet,
since no model module or Studio conditioning takes a reference image today.

Two adversarial review rounds found and fixed three real issues along the
way: MAX_BYTES (512MB) was dead code because aiohttp's own client_max_size
capped every upload far lower first in both prod and dev, with a JSON-parse
error leaking to the user on rejection instead of a clean message; the
index.json read-modify-write had no guard against concurrent writers; and
the devserver's bare Application didn't match ComfyUI's own 100MB default,
which also broke an unrelated e2e assertion once the review polluted the
real, unisolated media/ directory testing the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 16:58:18 +03:00
digital-garbage 739a720adc app, core: Settings matches v4 -- retractable rail, fixed frame, real About
Three things the unified Settings window was still missing next to v4's
own, all found by looking at it next to v4 rather than in isolation:

- The sidebar is a 52px icon rail now, expanding to a full list on
  hover/focus and overlaying the content instead of a splitPane's
  permanently-docked two-pane shape -- v4's own sidebar, not an
  approximation of it. Fixing this surfaced two real CSS bugs along the
  way: filterList's own min-width:220px silently defeated the collapse
  (clipping the search box to "Sear" instead of showing an icon), and
  .cx-filter-list's unset overflow-x (computed to auto once overflow-y
  was set) put a horizontal scrollbar under it. Centering the icon in
  the collapsed row needed the label's flex-basis AND the row's own gap
  to both go to zero -- an invisible label still claims its share of
  both otherwise, and the icon renders off-centre.
- The modal now has a real height (v4's own 660px) rather than sizing to
  whichever section is showing, so switching between a short section
  (About) and a long one (Node packs) doesn't visibly resize the whole
  window. That exposed a THIRD latent bug: .cx-modal-body never had
  flex:1 1 auto, invisible until a modal had a real height to fill --
  every prior modal shrink-wrapped its content, so head+body+foot always
  summed to exactly the card's height by accident. Fixed at the shared
  rule, not just for Settings.
- About now shows what v4's does: commit and dirty state, a codename
  when the checkout has one, and the machine's real hardware/software
  facts (chip, memory, GPU or MPS, disk, OS, ComfyUI/Python/Torch/
  attention-backend versions, hostname) -- a new core/sysinfo.py
  (ported from v4's own, individually guarded so a missing probe never
  takes the panel down) behind a new GET /funpack/api/system, injected
  into settings_window.js the same way every other network call here is.

Also: a nav row's tone (the coloured icon chip) is now validated against
its closed set the same way button.js already validates its own tone --
an unknown one used to render silently wrong instead of failing loudly.
2026-09-07 13:47:18 +03:00
digital-garbage b85c79a6a2 app, core: save/load a result, save/load a project, generate every scene
Player: Save to bin (whatever the viewer shows, if it has a real file
identity) and Save frame (canvas-capture the video on screen, upload
through ComfyUI's own /upload/image, no new route needed). Both add
without navigating the viewer -- an upload is a real round trip, and
the result is added to the bin, not shown as a stale finished pick.

Save/Load Project File: a real download route with Content-Disposition,
and an import route that never trusts an id in the file -- an import
always lands as its own project, never overwrites one already here.

Generate All: every scene in order, waiting for each to finish before
starting the next -- not a montage (v5 has no render/stitch stage),
N separate generations each landing on its own scene.

Both project-switching (open/newProject/importProject) and the earlier
run-reattach path shared a bug class this session kept finding: state
captured at one moment, applied at a later async moment with no check
that nothing had moved on in between. Closed it properly this time --
a generation counter every switch bumps and checks, including start()
(the page's own handlers are live before it resolves) and undo/redo
(also "a different project is open now"), and scheduleSave() captures
the object an edit actually touched instead of re-reading a variable
that can point somewhere else by the time its debounce timer fires.

Four adversarial rounds against this range, each fixed before landing.
2026-09-06 22:53:34 +03:00
digital-garbage 1185e2201f core, app: a decade of races closed -- log routes, restarts, results, defaults
Two routes silently shared /api/log; a pack's "updates available" badge
outlived the update; nothing server-side stopped a restart mid-generation,
and the fixes for that had their own dead end, a lock scoped to the wrong
event loop, an unlocked status fetch, and a check that only ran once
instead of at the moment it mattered. A run's result was lost outright if
the project it belonged to wasn't the one open when it finished -- on a
project switch or a page reload. A brand-new project inherited the
previous one's width, then inherited the wrong "default" for it.

Found and confirmed via an adversarial pass over 9dcd94c..HEAD, one round
per fix.
2026-09-06 18:08:48 +03:00
digital-garbage dae136b04c core, app: where a file went when it did not land in the bin
Fifth off the port list. ComfyUI writes previews and other transient outputs to
a temp directory and wipes it on restart, so everything in there is findable
for exactly as long as the server has been up -- which is the window in which
anyone asks where something went.

Read-only on purpose. This is a place to FIND a file, not to manage one;
deleting temp files is ComfyUI's job and it does it on restart.

Rows rather than tiles, which is the opposite of the bin and for the opposite
reason: a row shows the whole name, the size and when it was written, and the
grid draws none of those. Only media is listed -- a temp directory fills with
whatever any node felt like writing, and a browser listing 400 .pt files is not
a media bin. No <video> thumbnails, which wedged this app's API once already.

Three empty states, because they are three different facts: nothing was written
yet, the directory was wiped, and ComfyUI is not here to ask. An empty list
reads as the first one whichever is true.

One test taught me something about this environment again: opening a file puts
it in the Preview, the dev server has no /view, so the picture 404s and the kit
replaces it with its fallback -- correctly, and leaving nothing in the DOM to
assert on. What the viewer FETCHES is the evidence, so the test reads the
request rather than the picture.
2026-09-06 14:22:41 +03:00
digital-garbage 6371c4ad48 core, app: ComfyUI's log, where a user can reach it
Fourth off the port list. Users do not open devtools, and "it did not work"
with nothing to read is where a bug report dies -- especially on a rented box
behind a tunnel, where the terminal that has the answer is not on this desk.

Read from ComfyUI's own log FILE rather than from a buffer in this process.
v4 kept both and its own comment says why the file is the half that matters: a
crash and restart empties an in-memory buffer, and the lines from before the
crash are exactly the ones worth reading. So this is the smaller half and the
more useful one.

Three things the panel refuses to do, each because of what a log is FOR:

* It does not redraw while text is being selected. Copying a stack trace is the
  one thing anybody opens a log to do, and a panel that repaints under the drag
  makes it impossible.
* It keeps the last lines when the server stops answering. The most interesting
  thing a log can say is that ComfyUI just died, and replacing the lines from
  just before it went with the error that says it is gone throws away the only
  evidence of why.
* No log file is an ANSWER, with the reason and where to look instead. An empty
  list reads as a quiet log, and the difference matters precisely when somebody
  opened the panel because something else already broke.
2026-09-06 14:13:47 +03:00
digital-garbage ca913a3dec core, app: it restarted when nothing had changed, and could not see an update
Both findings from the adversarial pass, confirmed and fixed.

RESTARTING FOR NOTHING. Every successful git call scheduled a restart, whether
or not the checkout had moved. Pressing Update while already up to date is a
normal thing to do -- there is no way to know until it has been asked -- and it
cost a full boot. The route now restarts only when something actually moved: a
commit changed, or a branch did. Two branches at the same commit still count,
because they stop being the same the moment anything lands on either.

And the worse half, which the pass rated first: nothing checked whether a
generation was in flight. The restart takes the run with it, GPU time already
spent, with no warning anywhere. The window now says a run is going and turns
off the three buttons that would end it.

CHECK FOR UPDATES COULD NOT REPORT ONE. check_updates() answers under `checked`
and the app read `nodes`. The request succeeded, the JSON was valid, and every
pack reported nothing to update however far behind it was -- a fetch per pack
over the network for an answer that could never be shown. Neither side's tests
could see it: the route test asserted the server's shape, the window test never
pressed the button, and nothing compared the two.

So the tests now name each other. The Python one asserts the key the app reads;
the browser one stubs the server's real shape and reads the words off the row.
2026-09-06 14:05:03 +03:00
digital-garbage 8318b3da0b core, app: install, update and remove node packs
Third off the port list. Users will want to add the one pack a workflow needs
without being sent to another UI to do it, and a pack that ships experimental
work needs the same for itself.

custom_nodes.py comes across as core/nodes_manager.py, changed in two places:
where FunPack's root is, and one guard that the port made necessary.

THE GUARD. custom_nodes_root() asks ComfyUI first and falls back to "FunPack
lives inside custom_nodes" -- a reliable anchor in an install, and a wrong one
in a development checkout, where the parent is somebody's home directory. Every
rule protecting the delete measures against that answer, so with ComfyUI's
paths unavailable, "remove Foo" resolved against ~/Documents and all four
guards happily agreed. It now checks the assumption it is named after and
refuses with where it thinks it is. Found by running the function, not by
reading it.

Two faults of my own, both in the window:

* Update and Remove were a button GROUP -- radios -- for two actions that are
  not a choice between two states. It also left Remove looking selected after a
  click, which is an alarming thing to look at.
* A failure was drawn and then wiped by the redraw in the same finally block:
  the message appeared and vanished in one frame. What went wrong is state now,
  not an argument to one draw call.
2026-09-06 13:56:17 +03:00
digital-garbage e3793ee154 core, app: FunPack updates itself again
Second off the port list, and the one that gets reached for most: a pack whose
value is experimental work shipped continuously lives on switch-branch and
update.

git_update.py comes across from v4 as core/update.py, changed in three places
and no more: where the repo root is, a `remote=False` mode, and a 20-second
timeout on the one call that touches the network. It has been switching
branches on real rentals for months and every refusal in it -- no git on PATH,
not a checkout, a dirty tree, a branch that exists on neither side -- is a
lesson that cost somebody an afternoon. Rewriting it to look more like v5 would
trade proven plumbing for a fresh set of edge cases.

It is core rather than a module because it is about the INSTALL: no module can
announce the thing that updates every module.

Four routes, and every one of them ends by relaunching ComfyUI -- so the answer
is written first and the restart is a callback scheduled after it. `restart` is
looked up on the module rather than captured, or running the suite would
relaunch the test runner.

The window reads the checkout TWICE: once without the network, so there is
something to see instantly, then again with it for ahead and behind. A fetch
takes seconds on a good day and blocks until it times out on a machine that
cannot reach the remote, and neither is a reason to show an empty box. A dirty
tree is said before either button is pressed rather than as a refusal after.

Then it waits: an overlay that polls /api/health until the server answers, and
says so when it does not come back rather than spinning forever. That last part
needed the kit's blocking overlay to be able to change its message -- it could
not, so my give-up branch would have failed silently, which is the exact
pattern this project keeps finding.
2026-09-06 13:46:26 +03:00
digital-garbageandClaude Opus 5 9dcd94c156 app, core: a project of scenes, and the timeline that draws it
The app could run a pipeline and had nowhere to keep what it was making. A
project is an ordered list of scenes, one JSON file each, and it is the only
part of the app that has to survive a reload.

A scene owns its prompt text and the result it produced, and nothing else. v4's
Scene grew three dozen fields; each belongs to the feature that needs it. Per
scene length and fps are deliberately absent — a regenerate uses the project's
values, so a scene cropped on the timeline comes back whole.

The prompt box edits the current scene rather than holding its own copy, so
what is on screen and what a run uses cannot disagree.

app: the region toggles are named buttons in the timeline bar

v4's arrangement. The kit's fallback is one glyph on a rail at each edge of the
window, which is right for a kit that cannot know what its panels are called and
wrong here, where they have names and a bar to put them in.

A pressed toggle was styled only inside a button group, so a standalone one
carried aria-pressed and looked identical either way.

gallery.strip is selectable like its siblings: a filmstrip is the one shape
whose whole job is saying which of these you are on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 12:03:19 +03:00
digital-garbage 5b59043fc8 core, app: a prompt you can type, on the main window
The prompt was a slot input on a CLIPTextEncode, reachable only by opening
the pipeline window and finding the node. A slot can now say that one of
its inputs belongs on the app's surface -- a place and a label -- and the
shell offers a region with that name or does not, exactly as a module's
panel works. Core validates that a role is names and nothing else.

Values for those inputs travel addressed by slot rather than as a whole
pipeline, so the window that owns the structure and a box that owns one
value cannot disagree about the slots between them. An address that is
not there is refused: a prompt typed into a box for a removed slot has
gone nowhere, and a run that starts anyway is not the run that was asked
for.

Region stand-ins are empty states now, taken down once something mounts --
'modules appear here' left above the modules that appeared explained
nothing to nobody.
2026-08-30 20:27:44 +03:00
digital-garbage 1b0f489950 core, app: what the panels hold now reaches the run
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.
2026-08-30 20:16:46 +03:00
digital-garbageandClaude Opus 5 8bfd28950d app: the models and pipeline window
One wide modal, a card per group, and a group that opens into a node list
beside that node/'s parameters, saved a group at a time. Groups travel on the
slot, so a pipeline keeps its own arrangement and a new group is a slot
claiming a name nobody used.

Values are drafted until Save; adding, removing, replacing and moving a node
go to the server as they happen, because only it can say whether the result
still wires up.

An unset dropdown reads "not set" rather than showing the first file as though
it had been chosen -- v4 recorded that trap, and picking the entry the box
already shows fires no change, so the one click that looks like the fix does
nothing.

New: region.stack, a frame header, a modal footer that can be refilled.

Not yet: wiring. A socket says so where a control would be.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 16:36:01 +03:00
digital-garbage 2349c550cb core: a node's inputs as a form, and a dev server that has nodes at all
graph.py reads an input's TYPE, which is all a wire needs. Editing a value needs
the rest -- the choices a combo offers, the bounds on a number, whether the box
is multiline -- so that is asked separately rather than widening the question
graph.py asks. Read from ComfyUI's own INPUT_TYPES, so a node written for
ComfyUI is editable here without knowing anything about FunPack.

Caught while looking at the real output rather than after committing: deciding
"a socket is upper case" is wrong, because STRING is upper case -- every prompt
box in the pipeline came back as something nobody could type in. The honest rule
is ComfyUI's own primitive set, plus forceInput, which is a short list core
holds on purpose.

/funpack/api/nodes answers by name. ComfyUI's /object_info returns every
installed node, which on a machine with a few packs is megabytes for a question
about a dozen; a node nobody installed comes back as null rather than missing,
so "this slot points at something absent" reads differently from "I did not ask".

The dev server now registers ComfyUI's nodes and FunPack's own, through the same
function the tests use. Without it the pipeline was fourteen "there is no node
called X installed" and nothing to edit -- so the one thing the dev server exists
for could not reach the half of the app that matters. It now reports the three
problems that are real: no model, no CLIP, no VAE chosen.
2026-08-30 05:00:29 +03:00
digital-garbage a02581fbd0 foundation: the request itself was never checked, only what it carried
shape_problems() reads the slots array. Everything else in the body -- the body
being an object at all, which slot an edit targets, which node it replaces it
with -- was read straight off whatever JSON arrived. `[]` reached `.get` as an
AttributeError; `{"slot": ["a"]}` reached an `in` against a dict as an
unhashable key. Both answered 500 in plain text, which is the refusal nobody
can read that this route exists to avoid.

Round six, two findings, both confirmed.
2026-08-29 22:42:29 +03:00