Local reference images were reaching the gateway as localhost URLs, and selected image/video nodes still exposed duplicate inline settings while the composer is the active control surface. This keeps gateway media inputs self-contained, maps GPT Image 2 sizes from the actual aspect ratio controls, and keeps node bodies focused on preview/output state.
Constraint: NewApiWG upstream cannot fetch browser-local /api/images URLs.
Constraint: Selected-node composer owns image and video generation controls.
Rejected: Keep localhost reference URLs | remote gateway requests cannot dereference local browser routes.
Rejected: Keep duplicate inline settings | it creates conflicting controls with the composer.
Confidence: high
Scope-risk: moderate
Directive: Do not send /api/images temporary URLs to NewApiWG upstream; resolve them to data URLs before provider calls.
Tested: npm run test:run -- src/app/api/generate/providers/__tests__/newapiwg.test.ts src/app/api/generate/__tests__/route.test.ts src/components/__tests__/GenerateImageNode.test.tsx src/components/__tests__/GenerateVideoNode.test.tsx src/components/__tests__/PromptNode.test.tsx src/lib/__tests__/llmModels.test.ts src/store/__tests__/workflowStore.integration.test.ts
Tested: npm run build
Tested: git diff --check
Not-tested: npm run lint still fails because next lint resolves to a nonexistent lint directory under Next 16.
The welcome screen now matches the Popi.TV brand and gives unauthenticated users two explicit paths: jump to the main-site login page or provide a Popi/NewAPI gateway key directly. The manually provided key is persisted in the local user store and forwarded through the NewAPI-backed generation, model, LLM, and chat routes without requiring a login token.
Constraint: Users need a usable local path when they are not logged into the main site.\nRejected: Keep the startup login modal for missing tokens | it blocked the in-welcome login/key choices.\nRejected: Rename internal workflow prompts and storage keys | those are compatibility/internal identifiers, not the visible brand surface.\nConfidence: high\nScope-risk: moderate\nDirective: Keep manual gateway-key access scoped to NewAPI-backed paths; non-NewAPI providers should continue requiring a login token.\nTested: npm run test:run -- src/app/api/models/__tests__/route.test.ts src/app/api/models/[modelId]/__tests__/route.test.ts src/app/api/llm/__tests__/route.test.ts src/app/api/chat/__tests__/route.test.ts src/app/api/generate/__tests__/route.test.ts\nTested: npm run test:run -- src/components/__tests__/QuickstartInitialView.test.tsx src/components/__tests__/WelcomeModal.test.tsx\nTested: npm run build\nTested: Browser verification for Popi.TV welcome title and two unauthenticated entry paths\nTested: git diff --cached --check\nNot-tested: Full test suite\nNot-tested: npm run lint still fails because the existing Next 16 next lint script resolves /lint as a project directory
The welcome screen now matches the Popi.TV brand and gives unauthenticated users two explicit paths: jump to the main-site login page or provide a Popi/NewAPI gateway key directly. The manually provided key is persisted in the local user store and forwarded through the NewAPI-backed generation, model, LLM, and chat routes without requiring a login token.
Constraint: Users need a usable local path when they are not logged into the main site.\nRejected: Keep the startup login modal for missing tokens | it blocked the in-welcome login/key choices.\nRejected: Rename internal workflow prompts and storage keys | those are compatibility/internal identifiers, not the visible brand surface.\nConfidence: high\nScope-risk: moderate\nDirective: Keep manual gateway-key access scoped to NewAPI-backed paths; non-NewAPI providers should continue requiring a login token.\nTested: npm run test:run -- src/app/api/models/__tests__/route.test.ts src/app/api/models/[modelId]/__tests__/route.test.ts src/app/api/llm/__tests__/route.test.ts src/app/api/chat/__tests__/route.test.ts src/app/api/generate/__tests__/route.test.ts\nTested: npm run test:run -- src/components/__tests__/QuickstartInitialView.test.tsx src/components/__tests__/WelcomeModal.test.tsx\nTested: npm run build\nTested: Browser verification for Popi.TV welcome title and two unauthenticated entry paths\nTested: git diff --cached --check\nNot-tested: Full test suite\nNot-tested: npm run lint still fails because the existing Next 16 next lint script resolves /lint as a project directory
Vidu-style video models can load parameter schemas that change the Generate Video node height and trigger React Flow dimension updates. The fix keeps schema-driven resize notifications stable, skips no-op node height writes, and ignores duplicate React Flow node changes before they enter the workflow store.
It also preserves completed media outputs when a stopped or reloaded workflow still has transient loading/skipped state, so existing video results do not appear stuck after recovery.
Constraint: The crash was reported from a browser state where choosing Vidu hit React's maximum update depth in onNodesChange.
Rejected: Clear browser storage as the fix | it would hide persisted workflow loops without fixing the update path.
Rejected: Remove dynamic video handles | visible media/text inputs are product behavior and required for model-specific workflows.
Confidence: high
Scope-risk: moderate
Directive: Keep React Flow dimension and schema resize writes idempotent; new node resize paths should no-op when dimensions are already applied.
Tested: npm test -- --run src/components/__tests__/GenerateVideoNode.test.tsx -t 'should not resize repeatedly'
Tested: npm test -- --run src/components/__tests__/GenerateVideoNode.test.tsx src/components/__tests__/ModelParameters.test.tsx src/store/__tests__/workflowStore.integration.test.ts src/store/__tests__/skipPropagation.test.ts src/store/execution/__tests__/generateVideoExecutor.test.ts
Tested: npm run build
Tested: npm run test:run
The accumulated fixes move the test build toward the current product shape: Popi models are the default provider surface, remote community workflow APIs are removed from the local template flow, media nodes expose accurate preview/preview-modal behavior, and Generate Video owns first-class duration, resolution, and sound defaults instead of burying those values in provider schemas.
The test suite was brought back in line with the product mode split: Popi provider mode is the default runtime, while multi-provider model search remains covered explicitly under multi mode.
Constraint: Product direction is canvas-owned workflow structure with Popi Models as the tested default provider path.
Rejected: Keep legacy community workflow API surface | user requested removing remote data paths and retaining local templates.
Rejected: Treat video duration/resolution/sound as hidden provider params | these are user-facing Generate Video controls.
Confidence: high
Scope-risk: broad
Directive: Do not reintroduce remote community template loading without a product decision on hosted templates and i18n ownership.
Tested: npm test -- --run src/store/utils/__tests__/nodeDefaults.test.ts src/store/execution/__tests__/generateVideoExecutor.test.ts src/components/__tests__/GenerateVideoNode.test.tsx src/components/__tests__/GenerationComposer.test.tsx
Tested: npm run build
Tested: npm test -- --run src/components/__tests__/ModelSearchDialog.test.tsx src/store/utils/__tests__/localStorage.test.ts
Tested: npm run test:run
Not-tested: Live provider generation calls against paid external APIs.
The bottom composer now edits and reruns the current generation node while canvas-visible actions own downstream node creation. NewApiWG media normalization, video frame extraction, and node quick-add keep generated media flows explicit and connected rather than hidden behind the prompt box.
Constraint: Composer must not call providers directly or create hidden downstream branches when a node is selected
Constraint: NewApiWG video families use inconsistent media schemas, so common media aliases are normalized in the provider adapter
Rejected: Let the bottom composer create next-step nodes from selected outputs | hides graph mutation and conflicts with the PRD source-of-truth rules
Rejected: Put NewApiWG model-family payload rules in the composer | provider-specific schemas would leak into UI state
Confidence: high
Scope-risk: moderate
Directive: Keep provider payload normalization in provider/executor layers; do not move it into GenerationComposer
Tested: npm run test:run -- src/components/__tests__/GenerationComposer.test.tsx
Tested: npm run test:run -- src/components/__tests__/WorkflowCanvas.test.tsx
Tested: npm run test:run -- src/components/__tests__/GenerateVideoNode.test.tsx
Tested: npm run test:run -- src/app/api/generate/providers/__tests__/newapiwg.test.ts src/app/api/generate/__tests__/route.test.ts
Tested: npm run test:run -- src/store/execution/__tests__/videoProcessingExecutors.test.ts
Tested: npm run build
Not-tested: npm run lint fails because next lint is incompatible with the current Next 16 script configuration
- OutputNode download tests: add missing `ok: true` to fetch mock
responses so downloadMedia doesn't bail at the `!response.ok` check
- workflowStore integration test: update easeCurve bounding box
expectations from 480 to 280 to match actual default dimensions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolve conflicts in 4 executor files by combining develop's
runWithFallback structure with feature branch's client-side polling.
Both capabilities are now integrated: executors use runWithFallback
for model redundancy, and pollGenerateTask for long-running Kie tasks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Nodes downstream of the loop body (e.g. outputGallery connected to a looped
generateVideo) were classified as suffix and only executed once after all
iterations. Now the loop iteration set expands via forward-edge traversal
to include downstream observers, so they execute each pass and can accumulate
results (e.g. collecting each generated video into the gallery).
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Extract executeLevels helper that gets fresh node refs each level
- Import findLoopSubgraph and copyLoopOutput from executionUtils
- Partition workflow into prefix/loop-body/suffix levels
- Execute loop body N times with data feedback between iterations
- Use forwardEdges only for topological sort (exclude loop edges)
- Show toast warning for multiple loop edges
- Respect abort signal between loop iterations
- Non-loop workflows completely unaffected
All 21 loop edge tests pass, 2011 total tests pass, clean build
- Test loop subgraph executes N times
- Test prefix/suffix nodes execute once
- Test regression safety (no loops)
- Test loopCount: 1 behavior
- Test abort signal stops loop mid-iteration
Replace JSON.parse(JSON.stringify(...)) in captureUndoSnapshot with
clonePreservingStrings — a custom deep-clone that creates new
object/array containers but returns string primitives by reference.
Since JS strings are immutable, snapshots safely share base64 blobs
instead of duplicating them. Reduces undo history memory from ~500MB
to ~12.5MB for workflows with 10MB of media data (97.5% reduction).
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
React Flow v12's deleteElements fires onEdgesChange(remove) BEFORE
onNodesChange(remove), not after. The previous fix assumed the opposite
order, so the microtask-based nodeRemoveCheckpointActive flag was never
true when onEdgesChange ran.
Replace with deleteCheckpointActive — a bidirectional flag set by
whichever handler (edges or nodes) fires first. The second handler
skips its checkpoint. Use setTimeout(0) instead of Promise.resolve()
so the flag survives all microtasks in the current event-loop turn.
Also suppresses debounced undo snapshots from clearStaleInputImages
side effects during the same deletion cycle.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When deleting a connected node, clearStaleInputImages calls updateNodeData
on target nodes to clear stale input images. Each updateNodeData call was
creating a debounced undo snapshot, resulting in 3-4 undos needed instead
of 1. Now updateNodeData skips undo tracking when nodeRemoveCheckpointActive
is true, since the node removal checkpoint already captured full pre-deletion state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When deleting a node via the canvas (Delete key), React Flow fires
onNodesChange(remove) then onEdgesChange(remove) synchronously.
Previously this created two undo entries, requiring two undos and
leaving an intermediate state with orphaned edges.
Now onNodesChange sets a microtask-scoped flag that tells onEdgesChange
to skip its checkpoint, collapsing both into a single undo entry.
Added integration test verifying single-undo restores node + edges.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Captures snapshots before all undoable mutations (add/remove nodes,
connect/remove edges, paste, group operations, edge style, drag start).
Data edits are debounced (500ms). Execution outputs are not undoable.
History depth: 50 entries. Cleared on load/clear workflow.
Keyboard shortcuts added to WorkflowCanvas and shortcuts dialog.
15 integration tests covering all undo/redo scenarios through the store.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Standalone undo manager with push/pop/clear/max-depth (50) logic,
independent of Zustand. Includes 11 unit tests covering all operations.
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>
Zundo-based undo/redo was causing image flickering, failing to restore
deleted nodes, and creating memory issues from large state snapshots.
Removes the entire workflow undo/redo system while keeping the
independent annotation canvas undo/redo intact.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace BINARY_DATA_KEYS (strips all binary) with BINARY_TO_REF mapping
that only strips binary data when a corresponding ref exists (recoverable
from disk). Fields without refs (imageA, imageB, capturedImage, etc.) are
kept in snapshots since they're unrecoverable. After undo/redo, hydrate
missing binary data from refs using existing hydrateWorkflowImages().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract schema utilities from generate route
Extract INPUT_PATTERNS, InputMapping, ParameterTypeInfo, getParameterTypesFromSchema,
coerceParameterTypes, and getInputMappingFromSchema into schemaUtils.ts with 23 new tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract Gemini provider from generate route
Move MODEL_MAP and generateWithGemini() into providers/gemini.ts.
Remove unused imports (GoogleGenAI, uploadImageForUrl, shouldUseImageUrl,
deleteImages, ProviderModel) from route.ts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract Replicate provider from generate route
Move generateWithReplicate() into providers/replicate.ts.
It imports schema utilities from schemaUtils.ts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract fal.ai, Kie.ai, WaveSpeed providers from generate route
Move generateWithFalQueue, generateWithKie, generateWithWaveSpeed and all
their helpers into providers/fal.ts, providers/kie.ts, providers/wavespeed.ts.
Add barrel export at providers/index.ts.
route.ts reduced from 2,838 to 518 lines (POST handler + routing only).
Re-export clearFalInputMappingCache from route.ts for test backward compat.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract API header builder from workflow store
Replace 6 duplicated header-building if/else chains (across executeWorkflow
and regenerateNode) with buildGenerateHeaders() and buildLlmHeaders() utility
functions. Add 13 new tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract execution utilities from workflow store
Move groupNodesByLevel, chunk, revokeBlobUrl, clearNodeImageRefs,
and concurrency settings into executionUtils.ts with 21 new tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract getConnectedInputs and validateWorkflow as pure functions
Move getConnectedInputs (136 lines) and validateWorkflow (59 lines) into
connectedInputs.ts as pure functions taking (nodeId, nodes, edges).
Store methods become thin wrappers. Add 22 new tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: define node executor interface and context types
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract simple node executors
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: unify nanoBanana execution into shared executor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: unify generateVideo execution into shared executor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: unify llmGenerate execution into shared executor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: unify splitGrid execution into shared executor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: unify videoStitch and easeCurve executors
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: replace switch/if-else with node executor registry
Wire extracted executors into workflowStore, replacing the ~1000-line
switch statement in executeWorkflow and ~750-line if-else chain in
regenerateNode with executor registry calls. Store reduced from 3,402
to 1,782 lines.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: final cleanup of workflowStore module structure
Remove dead trackSaveGeneration function (no longer called after executor
extraction) and unused type imports. Store: 3,838 -> 1,721 lines (55% reduction).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: video regeneration spinner stuck + cancel error message
Fix video element not reloading on regeneration by adding key prop tied
to video history ID. Fix cancel showing timeout message by passing
"user-cancelled" reason through AbortController and checking it in all
three executor catch blocks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR #59 review — 18 bug, security, and robustness fixes
Providers:
- fal: don't cache failed schema fetches, remove duplicate param spread, SSRF-validate mediaUrl
- gemini: remove redundant header overrides, fix nano-banana model ID
- kie: module-level MAX_MEDIA_SIZE, post-download size check, upload size guard
- replicate: remove duplicate param spread, validate modelId format
- wavespeed: post-download size check, NaN-safe parseInt, prevent prompt overwrite
Executors:
- generateVideo: validate stored fallback inputs same as regular path
- nanoBanana: cap imageHistory at 50 entries
- splitGrid: handle img.onerror with warning + null data
- videoProcessing: add FileReader onerror/onabort handlers
Utilities:
- connectedInputs: use ?? instead of || for outputText/template
- executionUtils: guard orphan children in topological sort, validate chunk() size
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: additional PR review fixes — SSRF, data semantics, sync tracking, error propagation
Providers:
- replicate: add SSRF validation on mediaUrl before fetch
- fal/kie/replicate/wavespeed: large-video returns data="" with url set
instead of putting raw URL in data field (violates GenerationOutput.data semantic)
- kie: remove stale WaveSpeed JSDoc block from kie provider file
- route.ts: update all output handling to support data="" + url for large videos
Executors:
- generateVideoExecutor/nanoBananaExecutor: track save-generation fetch in
pendingImageSyncs via new trackSaveGeneration context method so auto-save
waits for server ID resolution
- videoProcessingExecutors: throw errors instead of returning in validation
branches (encoderSupported, insufficient videos, no video connected) so
Promise.allSettled sees rejections
- types.ts: add trackSaveGeneration to NodeExecutionContext interface
- workflowStore: wire trackSaveGeneration into both execution context sites
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: content-type fallback, replicate polling/validation, atomic gallery append
- fal.ts: Use model capabilities to determine fallback content-type
(video/mp4 vs image/png) instead of defaulting to video for all models
- replicate.ts: Increase polling timeout to 10 min for video models;
filter output array to valid strings before URL validation
- nanoBananaExecutor.ts: Replace read-modify-write on outputGallery with
atomic appendOutputGalleryImage using Zustand set() callback
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract _buildExecutionContext to deduplicate NodeExecutionContext construction
Replace identical inline context creation in executeWorkflow and regenerateNode
with a single shared helper, ensuring consistent behavior across both code paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add keyboard shortcuts help dialog
- New KeyboardShortcutsDialog component showing all available shortcuts
- Accessible via ? button in header and ? key on canvas
- Groups shortcuts by category: General, Add Nodes, Layout, Canvas
- Auto-detects Mac vs Windows/Linux for modifier key display
- Follows existing modal patterns (Escape to close, backdrop click)
* feat: add keyboard shortcuts dialog and 3D GLB viewer node
- Keyboard shortcuts: press ? or click keyboard icon to view all shortcuts
- 3D GLB viewer: upload .GLB files, orbit/rotate, capture as image output
- Fix: restore trailing newline in gemini.ts provider
* fix: make Prompt node editable when connected to LLM node
When a Prompt node receives text from an LLM Generate node, the text
should be editable by the user. Previously, the textarea was marked as
readOnly when there was an incoming text connection, preventing edits
in both the main view and the expanded modal.
Changes:
- Track last received LLM output to detect when it changes
- Only update prompt when LLM node runs again (output changes)
- Allow user edits in between LLM runs
- Remove readOnly restriction on textarea
- Update placeholder text to indicate editability
This allows users to edit the LLM-generated text, which will be
replaced only when the LLM node runs again, not on every render.
Fixes#1470061054246518927
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: PR review fixes — lazy-load three.js, blob URL cleanup, missing integrations
- Remove unused `useLoader` import from GLBViewerNode
- Add useEffect cleanup to revoke blob URL on node unmount (memory leak)
- Replace alert() with useToast for file validation errors
- Lazy-load GLBViewerNode via next/dynamic to avoid bundling three.js for all users
- Add glbViewer to ConnectionDropMenu IMAGE_SOURCE_OPTIONS
- Add glbViewer case to quickstart validation createDefaultNodeData (build fix)
- Restore trailing newline in gemini.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR #64 review — blob leak, error handling, tests, docs
Move @types/three to devDependencies, fix blob URL cleanup to track
changes (not just unmount), add try/catch to capture and scene disposal,
remove GLBViewerNode from barrel export to preserve lazy-loading, fix
JSDoc, update CLAUDE.md with glbViewer node and ? shortcut, add
connectedInputs and nodeDefaults tests for glbViewer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: improve GLB viewer — fix viewport sizing, add spot light, handle labels, scroll isolation
- Fix 3D viewport not filling node by adding resize={{ offsetSize: true }} for correct measurement in CSS-transformed React Flow nodes
- Replace grid/axis indicators with spot light for cleaner scene
- Add labeled input (3D) and output (Image) handles matching generate node style
- Add native wheel event listener to prevent graph zoom/pan while scrolling to zoom 3D model
- Remove redundant CSS workarounds (absolute positioning style, extra relative wrapper)
- Add edge-to-edge viewport via contentClassName (removes default padding when GLB loaded)
- Overlay controls bar on viewport with gradient background
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add customizable canvas navigation settings (#65)
* feat: add customizable canvas navigation settings
Add a new settings modal that allows users to configure canvas navigation preferences:
- Pan Mode: Space + Drag (default), Middle Mouse, or Always On (ComfyUI-style)
- Zoom Mode: Alt + Scroll (default), Ctrl + Scroll, or Scroll (no modifier)
- Selection Mode: Click (default) or Alt + Drag
Features:
- Settings persist in localStorage
- New canvas settings button in header (monitor icon)
- Modal UI with radio button groups for each setting
- React Flow props dynamically configured based on user preferences
- Updated wheel event handler to respect zoom mode settings
This addresses user requests for ComfyUI/TouchDesigner-style navigation where
panning and zooming work without holding modifier keys.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: PR #65 review — altDrag selection bug, icon, dedup, tests
- Fix altDrag selection mode: use selectionKeyCode="Alt" instead of
selectionOnDrag=true which made drag-select work without any key
- Replace misleading video camera icon with arrows-pointing-out for
canvas navigation settings button
- Extract duplicated settings buttons JSX into settingsButtons variable
shared by both configured/unconfigured project branches
- Add defensive useEffect to sync CanvasSettingsModal state on open
- Add missing semicolon in types/index.ts canvas re-export
- Add localStorage tests for getCanvasNavigationSettings and
saveCanvasNavigationSettings
- Add integration tests for updateCanvasNavigationSettings
- Fix WorkflowCanvas test mocks to include canvasNavigationSettings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: move canvas navigation settings into Project Settings modal
Consolidate canvas settings from standalone CanvasSettingsModal into a
new "Canvas" tab in ProjectSetupModal. Remove "like ComfyUI" text, add
Shift+Drag selection mode, and fix save button to bottom of dialog.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove double semicolon in types/index.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat: add executeSelectedNodes with topo sort, abort, and downstream propagation
Implements the "Run selected nodes" feature (PR #61) with fixes for all 8
review issues: topological ordering via groupNodesByLevel, AbortController
cancellation, glbViewer case coverage, AbortError filtering, downstream
consumer propagation with dedup guard, and dynamic button label. Also
extracts saveLogSession helper to replace 4 inline duplicates across
executeWorkflow and regenerateNode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: 3D model saving, error handling, and node improvements
- Save 3D models (GLB) to generations folder via save-generation API
- Add try-catch to WaveSpeed polling loop to handle network errors
- Skip downloading 3D model bodies in WaveSpeed (return URL directly)
- Fix schemaUtils false-positive matching for short property names
- Stabilize GenerateImageNode output handle ID (always "image")
- Fix GLBViewerNode: block-body forEach lint, store-based resize
- Use useRef instead of useState for PromptNode lastReceivedText
- Add executeGlbViewer tests and abort signal support
- Fix annotation stale pass-through when upstream image changes
- Skip error status on AbortError in all executor catch blocks
- Revoke glbUrl blob URLs in clearNodeImageRefs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: require per-task commits in multi-task plans
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Generate3DNodeData type definitions
Add "generate3d" to NodeType union, Generate3DNodeData interface,
Generate3DNodeDefaults, and update NodeDefaultsConfig.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add generate3d node defaults and dimensions
Add default dimensions (300x300) and createDefaultNodeData case for
the generate3d node type.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: create generate3d executor
Extracted from nanoBananaExecutor's 3D handling code. Handles 3D model
generation via /api/generate with mediaType: "3d", cost tracking, and
auto-save to generations folder.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: create Generate3DNode component
Dedicated node component for 3D model generation, modeled after
GenerateVideoNode. Features: dynamic input handles from schema,
3D output handle, model browsing, parameter controls, status overlays.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: register generate3d node type across codebase
Register in execution index, workflowStore (executeWorkflow,
regenerateNode, executeSelectedNodes), nodes index, WorkflowCanvas
(nodeTypes, getNodeHandles, minimap color, findCompatibleHandle).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add generate3d source output in connectedInputs
Add generate3d case in getSourceOutput() to return 3D URL data,
enabling downstream nodes to receive 3D model output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: route 3D models to generate3d in ModelSearchDialog
Add "3d" capability filter, update handleSelectModel to detect 3D
models and create generate3d nodes with capabilities passed through,
update recent models filter for 3D.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: update ConnectionDropMenu 3D source to use generate3d
Change THREE_D_SOURCE_OPTIONS from nanoBanana to generate3d with
cube icon and "Generate 3D" label.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: remove 3D code from GenerateImageNode and nanoBananaExecutor
- Remove text-to-3d/image-to-3d from IMAGE_CAPABILITIES
- Remove is3D memo and conditional handle/label styling
- Remove 3D output preview block
- Remove output3dUrl from model change handlers
- Remove is3DModel detection and mediaType from nanoBanana executor
- Remove 3D response handling from nanoBanana executor
- Remove output3dUrl from NanoBananaNodeData and its defaults
- Remove 3D fallback from nanoBanana in connectedInputs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: update tests and remaining registrations for generate3d
- Add Generate3DNode.test.tsx with render, handle, and output tests
- Add 3D handle tests to ConnectionDropMenu.test.tsx
- Add 3D model routing test to ModelSearchDialog.test.tsx
- Update ModelSearchDialog test expectations for capabilities passthrough
- Register generate3d in executeNode.ts dispatcher
- Add generate3d to WorkflowCanvas keyboard shortcut dimensions
- Add generate3d to quickstart validation (VALID_NODE_TYPES, dimensions, defaults)
- Add generate3d to chat tools (VALID_NODE_TYPES, description)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: unwrap array inputs for non-array API parameters
When multiple images are connected to a generation node, dynamicInputs
aggregates them into arrays. Providers wrapped single values into arrays
for array-typed params, but never unwrapped arrays for string-typed params,
causing API failures (e.g. fal's image_url receiving an array instead of
a string). Add symmetric unwrap logic across all four providers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: move 3D from floating action bar to Generate submenu
Replace top-level glbViewer button with generate3d option in the
Generate combo dropdown, between Video and Text (LLM).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: hide CostIndicator when non-Gemini providers are in workflow
The pricing calculator only has reliable data for Gemini models. When
non-Gemini providers (fal, replicate, kie, wavespeed) are used, the
displayed cost is incomplete/misleading. Add hasNonGeminiProviders()
utility and use it to hide the CostIndicator entirely in that case.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add 3D capability badges to ModelSearchDialog
Add text-to-3d and image-to-3d capability badge styling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add play/run icon to LLM Generate node title bar
Wire onRun and isExecuting props to BaseNode, matching the pattern
used in GenerateImageNode and GenerateVideoNode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: clear imageRef when user provides new image to prevent stale saves
When a user pastes/uploads a new image into a node that already has a
saved imageRef, the externalization logic incorrectly assumes the base64
is just hydrated data and discards it. Clear *Ref fields whenever new
image data is set so the save logic correctly persists the new image.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: guard against undefined from empty array unwrap in fal provider
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add 3x2 grid layout option to split grid node
Change layout selector from count-based to layout-based, allowing both
2x3 and 3x2 (portrait) layouts that produce 6 images. Now shows 7
layout options (2x2, 1x5, 2x3, 3x2, 2x4, 3x3, 2x5) with RxC labels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: charlieshoelace <charlieshoelace@gmail.com>
* feat: add customizable canvas navigation settings
Add a new settings modal that allows users to configure canvas navigation preferences:
- Pan Mode: Space + Drag (default), Middle Mouse, or Always On (ComfyUI-style)
- Zoom Mode: Alt + Scroll (default), Ctrl + Scroll, or Scroll (no modifier)
- Selection Mode: Click (default) or Alt + Drag
Features:
- Settings persist in localStorage
- New canvas settings button in header (monitor icon)
- Modal UI with radio button groups for each setting
- React Flow props dynamically configured based on user preferences
- Updated wheel event handler to respect zoom mode settings
This addresses user requests for ComfyUI/TouchDesigner-style navigation where
panning and zooming work without holding modifier keys.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: PR #65 review — altDrag selection bug, icon, dedup, tests
- Fix altDrag selection mode: use selectionKeyCode="Alt" instead of
selectionOnDrag=true which made drag-select work without any key
- Replace misleading video camera icon with arrows-pointing-out for
canvas navigation settings button
- Extract duplicated settings buttons JSX into settingsButtons variable
shared by both configured/unconfigured project branches
- Add defensive useEffect to sync CanvasSettingsModal state on open
- Add missing semicolon in types/index.ts canvas re-export
- Add localStorage tests for getCanvasNavigationSettings and
saveCanvasNavigationSettings
- Add integration tests for updateCanvasNavigationSettings
- Fix WorkflowCanvas test mocks to include canvasNavigationSettings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: move canvas navigation settings into Project Settings modal
Consolidate canvas settings from standalone CanvasSettingsModal into a
new "Canvas" tab in ProjectSetupModal. Remove "like ComfyUI" text, add
Shift+Drag selection mode, and fix save button to bottom of dialog.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove double semicolon in types/index.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
handleNodeDragStop used a hardcoded 300x280 fallback for node dimensions,
causing miscalculated centers for non-standard nodes (easeCurve 340x480,
videoStitch 400x280, audioInput 300x200). Now uses the same 3-level
fallback (measured → style → defaultNodeDimensions) as createGroup.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add level-based topological sort (groupNodesByLevel) to identify
nodes that can execute concurrently
- Change state from currentNodeId to currentNodeIds[] for tracking
multiple executing nodes
- Execute nodes in batches using Promise.allSettled with configurable
concurrency limit (default 3, adjustable 1-10)
- Integrate AbortController for fail-fast cancellation on errors
- Pass abort signals to API fetch calls for proper request cancellation
- Update UI: multiple nodes show execution border, FloatingActionBar
shows "N nodes" during parallel execution
- Add concurrency slider in Project Settings > Node Defaults tab
- Store concurrency preference in localStorage
Workflows with independent generate nodes (e.g., 3 parallel image
generations) now complete in ~20-25s instead of ~60-90s sequential.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The isRunning check and set were separated by an await, allowing a second
call to pass the guard during the async gap. Move set() before the await
to close the window.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Test: multiple images to same schema-mapped handle aggregate into array
- Test: single image to schema-mapped handle stays as plain string
- Test: multiple images with distinct schema handles remain separate strings
- Test: three images to same handle produce array of length 3
- Fix react-dom version mismatch (19.2.0 -> 19.2.3) to match react
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Test: multiple images to same schema-mapped handle aggregate into array
- Test: single image to schema-mapped handle stays as plain string
- Test: multiple images with distinct schema handles remain separate strings
- Test: three images to same handle produce array of length 3
- Fix react-dom version mismatch (19.2.0 -> 19.2.3) to match react
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
## Template Explorer Enhancements
- Add rich metadata and thumbnails for community workflows
- Create communityWorkflows.ts config for workflow metadata
- Reuse TemplateCard for community workflows with tag filtering
- Search now includes workflow descriptions
## Comment Navigation System
- Add comment navigation icon in header with unviewed count badge
- Navigate between nodes with comments using prev/next controls
- Tooltips show comment preview on hover
## Output Node Improvements
- Add custom filename and createDirectory options for output saves
- Remove default React Flow styling from output nodes
- Fix video output node connections
- Preserve node height on resize
## Image Handling Fixes
- Resolve image history race condition and duplicate images on save
- Preserve image history IDs when saving to new directory
- Verify outputImageRef matches selected history index on save
- Persist useExternalImageStorage setting in workflow config
- Paste images directly into selected imageInput nodes
## Model & Generation Fixes
- Use selectedModel.modelId for Gemini generation
- Return 200 with notFound flag instead of 404 for missing files
## Other
- Add git-lfs tracking for large workflow files
- Remove deprecated example workflows
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes issues with external provider models (e.g., seedream-4.5) where:
- Boolean/integer params with "image" in name were misclassified as image inputs
- Array params without items.type specification were incorrectly rejected
- Handle ID mismatch between node components (image-0) and store (image)
- dynamicInputs not passed to API in node regeneration code path
Changes:
- Update isImageInput() to check schema types, not just names
- Map both indexed (image-0) and legacy (image) handle IDs to schema names
- Add dynamicInputs to nanoBanana regeneration request body
- Add hidden backward-compat handles for edge routing
- Add comprehensive tests for schema classification and handle mapping
- Fix Toast test to use specific selector for close button
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Image data flow through node chains (imageInput, annotation, nanoBanana)
- Text data flow through node chains (prompt, llmGenerate)
- Dynamic inputs from schema-mapped connections
- State updates during execution (status, outputImage)
- Mixed image and text data flow