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.
Every modal already mounts a .cx-backdrop element over its layer, so the
stacked variant painting another one as `0 0 0 100vmax` of shadow spread was
redundant, and the area beneath it was darkened twice.
It was also the most expensive declaration in the sheet: a spread the size of
the viewport diagonal, on an element inside a fixed layer, with an entry
animation running. Removed in favour of a bounded shadow, and a test reads the
stylesheet for the pattern -- asserting on node.style in jsdom passes against
any rule at all, since no CSS is applied there.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
The plan asked for jsdom AND Playwright from the start and only jsdom was
ever built. jsdom has no layout engine: getBoundingClientRect answers zeros,
container queries never fire, a theme is an attribute rather than a computed
cascade. So every claim about POSITION or SIZE in this kit was unchecked, and
the unit suite could not have noticed either of these.
A popover bounded its height and not its width. `max-height: 60vh` was there
from the start; there was no max-inline-size at all, so a long unbroken
string -- a file path, a model filename -- made the popover wider than the
screen and anchor.js could not save it: it can move a box, not shrink one.
Measured at 1436px inside a 1280px viewport, off the right edge in all four
corners.
A horizontal split could not fit a narrow screen. The first pane carries an
inline size and `flex: 0 0 auto`, so it would not shrink, and the page
scrolled sideways at 360px -- 407px of content in a 360px viewport. It wraps
now, which stacks the panes, rather than a media query: the kit sizes itself
from its container and not from the window.
Nine layout tests: every overlay corner, an overlay taller than the viewport,
both themes resolving every token they use, light actually being lighter than
dark, and no horizontal scrollbar at five widths. `npm run test:all` runs both
suites.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
55 element groups across text, buttons, choice, input, slider, layout, status,
overlays, gallery and the wheel picker. Each lands in a registry-driven
catalogue that shows every variant in both themes, so an element that fails to
register is visibly absent rather than quietly missing.
rule_content_is_data iterates the REGISTRY, not a list: a new element is
attacked with markup the moment it calls define(), and one without a demo fails
the suite. That is announcement-not-enumeration applied to the tests.
Fixes found while building: checkbox silently ignored its label (inverted
logic left it with aria-label=toggle), the overlay root set z-index 0 and so
became a stacking context that trapped every overlay it was built to free, and
raising a window left its peers' z stale because z is derived from position --
claim() now notifies a rung when a peer moves.
The wheel highlights the actual sector rather than a box around the label: the
lit shape is the region that picks the item, which is what makes it aimable.