The in-tree visual harness mounts the real web/sidebar/ modules in
isolation so an agent (or maintainer) can verify rendered UI against a
docs/designs/ mockup without running a full ComfyUI
install. Component-level visual parity becomes a self-contained loop:
edit → reload → screenshot → diff.
This complements (does not replace) the dev-sync integration loop in
dev-iteration-loop.md. See When to use the
harness vs. dev-sync below.
| Path | Role |
|---|---|
.claude/launch.json |
Defines the design-server preview config — python3 -m http.server 8765 from the worktree root. The Claude Code preview tool reads this; running by hand from a terminal works equivalently. |
docs/designs/_harness/ |
One HTML file per dialog under verification. Each is a standalone page that mounts the corresponding component from web/sidebar/. |
web/sidebar/<module>.js |
Source-of-truth components. The harness imports these directly — no copies, no redefining. |
docs/designs/<feature>.html |
The locked design spec. Open side-by-side with the harness page for visual diff. |
Each page in docs/designs/_harness/:
- Imports
ensureStylefromweb/sidebar/constants.jsand calls it. That injects the live sidebar stylesheet so what renders is byte- for-byte the same CSS ComfyUI loads. - Imports the component under test from its real location (e.g.
showFolderPickerfromweb/sidebar/modals.js). All transitive imports resolve naturally via the static server — no module stubbing needed for code that doesn't touch ComfyUI globals. - Stubs only the capability functions the component receives as
constructor args (
browseDirectories,createBrowseDirectory, etc.). The stubs use canned filesystem-shaped data so navigation round-trips end-to-end inside the harness with no Python beyond the static file host. - Auto-mounts the default state on page load. The page header carries buttons that cycle through the additional states the matching mockup card illustrates (default / new-folder-input / empty / error / …). One button per mockup-illustrated state.
- Cache-busts the live-module imports with
?bust=+ load time so editingmodals.jswhile the harness is open picks up the fresh code onwindow.location.reload()instead of serving the browser's module cache. This is dev-only — the harness directory is never shipped (filtered out of the Comfy Registry publish, ignored by the sidebar loader).
From the worktree root:
python3 -m http.server 8765Then open both pages in the same browser tab group:
- Harness:
http://127.0.0.1:8765/docs/designs/_harness/<feature>.html - Mockup:
http://127.0.0.1:8765/docs/designs/<feature>.html
Edit → save → switch to the harness tab → ⌘R / Ctrl-R. The harness
imports re-fetch from the static server; the new code renders.
When invoked via the Claude Code preview tool, the equivalent flow is
preview_start({ name: "design-server" }) followed by preview_eval
(navigation) and preview_screenshot. Resize the viewport to at least
1000 × 700 via preview_resize so the picker / dialogs aren't
collapsed into the harness panel's narrow default.
- Pick the matching mockup card in
docs/designs/<feature>.html. Read the surrounding annotations — they specify state names (default, hover, error, …) that become the harness buttons. - Copy the structure of an existing harness page (e.g.
folder-picker.html) and adapt:- Replace the imported component with yours.
- Replace the canned stubs with the capability surface your component expects.
- Add one trigger button per state the mockup shows. Auto-mount the default state on load.
- Verify the harness renders matching the mockup before opening the PR. Note in the PR description "visual diff vs. mockup section N" for each cycle that touched rendered output.
Use the harness for any change whose assertion lives at the rendered surface and can be exercised in isolation:
- Single-dialog layouts, state transitions inside one component, CSS changes, copy changes, icon swaps.
- Stubs cover all the integration surface the component touches.
- Iteration loop is ~5 seconds (edit → reload → screenshot).
Use dev-sync for anything that needs a full ComfyUI environment:
- Cross-dialog flows (Save dialog opens → Load dialog reflects the written file in the next session).
- Theme variables that come from Comfy's frontend chrome (light/dark toggle, custom palette overrides).
- End-to-end snapshot library round-trip against a real preset folder on disk.
- Any sidebar interaction with ComfyUI's canvas, graph state, or keyboard manager.
A common pattern: harness during implementation to nail the layout, dev-sync once before opening the PR to confirm it still looks right under real Comfy chrome.
- That the component's integration with the rest of the sidebar still works. (Use dev-sync.)
- That theme tokens resolve the same way under real Comfy theming.
The harness fakes
--comfy-menu-bgetc. with dark-theme defaults; Comfy's actual values may differ slightly. - That the component renders the same way at narrow widths or on
mobile breakpoints. Test those explicitly via
preview_resize.
If your change might touch any of the above, follow harness verification with a dev-sync pass before requesting review.
- Backticks in CSS comments break the entire sidebar. The CSS in
web/sidebar/constants.jsis a JS template literal. A literal backtick inside a comment closes the template early, the rest of the file becomes invalid syntax, and the whole sidebar fails to load. Use plain quotes in comments, never backticks. (There's a sentinel comment inconstants.jscalling this out — read it before adding new CSS.) - Stale module cache. Without the harness's cache-busting query
string, the browser may serve the previous version of the module
even after a reload. The harness handles this automatically; if you
hand-write a one-off harness, mirror the
?bust=+Date.now()pattern. - The harness is dev-only. Do not import harness HTML or its stubs
from any code under
web/orkoolook_routes.py. The harness lives underdocs/designs/_harness/precisely so it's outside the Comfy Registry archive scanner's walk.