Add explicit constraints to prevent AI model from:
- Extending/outpainting beyond original image boundaries
- Hallucinating details not visible in source (closed eyes, hidden areas)
- Reference image is always sent as second image to model (not limited
to smart free mode), original image stays as first image
- Prompt explicitly tells model: first image is source, second is
lighting reference to analyze and apply
- Preview always shows original image, reference image no longer
replaces it in the globe preview
- Show uploaded reference image thumbnail with clear button in UI
- Add rim light prompt with specific position description and color
Replace vague "visible rim light around the subject" with explicit
position description based on rimLightPosition (behind/beside/front,
left/right/center, above/below) and include the main light color.
- Add visible intersection dots at all grid crossings
- Skip 0°/180° longitude in front view (no center vertical line)
- Increase perspective pitch to -28° for stronger top-down view
- Smart drag-to-back: detect nearest snap on both hemispheres
- Increase dashed line visibility (opacity 0.6, wider dash spacing)
- Add pole marker dots in perspective view
- Make rim light source dot match main source style (black core, white glow)
Replace hand-tuned 2D ellipse approach with consistent 3D sphere
sampling and projection. All latitude/longitude lines are now
generated from 3D sphere points projected through the same rotation,
with front/back face splitting for perspective view. Light source
and snap points use the same projection for exact grid alignment.
- 3D sphere projection: sample points on unit sphere, apply
positionToView rotation, scale to SVG coordinates
- Front view: all grid lines drawn solid (no back-face split)
- Perspective view: back-facing segments shown as dashed lines
- Smart drag-to-back: detects nearest snap point on both hemispheres
and flips to back when it's closer in screen space
- Pole markers: visible dots at north/south poles in perspective view
- Smaller centered subject image with side-tilt in perspective view
Provider video URLs can expire before users finish composing, and the bottom composer was too visually dominant for canvas work. This change keeps remote videos playable while background-localizing them, teaches video previews to use the media proxy, makes audio nodes first-class in the composer and executor, and adds a compact composer state for canvas visibility.
Constraint: Provider media URLs may be remote, signed, or CORS-restricted during playback and thumbnail extraction
Constraint: Audio generation nodes need to work from their internal prompt as well as connected text
Rejected: Store only provider URLs | remote URLs can expire and break later playback
Rejected: Treat audio nodes as unsupported composer nodes | it falls back to the image model and corrupts user intent
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep provider media playable through the resolver/proxy path and preserve local refs as the durable source when available
Tested: npm run test:run
Tested: npm run test:run -- src/components/__tests__/GenerationComposer.test.tsx src/store/execution/__tests__/generateAudioExecutor.test.ts src/store/execution/__tests__/generateVideoExecutor.test.ts src/components/__tests__/VideoStitchNode.test.tsx src/components/__tests__/GenerateVideoNode.test.tsx
Tested: npm run build
Not-tested: npm run lint currently fails because next lint is not supported by this Next.js setup
Not-tested: npx tsc --noEmit still reports pre-existing test typing errors outside this change
The bottom composer now treats video stitch and ease curve as processing-node inspectors instead of unsupported prompt editors. The connection menu also keeps video streams and ease-curve settings on separate handle paths so downstream creation matches the node graph semantics.
Constraint: Processing nodes should run existing local media operations and must not expose model or prompt controls
Constraint: Ease curve Settings handles carry curve parameters, not video media
Rejected: Let Settings handles reuse generic video/text drop options | creates misleading nodes and invalid graph intent
Rejected: Make Video Input a downstream target from Video Out | Video Input is an import/source node, not a processing destination
Confidence: high
Scope-risk: moderate
Directive: Keep media handles and settings handles separated in connection menus and validation
Tested: npm run test:run -- src/components/__tests__/ConnectionDropMenu.test.tsx
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 build
Tested: git diff --check
Not-tested: npm run lint still uses the existing Next 16-incompatible next lint script
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>