- Duplicate: derive the shift delta from the copy's actual bounding box (not the group frame, since a
member can protrude after individual editing) and apply one uniform delta with no per-clone clamp, so
the copy's internal layout is preserved exactly. Falls back to a negative shift when the group is
pinned to an edge (no more copy-on-top-of-original).
- Mirror: skip whole LOCKED sub-branches as a unit (collect members without crossing a locked box) so a
nested sub-group frame and its children always mirror together; clamp the whole reflected interval
(keeps box size, stays on-canvas) instead of one edge; validate the axis.
JS parses clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three buttons in a selected group's panel:
- Duplicate: clones the group and its whole subtree (fresh ids, parents remapped within the copy,
shifted slightly so the copy is visible and stays on-canvas; copy is editable).
- Mirror ⇆ / Mirror ⇅: flip the group's members left↔right / top↕bottom about the group's frame
(frame unchanged). Locked and unplaced members are left as-is.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Previously a group frame could be resized but it did nothing to the children. Now resizing a
group's frame (corner/edge handle) scales its placed children proportionally about the same anchor
edge — reusing the existing group-resize transform. Skips locked sub-branches and unplaced (nobbox)
children. Resizing a normal region is unchanged (children don't scale). Show frames with 👁️ first
to grab a group's handles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Toolbar button icon is now 👁️ (lit/active = group frames visible).
- Group frames are HIDDEN on the canvas by default (showGroups defaults off; only `true` shows them).
Click 👁️ to reveal them. Groups remain in the Overview tree at all times and are never exported.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- New 📁 toolbar button toggles on-canvas visibility of group frames (node.properties.showGroups).
When off, group frames aren't drawn and aren't grabbable on the canvas — but groups still appear
in the Overview tree. State persists and is restored on load. Groups are never exported regardless.
- Dragging a multi-selection in the Overview now shows live feedback WHILE dragging (not just on
release): every dragged row fades, and a small pill follows the cursor showing "N regions → <target>"
(the group/region you're about to drop onto, or "→ root"). Cleared on drop/cancel/teardown.
JS parses clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Locked rows no longer reparent (lock = frozen): pointerdown bails on a locked row, and locked
boxes are filtered out of a multi-selection drag.
- Drop cue only highlights a target when at least one dragged box would actually change parent
(no more misleading blue ring over a box's current parent).
- Trailing-click suppression now fires only when a reparent actually happens, so a tiny accidental
drag that lands on nothing no longer eats the row's click.
- Keep _selAnchor in sync after a multi-reparent so a later Shift-click range starts correctly.
JS parses clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Headline (requested): dragging a row that's part of a multi-selection now reparents the WHOLE
selection — drop it onto a group to add all selected regions at once, or onto the header to send
them all to root. Only the selection's top-level boxes move (internal nesting preserved); cycle-
guarded against dropping onto any dragged box or a descendant. The moved boxes stay selected.
Group-review fixes:
- Major: selecting a group showed the region editor; its bbox fields silently detached the group
from its children. Groups now get a dedicated panel (name + frame color only) — no bbox fields.
- Moving a parent/group no longer places its UNPLACED (nobbox) descendants (which would alter the
export); only directly-selected boxes get placed on a drag.
- Groups no longer reserve a phantom number badge / invisible title hotspot (tagRects + titleAt skip them).
- Locked groups now show the tight frozen dash + a 🔒 glyph.
- Groups can't be duplicated into orphans (startPlacing/copySelection skip them; ⧉ hidden on group rows).
JS parses clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Right-click the Overview → "Create group from selection" wraps the selected regions in a new,
named **group**. Groups are editor-only organizers — never exported to the caption.
- A group is a box with `group:true` + `name`, parented over the selection (its bbox = the
selection's union + a small margin so its dashed frame is grabbable). Only the top-level selected
boxes are reparented, so internal sub-hierarchies are preserved.
- Move the group on the canvas → the whole group moves (Phase-2 propagation). Drawn as a dashed
named frame; shown in the Overview tree with a 📁 label and a collapse triangle.
- Double-click a group's Overview row to rename it.
- Deleting a group keeps its children (they promote to the group's parent) — already how delete works.
- Excluded from export in BOTH the editor (buildCaption) and the Python node (execute elements,
bbox output, and preview render), so the Ideogram caption stays a flat list of real regions.
JS parses clean (ES module); Python parses clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- The region panel is now titled "Overview" (with a tooltip describing the gestures); README updated.
- Overview rows: Ctrl/Cmd-click toggles a row in/out of the selection; Shift-click selects the
range from the anchor to the clicked row in displayed (tree) order. Plain click still single-selects
and sets the range anchor. Selection is shared with the canvas, so multi-selects show + move there.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adversarial review of the hierarchy change found three real bugs + polish:
- Major: keyboard Delete (removeSelected) dropped a deleted parent's children to ROOT, while the
per-row ✕ promoted them to the grandparent. removeSelected now promotes orphans to the nearest
surviving ancestor (skipping co-deleted parents), matching the ✕ behavior.
- Major: a cycle or self-parent (possible via a hand-edited/imported save) silently hid regions
from the tree — ensureParentsValid only nulled MISSING refs. It now also nulls self-parents and
breaks cycles by cutting the back-edge, so every box stays reachable from a root.
- Major: dragging a parent tore apart a locked sub-branch — a locked intermediate box stayed put
but its children still moved. Move-group build now skips locked boxes AND their whole subtree.
- Minor: snap-then-clamp could overflow the canvas by a grid cell (now reachable via single-parent
drag); snap first, clamp last so the clamp always wins.
- Minor: setParent now returns false when the parent is unchanged (no churn commit), and the drop
cue skips the box's current parent.
- Minor: clear any prior _exDragCleanup before a new row drag (no leaked listeners on overlap);
clear the click-suppress flag on consume.
JS parses clean as an ES module.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Editor-only parent/child hierarchy on top of the stable ids — never reaches the exported caption
(Python ignores unknown box fields).
- Data: each box gets an optional `parent` (another box's id). Helpers: boxById, isAncestor,
descendantIds, setParent (rejects self/missing/cycles), ensureParentsValid (drops dangling refs
to root on delete/import; runs in serialize()).
- Move propagation: dragging a box on the canvas moves its whole subtree (children follow). Resize
affects only explicitly-selected boxes — children don't scale. Locked descendants stay put.
- Object Explorer is now a tree: indentation by depth, per-parent ▶/▼ collapse (persists on the box).
Drag a row ONTO another to nest it (cycle-guarded, live drop highlight); drop on the "Regions"
header to send it back to root. Trailing-click after a reparent is suppressed.
- Delete promotes a box's children to its parent (no surprise subtree wipe).
- Paste/duplicate clear parent (become roots) so clones never carry stale parent ids.
- Z-order stays independent of hierarchy (still reorder via the right-click region menu).
JS parses clean as an ES module.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adversarial review of the Phase 1 change surfaced one real bug + polish:
- Major: drag-reorder called commit() -> renderExplorer(), which cleared the list and destroyed
the row whose _dragged guard was just set, so the trailing click wasn't suppressed and fired a
spurious re-select. Drag-end now repaints in place (serialize/renderPanel/drawCanvas/tokens)
without rebuilding the list — mirrors the right-click region menu.
- Leaked listeners: explorer drag binds document-level pointer listeners; store _exDragCleanup and
call it from onRemoved so a mid-drag node deletion can't leak them.
- Resizer stayed draggable while collapsed (silently mutating explorerW); hide it when collapsed.
(Also reordered the init call so applyExplorerCollapsed never references exRsz before it exists.)
- scrollIntoView fired on every commit; now only scrolls when the active row is actually off-view
and the rail isn't collapsed.
- startPlacing (duplicate from the list) now refreshes the explorer immediately.
- Hardened .ai2go-ideo-cv with explicit min-width:0; widened resizer max-clamp by the divider width.
JS parses clean as an ES module.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Object Explorer: a persistent, collapsible rail left of the canvas listing every region
(number, color swatch, label, lock, duplicate, delete, drag-reorder). Click a row to select
a region — two-way synced with the canvas — so regions can be picked and edited from the list
without dragging them by accident. Width + collapsed state persist in node.properties.
Stable IDs: each box gets a makeUUID() id (stamped in serialize via ensureIds; regenerated on
paste/duplicate). Editor-only groundwork for parenting — Python ignores unknown box fields, so
the id never reaches the exported caption.
JS parses clean as an ES module.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Set the GitHub repository URL (pyproject + README clone) and the publish
workflow's owner gate to Little-God1983.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port the Ideogram 4 Prompt Builder node and its visual bbox editor from
ComfyUI-KJNodes (GPL-3.0, by Kijai) into this pack, fully renamed so it can
coexist with KJNodes if both are installed:
- nodes/ideogram4_nodes.py class/node_id/display_name -> AI2Go*, category AI2Go/text
- web/js/ideogram4_prompt_builder.js extension name, CSS id + 'kjideo-' prefix -> 'ai2go-ideo-',
CSS vars '--kj-dock-' -> '--ai2go-dock-', template dir -> ai2go/ideogram4
- web/js/utility.js shared helpers the editor imports (sibling module)
- fonts/FreeMono.ttf preview font
- __init__.py registers AI2GoIdeogram4PromptBuilder
GPL-3.0 attribution headers added to each derived file. Python + JS syntax verified.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>