- GroupsOverlay: reset color picker when menu closes, guard locked groups
from drag/resize/rename
- AudioInputNode: add keyboard accessibility to dropzone (role, tabIndex,
onKeyDown)
- list-workflows: fix name regex to handle escaped quotes
- GroupsOverlay.test: add missing NBP Input menu item assertion
- WorkflowBrowserView: make relativePath required to match API contract
- WorkflowBrowserModal: add dialog role, aria-modal, aria-labelledby
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- cleanup-workflow.js: Filter only embedded data: images from outputGallery
instead of wiping entire array; preserve imageRef/video on output nodes
- workflow/route.ts: Validate parsed.version is number, nodes/edges are
arrays; sort candidates by mtime desc for deterministic file selection
- mediaStorage.ts: Guard against undefined mediaData in loadMediaById
before caching/returning
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The group controls (lock, delete, color) moved from individual toolbar
buttons into a dropdown menu behind "Group options". Update tests to
open the menu first, then interact with items by text label.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add recursive directory scanning (up to 3 levels deep) to the
list-workflows API so projects organized into subdirectories are
visible in the workflow browser. Skips hidden dirs and node_modules.
Returns relativePath field displayed as the subtitle in the UI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the local Set variable in executeWorkflow() with direct
reads/writes to the Zustand store field. The dual tracking was
redundant since get() is synchronous and Set.has() is O(1).
Extract a resetSkippedNodes() helper to DRY up the two cleanup blocks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The rebase dropped the commit that re-added skippedNodeIds to the store
interface since develop had already extracted it to a local variable.
However, WorkflowCanvas.tsx needs to observe skippedNodeIds from the
store to apply visual dimming during execution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- WelcomeModal: "Load workflow" now navigates to WorkflowBrowserView
instead of calling browse-directory API directly. Replaced 4 obsolete
fetch-based tests with 3 tests covering view navigation and callback.
- QuickstartInitialView: updated description regex to match new copy
("generative AI pipelines" instead of "AI image generation").
All 1934 tests now pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove unused isBase64DataUrl legacy alias from mediaStorage.ts
- Move cleanup-workflow.js to scripts/ directory
- Add parallel batch processing (batch size 3) to hydrateWorkflowMedia,
matching the pattern already used by externalizeWorkflowMedia
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
skippedNodeIds was in the Zustand store interface but never read by any
component — it was only used as a local variable during executeWorkflow.
Removed the store field/initial state/set() calls, keeping only the local
variable.
Also extracted the duplicate 5-line skip-reset block (success + error paths)
into a resetSkippedNodes() helper inside executeWorkflow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The open button now opens a WorkflowBrowserModal instead of calling
the browse-directory API. Updated the test to assert modal rendering
and removed two obsolete tests that tested the old fetch-based flow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Back button sits on its own row so the title and directory path are
flush-left instead of indented beside the arrow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add folder icon with background per row for clear visual anchoring
- Show directory basename as secondary text under workflow name
- Right-align timestamps as de-emphasized metadata
- Add project count in header
- Richer empty state with icon and two-line message
- Border-based hover/active states instead of flat background tint
- Footer actions get small icons for scannability
- Deduplicate browseAndSetDir handler
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of parsing entire workflow JSON files (which can be many MB with
embedded base64 data), read only the first 1KB to extract the name via
regex. Drop nodeCount from the listing. Probe all directories in parallel.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Header's folder icon now opens the workflow browser modal instead of
the OS directory picker directly. The modal wraps WorkflowBrowserView
in the standard overlay pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Load workflow button now navigates to the WorkflowBrowserView instead
of immediately opening the OS directory picker.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reusable component with two states: empty state prompting user to choose
a workflows folder, and listing state showing all workflows with name,
node count, and relative time. Supports changing folder and one-off browsing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Scans subdirectories of a given parent path for valid workflow JSON files,
returning name, path, node count, and last modified time.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Same approach as Header.tsx — replace browser file picker with native OS
directory picker so loadWorkflow receives the filesystem path for media
hydration from sibling inputs/ and generations/ folders.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace browser file picker with the existing /api/browse-directory native
OS folder picker so loadWorkflow receives the actual filesystem path,
enabling media hydration from sibling inputs/ and generations/ folders.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The directoryPath field was defined in WorkflowFile and checked during
load, but never written during save. Workflows opened from a different
machine or via file picker had no way to find their externalized media
files, so all ref-based nodes appeared empty.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Videos and audio were saving to the project root instead of the
generations/ subfolder. Also strips legacy inline base64 image data
from imageHistory items (~4.5MB each) that was inflating workflow
JSON files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use API response imageId instead of locally generated ID in
saveVideoAndGetRef/saveAudioAndGetRef (filename mismatch)
- Read result.video/result.audio instead of result.data in
loadMediaById (response field mismatch)
- Add audio extensions and MIME types to load-generation route
so audio files can be found and served
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Critical bug fix - the mediaStorage.ts functions were using incorrect
parameter names when calling the save-generation and load-generation APIs.
Fixed parameter mappings:
- workflowPath → directoryPath (API expects this name)
- id → imageId (API expects this name)
- data + type → video/audio (API expects media in named fields)
- Changed load from GET to POST with correct body structure
Also increased serverActions bodySizeLimit from 50mb to 100mb to handle
large video files during externalization.
This fixes the save failures when externalizing workflows with videos/audio.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Convert null to undefined when assigning to optional ref fields
to satisfy TypeScript type constraints.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add audioFileRef, imageARef, imageBRef, and capturedImageRef fields
to AudioInputNodeData, ImageCompareNodeData, and GLBViewerNodeData
to support external media storage optimization.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
OutputGallery nodes were not being handled in externalizeWorkflowImages(),
causing their images arrays (which can contain dozens of high-res base64
images) to remain embedded in workflow JSON even when "Embed images as
base64" was disabled.
This caused workflow files to balloon to 500MB+ and hit JavaScript's
"Invalid string length" limit when saving.
Changes:
- Add outputGallery case to externalizeNodeImages() to clear images array
- Add outputGallery case to hydrateNodeImages() for consistency
- Add cleanup-workflow.js script for fixing existing affected workflows
OutputGallery content is display-only and regenerated on workflow execution,
so it doesn't need to be persisted.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Restructure README following a center-aligned hero style with badges,
feature tables, provider list, tech stack badges, and community links.
Replace screenshot with updated workflow canvas image.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 14 tests covering:
- Optional inputs with no data are skipped
- Skip propagation cascades downstream (ANY merge policy)
- Shared nodes are not skipped
- Backward compatibility without isOptional
- Skipped status cleared after workflow completes
- Multi-level cascade
Also add skippedNodeIds to WorkflowCanvas test mock.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add node-skipped CSS class (opacity 0.35) for skip dimming
- Expose skippedNodeIds in Zustand store for reactive CSS updates
- Apply node-skipped class in WorkflowCanvas alongside switch-dimmed
- Clear skippedNodeIds on workflow stop, reset, and completion
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shows a toggle in the FloatingNodeHeader for imageInput, audioInput,
and prompt nodes. When toggled on, the button turns amber and shows
"Optional"; when off, it shows "Required" in the default style.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ImageInputNode: dashed border + "Optional" text when empty, badge when loaded
- AudioInputNode: same pattern as ImageInputNode
- PromptNode: updated placeholder text when isOptional is set
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When an optional input node has no data, it and all downstream nodes
with a skipped source are silently skipped during execution. Uses
ANY merge policy — if any source is skipped, the downstream node
is also skipped. Skipped status is reset to idle after workflow
completes or errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add `isOptional?: boolean` to ImageInputNodeData, AudioInputNodeData,
and PromptNodeData. Add "skipped" to NodeStatus union type to support
skip propagation in the execution engine.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>