Picking Node packs, Updates, the log or Temp files used to close the
Settings window and open that feature's own separate modal, with no way
back except reopening Settings from the menu bar -- not what v4 does,
and not what "unified" is supposed to mean. Each of those five files
(plus pipeline_window.js) now exports mount({ setFooter, close }), the
same content its standalone open() already built, taking the modal
chrome's footer and close from whoever hosts it instead of owning a
modal directly. open() is unchanged for anyone opening one alone --
pipeline_window.test.js and every Playwright spec for these five prove
it, unedited, still passing.
Also fixes two findings from the adversarial pass on the earlier
deep-link version: filterList's search only matched a section's visible
label/hint, so a section findable only by a keyword (e.g. "loaders" for
"Models and pipeline") silently vanished from the filtered list; and
createSettingsWindow's "one at a time" state lived at module scope, so
a second independent instance would have redrawn the first one's modal
instead of opening its own. Both now have regression tests.
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.
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.
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.