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 LLM selector exposed display-style Doubao model names that the NewApiWG test gateway cannot route. The UI now stores the gateway model ids returned by /models, while the API route maps legacy saved selector values to those ids so existing workflows do not keep failing.
Constraint: Preserve GPT-4.1 Mini as the NewApiWG default and keep old workflow JSON compatible.
Rejected: Remove Doubao from the LLM selector | the gateway exposes working Doubao chat model ids, so the issue is stale ids rather than product support.
Confidence: high
Scope-risk: narrow
Directive: LLM selector values must be callable provider ids; use labels only for display text.
Tested: Live NewApiWG chat completion with doubao-seed-2-0-lite-260428 and doubao-seed-2-0-pro-260215.
Tested: npm run test:run -- src/lib/__tests__/llmModels.test.ts src/app/api/llm/__tests__/route.test.ts src/components/__tests__/LLMGenerateNode.test.tsx src/store/utils/__tests__/nodeDefaults.test.ts
Tested: npm run build
Tested: git diff --check
P0 regressions showed several workflow paths needed to stay visible and deterministic: language switching had a hardcoded composer label, Video Trim hid controls before media loaded, and media handoffs to LLM needed real canvas edges rather than implicit conversion. P1 fixes keep canvas-owned structure by adding explicit image/video LLM handles, audio composer validation, 3D output/file routing, and Chat proposal cards that require user apply before mutating nodes.
Constraint: User asked to stop new feature work and only repair P0/P1 product-fit gaps.
Rejected: Auto-convert image or video directly into text for LLM | hides workflow structure and violates the visible-node product model.
Rejected: Let Chat tool calls silently add or edit nodes | canvas structure must be previewed and explicitly applied.
Confidence: high
Scope-risk: moderate
Directive: Keep media transformations represented by visible handles, nodes, and edges; do not add silent Chat or media mutation paths without product review.
Tested: Browser automation for language switch, video input to Generate Video, prompt to Generate Video, LLM default model, Video Trim controls, and video media to LLM input.
Tested: npm run test:run -- src/components/__tests__/ConnectionDropMenu.test.tsx src/components/__tests__/GenerationComposer.test.tsx src/components/__tests__/VideoTrimNode.test.tsx src/components/__tests__/LLMGenerateNode.test.tsx src/store/execution/__tests__/llmGenerateExecutor.test.ts src/app/api/llm/__tests__/route.test.ts src/store/execution/__tests__/generateAudioExecutor.test.ts src/store/execution/__tests__/generate3dExecutor.test.ts src/store/execution/__tests__/simpleNodeExecutors.test.ts src/components/__tests__/OutputNode.test.tsx src/components/__tests__/Generate3DNode.test.tsx src/components/__tests__/ChatPanel.test.tsx src/lib/chat/tools.test.ts src/lib/chat/editOperations.test.ts src/lib/__tests__/llmModels.test.ts src/store/utils/__tests__/nodeDefaults.test.ts
Tested: npm run build
Tested: git diff --cached --check
Not-tested: npm run lint, because the existing script calls removed Next.js lint command and exits with invalid project directory /lint.
Not-tested: Live paid provider calls for LLM, audio, and 3D generation.
P0/P1 reports clustered around unavailable LLM defaults, video media connections, and missing controls on the generation surface. This routes the default NewApiWG LLM away from the unavailable Doubao channel, makes video a first-class schema and canvas connection type, carries request ids into node errors, and fills the composer, trim, audio, and 3D gaps without changing the canvas workflow model.
Constraint: NewApiWG Doubao channels are not reliably routed in the default gateway
Constraint: Video model schemas expose video fields that must not flow through image handles
Rejected: Continue accepting video through image handles | hides incorrect graph types and loses schema-specific dynamic inputs
Confidence: high
Scope-risk: moderate
Directive: Keep media handle types explicit; add schema mapping when adding a new connectable media type
Tested: npm run test:run -- src/store/execution/__tests__/generateVideoExecutor.test.ts src/store/utils/__tests__/connectedInputs.test.ts src/store/execution/__tests__/generateAudioExecutor.test.ts src/store/execution/__tests__/llmGenerateExecutor.test.ts src/store/__tests__/workflowStore.integration.test.ts src/lib/__tests__/llmModels.test.ts src/store/utils/__tests__/nodeDefaults.test.ts src/components/__tests__/GenerationComposer.test.tsx src/app/api/chat/__tests__/route.test.ts src/app/api/llm/__tests__/route.test.ts src/app/api/models/[modelId]/__tests__/route.test.ts src/app/api/generate/providers/__tests__/newapiwg.test.ts
Tested: npm run build
Tested: git diff --check
Not-tested: npm run lint | package script calls removed Next 16 next lint command
Not-tested: npx tsc --noEmit | existing test type errors remain; changed runtime files filtered clean
Browser-local projects cannot load node carousel history through the server-side generations API. Each new image history entry now keeps the generated data URL for immediate in-session switching, while server-backed projects can still fall back to loading by generation id. Browser-local generation paths also skip /api/save-generation because that endpoint can only write server filesystem paths.
Constraint: Project storage can be browser-local and therefore unavailable to server filesystem APIs.
Rejected: Force carousel switching through /api/load-generation | browserfs paths cannot be resolved by the server and cause switching failures.
Confidence: high
Scope-risk: narrow
Directive: Keep carousel history able to switch without server round-trips when images were generated in the current session.
Tested: npm test -- --run src/components/__tests__/GenerateImageNode.test.tsx src/store/execution/__tests__/nanoBananaExecutor.test.ts
Tested: npx tsc --noEmit --pretty false filtered to changed production files
This aligns image, video, and text defaults with the PopiArt NewApi gateway while keeping provider-specific escape hatches available. Chat now uses the OpenAI-compatible NewApiWG route so workflow editing and LLM generation share the same Doubao-first text default. The canvas composer and quick-add controls are tightened so visible actions map to real clickable behavior, while temporarily unused header actions stay hidden.
Constraint: Text/chat requests must prefer Doubao-Seed-2.0-lite through NewApiWG.
Constraint: Image and video defaults should prefer PopiArt NewApi gateway models when configured.
Rejected: Keep /api/chat on Gemini 3 Flash | conflicts with the requested NewApiWG text/chat default.
Rejected: Hand-roll AI SDK chat streaming | would risk tool-call and UI stream compatibility.
Confidence: high
Scope-risk: moderate
Directive: Do not add new text/chat defaults without updating src/lib/llmModels.ts and the chat route together.
Tested: npm test -- --run src/app/api/chat/__tests__/route.test.ts src/components/__tests__/LLMGenerateNode.test.tsx src/lib/__tests__/llmModels.test.ts src/store/utils/__tests__/nodeDefaults.test.ts src/components/__tests__/GenerationComposer.test.tsx src/lib/chat/tools.test.ts
Tested: npm test -- --run src/lib/__tests__/llmModels.test.ts src/store/utils/__tests__/nodeDefaults.test.ts src/components/__tests__/GenerationComposer.test.tsx src/lib/quickstart/__tests__/templates.test.ts src/lib/quickstart/__tests__/validation.test.ts src/lib/quickstart/__tests__/prompts.test.ts src/components/__tests__/ProjectSetupModal.test.tsx
Tested: npm test -- --run src/components/__tests__/WorkflowCanvas.test.tsx
Not-tested: Full suite; repository has pre-existing unrelated type/test warnings outside this change.
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 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
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
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>
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>
- Restore FTUX production logic (remove testing override)
- Fix generate3dExecutor clearOutput field and add modelId validation
- Remap startLevel index after loop partitioning in executeWorkflow
- Add missing mockTutorialExecution dep in FloatingActionBar
- Reset settingsTab to "primary" when fallbackModel is removed
- Clear orphaned fallbackParameters when removing fallback model
- Fix OutputGalleryNode removeMedia to splice single item, not all duplicates
- Add data-tutorial="save-button" to untitled save button in Header
- Move data-tutorial to layout-producing element in BaseNode
- Hydrate FTUXApiKeysStep localKeys from providerSettings
- Remove broken docs URL from ftuxStore tutorial steps
- Add error handling in loadTutorialSampleImage
- Fix mediaStorage ref array misalignment in outputGallery hydration
- Make OutputGalleryNodeData.videos optional to match usage
- Remove unused fitView destructuring from WorkflowCanvas
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Adds SettingsTabBar to LLMGenerateNode with temperature and max tokens
controls for the fallback model. Updates llmGenerateExecutor to use
fallbackParameters override when running the fallback model.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Adds fallbackParameters?: Record<string, unknown> to NanoBananaNodeData,
GenerateVideoNodeData, Generate3DNodeData, and GenerateAudioNodeData so
fallback models can have their own parameter configurations.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Introduces a shared runWithFallback helper that wraps primary model
attempts with an optional fallback. On primary failure (non-abort),
the fallback runs once and metadata is stamped on the node so the UI
can render a "Fallback used" badge. Fallback is skipped when primary
and fallback resolve to the same provider/model to avoid double-billing.
Wired through all generation executors (nanoBanana, generateVideo,
generateAudio, generate3d, llmGenerate) with accompanying tests.
JSON-compatible with Node Banana Pro: fallbackModel and __-prefixed
metadata fields match NBP's shape for clean config round-trips.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat(49-01): add FTUX types and localStorage helpers
- Create src/types/ftux.ts with FTUXStep, FTUXModalProps, FTUXStepProps
- Add FTUX_COMPLETED_KEY to localStorage keys
- Add getFTUXCompleted() and setFTUXCompleted() helpers
- Follow existing localStorage pattern with server-safe checks
* feat(49-02): create tutorial state management with Zustand
- TutorialStep interface with id, message, highlightSelector, requiredAction
- FTUXState with tutorialActive, currentTutorialStep, tutorialSteps, lockedFeatures
- Actions: startTutorial, skipTutorial, completeCurrentStep, nextTutorialStep, resetTutorial
- setFTUXCompleted/getFTUXCompleted localStorage helpers
- Initial tutorial steps (5 steps, placeholders for Plan 03)
* feat(49-01): build FTUXModal container and step components
- Create FTUXModal.tsx: 4-step modal with navigation, skip confirmation
- Create FTUXWelcomeStep.tsx: Welcome screen with project intro
- Create FTUXApiKeysStep.tsx: Provider list with .env status checking
- Create FTUXModelDefaultsStep.tsx: Model selection via ModelSearchDialog
- Create FTUXReadyStep.tsx: Completion screen with tutorial option
- Follow WelcomeModal and ProjectSetupModal patterns
- Use Tailwind neutral-800 theme throughout
- Step indicators show progress (1-4)
- Back button hidden on step 1
- Skip confirmation shows nested dialog overlay
* feat(49-02): build typewriter hook and tutorial UI components
- useTypewriter hook with 50ms/char animation and isComplete flag
- TutorialMessage component with typewriter animation and dark backdrop
- ElementHighlight component with pulsing blue ring (2px border, 8px blur)
- Three-layer highlight: dimmed overlay (bg-black/60), blue ring (z-91), clickable window (z-92)
- TutorialOverlay coordinator with action detection (add-image-node, connect-nodes, run-workflow)
- Skip tutorial button in top-right corner
- pulse-ring keyframes added to globals.css
- Uses React portals for correct z-index stacking
* feat(49-03): replace WelcomeModal with FTUXModal in app entry point
- Import FTUXModal, getFTUXCompleted, setFTUXCompleted, useFTUXStore
- Add SSR-safe showFTUX state (initialized via useEffect)
- Render FTUXModal when showFTUX=true
- onComplete callback sets showFTUX=false and marks FTUX complete
- onStartTutorial callback hides modal and starts tutorial via ftuxStore
- WelcomeModal still available via logo click (not removed, just not on first visit)
* feat(49-03): integrate tutorial overlay and add data-tutorial attributes
WorkflowCanvas.tsx:
- Import TutorialOverlay and useFTUXStore
- Get tutorialActive and lockedFeatures from ftuxStore
- Apply dimming (opacity-30 + pointer-events-none) to Background, Controls, MiniMap when tutorial active
- Render TutorialOverlay component after AnnotationModal
FloatingActionBar.tsx:
- Add optional dataTutorial prop to NodeButton interface
- Add data-tutorial="image-button" to Image input button
- Add data-tutorial="output-button" to Output button
- These attributes allow ElementHighlight to target buttons during tutorial
* fix(49-03): resolve Zustand SSR hydration issue in WorkflowCanvas
- Replace direct useFTUXStore hook call with useState + useEffect pattern
- Subscribe to ftuxStore on client-side only via useEffect
- Initialize state with getState() after subscription
- Prevents 'getServerSnapshot should be cached' error during SSR
- Prevents 'Maximum update depth exceeded' infinite loop
* chore(49-03): add FTUX testing override to always show modal
- Force setShowFTUX(true) to bypass localStorage check
- Allows repeated testing without clearing localStorage each time
- Added clear TODO comment to revert before production
- Original production code commented out for easy restoration
* ux(49-03): improve FTUX modal copy and layout based on user testing
FTUXModal.tsx:
- Increase modal width from max-w-[520px] to max-w-[640px] for better readability
FTUXWelcomeStep.tsx:
- Remove large banana icon (w-20 h-20) for cleaner look
- Change headline from "Welcome to Node Banana" to "Let's get set up."
- Keep workflow description, update subtext to "This will only take a few quick steps."
FTUXApiKeysStep.tsx:
- Simplify description to emphasize .env.local security benefit
- New: "Add your API keys here, or save them to .env.local to keep them secure across sessions."
- Remove mention of settings (cleaner messaging)
FTUXModelDefaultsStep.tsx:
- Remove redundant "You can always change these later" from main description
- Remove "These defaults are applied when creating nodes" from footer (redundant)
- Keep single mention: "You can skip this step and configure them later in settings."
FTUXReadyStep.tsx:
- Remove large green checkmark icon (w-16 h-16 circular bg)
- Keep "All Set Up!" headline and description
- Keep both tutorial buttons unchanged
* ux(49-03): remove redundant .env.local text below API keys list
- Remove duplicate messaging at bottom of FTUXApiKeysStep
- Top description already mentions .env.local security benefit
- Cleaner, less repetitive UI
* copy(49-03): rewrite FTUX copy for clarity and conciseness
Applied principles: Clear, Concise, Consistent, Useful, Human
FTUXWelcomeStep:
- Headline: "Let's get set up." → "Let's get started."
- Body: Simplified from wordy workflow/pipeline jargon to clear action
Old: "Build AI workflows visually with nodes and connections. Create complex image and video generation pipelines by connecting simple building blocks."
New: "Connect AI models like building blocks to generate images, videos, and more."
- Removed defensive subtext ("This will only take a few quick steps")
FTUXApiKeysStep:
- Description: Clearer security benefit, less technical
Old: "Add your API keys here, or save them to .env.local to keep them secure across sessions."
New: "Add keys to use AI providers. For security, save them to your .env.local file instead of the browser."
FTUXModelDefaultsStep:
- Headline: "Default Models (Optional)" → "Choose Your Models (Optional)"
- Description: Removed redundant "by default when creating new nodes"
Old: "Choose which AI models to use by default when creating new nodes."
New: "Pick which AI models to use for images and videos."
- Removed defensive footer text ("You can skip...")
FTUXReadyStep:
- Headline: "All Set Up!" → "You're ready!"
- Body: Removed jargon and redundancy
Old: "You're ready to start building workflows. Would you like a quick tutorial to get started?"
New: "Want a quick tutorial?"
- Buttons: "Skip Tutorial" → "Skip", "Start Tutorial" → "Show me how"
* fix(49-03): remove double scrollbar in API Keys step
- Remove max-h-[380px] overflow-y-auto from provider list container
- FTUXModal already has overflow-y-auto on content area (line 112)
- Provider list now scrolls with rest of modal content, not separately
- Fixes nested scroll container causing double scrollbar
* copy(49-03): clarify browser storage in API Keys step
- Make it explicit that adding keys in UI = stored in browser
- Parenthetical "(stored in browser)" right after action
- "for better security" instead of "For security" flows better
- Clearer tradeoff between convenience (browser) and security (.env.local)
Old: "Add keys to use AI providers. For security, save them to your .env.local file instead of the browser."
New: "Add keys here to use AI providers (stored in browser), or save them to your .env.local file for better security."
* copy(49-03): add "across sessions" to API Keys security benefit
- Change "better security" to "better security across sessions"
- Makes the persistence benefit of .env.local more explicit
- Clarifies why file storage is better than browser storage
New: "Add keys here to use AI providers (stored in browser), or save them to your .env.local file for better security across sessions."
* fix(49-03): save API keys entered during FTUX onboarding
Problem: API keys entered in FTUX modal were stored in local state only
and disappeared when modal closed, requiring users to re-enter them.
Original plan decision (49-01): "API key inputs not persisted during FTUX flow"
Rationale: "Setup is informational only, actual persistence happens in ProjectSetupModal"
This created broken UX - users entered keys expecting them to work.
Solution:
- Import useWorkflowStore and updateProviderApiKey function
- Create handleKeyChange that saves to localStorage immediately via updateProviderApiKey
- Replace direct setLocalKeys with handleKeyChange in input onChange
- Keys now persist automatically as user types (saved as null if empty)
Impact: Users can now enter API keys during onboarding and they'll be
available immediately in the app without re-entry.
Deviation: Auto-fix bug (Rule 1) - keys not persisting broke user expectations
* fix(49-03): close FTUX modal and center tutorial message
Issue 1 - FTUX modal still visible after starting tutorial:
- Add setFTUXCompleted(true) to handleStartTutorial
- Ensures modal won't re-render even with testing override
- Modal now properly closes before tutorial starts
Issue 2 - Tutorial message overlapping floating action bar:
- Change TutorialMessage position from bottom-4 to center of screen
- Use top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 for true center
- Message now displays prominently in center, not at bottom
- Update comment: "contextual placement" → "center of screen for maximum visibility"
Before: bottom-4 left-1/2 -translate-x-1/2 (bottom center)
After: top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 (true center)
* fix(49-03): close WelcomeModal when starting tutorial
Problem: WelcomeModal (original quickstart modal with "New Project", "Load Workflow",
etc.) remained visible when user clicked "Show me how" to start tutorial, blocking the
tutorial UI.
Root cause: WelcomeModal is controlled by workflowStore.showQuickstart state and was not
being closed when tutorial started.
Solution:
- Import setShowQuickstart from useWorkflowStore
- Add setShowQuickstart(false) to handleStartTutorial
- WelcomeModal now closes before tutorial overlay appears
Now when tutorial starts, both FTUXModal and WelcomeModal are closed, allowing tutorial
overlay to display without obstruction.
* fix(49-03): implement complete tutorial progression flow
Problem: Tutorial stuck on "Let's cook." message without progressing.
Root causes:
1. Step 1 (welcome) had no requiredAction, so action detection never triggered
2. Steps 3-5 were placeholder steps with no content or actions
3. No auto-advance logic for informational steps
Solution:
ftuxStore.ts - Replace placeholder steps with real tutorial:
- Step 1: "Let's cook." (welcome, auto-advances after 3s)
- Step 2: "Click the Image button to add an image node." (highlights image button, requires add-image-node)
- Step 3: "Now add an Output node to see your results." (highlights output button, requires add-output-node)
- Step 4: "You're all set! Connect nodes to build workflows, or press Cmd+Enter to run." (completion, auto-advances)
- Add "add-output-node" to TutorialStep requiredAction type
TutorialOverlay.tsx - Add auto-advance and output detection:
- Steps without requiredAction now auto-advance after 3 seconds (typewriter completes ~2s)
- Add detection for "add-output-node" action (checks for node.type === "output")
- Early return cleanup timer to prevent memory leaks
Tutorial now flows: Welcome → Add Image → Add Output → Complete
Each step progresses based on user action or auto-advances for info steps.
* fix(49-03): make tutorial highlighted elements clickable and visible
Problem 1: Floating action bar was darkened during tutorial even though it
contains the highlighted image button that user needs to see and click.
Problem 2: Image button was not clickable - Layer 3 "clickable window" was
blocking clicks instead of allowing them through.
Root cause: ElementHighlight had full-screen dimmed overlay (Layer 1) blocking
clicks, and Layer 3 created an empty div that blocked the actual element.
Solution - Use box-shadow cutout technique:
- Remove solid dimmed overlay (bg-black/60)
- Remove Layer 3 entirely (not needed)
- Use box-shadow: "0 0 0 9999px rgba(0, 0, 0, 0.6)" on positioned div
- Creates dimmed overlay with cutout hole where highlighted element is
- Highlighted element remains fully visible (not dimmed)
- Highlighted element is fully clickable (no overlay blocking it)
- Everything else gets dimmed
FloatingActionBar.tsx:
- Import useFTUXStore (prepared for future use)
- Add tutorial state awareness (not currently used but ready for expansion)
ElementHighlight.tsx:
- Replace full-screen overlay + clickable window with box-shadow cutout
- Simpler 2-layer approach: dimmed background with hole + blue ring
- All pointer-events-none, so nothing blocks the actual DOM element
* fix(49-03): resolve Zustand SSR hydration issue in FloatingActionBar
Error: "The result of getServerSnapshot should be cached to avoid an infinite loop"
at FloatingActionBar (src/components/FloatingActionBar.tsx:345:58)
Same issue as WorkflowCanvas - direct useFTUXStore hook call during SSR.
Solution (identical pattern to WorkflowCanvas fix):
- Replace direct useFTUXStore hook with useState + useEffect pattern
- Initialize with false defaults (SSR-safe, no hydration mismatch)
- Subscribe to ftuxStore on client-side only via useEffect
- Initialize state with getState() after subscription
- Return cleanup function to unsubscribe on unmount
Before: const { tutorialActive, lockedFeatures } = useFTUXStore(...)
After: useState + useEffect subscription (client-side only)
This prevents the SSR hydration error while maintaining reactive updates.
* chore(49-03): restore production FTUX localStorage check
- Remove testing override that forced FTUX to show every time
- Restore production code: if (!getFTUXCompleted()) { setShowFTUX(true); }
- Remove TODO comment about reverting
- FTUX now only shows on first visit as intended
* feat(tutorial): add connection drop menu workflow steps
Extend the FTUX tutorial with 4 new steps that teach users how to use the
connection drop menu workflow pattern:
Step 6: Drag from output handle
- Prompts user to drag from the image node's output handle
- Tracks drag start via onConnectStart handler
Step 7: Release in empty space
- Prompts user to release to show the connection drop menu
- Tracks menu appearance via handleConnectEnd
Step 8: Explain connection menu
- Highlights the drop menu with blue ring
- Explains it shows compatible nodes
- Requires click to continue
Step 9: Select Generate Image
- Highlights "Generate Image" button in menu
- Tracks nanoBanana selection via handleMenuSelect
- Only advances when selected from menu (not FloatingActionBar)
Step 10: Tutorial complete
- Shows completion message with workflow tips
- Auto-advances after 3s and marks FTUX done
Implementation details:
- Add 3 new requiredAction types to TutorialStep interface
- Add 3 state tracking flags to FTUXState (connectionDragStarted, etc.)
- Add tutorial tracking in WorkflowCanvas connection lifecycle
- Add data-tutorial attributes for highlighting menu elements
- Import OnConnectStart type and create handleConnectStart handler
- Track menu appearance and nanoBanana selection with tutorialActive guards
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* refactor(tutorial): simplify drag-and-drop flow
Combine drag and drop instructions into a single step and immediately
highlight the Generate Image button when the context menu appears:
Changes:
- Combine step 6 (drag) and step 7 (drop) into one: "Drag from the output
handle and drop into empty space"
- Remove step 8 (explain menu) - go straight to highlighting Generate Image
- Step 6 now waits for menu to appear (show-connection-menu)
- Step 7 highlights Generate Image button immediately (add-nanoBanana-from-menu)
Removed unused tracking:
- Remove start-connection-drag requiredAction type
- Remove connectionDragStarted state flag and setter
- Remove handleConnectStart callback from WorkflowCanvas
- Remove onConnectStart prop from ReactFlow
- Remove OnConnectStart import
New flow is cleaner and more direct:
Step 5: Explain outputs (click to continue)
Step 6: Drag and drop to show menu (auto-advance)
Step 7: Select Generate Image (auto-advance)
Step 8: Complete
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(tutorial): highlight Generate Image button immediately
Combine drag-and-drop and selection steps into one to ensure the Generate
Image button is highlighted as soon as the connection menu appears.
Problem:
- Previous flow had two separate steps (drag-drop, then select)
- 1 second delay between menu appearing and Generate Image highlighting
- Output handle was highlighted while menu was already visible (confusing)
Solution:
- Single step: "Drag from the output handle and drop into empty space. Then
select 'Generate Image'."
- highlightSelector targets the Generate Image button from the start
- ElementHighlight polls every 500ms, so highlights button immediately when
menu appears (no delay)
- Cleaner UX: instruction → action → immediate visual feedback
Removed:
- show-connection-menu requiredAction (no longer needed)
- connectionMenuShown state flag and setter
- Tutorial tracking in handleConnectEnd
New flow:
- Step 5: Explain outputs (click to continue)
- Step 6: Drag, drop, select Generate Image (highlight on button, auto-advance)
- Step 7: Complete
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(tutorial): increase highlight z-index above connection menu
The ElementHighlight component was rendering at z-index 91, but the
ConnectionDropMenu is at z-index 100, causing the highlight ring to
appear behind the menu.
Change: Increase ElementHighlight z-index from 91 to 101
This ensures the pulsing blue ring appears on top of the menu while
still allowing clicks through (pointer-events-none).
Z-index stack:
- ElementHighlight: 101 (above menu)
- ConnectionDropMenu: 100
- Skip tutorial button: 94
- TutorialMessage: 93
- Click overlay: 92
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(tutorial): highlight output handle when prompting drag action
Split the combined step back into two sequential steps with proper highlights
and instant transition between them.
Problem:
- Single step told user to "Drag from the output handle" but highlighted
the Generate Image button (which doesn't exist yet)
- Confusing visual feedback - wrong element highlighted for the instruction
Solution:
- Step 6: "Drag from the output handle and drop into empty space."
- Highlights: node-output-handle
- Action: show-connection-menu
- advanceDelay: 0ms (instant transition)
- Step 7: "Select 'Generate Image' to add an AI image generation node."
- Highlights: generate-image-option
- Action: add-nanoBanana-from-menu
- advanceDelay: 1000ms (default)
New feature: advanceDelay property
- Added to TutorialStep interface
- Configurable delay before advancing to next step after action completes
- Default: 1000ms (preserves existing behavior)
- Step 6 uses 0ms for instant transition when menu appears
Flow:
1. User sees output handle highlighted → drags and drops
2. Menu appears → step advances instantly (0ms delay)
3. Generate Image button immediately highlighted
4. User clicks → tutorial advances after 1s
Restored:
- show-connection-menu requiredAction
- connectionMenuShown state and setter
- Tutorial tracking in handleConnectEnd
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(tutorial): subscribe to ftux state for action detection
The action detection useEffect wasn't re-running when connectionMenuShown
or nanoBananaAddedFromMenu changed because these states weren't in the
dependency array.
Problem:
- User drags and drops → menu appears
- setConnectionMenuShown(true) updates ftuxStore
- But action detection useEffect doesn't re-run (missing dependency)
- Step doesn't advance to highlight Generate Image button
Solution:
- Subscribe to connectionMenuShown and nanoBananaAddedFromMenu at component level
- Use these subscribed values instead of getState() calls
- Add them to useEffect dependency array
Now when these states change:
1. Component re-renders with new values
2. useEffect re-runs due to dependency change
3. Action is detected as completed
4. Step advances with configured delay (0ms for instant transition)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat(tutorial): add prompt node and connection steps
Extend tutorial with steps 8-9 to teach users:
- Adding nodes via FloatingActionBar (different from connection menu)
- Connecting nodes by dragging between existing handles
Changes:
- Add data-tutorial="prompt-button" to Prompt button
- Add data-tutorial="prompt-output-handle" to Prompt output handle
- Add "add-prompt-node" to requiredAction types
- Add step 8: Add Prompt node via FloatingActionBar
- Add step 9: Connect Prompt to Generate Image node
- Update step 10 (complete) message for clarity
- Add action detection for add-prompt-node in TutorialOverlay
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(tutorial): wait for prompt node connection in step 9
Step 9 was advancing immediately because it checked for ANY edges,
but an edge already existed from step 7 (Image → Generate Image).
Changes:
- Add "connect-prompt-node" requiredAction type
- Update step 9 to use "connect-prompt-node" instead of "connect-nodes"
- Add detection logic that specifically checks for edges FROM prompt nodes
- Track edges in TutorialOverlay component state for reactive detection
- Add edges to useEffect dependency array
Now step 9 correctly waits for user to connect the prompt node
before advancing to the completion step.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat: prevent node overlap on creation with automatic collision detection
Implements AABB collision detection to automatically place new nodes in collision-free positions. When users add nodes via FloatingActionBar or keyboard shortcuts, the system now finds the nearest free position using a spiral search pattern (right, down, left, up, diagonals).
Changes:
- Add spatialLayout.ts with rectanglesIntersect() and findNearestFreePosition()
- Update addNode() to use collision detection before placing nodes
- Remove random offset from FloatingActionBar for more deterministic placement
- Use consistent 20px gap (COLLISION_GAP) matching existing STACK_GAP pattern
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat(tutorial): extend FTUX with content setup and resource guidance
Add four new tutorial steps to guide users through completing their first workflow:
- Prompt users to add text to the Prompt node
- Prompt users to add an image to the Image node
- Instruct users to run the workflow with Cmd/Ctrl+Enter
- Inform users about saving projects, templates, and Discord community
This ensures users complete a full workflow cycle during onboarding and know where to find help and resources.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(tutorial): remove emojis from tutorial messages
Remove decorative emojis from the save-and-resources and completion steps for a cleaner, more professional appearance.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(tutorial): improve viewport zoom timing with auto-populated nodes
Increase timeout from 400ms to 600ms and improve viewport setting logic
to ensure proper 0.7 zoom level when tutorial image node is added with
pre-loaded base64 data. Fetch fresh nodes from store and extend animation
duration for smoother transition.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat(tutorial): delay auto-population until after prompt connection
Change tutorial flow to create empty nodes initially and auto-populate
with sample content only after the prompt node is connected to generate.
This maintains hands-on learning while still removing friction.
Changes:
- Nodes created empty during tutorial
- Auto-populate after "connect-prompt-node" step completes
- Switch sample image: model.png → owl.jpg
- Update prompt: "wearing aviator sunglasses and a leather jacket"
- Add nodesPopulated ref to prevent duplicate population
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat(tutorial): improve messaging, dual highlights, and pulsing animation
- Change welcome message: "Let's cook" → "Let's go over the basics"
- Add dual highlight for connect-prompt step: both prompt output and
generate text input handles are now highlighted simultaneously
- Position connect-prompt message on left side of screen
- Update ElementHighlight to support array of selectors
- Add data-tutorial attribute to generate node's text input handle
- Pulsing animation already present via pulse-ring animation
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat(tutorial): implement mock execution and organized node demonstration
- Add mock workflow execution to save API costs during tutorial
- Mock execution simulates 5-second generation time with edge animations
- Loads pre-made owl aviator image instead of calling Gemini API
- Provides realistic experience without incurring charges
- Implement visible workflow tree demonstration
- Automatically builds branching workflow after mock execution
- Video branch: Prompt → Generate Video → Output
- LLM branch: Prompt → LLM → Generate Image #2 → Output
- Clean horizontal layout with 350px spacing between nodes
- Vertical separation of ±350px for clear branch distinction
- Improve user experience
- Auto-close run menu when advancing past tutorial steps
- Unlock canvas navigation after demonstration completes
- Zoom to 0.35 with centered viewport for complete tree overview
- Updated LLM prompt for nightclub scene generation
- Add tutorial assets
- Add owl aviator image (public/tutorial/owl-aviator.png)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: restore .env.example file
Restore .env.example that was accidentally deleted in commit 9be881c.
This file is needed as a reference for users to set up their environment.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
The video stitch node was only copying video tracks from source clips,
silently discarding any embedded audio. Now extracts and concatenates
audio from source videos when no external audio node is connected.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
When batch mode is enabled on an array node, all parsed items are sent
through a single connection to a downstream generate node, which loops
through them sequentially. This eliminates the need to create separate
downstream nodes for each array item.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add "audio-to-video" to ModelCapability union, extend ModelInput.type
and ModelInputDef.type with "audio", and include "audio-to-video" in
fal.ai RELEVANT_CATEGORIES for model discovery.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a new videoInput node type that allows users to drag-and-drop or
browse for video files (MP4, WebM, QuickTime), preview them inline with
native video controls, and feed them into downstream video processing
nodes. Includes full media externalization/hydration support, Shift+Y
keyboard shortcut, and ConnectionDropMenu integration.
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>
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>
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>
Allow marking groups as NBP inputs for workflow export. Adds isNbpInput
flag to NodeGroup, a toggle row in the group context menu with checkmark
indicator, and a dashed border on marked group backgrounds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Store measured settings panel height in node data (_settingsPanelHeight).
On workflow load, subtract it from persisted height so BaseNode's expand
effect re-adds the real panel height from a clean baseline instead of
double-counting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add parametersExpanded field to 5 generation node data types (NanoBanana, GenerateVideo, Generate3D, GenerateAudio, LLMGenerate)
- Create useInlineParameters hook for localStorage-backed global setting (default: false)
- Create InlineParameterPanel component with collapsible UI and chevron toggle
- All TypeScript compiles without new errors
Adds an evaluationPaused flag that lets users reset evaluation state
and un-dim all downstream paths. The flag auto-resets on workflow
execution or manual rule edits.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Anthropic as a third LLM provider with Claude Opus 4.6, Sonnet 4.5,
and Haiku 4.5 models. Extend Google provider with Gemini 3.1 Pro.
Includes full API route support with multimodal content blocks,
provider settings UI, temperature clamping (Anthropic max 1.0),
header builder plumbing, and comprehensive test coverage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Nano Banana 2 (gemini-3.1-flash-image) supports a new searchTypes API
that allows enabling web search and image search independently. This adds
a useImageSearch toggle visible only for Nano Banana 2, while keeping the
existing Google Search toggle using the legacy format for Nano Banana Pro.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Make aspect ratio dropdowns model-aware: nano-banana-2 shows 14 ratios
while other models keep showing the base 10. Also add nano-banana-2 to
the SplitGrid settings model list.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add "nano-banana-2" to ModelType union for Gemini 3.1 Flash Image Preview.
Add "512" resolution tier. Add MODEL_DISPLAY_NAMES lookup to replace ternaries.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add MatchMode type (exact, contains, starts-with, ends-with)
- Add ConditionalSwitchRule interface with id, value, mode, label, isMatched
- Add ConditionalSwitchNodeData interface with incomingText and rules array
- Add conditionalSwitch to NodeType union (after switch)
- Add ConditionalSwitchNodeData to WorkflowNodeData union
- Add default data with single rule (mode: contains, empty value)
- Add default dimensions (260x180, wider than Switch for mode dropdown)
- Add executeConditionalSwitch passthrough executor
- Add conditionalSwitch to VALID_NODE_TYPES in validation
- Export ConditionalSwitchNode from components index (component created in next task)
- Add "switch" to NodeType union in types/nodes.ts
- Create SwitchNodeData interface with inputType and switches array
- Add SwitchNodeData to WorkflowNodeData union
- Register switch dimensions (220x120) in nodeDefaults.ts and validation.ts
- Add createDefaultNodeData case with single default switch
- Implement executeSwitch passthrough executor in simpleNodeExecutors.ts
- Add SwitchNode export to components/nodes/index.ts
- Add switch dimensions to WorkflowCanvas.tsx default dimensions map
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add 'router' to NodeType union in types/nodes.ts
- Add RouterNodeData interface (extends BaseNodeData, no internal state)
- Add RouterNodeData to WorkflowNodeData union
- Add router dimensions (200x80) to nodeDefaults.ts and validation.ts
- Add router default data (empty object) to both files
- Add executeRouter pure passthrough function to simpleNodeExecutors.ts
- Add RouterNode export to components/nodes/index.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add 'videoFrameGrab' to NodeType union and VideoFrameGrabNodeData interface
- Register default dimensions (320x320) and default data in nodeDefaults
- Wire getSourceOutput to return image type from videoFrameGrab.outputImage
- Add videoFrameGrab dimensions to local Record types in WorkflowCanvas and validation
- Add 'videoTrim' to NodeType union and VideoTrimNodeData interface in nodes.ts
- Add VideoTrimNodeData to WorkflowNodeData union
- Add default dimensions (360x360) and default data to nodeDefaults.ts
- Create useTrimVideo.ts hook with MediaBunny frame-by-frame trim + audio preservation
- Add getSourceOutput case for videoTrim in connectedInputs.ts
- Add executeVideoTrim executor to videoProcessingExecutors.ts
- Wire 'videoTrim' case into executeNode.ts dispatcher
- Fix duplicate Record<NodeType> dimensions in WorkflowCanvas.tsx and validation.ts
Add audio support to the Output node so GenerateAudio can pipe audio
through for playback and download. Adds audio input handle, <audio>
player, audio→output connection validation, and executeOutput audio
handling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add generateAudio to getNodeHandles() for connection validation, wire
audio handle type in connection drop menu, and add "audio" mediaType
to GenerateRequest/GenerateResponse API types.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add comprehensive text-to-speech (TTS) support with a new generateAudio node
that integrates with multiple AI providers (Kie.ai, Replicate, fal.ai, WaveSpeed).
Changes:
- Add 'text-to-audio' capability to ModelCapability type
- Add GenerateAudioNodeData type and CarouselAudioItem for audio history
- Create GenerateAudioNode component with audio playback and waveform visualization
- Add TTS models from Kie.ai (ElevenLabs Turbo v2.5, Multilingual v2, Eleven V3)
- Update fal.ai category mapping to support 'text-to-speech' -> 'text-to-audio'
- Add audio filter option to ModelSearchDialog
- Register generateAudio node in WorkflowCanvas with fuchsia minimap color
- Add generateAudio to ConnectionDropMenu for both audio source and text target
- Update connectedInputs to handle audio output from generateAudio nodes
- Add default node data and dimensions for generateAudio type
- Update quickstart validation to include generateAudio node type
The node supports:
- Text-to-audio generation with multiple TTS models
- Real-time audio playback with waveform visualization
- Audio history carousel for navigating generated audio
- Dynamic model parameters based on selected provider
- Integration with existing audio handle system
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add localhost-only guard to /api/open-file (rejects non-localhost with 403)
- Restrict open-file paths to user's home directory
- Change savedFilename/savedFilePath from optional to required-but-nullable
- Remove zip from KNOWN_3D_EXTENSIONS (not a valid 3D format)
- Export getExtensionFromUrl for testability
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add optional fields to track the auto-saved 3D model filename and
file path, with null defaults in nodeDefaults and quickstart validation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>