* feat(saved-views): keep a collection in a saved view Images saved views gain a collection key (#331 D140): empty or a collection id of 1 to 9 digits. Writes refuse favorites together with a collection; older rows read an empty collection, and a stored row with both opens the collection, as the listing does for a URL with both. Part of #331 * feat(collections): store collections in the library database * feat(collections): add, remove and rename collections and keep members through duplicate cleanup * feat(collections): add collection routes and the Images collection filter * feat(images): store images' own categories in the library database (#332) One new table, image_categories, and its category index, created empty by a forward-only migration that reads and writes only its own objects. Startup checks the structure; restore also checks the values. The store sets, clears and reads own categories by file key (the deepest output source holding the file and the path below it), and copies a removed duplicate's category to a kept copy that has none. * feat(images): duplicate cleanup keeps images' own categories (#332) The kept copy's own category wins; a kept copy without one receives the removed copy's, keyed by the deepest output source holding each file. The removed copy's row is never deleted, so a give-back or a failed delete loses nothing. The job log reports how many were kept. * feat(images): set, remove and list images' own categories (#332) PUT /prompt_manager/images/category sets the own category of up to 500 listed files, DELETE removes it (listed or not), and GET /prompt_manager/images/categories lists the values whose file is listed. Targets are the Images listing identity of an output source, checked by media_target (shared with Collections). The routes never touch prompts, recipes or links; POST /prompt_manager/bulk/category is unchanged. * fix(images): key symlinked output folders and keep key work off the event loop (#332) Review fixes: a source matches by its configured spelling too, so a detected output folder that is a symlink keys its files (routes and duplicate cleanup). File keys are computed in the executor. A path no source holds is a plain 403, a lone surrogate a 400. The install repairs a table that lacks its category index instead of refusing to open. * fix(collections): address review findings * feat(images): set or remove a category on all N matching images (#332) The category routes also accept the Images listing filter of "Select all N matching" (#392) instead of explicit items, resolved now and capped at 500 images like an explicit selection. * feat(collections): browse a collection from the sidebar The sidebar gains the concept's COLLECTIONS group (D136): one row per collection with its folder icon, name and listed-member count, without the PROPOSED badge; the label shows once a collection exists. A row opens #/collection/<id>, the Images page filtered to that collection (D137): the collection's name as h1, Collection as breadcrumb and title, Edit to rename or delete it through the saved views' Edit dialog with collection nouns, and a note counting members the sources do not list now. An unknown or deleted collection shows a page that says so with a link to Images. A saved view on a deleted collection lists nothing and says so (MC6, D140). The list is read again after a restore and the counts 300 ms after a change. Part of #331 * test(collections): compare the COLLECTIONS group with the concept The concept hides COLLECTIONS at 950 px and below, which would leave a phone no way to open a collection; production shows the rows there in the wrapped layout of every other sidebar nav (D136). The shell comparison now compares the group instead of filtering it out: the concept fixture seeds Landscapes (12) and Experiments (8), the rows compare by name order, and at phone width the nav compares against the concept's own sidebar-nav layout. The collection rows are checked at 1600x1100 and 390x844 in both themes. Part of #331 * test(vendor): count the #332 host case in the execution matrix image-attribution.spec.mjs gains the image category route case, so the execution config lists 92 tests (3 attribution cases per project). * test(browser): install the slideshow clock before wrapping setTimeout The slideshow fixture wrapped window.setTimeout before page.clock.install(), so the viewer's timer ran on real time and clock.runFor() did not drive it; a late WebKit timer failed 'slideshow exposes exact state labels' on CI. * fix(collections): address review findings - A list read superseded by a newer one (the 300 ms count read, a restore) no longer shows "could not be loaded": the collection and saved-view pages wait for the list to settle. - A listing that finds the open collection gone shows the deleted page once the list no longer holds it; when the list read fails or still holds it, the page keeps the listing's error and Retry instead of staying blank, and each failed read is reported again. - The missing-members note follows every list read and goes away at zero. - A rename that settles after its Edit dialog closed leaves any dialog opened since alone. - The deleted-collection, list-failed and saved-view failure pages focus their title after a route change, as every page does. Part of #331 * test(coverage): keep main's floors for files this PR does not touch coverage:update raised nine floors of untouched files from one run; metadata-autocomplete.js branches then measured 84.84 against 85. * test(legacy): record the image category routes in the legacy contract snapshot (#332) #332 adds PUT/DELETE /prompt_manager/images/category and GET /prompt_manager/images/categories; the legacy pages' routes are unchanged. * test(duplicates): a removed copy with a collection and a category moves both (#331, #332)
Viewer.js maintenance
ComfyUI serves the committed files in web/lib/viewerjs/. Node and npm are only
needed by maintainers and CI. Keep upstream files pristine; application behavior
belongs in web/js/, and theme overrides belong in comfyui-theme.css.
Install and verify
Use Node 24 or newer. From the repository root:
npm ci --prefix tools/vendor --ignore-scripts
npm run --prefix tools/vendor check
npm run --prefix tools/vendor test
node --test tests/js/*.test.js
npx --prefix tools/vendor playwright install chromium
npm run --prefix tools/vendor test:browser
On Linux, use playwright install --with-deps chromium when browser system
libraries are missing. The browser tests use synthetic images and mock the
ComfyUI API boundary; they run production viewer methods and the committed
Viewer.js JS/CSS, without a running ComfyUI instance.
check is read-only and fails if any JS, CSS, license, or provenance file differs
from the installed package. The manifest, lockfile, installed package, and asset
version banners must agree. The lockfile records npm's archive integrity;
provenance.json records the upstream source, package integrity, and SHA-256 of
each copied file. A normal npm ci --ignore-scripts verifies archive integrity.
Full-page workflows, frontend coverage gates, and required merge checks are
documented in TESTING.md. The viewerjs CI check runs both
the fixture tests above and the real-page workflows on every PR.
Update
npm run --prefix tools/vendor update
# Or select a specific stable version:
npm run --prefix tools/vendor update -- --version 1.15.2
The command installs an exact version with lifecycle scripts disabled and copies
dist/viewer.min.js, dist/viewer.min.css, and LICENSE. It writes deterministic
provenance with no timestamps. Repeat the checks above and commit the manifest,
lockfile, and all vendor files together. To regenerate files from the current
pin without changing versions, run npm run --prefix tools/vendor sync.
Automated update PRs
Update vendored Viewer.js checks npm's stable release daily at 07:30 UTC and
supports Actions → Update vendored Viewer.js → Run workflow, with an optional
version. It tests the generated update before opening deps/viewerjs-X.Y.Z.
Existing open or closed PRs for that release are left alone. A failed test does
not publish a branch or PR. Changes require review; the workflow never merges
or approves PRs.
The organization and repository must enable Actions → General → Workflow
permissions → Allow GitHub Actions to create and approve pull requests.
Keep default workflow permissions read-only. Only the update job requests
contents: write, pull-requests: write, and actions: write.
The workflow uses GITHUB_TOKEN; no personal token is needed. Because bot-token
pushes do not normally trigger other workflows, it explicitly dispatches the
read-only Viewer.js vendor and compatibility workflow on the update branch.
Both workflow files must be merged into the default branch before scheduling or
manual dispatch becomes available.
Compatibility checklist
Check gallery grid/list views, navigation across 100-image windows, metadata, slideshow/fullscreen, keyboard navigation, Escape and focus cleanup. Check the admin gallery/filmstrip and tag viewer. On touch devices, test long swipes, short drags, and pinch/rotate gestures. Confirm the ComfyUI theme remains usable.
The gallery keeps its bounded viewer and custom slideshow. Upstream navbar windowing is not a replacement for bounding the supplied image list: native slideshow playback can still preload every supplied original. Review new preloading defaults, gesture behavior, CSS changes, and any adapter use of Viewer.js internal methods before adopting another release.
Shared ComfyAssets test tooling
The exact toolkit commit archive in package.json / package-lock.json supplies
host orchestration and coverage helpers. comfyui-host.mjs is PromptManager's
configuration adapter; keep reusable lifecycle changes in the toolkit source.
The existing npm scripts remain the public maintainer entry points. See
TESTING.md for the managed real-host matrix and update policy.
Shared ComfyAssets browser controls
Prompt Manager (Classic) consumes the same pinned toolkit's browser controls.
The node identifier remains PromptManager; existing workflows, sockets, and
immediate result selection are preserved. PromptManager Text is unchanged.
Committed assets in web/js/shared/toolkit/ work without npm or KikoTools installed.
Maintain common controls in ComfyAssets Toolkit,
then update the exact archive pin and lockfile here. Regenerate using
npm run --prefix tools/vendor sync:toolkit; npm run --prefix tools/vendor check
verifies both Viewer.js and the toolkit without rewriting files. The toolkit
distribution includes its license, source history, archive integrity and per-file
hashes. Do not edit its copied files directly. Classic's controller and layout
remain in web/js/classic-node.js and web/js/classic-node.css.
Run the frontend coverage/browser suites and managed Classic/Nodes 2.0 matrix after updates. Shared modules participate in the frontend coverage gate. Pixaroma's reusable framework inspired this architecture; no Pixaroma code, styles or assets are included.