Files
malkuthroandClaude Opus 4.7 4409d3c9d8 feat(ai_pipeline): no_subfolders toggle + path normalisation + tooltips (#156)
* feat(ai_pipeline): no_subfolders toggle + path normalisation + tooltips

Three EasyAIPipeline improvements with a 49-case test suite pinning
the behavior so the user-reported "phantom undefined/ folder" bug
can't regress.

no_subfolders (default off): write directly into base_directory_path
with no shot_name/ai_method/vNNN subfolders appended. Base folder is
still auto-created if missing.

Paste-input hardening: _normalize_base_path strips surrounding
whitespace, one matched pair of quotes ("..." from Explorer's Copy
as path), and trailing path separators. Drive roots (C:\, n:/) are
preserved. Plus a belt-and-suspenders rstrip on the final
output_directory — fixes the symptom where a trailing backslash on
the user's pasted base path caused downstream save nodes to split
on /, stringify the empty tail as "undefined", and write into
.../undefined/... on disk.

Smart filename: empty pieces of shot_name / ai_method / vNNN are
filtered before the underscore join, so blank ai_method yields
RTX-upscale.exr (not RTX-upscale_.exr). Same rule for subfolder
build.

Tooltips on every input explain interactions (e.g. ai_method
becomes both filename segment and subfolder; enable_overwrite only
blocks an existing file, not directory).

JS preview helpers in web/ai_pipeline.js mirror the Python so the
Get output directory / file path buttons stay accurate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ai_pipeline): sanitize shot_name/ai_method to prevent base-path escape

Reviewer-flagged blocker in PR #156: os.path.join's "last absolute path
wins" rule let absolute-looking shot_name or ai_method values (typos
like "/oops", or pasted full paths like "C:/Windows/junk") replace
base_directory_path entirely. The pre-#156 code had a partial defense
(.lstrip('/')) that the refactor dropped without replacing.

New _sanitize_segment helper: lstrip leading separators → splitdrive →
lstrip again. Strips leading seps BEFORE splitdrive so multi-slash
input like "///oops" doesn't get swallowed as a UNC prefix on Windows
(empirically observed — the user's intended segment name would be lost).
Applied symmetrically to directory build and filename build. Raw
shot_name kept in the return tuple for downstream label-only consumers.

Also fixes a minor JS preview mismatch at web/ai_pipeline.js: empty
base + no_subfolders=true used to render "/name.exr" while Python
returns "name.exr". Filter empties before joining the file_path.

16 new tests pin both the helper and the end-to-end escape vectors:
- TestSanitizeSegment (7 unit tests, platform-aware drive handling)
- 4 parametrised absolute shot_name escape cases
- 4 parametrised absolute ai_method escape cases
- 1 filename-leak regression

Full pytest: 102 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ai_pipeline): flatten embedded separators in shot_name for filename

Maintainer-reported: with no_subfolders=true and an upstream node feeding
shot_name="ComfyUI-working-folder/LTX-Director-2K", the output was
base/ComfyUI-working-folder/LTX-Director-2K.%04d.exr — still creating
subfolders via the filename concat, defeating the toggle entirely.

Root cause: the prior fix sanitized only LEADING separators (path-escape
case). Internal separators in shot_name slipped through and became
subfolders when the filename string with embedded "/" was joined onto
output_directory by the filesystem.

Fix: filenames can't contain "/" or "\\" on any OS, so always flatten
them to "_" in the filename build. Directory build with
no_subfolders=false still uses the separators as subfolder delimiters
(intentional — users organizing into Project/v1/ai_method/vNNN want this).

With no_subfolders=true, shot_name="job/shot" now correctly produces
base/job_shot.ext (flat in base), not base/job/shot.ext.

3 new tests:
- Maintainer's exact case (no_subfolders=true + slash in shot_name)
- Belt-and-suspenders across 5 shot_name/ai_method/toggle combinations
- Verify no_subfolders=false still creates subfolders from slashes
  (preserves intended use case)

Full pytest: 110 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ai_pipeline): strip control chars + single-line Output Preview

Maintainer reported the file_path preview rendering as two visual lines
("n:/.../bear/...LTX-Director-2K" + newline + "_0.%04d.exr") and the
output looking like it contained a literal newline. Two contributing
causes:

1. The multi-line Output Preview widget word-wrapped long single-line
   paths at the textarea edge. The data was always one logical string,
   but the visual break made it look broken. Fix: set wrap="off",
   whiteSpace="pre", overflowX="auto" on the input element so long
   paths render on a single horizontal-scrollable line.

2. Upstream nodes (WAS Text Multiline etc.) can legitimately emit
   strings with embedded \n / \r / \t — a stray paragraph break in the
   widget content. Those would silently break filesystem saves and ALSO
   wrap the preview at the actual newline character (not just the
   textarea edge). Fix: new _strip_control_chars helper, applied at the
   top of _normalize_base_path and _sanitize_segment (covers
   base_directory_path, shot_name, ai_method) plus explicitly on
   extension in generate_pipeline. Mirrored in JS for preview parity.

9 new tests: 6 helper unit tests, 3 end-to-end (shot_name w/ newline,
base path w/ newline, extension w/ newline).

Full pytest: 119 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ai_pipeline): strict whitespace strip on extension + revert widget

Two issues, one commit:

1. Maintainer reported ComfyUI-HQ-Image-Save bailing with "Filepath
   needs to end in .exr" even though our preview showed a clean
   .%04d.exr suffix. Root cause: HQ-Image-Save validates via
   os.path.splitext(filepath)[1].lower() != ".exr" — a single trailing
   space on the extension widget (easy to acquire from a paste) makes
   splitext return ".exr " with the space, which fails the strict
   equality. Previous control-char strip only handled \r\n\t and
   left regular spaces alone.

   Fix: extension now runs through "".join(extension.split()) which
   removes ALL whitespace (internal + surrounding, including unicode
   whitespace and non-breaking spaces). Mirrored as cleanExtension
   in JS. Also added .strip() to _sanitize_segment so shot_name /
   ai_method with stray surrounding whitespace canonicalise too.

