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
Server-side native folder pickers cannot select a Windows user's local directory when the app is deployed remotely. This adds a browser File System Access path for supported browsers so the user can choose a local folder, create the project subfolder, and save the workflow JSON directly from the client.
Constraint: File System Access handles are browser-only and cannot be passed to server filesystem APIs
Constraint: External media folder storage still depends on server filesystem endpoints, so browser-local projects keep media embedded or remote in the workflow JSON
Rejected: Make /api/browse-directory choose the user's Windows folder | that endpoint runs on the server, not in the browser
Confidence: medium
Scope-risk: moderate
Directive: Extend the browser filesystem utility for local media inputs/generations before enabling external media storage for browserfs paths
Tested: npx vitest run src/components/__tests__/ProjectSetupModal.test.tsx src/components/__tests__/Header.test.tsx
Tested: npm run build
Not-tested: Manual Chrome/Edge File System Access flow on Windows test server
Remote and server-hosted deployments cannot rely on the browser user's desktop tools being available inside the Node server process. Directory browsing now checks for native picker commands first and falls back to a configurable server-side workflows directory instead of surfacing command-not-found errors.
Constraint: The browse endpoint runs on the server, not on the Windows client browser
Constraint: Test servers may not have osascript, zenity, kdialog, PowerShell, or an interactive desktop session
Rejected: Keep propagating native picker failures to the modal | successful server deployments without desktop tools showed red command errors
Confidence: high
Scope-risk: narrow
Directive: For true client-local folder selection, use a separate File System Access API flow instead of this server filesystem endpoint
Tested: npx vitest run src/app/api/browse-directory/__tests__/route.test.ts src/components/__tests__/ProjectSetupModal.test.tsx
Tested: npm run build
Not-tested: Interactive Windows Server desktop picker session
NewApiWG image and video models need to work against the PopiArt test gateway, with generated remote media visible in the canvas and UI text localized for Chinese users. This wires the gateway-specific image/video paths, model fallback display, remote media proxying, and related i18n cleanup into one releaseable change.
Constraint: Test gateway exposes image and video models through mixed OpenAI-compatible and Gemini-native response shapes
Constraint: Browser video playback can fail when loading signed third-party media URLs directly
Rejected: Keep remote video URLs as direct <video> sources | CORS and range handling made successful generations appear unloaded
Confidence: high
Scope-risk: moderate
Directive: Do not remove the media proxy without validating remote video playback, trimming, and stitching against signed CDN URLs
Tested: npm run build
Tested: npx vitest run src/components/__tests__/OutputNode.test.tsx src/components/__tests__/GenerateVideoNode.test.tsx src/components/__tests__/VideoStitchNode.test.tsx
Tested: npx vitest run src/components/__tests__/WorkflowCanvas.test.tsx src/store/execution/__tests__/generateVideoExecutor.test.ts
Tested: npx vitest run src/app/api/generate/providers/__tests__/newapiwg.test.ts
Not-tested: Live browser replay after pushing to remote deployment
Kie rejects the call when both reference_image_urls and first_frame_url
(or last_frame_url) are populated — the API treats them as mutually
exclusive scene modes. Two changes:
1. kie.ts submit: skip the legacy image fallback whenever any
schema-driven image key was already handled via dynamicInputs. The
executor passes both `images` and `dynamicInputs`, so the fallback
was duplicating user-provided references into first_frame_url.
2. seedance2 I2V schema: mark first_frame_url optional and note the
mutual exclusivity in the handle descriptions so users see it in
the node UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add last_frame_url, reference_image_urls, reference_video_urls, and
reference_audio_urls handles to Seedance 2.0 and Seedance 2.0 Fast I2V
schemas so users can seed an end frame, supplementary reference images,
reference videos, and reference audio in addition to the first frame.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The input schema mapping was forcing all non-image inputs to "text",
misclassifying audio inputs. Now correctly maps audio type so handles
get the right data-handletype, CSS dot color, and label color.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
AudioInputNode, EaseCurveNode, and VideoStitchNode tests used manual
@xyflow/react mocks that didn't include useConnection, which is now
called by the useShowHandleLabels hook. Add the mock to fix all 40 tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Video handles/edges/labels were inconsistent — using white, green, blue,
and hardcoded purple across different files. Standardize all to pink and
use the CSS variable var(--handle-color-video) for labels. Also adds the
missing .react-flow__handle[data-handletype="video"] CSS rule.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add --handle-color-video CSS variable
- Fix tutorial overlay race conditions by reading nodes from store
directly and add keyboard accessibility to click-to-continue overlay
- Fix audio extraction in video stitching: defer silent buffer creation
until reference format is known, and only trust probed duration when
all blobs were successfully probed
- Clear saved filename/path when clearing 3D output
- Retry 429/408 transient errors during task polling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update all 18 node files to use the centralized HandleLabel component.
Labels now fade in/out with a 150ms opacity transition instead of
mounting/unmounting (pop-in). ~430 lines removed across 18 files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extracts the repeated ~10-line handle label pattern into a reusable
component with opacity-based fade transition (150ms ease-in-out)
instead of mount/unmount pop-in.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add useShowHandleLabels hook that combines node selection state with
React Flow's useConnection to show handle labels only when relevant,
reducing visual clutter on the canvas.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
- TutorialOverlay: move completeCurrentStep() inside setTimeout to prevent
cleanup from clearing the advance timeout, fixing stuck tutorial steps
- useStitchVideos: push silent AudioBuffer for clips with no/incompatible
audio instead of skipping, preventing audio-video timeline desync
- generate3dExecutor: clear savedFilename/savedFilePath on new generation
to prevent stale save metadata from previous runs
- pollTaskCompletion: restructure abort listener/timer cleanup to prevent
listener accumulation across polling iterations
- connectedInputs: exclude loop edges in validateWorkflowPure checks for
nanoBanana/generateVideo text inputs, matching runtime behavior
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Integrates client-side polling for long-running Kie tasks into all
generation executors alongside the existing runWithFallback support.
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>
All executors that call /api/generate now check for the polling flag
and enter the pollGenerateTask loop for long-running Kie tasks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After the initial /api/generate response, check for polling flag and
enter pollGenerateTask loop instead of waiting on a single long request.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shared polling utility that calls /api/generate/poll with short-lived
requests (3-8s interval with linear backoff). Handles abort signals,
network error retries, and 10-minute client-side timeout.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New endpoint accepts POST with taskId/provider/modelId and returns
either a polling-continue response or the final media result. Each
request is short-lived (~1s) instead of holding a connection open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The /api/generate route now submits Kie tasks and returns immediately
with polling metadata (taskId, pollProvider, etc.) instead of holding
the HTTP connection open for 2-10 minutes. Export buildMediaResponse
for reuse by the poll endpoint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split the monolithic generateWithKie into three reusable lifecycle functions
for client-side polling support. generateWithKie now composes them internally
for backward compatibility. Added polling fields to GenerateResponse type.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrap fetch and JSON parsing in try-catch to handle network errors,
add 429/5xx to retryable status codes, and implement exponential
backoff (2s → 10s) to reduce API load during long-running generations.
Caps consecutive errors at 5 before failing permanently.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Kie API can return 404/422 or HTTP 200 with non-200 code when
polling starts before the task is fully registered. Previously these
were treated as fatal errors, causing the client to see a network error
even though the generation was still processing on Kie's side. Now
these transient responses are retried instead of aborting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
maxDuration was 5 minutes which caused Next.js to kill the route handler
before Seedance 2.0 video generation polling could complete, resulting
in a network error on the client despite successful generation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Kie API expects "bytedance/seedance-2" as the model value, but we
were sending our internal ID "bytedance/seedance-2/text-to-video" which
includes the capability suffix. Strip the suffix for Seedance models
before sending to the API.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- OutputGalleryNode: add aria-label to gallery tile buttons so screen
readers can identify video tiles (images already had alt via
AdaptiveGalleryThumbnail)
- OutputGalleryNode: add nodeData.imageRefs and nodeData.videoRefs to
removeMedia dependency array so splice operates on current refs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- OutputGalleryNode: splice imageRefs/videoRefs alongside images/videos
in removeMedia to keep arrays in lockstep
- TutorialOverlay: track and clean up actionCompleted advance timeout
via advanceTimeoutRef to prevent stale nextTutorialStep after skip
- TutorialOverlay: track and clean up populate-content nested timeouts
via populateTimeoutIds ref to prevent post-teardown state mutations
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- TutorialOverlay: track all nested setTimeout IDs and clear on cleanup
to prevent ghost animations when tutorial is skipped mid-demonstration
- FTUXReadyStep: use strict local interface requiring both callbacks
instead of optional FTUXStepProps
- FTUXModelDefaultsStep: replace hardcoded "Gemini nano-banana-pro" with
"None set" for the no-model-selected state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- copyLoopOutput: add console.warn for unrecognized target node types
- connectedInputs: exclude loop edges from easeCurve edge lookup
- mediaStorage: trim gallery image/video refs to match actual array
lengths after item deletion
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- OutputGalleryNode: replace hardcoded .png/.mp4 download with shared
downloadMedia utility for correct MIME-based extensions
- OutputNode: wrap downloadMedia call in try/catch to prevent unhandled
rejection
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ModelSearchDialog: show "Remove fallback" button even when no models match
- Generate3DNode: reset settingsTab to "primary" when fallback is removed
- GenerateAudioNode: same settingsTab reset fix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- runWithFallback: use "loading" (valid NodeStatus) instead of "running"
- WorkflowCanvas: clear fallbackParameters when selecting a new fallback model
- workflowStore: add response.ok check before reading tutorial image blob
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract shared downloadMedia utility from OutputNode and add download
buttons to ImageInput, VideoInput, Annotation, AudioInput, GenerateImage,
GenerateVideo, GenerateAudio, and GLBViewer nodes. Refactor OutputNode to
use the shared utility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Normalize and clamp loopCount at execution time (1-100 range)
- Validates imported/malformed values are finite numbers
- Falls back to default of 3 if invalid
- Prevents NaN, Infinity, or extreme values from bypassing setLoopCount validation
2. Fix resume logic for nodes inside loop bodies
- Compute loopStartLevel when startFromNodeId is in loopLevels
- On first iteration, start from that level instead of level 0
- On subsequent iterations, start from level 0 as normal
- Preserves existing copyLoopOutput skip on first iteration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reset fallbackParameters when saving a new fallback model provider to
prevent stale parameter values (e.g. temperature=1.5) being sent to a
provider with different valid ranges (e.g. Anthropic max=1).
Wire AbortController signal through mockTutorialExecution so clicking
Stop actually cancels the 5s delay, fetch request, and FileReader,
returning the node to idle state instead of completing silently.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Gemini image models (nano-banana, nano-banana-pro, nano-banana-2) were
not showing parameters when set as fallback models. Three root causes:
1. Schema API returned empty for Gemini image models — added
getGeminiImageSchema() with aspect ratio, resolution, and search
grounding parameters per model
2. ModelParameters had an early-return guard skipping all non-Veo Gemini
models — removed to make the component provider-agnostic
3. nanoBananaExecutor ignored parametersOverride for Gemini-specific
fields (aspectRatio, resolution, useGoogleSearch, useImageSearch) —
now reads from override when available
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>