Skip to content

Carousel layout redesign - #71

Open
Psylenceo wants to merge 13 commits into
ethanfel:mainfrom
Psylenceo:carousel-layout-redesign
Open

Psylenceo wants to merge 13 commits into
ethanfel:mainfrom
Psylenceo:carousel-layout-redesign

Conversation

@Psylenceo

Copy link
Copy Markdown

Summary

Reshapes the Project Asset Carousel's DOM widget from a flat top-controls-plus-card-strip layout into a source-tree layout that surfaces two kinds of asset relationships that previously had no visual representation: crop/edit lineage and tag-based version families.

Layout

  • Run-name/import/upload/duplicate/refresh controls and the filter tabs stay as full-width rows at the top, as before.
  • Below that, a two-column layout: a resizable source-tree column on the left, and the preview + asset-detail panel side by side on the right (unchanged from before, aside from reflowing).
  • A new full-width row at the bottom shows the currently selected asset's related versions/edits as a row of larger cards.

Crop/edit lineage tree

Cropping or editing an asset already created a new catalog entry carrying a parent_asset_id back-reference (register_derived_image() in project_assets.py) — a backward pointer only, with no forward index anywhere. The left column now builds that forward index client-side (lineageChildren()) and nests a source asset's crops/edits under it with an expand/collapse toggle, instead of listing every asset as a flat strip.

Tag-family version stacking