2. Reverted the Output Preview widget from the previous commit's
   single-line wrap=off + horizontal-scroll layout back to the
   original multi-line word-wrap (100px height, default textarea
   behavior). The earlier change traded mouse-drag-selectability for
   single-line display — wrong tradeoff. The path is one logical
   string anyway (cleanLine strips real newlines), so visual wrap is
   just cosmetic.

9 new tests:
- 6 parametrised extension whitespace cases (trailing/leading/internal
  space, tab, non-breaking space) — all verify HQ-Image-Save's
  splitext check passes after sanitization
- 3 parametrised shot_name surrounding-whitespace cases

Full pytest: 128 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(ai_pipeline): no_subfolders keeps version folder; v### in tooltips

Maintainer-clarified semantics for the no_subfolders toggle: it should
only drop shot_name / ai_method from the directory chain, NOT the
version folder. Previously, no_subfolders=true stripped everything,
which meant versioned outputs all collided in the same flat directory —
defeating versioning whenever the toggle was on.

New behavior matrix for no_subfolders=true:
- disable_versioning=true  → output_directory = base
- disable_versioning=false → output_directory = base/v###

So truly flat output (no subfolders at all) now requires BOTH toggles,
matching VFX convention. Versioned-but-no-name-subfolders is now
expressible with just no_subfolders=true, which is the common case.

Tooltip updates:
- no_subfolders: rewritten to describe new behavior
- base_directory_path: path-shape description updated to
  shot_name/ai_method/[v###]
- ai_method: clarifies subfolder application is gated by no_subfolders
- version, disable_versioning: vNNN → v### (Nuke-style convention
  consistent with the existing .%04d.exr default)

2 new tests:
- no_subfolders=True + disable_versioning=False → base/v###
- no_subfolders=True + disable_versioning=True  → base (truly flat)

Existing tests use the disable_versioning=True default with
no_subfolders=True so they continue to assert base-only — they still
pass under the new logic since version_str="" gets filtered out of the
join.

Full pytest: 130 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ai_pipeline): strip sentinel path components

* fix(ai_pipeline): resolve EasyUse get-set preview values

* docs: document ComfyUI preview button debugging

* fix(ai_pipeline): align drive segment sanitization

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:01:43 +02:00
..
2026-05-16 01:25:39 +02:00

Maintainer docs

Project-internal procedures. If you're cutting a release, forking a node, hitting the Comfy Registry API, or debugging CI — read these first.

These are docs for humans doing the work. The corresponding agent workflows for Claude Code live in ../../.claude/skills/ and should be kept in sync with these prose docs.

Contents

File What it covers
conventions.md Cross-cutting project conventions that don't fit any single topic doc — currently the Mermaid-for-diagrams rule. Add new cross-cutting rules here as they accrete.
releasing.md Canonical step-by-step release procedure: branch → PR → publish-validate → squash-merge → tag → GitHub Release. Anti-patterns to avoid. The per-release tickbox mirror is .github/ISSUE_TEMPLATE/release_checklist.md.
registry-api.md The Comfy Registry's undocumented version-management endpoints (deprecate, delete, query) reverse-engineered while cleaning up v0.1.0–0.1.5. Includes ready-to-paste curl commands and an optional GitHub Actions workflow YAML for codified version management.
node-versioning.md Hard-won rules for changing INPUT_TYPES / RETURN_TYPES / class names without breaking saved user workflows. The five rules + the suffix-version pattern (MyNode → MyNodeV2) + the alias-then-deprecate migration path.
curated-sidebar.md The Kforge Labs sidebar starter-preset workflow. How to customize the bundled web/starter_preset.json shipped to fresh users (Snapshot → Load → starter). Storage keys, library directory resolution, reset procedure.
workflows-sidebar.md The "Workflows" section of the same sidebar. Save canvas / save selection / directories / archive on collision / load with named tab. Storage shape, action semantics, reset procedure.
dev-iteration-loop.md The fast iteration pattern we used to build the sidebar — the dev-sync loop, agent trigger phrases (go, dev-sync, paste-JSON), what survives across re-syncs vs what doesn't, push/publish gates. Read this when returning to UI work after a break.

Read this first if you're confused about version numbers: ../reference/versioning.md explains the three independent version axes in this codebase (pack version, fork wrapper version, upstream pinned commit) and which skill/doc owns each one. Worth a one-time read before starting any version-bump work.

Companion agent skills

Skill (in .claude/skills/) Mirrors this doc
license-pre-check Runs before any third-party fork; refuses to proceed on incompatible licenses. The principles also apply when a human is doing it manually.
add-external-fork The forks/ workflow — clone outside MAIN, register in forks_manifest.yaml, namespace the IDs. Phase 0 calls license-pre-check.
docs-sync Find-and-replace a version tag (or any string token) across docs, code constants, and manifests in one controlled pass. Used during fork wrapper version bumps.
preflight-release Runs the release-gate checks from tools/preflight_release.py — static AST node-ID extraction, VAE wrapper rank/type dispatch roundtrip, and workflow-fixture stability. The ComfyUI-Manager extension-node-map drift check remains available as an explicit advisory check (--check manager-meta) but does not block releases because Manager metadata is maintained independently. Run before opening any release PR — releasing.md step 1.
menu-comfy Lists all project skills with one-liners — quick inventory when you can't remember what's available. Distinct from the global /menu skill so the two don't collide.