Two real findings from adversarial review of the previous commit (the v4
UI port's route change), both fixed:
- app/tests-layout/*.spec.js (20 files, 133 tests): every one of these
navigated to /funpack/ and asserted on composer-specific DOM
(.cx-workspace*, window.FunPack) that no longer exists there now that
the ported v4 frontend is served at that URL instead. Left as-is they
would silently time out on every run. Each file now carries an explicit
test.skip(true, "...") with a comment explaining why and pointing at
the plan's pixel-exactness step, which is what will eventually rewrite
these against the new DOM -- declared and tracked, not quietly broken.
- core/tests/test_registry.py's fixture-based tests never exercised the
new category field through the real announcement_of()/scan() path --
only test_schema.py's direct validate() calls did, which bypass
registry.py's FIELDS name-mapping entirely. A typo there (e.g.
"Category" instead of "CATEGORY") would have passed every existing
test. The "good module" fixture gained CATEGORY = "guidance" and
test_the_good_module_loads now asserts on it, closing the gap.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Round four, both findings confirmed against the code.
A cycle is the one wrong graph that passes a per-link check: every type
matches, every id exists, every required input is filled. build() returned it
as queueable, and ComfyUI caught it at execution instead -- a run accepted and
then killed from inside the executor, with the reason in a place the app has no
reason to be reading. Walked with an explicit stack: slots come from a request,
so a long chain must produce a refusal, not a RecursionError.
The window clamp measured size against the viewport and position against the
viewport minus a grabbable strip. Those are two budgets that do not add up, so
a 900px window remembered at x=1000 opened at x=720 of an 800px screen with its
close button and grip past the right edge -- while every "a strip is reachable"
assertion still passed. Dragging keeps the loose rule, because putting a window
half off the screen on purpose is ordinary; opening, restoring and a shrinking
viewport are not placements anybody made, so the window is placed whole when it
fits.
A malformed slots payload crashed the route with a TypeError and answered
500 in plain text. The round-two fix handled the ABSENCE of slots and not
their SHAPE, so reading slot["id"] off a string went straight through -- and
a refusal the app cannot parse is the failure this endpoint exists to avoid.
The shape check is its own function now, because a malformed REQUEST and an
unfinished PIPELINE are different things and get reported separately.
And a window could still be made unreachable, by resizing rather than
moving. Round two clamped position and said "a window can never be put
somewhere it cannot be recovered from"; size was never bounded, so a corner
drag grew it to 4937px in a 900px viewport and carried its own close and
maximise buttons off the screen, leaving Escape as the only way out.
Capping size to the viewport was not enough either: with x above zero the
right edge still left. Resizing is now capped to the room available from
where the window sits, which is a deliberately different rule from dragging
-- a window may be DRAGGED partly off, because a grabbable strip is
guaranteed and that is ordinary behaviour, but it may not be GROWN past the
edge it is anchored to.
Two rounds of fixes, two rounds of holes in those fixes. Both found only by
attacking the code as it stood after the previous fix rather than stopping
at the report that prompted it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A floating window remembers its position per id, and only the DRAG handler
clamped. So a window moved to the right of a wide screen and reopened on a
narrow one opened entirely off the viewport: no titlebar on screen to grab,
nothing to drag back, and the position remembered -- unrecoverable without
clearing localStorage by hand. Reproduced at x=1400 in an 800px viewport.
The clamp lives in apply() now, so every path goes through it: opening,
restoring a remembered box, coming back from maximised, and the viewport
shrinking under an open window. Also guarded for a viewport narrower than
the grabbable strip, where the old bound went negative and pushed the window
further off rather than back on.
jsdom cannot see any of this -- getBoundingClientRect is zeros there, so a
window off the right edge measures identically to one in the middle. The
three tests are Playwright, and removing the clamp fails two of them.
And POST /api/pipeline could not accept an empty pipeline: `slots or
default()` resurrects the built-ins, because an empty list is falsy. A client
that removed every slot got them handed back. Absent means "use the default";
empty means "there is nothing", and only one of those is a fallback.
One of the tests I first wrote for this asserted len(x) < len(y) + 1, which
is true of almost anything. It counts the pipeline at every step now and
fails if it ever grows.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>