Adds recognition of the -v<N> tag suffix (@char_bob, @char_bob-v1, @char_bob-v2, ...) as a "version family":

  • Multiple versions of the same tag collapse into a single card showing the latest version, with a caption noting how many versions exist.
  • The family's stack renders at whatever tree position is actually correct — at the root if the base version has no parent, or nested under a shared ancestor if the family's members are themselves crops of something else (rather than always being hoisted to the top of the tree).
  • This is a separate relationship from crop lineage (a crop's parent/child link and a tag-family's version link don't imply each other), and both can be surfaced for the same asset — the bottom detail row unions both when present (e.g. "2 versions, 1 edit").

Backend: -v<N> capture-tag suffix

Review Gate's frame-capture flow already had a "re-use the same tag as an updated take" behavior (_capture_family_tag() in project_assets.py) for exactly this use case, but it used a bare trailing digit (@char_bob1, @char_bob2). Switched to -v<N> and made the ordinal scan match the suffix pattern exactly rather than via startswith, so a tag that merely ends in a number or the letter v@vehicle-van, @char-venessa, @char_bob_house — can no longer be misread as a version of an unrelated family. This only affects newly-created capture tags; no migration of existing data.

Other fixes bundled in

  • Thumbnails: left-column cards are now a fixed 168×126 size with a 92px-tall, center-cropped image — matching the bottom detail row's card size, instead of stretching to the column's full width.
  • Node resize floor: the node previously had no minimum width it could be resized to (only a minimum height). Traced this to ComfyUI's DOM widget always reporting minWidth: 0 from its default computeLayoutSize() — overrode it directly on this widget to report an actual floor derived from the layout's column minimums.

Testing

  • node --test tests/*.mjs — full JS suite passes (53 tests on this branch; literal-string assertions in tests/_project_asset_manager_js_test.mjs etc. are unaffected).
  • PYTHONPATH=. python tests/_project_asset_store_unit_test.py / tests/_asset_store_unit_test.py — pass, including new coverage for the -v<N> capture-tag scheme and a regression guard against the @vehicle-van/@char_bob_house false-match case.
  • Manually verified in a live ComfyUI instance across several rounds: crop lineage nesting/toggle, tag-family stacking at both root and nested attach points, thumbnail sizing, and node width resize floor.

Known limitations

  • Tag-family grouping is purely a naming convention (-v<N> suffix match) — it can't detect a typo (@human_herm-v1 vs @human-herm-v1 won't match) and has no relation to the underlying asset content.
  • Family stacking and folder grouping don't compose: an asset inside a folder is not considered for family stacking.

Psylenceo and others added 11 commits September 7, 2026 18:13
Lists this fork's branches with work not yet in Ethanfel's upstream:
fix/prompt-optimizer-settings-visibility and
feature/settings-based-origin-allowlist (both not yet opened as PRs),
plus a note that Review Gate frame-capture already merged into
nightly. Update this section whenever a branch here is opened as a
PR, merged, or dropped.
Also updates the fork status section: the Review Gate prompt-resize
branch (PR ethanfel#50) and frame-capture work are now both merged and
released upstream as of 0.6.6.
Upstream's own version of _decode_lazy_motion_video only pads a single
rounding frame (display_end = previous_time + 1/source_fps) past the
last decoded frame. Our local fix, which holds the last frame for the
rest of the requested window when the reference clip is genuinely
shorter than the scene needs, got silently reverted to upstream's
narrower behavior by the 0.6.8 merge (clean merge, no conflict, so it
was never flagged) - exactly the regression MY_CHANGES.md's checklist
exists to catch. Without this, any reference shorter than its window
by more than one frame now raises "yielded only N of M requested
frames" instead of tolerating it, which is what actually happened
during today's Studio workflow test.
…atus

Both branches are now submitted upstream as PR ethanfel#55 and PR ethanfel#56.
…rees

Reshapes the Asset Carousel DOM widget into a left source-tree column
(crops/edits nested via parent_asset_id, tag-family "-vN" versions
grouped and nested at their correct attach point) alongside a
preview+editor stage, with a bottom panel showing the selected asset's
combined crop and version relationships. Also gives the node an actual
horizontal resize floor (ComfyUI's DOM widgets only report one for
height by default) and switches Review Gate's capture-family tag
suffix from a bare digit to "-vN" to avoid false-positive matches like
@vehicle-van or @char-venessa.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Psylenceo added a commit to Psylenceo/ComfyUI-MiniMaxH3-Context-Loop that referenced this pull request Sep 15, 2026
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ethanfel

Copy link
Copy Markdown
Owner

Thanks for working on this. I like the direction of the source tree and the separate preview/edit area. I checked commit a316f38faecc62ac82e7c63de22919469c7d897b, and there are a few things to correct before we merge it, especially around keeping existing projects usable.

The UI cases below were reproduced using the actual rendering/grouping functions from this commit with a small mocked DOM and synthetic catalogs. These are code-level reproductions, not a full live-ComfyUI browser test. No existing project was modified.

1. A child assigned to another folder is displayed under its parent's folder

Relevant code: renderCarousel, root detection and folder rendering

Reproduction:

  1. Put an original image in folder References.
  2. Create a crop with parent_asset_id pointing to that image.
  3. Assign the crop to folder Characters.
  4. Expand both folders and the original's edits.

The crop is displayed beneath the original in References, even though its saved folder_id is Characters. With only that crop assigned to Characters, the latter is rendered as an empty folder with a count of zero.

byParent and byId use the complete catalog. The child is skipped at line 2082 before its folder is considered, then rendered through the parent's children without checking its folder. The fallback at line 2110 gives the skipped folder a hardcoded zero count.

Please keep folder membership authoritative for placement and counts. A child whose parent belongs to another folder should remain visible as a root item in its own folder. The related-edits strip can still show its relationship to the original; that relationship should not override where the user put it.

2. Role tabs can hide matching children and show nonmatching ones

Relevant code: full-catalog lineage lookup

Reproduction:

  1. Have a parent with role picture and a derived child with role semantic_anchor.
  2. Switch to the Semantic tab.

The parent is filtered out, but the child is also skipped because the parent still exists in the unfiltered catalog. The result is an empty browser despite having a matching semantic asset. Switching to Images shows the semantic child beneath the image parent instead.

Please build the main tree relative to the active filter as well as folder membership. A matching child whose parent is outside that visible scope needs its own visible entry. Expanding a parent should not bypass the active role filter. This filtering rule can be separate from the intentionally cross-role related-assets strip.

3. Version stacks disappear when their parent is deleted

Relevant code: familyAttachPoint

Reproduction:

  1. Have two unfiled children tagged hero and hero-v1, both pointing to the same parent.
  2. Delete the parent while keeping the children.

Both remaining assets disappear from the main tree. They are still in the catalog; this is a display problem, not deletion of their files.

familyAttachPoint returns the missing parent's ID without checking that it exists. Both members are then marked as hoisted, so neither gets a normal card, and the stack has no existing parent under which to render. The current backend deletion path leaves surviving children's parent references in place, so this is a reachable state.

Please fall back to displaying the stack at the appropriate visible root when its attachment point is absent or outside the current scope. A stale parent reference must not make surviving assets inaccessible.

4. Version naming needs a compatibility policy, and names alone are not enough to identify a family

Backend naming / frontend grouping

hero-v1 is a valid tag, and the delimiter is clearer than assuming every trailing digit means a version. The problem is the transition and the grouping logic:

  • Existing captures hero, hero1, hero2 are not recognized as one version family. Adding a new hero-v1 groups it with hero, while hero1 and hero2 remain separate.
  • assetFamilies groups purely by tag text, without checking any actual capture/version relationship. Two unrelated assets named hero and hero-v1 are grouped, even when one is an image and the other is an audio reference. I reproduced that case too.

Please preserve existing tags and prompt references. Explicit family/version metadata would be safer than inferring identity entirely from a naming convention. For old assets where the relationship cannot be established reliably, keeping them separate and accessible is preferable to guessing or renaming them automatically. Please document how existing captured takes are handled and test mixed old/new catalogs.

To be clear: the PR does not rename existing tags, and the three failing capture tests are not evidence of broken prompt resolution. They currently assert the old naming contract. Those expectations need to be reconciled with the chosen behavior, while retaining their collision, concurrency, and capture-integrity checks.

5. Please separate the unrelated motion-decoding change

Relevant code: _decode_lazy_motion_video EOF handling

This PR also changes early-EOF handling from a bounded last-frame display interval to repeating the final frame through the entire remaining request.

I tested the decoder with a synthetic 12-frame, 24 fps video and a requested window of 0:240. It returned 240 frames, with the additional 228 frames all repeating the last source frame. That is a substantial behavior change, not just frame-rate rounding tolerance. It can conceal insufficient or unexpectedly truncated source media at the decoder level.

Please remove this from the carousel PR and discuss it separately, with an explicit policy and tests for short references versus unexpected early EOF. This reproduction calls the decoder directly; it does not establish that every higher-level preflight path allows such a request.

Also please remove the fork-specific status section added to the upstream README.

Verification and requested regression coverage

Current results in the isolated PR checkout:

  • node --test tests/*.mjs: 53 passed. The existing tests do not cover the new tree/family cases above.
  • tests/_project_asset_store_unit_test.py: passed.
  • tests/_lazy_motion_path_unit_test.py: passed.
  • tests/_review_frame_capture_test.py: 9 passed, 3 failed on tag expectations (test_capture_pixels_and_numbered_takes, test_capture_tag_does_not_collide_with_disabled_asset, and test_concurrent_captures_preserve_all_takes).

Please add regression tests for moving a child between folders, a folder containing only derived children, parent/child roles across tabs, missing/deleted family parents, mixed legacy/new capture tags, and unrelated assets with version-looking names. Include a save/reload check so placement and visibility remain correct after reopening a workflow.

The layout can stay; the important part is that organizing or filtering assets must not make them appear in the wrong place or disappear. A small, scope-aware grouping fix with tests is enough here—we do not need a storage migration or automatic project rewrite.

Psylenceo and others added 2 commits September 16, 2026 22:34
isLineageRoot() only considered parent_asset_id, so a crop moved to a
different folder than its parent still rendered nested under it, and
the crop's real folder showed as empty with a hardcoded zero count.
isLineageRoot() and collectChildItems() now also check that a child
shares its parent's folder_id before treating it as nested; a folder
mismatch surfaces the child as its own root item in its real folder
instead. The crop/edit relationship itself is unaffected and still
shown in the bottom detail row.

Reported by ethanfel on PR ethanfel#71.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
assetFamilies() grouped purely by tag text, so an image and an
unrelated audio reference sharing a coincidental -vN tag pattern
(e.g. @hero and @hero-v1) were treated as versions of each other.
Groups (and looks them up) by tag *and* kind together via a new
familyGroupKey(), so a family can never mix images/video/audio.

No change to the "no migration" behavior for older bare-digit capture
tags (@char_bob, @char_bob1, @char_bob2) — they intentionally stay
separate and untouched, since a naming pattern alone can't prove two
old assets are actually related. Documented that explicitly in
docs/PROJECT_ASSETS.md.

Also updates tests/_review_frame_capture_test.py's three assertions
that hard-coded the old bare-digit suffix contract to expect -vN
instead; their actual purpose (collision handling, concurrent-capture
uniqueness, disabled-asset non-collision) is unchanged, only the
literal expected tag strings were stale. One assertion's input was
also changed to request the exact prior "-v1" tag rather than the
old scheme's coincidental "hero1", so it still exercises a genuine
collision against a "-vN" tag.

Reported by ethanfel on PR ethanfel#71.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants