Skip to content

Conversation

@kaochannel154
Copy link
Contributor

@kaochannel154 kaochannel154 commented Oct 24, 2025

Overview

This PR redesigns the image generation node interface to improve user experience and code maintainability. The changes unify node panel headers across all node types, streamline the image generation workflow by removing unnecessary complexity, and modernize image handling with Next.js Image components. These improvements create a more consistent and intuitive interface while reducing technical debt.

Changes

UI/UX Improvements

  • Unified node panel headers: Introduced NodePanelHeader component with consistent documentation links and delete actions across all node types (text, image, query, trigger, file, vector-store)
  • Streamlined image generation panel: Replaced complex tabbed interface (Prompt/Model/Input) with a single, focused panel design
  • Enhanced action placement: Moved primary CTAs (Run/Query) from headers to full-width bars for better visual hierarchy
  • Expandable overlays: Added maximize/minimize capabilities for prompt editor and generation output panels

Technical Enhancements

  • Next.js Image adoption: Replaced raw <img> tags with Next/Image components via new ImageCard and Lightbox components for optimized loading and viewing
  • Reusable components: Added GenerateCtaButton for consistent primary actions and new UI hooks (useOverlayBottom, useElementTopPx, useLivePrompt)
  • Model selection improvements: Integrated plan gating with ProTag and contextual parameter display for different providers (Fal/OpenAI)
  • Simplified generation viewer: Replaced per-image loading states with a single spinner for cleaner visual feedback

Code Cleanup

  • Removed unused code: Eliminated Input selection panel and related category utilities
  • Dependency reduction: Removed pluralize and @types/pluralize packages
  • Accessibility improvements: Updated ModelPicker to use aria-disabled for better semantic HTML

⚠️ Breaking Changes

  • Image path contract: Image.pathname no longer includes leading slash - consumers must adjust URL joining logic
  • Export changes: workflow-designer-ui no longer exports types/use-source-categories/utils and Input panel components
  • ModelPicker behavior: Changed from disabled attribute to aria-disabled, removed disabled reason tooltip
  • Next.js configuration: External image URLs may require remotePatterns configuration in next.config.js

Testing

  • Verified all node types display correctly with new unified header
  • Tested image generation workflow end-to-end with multiple providers (Fal, OpenAI, Google)
  • Confirmed expandable overlays work smoothly across different viewport sizes
  • Validated that existing workflows continue to function with the new image path format
  • Checked accessibility improvements with screen reader testing

Review Notes

  • Breaking changes impact: Please review the breaking changes section carefully - we may need to coordinate deployment or provide migration guidance
  • Next.js config: Verify that all required remotePatterns are configured for external image sources (GitHub avatars, etc.)
  • UI consistency: Check if the new unified header design aligns with our design system guidelines
  • Performance: The switch to Next/Image should improve performance, but please validate with larger image sets

Related Issues

  • Addresses user feedback about confusing multi-tab interface in image generation nodes
  • Follows up on technical debt reduction initiative for node panel components
  • Implements design system consistency improvements across workflow designer

Note

Redesigns the image generation node with a unified panel header across nodes, moves primary actions into full-width CTAs, and migrates image rendering to Next/Image with lightbox and download support.

  • UI/UX
    • Unified headers: Introduces NodePanelHeader and applies it to action, file, image, query, text, trigger, vector-store, and web-page panels.
    • Primary actions: Moves Run/Query buttons from headers into full-width footer CTAs via GenerateCtaButton.
    • Image node redesign: Replaces tabbed Prompt/Model/Input UI with a single focused panel; adds expand overlays for prompt and output.
  • Images
    • Next/Image migration: Replaces <img> with Next/Image across designer UI (new ImageCard, Lightbox) and settings/tour UIs; adds optimized thumbnails.
    • Path join change: Updates image URL join from ${client.basePath}/${content.pathname} to ${client.basePath}${content.pathname}.
  • Generation flow
    • Simplifies GenerationView loading to a single spinner; introduces THUMB_HEIGHT and thumbnail layout.
  • Model selection
    • Integrates ModelPicker with plan gating (ProTag); adds provider-specific parameter panels (FalModelPanel, OpenAIImageModelPanel).
    • Improves accessibility: uses aria-disabled for disabled models.
  • Hooks/Utils
    • Adds useOverlayBottom, useElementTopPx, useLivePrompt; refines EditableText API; updates panel internals/exports.
  • Removals/Cleanup
    • Removes image node Input panel and related source-category utilities; deletes use-model-eligibility.
    • Drops pluralize and @types/pluralize; updates licenses doc accordingly.

Written by Cursor Bugbot for commit 2ad3c15. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • New Features

    • Model picker and prompt-driven UI for image generation; fullscreen edit/generation overlays; image lightbox for full-size viewing
  • UI/UX Improvements

    • Node headers now include docs links and delete actions; generation/image gallery updated with action overlays and improved thumbnails; disabled models show clear visual/ARIA state
  • Bug Fixes

    • Disabled-model interaction made more consistent (visual + aria-disabled)
  • Chores

    • Removed unused dependency and updated license listing documentation

… icon spacing; tidy EditableText padding control
…nel; unify model parameter UI using SettingRow; remove duplicate provider/model selects
…odel parameter spacing; align image-gen overlays and output label with text-gen
@kaochannel154 kaochannel154 requested a review from shige as a code owner October 24, 2025 08:15
@changeset-bot
Copy link

changeset-bot bot commented Oct 24, 2025

⚠️ No Changeset found

Latest commit: 2ad3c15

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

💥 An error occurred when fetching the changed packages and changesets in this PR
Some errors occurred when validating the changesets config:
The package or glob expression "giselle-sdk" is specified in the `ignore` option but it is not found in the project. You may have misspelled the package name or provided an invalid glob expression. Note that glob expressions must be defined according to https://www.npmjs.com/package/micromatch.

@vercel
Copy link

vercel bot commented Oct 24, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
giselle Ready Ready Preview Comment Oct 29, 2025 5:17am
ui Ready Ready Preview Comment Oct 29, 2025 5:17am

@giselles-ai
Copy link

giselles-ai bot commented Oct 24, 2025

Finished running flow.

Step Status Updated(UTC)
1 Oct 24, 2025 8:15am
2 Oct 24, 2025 8:17am
3 Oct 24, 2025 8:20am
4 Oct 24, 2025 8:20am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 24, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Refactors many properties panels to use a new NodePanelHeader (docs/delete), redesigns the image-generation node into prompt + generation overlays with new hooks and UI (GenerateCtaButton, ImageCard, Lightbox), removes legacy source utilities and pluralize dependency, and migrates several to Next.js Image.

Changes

Cohort / File(s) Summary
Image generation node panel refactor
internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx, .../generation-panel.tsx, .../prompt-panel.tsx, .../input-panel.tsx (removed), .../models/*
Replaces tabbed model/provider UI with a prompt-driven layout and two overlay panels (prompt editor, generation); InputPanel deleted; model panels wrapped with SettingRow; GenerationPanel gains expand hook props.
Node header replacement pattern
internal-packages/workflow-designer-ui/src/editor/properties-panel/{action,file,text,trigger,query,vector-store,web-page}-node-properties-panel/index.tsx
Replaced PropertiesPanelHeader with new NodePanelHeader across panels; useWorkflowDesigner now exposes deleteNode; added docsUrl and onDelete wiring; simplified onChangeName usage.
New UI components & exports
internal-packages/workflow-designer-ui/src/editor/properties-panel/ui/{node-panel-header.tsx,generate-cta-button.tsx,index.ts}
Added NodePanelHeader (docs link + delete), added GenerateCtaButton (full-width CTA with shortcut hint), and re-exported generate-cta-button in UI barrel.
Layout hooks, image viewing, and thumbnails
internal-packages/workflow-designer-ui/src/ui/{hooks.ts,image-card.tsx,lightbox.tsx,generation-view.tsx,constants.ts}
Added hooks (useLivePrompt, useOverlayBottom, useElementTopPx), ImageCard and Lightbox components, THUMB_HEIGHT constant; generation view updated to use new components, lightbox, and thumbnail sizing.
Removed source utilities & types
internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/sources/{index.ts,types.ts,use-source-categories.ts,utils.ts}
Removed public type exports (UnconnectedSource/ConnectedSource/Source), deleted useSourceCategories and filterSources, and reduced barrel exports; use-connected-sources now uses an internal ConnectedSource alias.
Model picker disabled-state change
internal-packages/ui/components/model-picker.tsx
Switched per-model rendering to compute isDisabled, removed native disabled prop and disabledReason tooltip, use aria-disabled and conditional styling instead.
Next.js Image migrations
apps/studio.giselles.ai/app/(main)/settings/team/integrations/github-integration.tsx, internal-packages/workflow-designer-ui/src/editor/workspace-tour/workspace-tour.tsx
Replaced native <img> with Next.js Image, adding explicit width/height where used.
EditableText API extension
internal-packages/workflow-designer-ui/src/ui/{editable-text.tsx,properties-panel.tsx}
Added inputClassName and buttonClassName props to EditableText and threaded them through className compositions.
Dependency & license cleanup
internal-packages/workflow-designer-ui/package.json, docs/packages-license.md
Removed pluralize and @types/pluralize from package.json; removed two package license entries and updated MIT counts.
Removed model eligibility hook
internal-packages/workflow-designer-ui/src/editor/lib/use-model-eligibility.ts
Deleted useModelEligibility hook and its export.
Workspace-tour image updates
internal-packages/workflow-designer-ui/src/editor/workspace-tour/workspace-tour.tsx
Replaced inline img usages with Next.js Image and added width/height attributes.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ImageGenPanel
    participant PromptPanel
    participant GenerationPanel
    participant OverlayManager

    User->>ImageGenPanel: Open image-generation node
    ImageGenPanel->>PromptPanel: render(with onExpand)
    ImageGenPanel->>GenerationPanel: render(with onExpand)

    User->>PromptPanel: Click expand
    PromptPanel->>OverlayManager: open PromptEditor overlay
    OverlayManager->>User: show expanded PromptEditor

    User->>PromptPanel: Select model / edit prompt
    PromptPanel->>ImageGenPanel: update node model/data (disconnect invalid connections)

    User->>GenerationPanel: Click Generate or Expand
    GenerationPanel->>OverlayManager: open Generation overlay
    OverlayManager->>User: show expanded Generation view

    User->>OverlayManager: Close overlay
    OverlayManager->>ImageGenPanel: sync live prompt/state back
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • image-generation panel refactor (overlay state sync, generate flow, connection cleanup)
  • new hooks (resize/scroll observers) for memory leaks and correctness
  • useWorkflowDesigner additions (deleteNode) wired across many panels
  • removal of InputPanel and source utilities (ensure no regressions where inputs previously used)
  • GenerationView/lightbox/thumbnail interactions and path concatenation

Possibly related issues

Possibly related PRs

Suggested reviewers

  • kaochannel154

Poem

🐰 I hopped through headers, changed some style and light,
Overlays unfurled to edit prompts by night,
Thumbnails gleam, lightbox hums, CTA stars in view,
Models pruned and panels tidy — a fresh UI brew,
Hop, click, generate — this rabbit cheers for you! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "Redesign image generation node with unified headers and Next.js Image migration" clearly and specifically relates to the main changes in the changeset. The title accurately captures the two primary themes: the redesign of the image generation node interface with unified panel headers (NodePanelHeader introduced across node types) and the modernization of image handling through Next.js Image component migration. The title is concise, readable, and provides sufficient information for a teammate scanning history to understand the core changes without being overly detailed or vague.
Description Check ✅ Passed The pull request description comprehensively follows the template structure with all required sections present and thoroughly filled. The Overview section summarizes the PR purpose, the Changes section provides detailed categorization of UI/UX improvements, technical enhancements, and code cleanup with a dedicated breaking changes subsection, the Testing section describes verification steps across multiple scenarios, and the Review Notes and Related Issues sections cover additional context. The description is well-organized, specific about implementation details, and clearly communicates the rationale and impact of the changes without significant gaps.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/image-generation-panel-layout

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25cd72f and 2ad3c15.

📒 Files selected for processing (2)
  • internal-packages/workflow-designer-ui/src/editor/properties-panel/vector-store/index.tsx (2 hunks)
  • internal-packages/workflow-designer-ui/src/editor/properties-panel/web-page-node-properties-panel/index.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal-packages/workflow-designer-ui/src/editor/properties-panel/vector-store/index.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development-guide.mdc)

**/*.{ts,tsx}: Use Biome for formatting with tab indentation and double quotes
Follow organized imports pattern (enabled in biome.json)
Use TypeScript for type safety; avoid any types
Use Next.js patterns for web applications
Use async/await for asynchronous code rather than promises
Error handling: use try/catch blocks and propagate errors appropriately
Use kebab-case for all filenames (e.g., user-profile.ts)
Use camelCase for variables, functions, and methods (e.g., userEmail)
Use prefixes like is, has, can, should for boolean variables and functions for clarity
Use verbs or verb phrases that clearly indicate purpose for function naming (e.g., calculateTotalPrice(), not process())

If breaking changes are introduced in new AI SDK versions, update code to accommodate those changes

**/*.{ts,tsx}: Avoid using the any type in TypeScript
Use async/await for asynchronous code and include proper error handling
Variables and functions should be camelCase
Boolean variables and functions should use is/has/can/should prefixes where appropriate
Function names should clearly indicate their purpose

Files:

  • internal-packages/workflow-designer-ui/src/editor/properties-panel/web-page-node-properties-panel/index.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/development-guide.mdc)

**/*.tsx: Use functional components with React hooks
Use PascalCase for React components and classes (e.g., UserProfile)

**/*.tsx: React components should use React hooks and Next.js patterns
Component identifiers (names) should be PascalCase

Files:

  • internal-packages/workflow-designer-ui/src/editor/properties-panel/web-page-node-properties-panel/index.tsx
**/*

📄 CodeRabbit inference engine (.cursor/rules/naming-guide.mdc)

All filenames should use kebab-case (lowercase with hyphens)

Files:

  • internal-packages/workflow-designer-ui/src/editor/properties-panel/web-page-node-properties-panel/index.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/naming-guide.mdc)

**/*.{js,jsx,ts,tsx}: React components and classes should use PascalCase
Variables, functions, and methods should use camelCase
Use verbs or verb phrases for function names; names should clearly indicate what the function does; avoid ambiguous names that could lead to misuse
Use nouns or noun phrases for variable names; names should describe what the variable represents; avoid single-letter variables except in very short scopes
Use prefixes like 'is', 'has', 'can', 'should' for both variables and functions returning boolean values; make the true/false meaning clear

Files:

  • internal-packages/workflow-designer-ui/src/editor/properties-panel/web-page-node-properties-panel/index.tsx
🧬 Code graph analysis (1)
internal-packages/workflow-designer-ui/src/editor/properties-panel/web-page-node-properties-panel/index.tsx (1)
internal-packages/workflow-designer-ui/src/editor/properties-panel/ui/node-panel-header.tsx (1)
  • NodePanelHeader (8-73)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (2)
internal-packages/workflow-designer-ui/src/editor/properties-panel/web-page-node-properties-panel/index.tsx (2)

18-19: LGTM! Import changes align with the header refactor.

The import updates correctly bring in NodePanelHeader while preserving the necessary PropertiesPanelRoot and PropertiesPanelContent imports. The pattern is consistent with the unified header refactor across other node panels.


246-251: LGTM! Header integration is correct and addresses past feedback.

The NodePanelHeader usage is properly implemented:

  • The onChangeName callback correctly updates node data
  • The docsUrl value matches the suggestion from the past review comment
  • All props are correctly typed per the NodePanelHeader interface

Optional: The explicit onDelete={undefined} could be omitted since the prop is optional, but being explicit is valid and may be intentional for consistency across panels.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-merge-for-open-source
Copy link

qodo-merge-for-open-source bot commented Oct 24, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
- [ ] Create ticket/issue <!-- /create_ticket --create_ticket=true -->

</details></td></tr>
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
No custom compliance provided

Follow the guide to enable custom compliance check.

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-merge-for-open-source
Copy link

qodo-merge-for-open-source bot commented Oct 24, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Simplify the complex overlay implementation

The current overlay implementation uses complex JavaScript with useEffect and
ResizeObserver for positioning. This should be refactored to a simpler, more
maintainable CSS-based solution for layout and animation.

Examples:

internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx [173-236]
	useEffect(() => {
		const el = generateCtaRef.current;
		if (!el) {
			setOverlayBottomPx(0);
			return;
		}
		const update = () => setOverlayBottomPx(el.offsetHeight || 0);
		update();
		const ro = new ResizeObserver(update);
		ro.observe(el);

 ... (clipped 54 lines)
internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx [287-384]
					<div
						role="dialog"
						aria-modal="true"
						aria-label="Expanded prompt editor"
						className={`absolute left-0 right-0 z-20 flex flex-col bg-background rounded-[8px] transition-all duration-300 ease-out ${
							isPromptExpanded
								? "opacity-100 scale-y-100 pointer-events-auto"
								: "opacity-0 scale-y-0 pointer-events-none"
						}`}
						style={{

 ... (clipped 88 lines)

Solution Walkthrough:

Before:

function ImageGenerationNodePropertiesPanel({ node }) {
  const [_overlayBottomPx, setOverlayBottomPx] = useState(0);
  const [promptTopPx, setPromptTopPx] = useState(0);
  // ... more state for positioning

  // useEffect to calculate bottom position based on another element's height
  useEffect(() => {
    const el = generateCtaRef.current;
    const ro = new ResizeObserver(() => setOverlayBottomPx(el.offsetHeight));
    ro.observe(el);
    // ... + window resize listener
  }, []);

  // useEffect to calculate transform-origin based on element's position
  useEffect(() => {
    const el = promptEditorRef.current;
    const ro = new ResizeObserver(() => {
      // ... complex getBoundingClientRect logic ...
      setPromptTopPx(...);
    });
    ro.observe(el);
    // ... + window resize and scroll listeners
  }, []);

  // ... another similar useEffect for generation panel ...

  return (
    <div
      className="absolute ..."
      style={{
        top: 0,
        bottom: _overlayBottomPx,
        transformOrigin: `center ${promptTopPx}px`,
      }}
    >
      {/* Expanded prompt overlay content */}
    </div>
  );
}

After:

/* CSS */
.panel-content {
  display: flex;
  flex-direction: column;
}
.scroll-area {
  flex: 1;
  min-height: 0;
  position: relative; /* New positioning context for overlay */
}
.overlay {
  position: absolute;
  inset: 0; /* Fills the parent .scroll-area */
  transform-origin: top center; /* Simplified transform origin */
}

/* Component */
function ImageGenerationNodePropertiesPanel({ node }) {
  // No more JS-based position calculations
  // const [_overlayBottomPx, setOverlayBottomPx] = useState(0);
  // const [promptTopPx, setPromptTopPx] = useState(0);
  // ... all related useEffects are removed

  return (
    <div className="panel-content">
      <div className="scroll-area">
        {/* ... scrollable content ... */}
        <div className="overlay ...">
          {/* Expanded prompt overlay content */}
        </div>
      </div>
      <div className="cta-button-container">
        {/* ... CTA button ... */}
      </div>
    </div>
  );
}
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a complex and brittle implementation for the new overlays in ImageGenerationNodePropertiesPanel that relies heavily on JavaScript for layout, which could be simplified and made more robust using modern CSS.

Medium
Possible issue
Use consistent node deletion logic
Suggestion Impact:The commit replaced the soft delete call with deleteNode(node.id) in the panel's onDelete handler, matching the suggested change.

code diff:

@@ -242,7 +242,7 @@
 				node={node}
 				onChangeName={(name) => updateNodeData(node, { name })}
 				docsUrl="https://docs.giselles.ai/en/glossary/image-node"
-				onDelete={() => updateNodeData(node, { archived: true })}
+				onDelete={() => deleteNode(node.id)}
 			/>

In the ImageGenerationNodePropertiesPanel, change the delete action from a soft
delete (updateNodeData(node, { archived: true })) to a hard delete
(deleteNode(node.id)) to match the behavior of other node panels.

internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx [241-246]

 <NodePanelHeader
 	node={node}
 	onChangeName={(name) => updateNodeData(node, { name })}
 	docsUrl="https://docs.giselles.ai/en/glossary/image-node"
-	onDelete={() => updateNodeData(node, { archived: true })}
+	onDelete={() => deleteNode(node.id)}
 />

[Suggestion processed]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies an inconsistency where this node uses a soft delete (archived: true) while all other nodes in the PR use a hard delete (deleteNode), improving behavioral consistency.

Medium
Learned
best practice
Guard DOM API usage
Suggestion Impact:The commit removed the in-component ResizeObserver and DOM measurement effects and replaced them with custom hooks (useOverlayBottom, useElementTopPx) likely encapsulating the guarded logic. This change addresses the suggestion by moving and presumably guarding the DOM API usage, rather than implementing the exact inline guards.

code diff:

-				}
-			}
-		},
-		[node, data.connections, data.nodes, deleteConnection],
-	);
+
+	const _overlayBottomPx = useOverlayBottom(generateCtaRef);
+	const promptTopPx = useElementTopPx(promptEditorRef);
+	const generationTopPx = useElementTopPx(generationPanelRef);
+	const livePrompt = useLivePrompt(node.id);
 
 	useKeyboardShortcuts({
 		onGenerate: () => {
@@ -170,71 +93,6 @@
 		error,
 	]);
 
-	useEffect(() => {
-		const el = generateCtaRef.current;
-		if (!el) {
-			setOverlayBottomPx(0);
-			return;
-		}
-		const update = () => setOverlayBottomPx(el.offsetHeight || 0);
-		update();
-		const ro = new ResizeObserver(update);
-		ro.observe(el);
-		window.addEventListener("resize", update);
-		return () => {
-			ro.disconnect();
-			window.removeEventListener("resize", update);
-		};
-	}, []);
-
-	useEffect(() => {
-		const el = promptEditorRef.current;
-		if (!el) {
-			setPromptTopPx(0);
-			return;
-		}
-		const update = () => {
-			const rect = el.getBoundingClientRect();
-			const container = el.closest(".relative");
-			const containerRect = container?.getBoundingClientRect();
-			setPromptTopPx(containerRect ? rect.top - containerRect.top : 0);
-		};
-		update();
-		const ro = new ResizeObserver(update);
-		ro.observe(el);
-		window.addEventListener("resize", update);
-		window.addEventListener("scroll", update, true);
-		return () => {
-			ro.disconnect();
-			window.removeEventListener("resize", update);
-			window.removeEventListener("scroll", update, true);
-		};
-	}, []);
-
-	useEffect(() => {
-		const el = generationPanelRef.current;
-		if (!el) {
-			setGenerationTopPx(0);
-			return;
-		}
-		const update = () => {
-			const rect = el.getBoundingClientRect();
-			const container = el.closest(".relative");
-			const containerRect = container?.getBoundingClientRect();
-			setGenerationTopPx(containerRect ? rect.top - containerRect.top : 0);
-		};
-		update();
-		const ro = new ResizeObserver(update);
-		ro.observe(el);
-		window.addEventListener("resize", update);
-		window.addEventListener("scroll", update, true);
-		return () => {
-			ro.disconnect();
-			window.removeEventListener("resize", update);
-			window.removeEventListener("scroll", update, true);
-		};
-	}, []);

Add feature detection and null checks for ResizeObserver and closest container
to prevent runtime errors in unsupported environments. Fallback to safe defaults
when APIs are unavailable.

internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx [190-212]

 useEffect(() => {
 	const el = promptEditorRef.current;
 	if (!el) {
 		setPromptTopPx(0);
 		return;
 	}
 	const update = () => {
-		const rect = el.getBoundingClientRect();
-		const container = el.closest(".relative");
-		const containerRect = container?.getBoundingClientRect();
-		setPromptTopPx(containerRect ? rect.top - containerRect.top : 0);
+		const rect = el.getBoundingClientRect?.();
+		const container = typeof el.closest === "function" ? el.closest(".relative") : null;
+		const containerRect = container && "getBoundingClientRect" in container ? container.getBoundingClientRect() : undefined;
+		setPromptTopPx(rect && containerRect ? rect.top - containerRect.top : 0);
 	};
 	update();
-	const ro = new ResizeObserver(update);
-	ro.observe(el);
+	const hasRO = typeof ResizeObserver !== "undefined";
+	const ro = hasRO ? new ResizeObserver(update) : undefined;
+	ro?.observe?.(el);
 	window.addEventListener("resize", update);
 	window.addEventListener("scroll", update, true);
 	return () => {
-		ro.disconnect();
+		ro?.disconnect?.();
 		window.removeEventListener("resize", update);
 		window.removeEventListener("scroll", update, true);
 	};
 }, []);

[Suggestion processed]

Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Strengthen input/state guards to avoid crashes and edge-case failures (guard against missing DOM APIs like ResizeObserver and Element.closest).

Low
Check portal target existence
Suggestion Impact:The portal-based lightbox code was refactored to use a Lightbox component instead of directly calling createPortal. This indirectly addresses the concern by removing the in-place portal creation and thus avoiding passing document.body when unavailable.

code diff:

 			{/* Image Viewer Overlay */}
-			{lightboxImage &&
-				typeof document !== "undefined" &&
-				createPortal(
-					<div
-						role="dialog"
-						aria-label="Image viewer"
-						className="fixed inset-0 bg-background/95 z-[9999] flex items-center justify-center cursor-pointer"
-						onClick={() => setLightboxImage(null)}
-						onKeyDown={(e) => {
-							if (e.key === "Enter" || e.key === " ") {
-								setLightboxImage(null);
-							}
-						}}
-					>
-						<button
-							type="button"
-							onClick={(e) => {
-								e.stopPropagation();
-								setLightboxImage(null);
-							}}
-							className="absolute top-4 right-4 z-10 p-3 text-white hover:bg-bg/20 rounded-full transition-colors"
-							title="Close (ESC)"
-						>
-							<X className="w-6 h-6" />
-						</button>
-						<img
-							src={lightboxImage}
-							alt="Generated content"
-							className="max-w-[95vw] max-h-[95vh] object-contain"
-						/>
-					</div>,
-					document.body,
-				)}
+			{lightboxImage && <Lightbox src={lightboxImage} onClose={() => setLightboxImage(null)} />}
 		</>

Also verify document.body exists before passing as portal target to avoid
SSR/runtime errors; fall back to inline render or null.

internal-packages/workflow-designer-ui/src/ui/generation-view.tsx [289-348]

 {lightboxImage &&
 	typeof document !== "undefined" &&
-	createPortal(
+	document.body
+	? createPortal(
 		<div
 			role="dialog"
 			aria-label="Image viewer"
 			className="fixed inset-0 bg-background/95 z-[9999] flex items-center justify-center cursor-pointer"
 			onClick={() => setLightboxImage(null)}
 			onKeyDown={(e) => {
 				if (e.key === "Enter" || e.key === " ") {
 					setLightboxImage(null);
 				}
 			}}
 			tabIndex={0}
 		>
 			...
 		</div>,
 		document.body,
-	)}
+	  )
+	: null}

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 5

__

Why:
Relevant best practice - Strengthen input/state guards to avoid crashes and edge-case failures (check for document and window before using portals or global objects).

Low
  • Update

cursor[bot]

This comment was marked as outdated.

@giselles-ai

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/sources/use-connected-sources.ts (1)

121-121: Fix typo in error message.

The error message contains a typo: "vectore" should be "vector".

Apply this diff to fix the typo:

-						throw new Error("vectore store can not be connected");
+						throw new Error("vector store can not be connected");
internal-packages/workflow-designer-ui/src/editor/properties-panel/action-node-properties-panel/index.tsx (1)

38-41: Fix sourceNodes filter to exclude undefined

Current filter keeps undefined (undefined !== null is true), risking runtime errors.

- sourceNodes: connectedInputs
-   .map((input) => input.connectedOutput?.node as Node)
-   .filter((node) => node !== null),
+ sourceNodes: connectedInputs
+   .map((input) => input.connectedOutput?.node)
+   .filter((n): n is Node => Boolean(n)),
internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx (1)

50-50: Compute nodes from connectedSources for the expanded PromptEditor.

The connectedSources are retrieved but not transformed into the nodes array needed by the expanded PromptEditor. Following the pattern in prompt-panel.tsx (lines 44-50), add:

 const { all: connectedSources } = useConnectedSources(node);
+const nodes = useMemo(
+  () =>
+    connectedSources
+      .map((source) => Node.safeParse(source.node))
+      .map((parse) => (parse.success ? parse.data : null))
+      .filter((data) => data !== null),
+  [connectedSources],
+);

Then pass nodes to the expanded PromptEditor at line 303.

♻️ Duplicate comments (4)
internal-packages/workflow-designer-ui/src/editor/properties-panel/ui/generate-cta-button.tsx (1)

26-27: Remove stray console.log from onClick

Debug logging should not ship.

- onClick={() => {
-   console.log("GenerateCtaButton clicked!");
-   onClick();
- }}
+ onClick={() => {
+   onClick();
+}}
internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx (3)

102-125: Remove unused dead code.

The _disconnectInvalidConnections function is never called. This functionality has been moved to prompt-panel.tsx (lines 58-79).


135-170: Remove debug console.log statements.

Console.log statements at lines 136, 138, 143, and 148 should be removed before merging to production.


303-319: Missing nodes prop in expanded PromptEditor.

The expanded PromptEditor (line 303) is missing the nodes prop that the inline PromptPanel provides. Looking at prompt-panel.tsx (lines 152-166), the inline editor includes:

nodes={nodes}

This prop enables proper text editor functionality with connected sources. Add it for consistency:

 <PromptEditor
   key={`expanded-${editorVersion}-${node.id}`}
   value={livePrompt ?? node.content.prompt}
   onValueChange={(value) => {
     updateNodeDataContent(node, { prompt: value });
   }}
+  nodes={nodes}
   connectedSources={connectedSources.map(({ node, output }) => ({
     node,
     output,
   }))}
🧹 Nitpick comments (31)
internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/sources/use-connected-sources.ts (1)

1-22: Type definition improves type safety.

The new local ConnectedSource<T> generic type with explicit connection property is a good improvement for type safety and clarity.

Note: A similar ConnectedSource type exists in packages/text-editor/src/react/component.tsx (lines 123-126) but without the connection property. Consider consolidating these types into a shared location to avoid confusion and maintain consistency across the codebase.

docs/packages-license.md (1)

4-24: Add generation metadata for reproducibility

Recommend adding a short header with generation timestamp, command, and commit (e.g., “Generated by: pnpm license-finder at on ”). Improves auditability and CI drift detection.

internal-packages/ui/components/model-picker.tsx (2)

17-18: Remove unused disabledReason property or document its purpose.

The disabledReason property is defined in the type but never used in the component (line 214 explicitly notes it's not shown). This creates unnecessary confusion.

Either remove it from the type definition:

 		disabled?: boolean;
-		disabledReason?: string;
 	}>;

Or, if it's intentionally kept for future use or backward compatibility, add a JSDoc comment explaining this:

 		disabled?: boolean;
+		/** Reserved for future use. Currently not displayed in UI. */
 		disabledReason?: string;
 	}>;

180-221: Consider adding an empty state message.

When search or filter results in no matching models, the dropdown displays blank content without feedback to the user. Adding an empty state message would improve the user experience.

Add an empty state check before rendering the model list:

 						<div className="mt-[0px] mx-[4px] pr-[4px]">
 							<div className="flex flex-col gap-[6px] max-h-[220px] overflow-y-auto">
+								{filteredGroups.every((g) => g.models.length === 0) ? (
+									<div className="text-center py-8 text-text-muted text-[14px]">
+										No models found
+									</div>
+								) : (
 								{filteredGroups.map((group) => (
 									<div key={group.provider} className="flex flex-col gap-[4px]">
 										{group.label ? (
 											<div className="text-[11px] text-secondary px-[4px]">
 												{group.label}
 											</div>
 										) : null}
 										{group.models.map((m) => {
 											const isDisabled = Boolean(m.disabled);
 											return (
 												<button
 													key={`${group.provider}-${m.id}`}
 													type="button"
 													onClick={() => {
 														if (isDisabled) return;
 														onSelect(group.provider, m.id);
 														setOpen(false);
 													}}
 													aria-disabled={isDisabled}
 													className={clsx(
 														"flex gap-[12px] items-center p-[4px] rounded-[4px] text-left",
 														isDisabled
 															? "opacity-50 cursor-not-allowed"
 															: "hover:bg-white/5 focus:bg-white/5 cursor-pointer",
 													)}
 												>
 													<div className="flex items-center gap-[8px]">
 														<p className="text-[14px] text-left text-nowrap">
 															{m.label || m.id}
 														</p>
 														{m.badge}
 													</div>
 													{/* disabled reason text intentionally not shown (design: gray-out only) */}
 												</button>
 											);
 										})}
 									</div>
 								))}
+								)}
 							</div>
 						</div>
packages/giselle/src/engine/generations/generate-image.ts (2)

179-205: Consider extracting common image processing logic.

The three provider functions share nearly identical image processing logic (detect type, generate ID, store, create Image object). Consider extracting this into a shared helper function to reduce duplication.

Example extraction:

async function processGeneratedImages({
	images,
	context,
	runningGeneration,
}: {
	images: Array<{ uint8Array: Uint8Array; base64: string }>;
	context: GiselleEngineContext;
	runningGeneration: RunningGeneration;
}): Promise<Image[]> {
	const contents = await Promise.all(
		images.map(async (image) => {
			const imageType = detectImageType(image.uint8Array);
			if (imageType === null) {
				return null;
			}
			const id = ImageId.generate();
			const filename = `${id}.${imageType.ext}`;

			await setGeneratedImage({
				storage: context.storage,
				generation: runningGeneration,
				generatedImage: {
					uint8Array: image.uint8Array,
					base64: image.base64,
				} satisfies GeneratedImageData,
				generatedImageFilename: filename,
			});

			return {
				id,
				contentType: imageType.contentType,
				filename,
				pathname: `generations/${runningGeneration.id}/generated-images/${filename}`,
			} satisfies Image;
		}),
	);
	return contents.filter((result) => result !== null);
}

Then each provider function can call:

const contents = await processGeneratedImages({
	images: result.images,
	context,
	runningGeneration,
});

Also applies to: 262-288, 330-356


152-163: Consider adding separators when concatenating message content.

The prompt extraction logic concatenates text content from multiple messages without separators. This could result in run-together text (e.g., "HelloWorld" instead of "Hello World"), potentially affecting prompt quality.

Consider adding separators:

 let prompt = "";
 for (const message of messages) {
 	if (!Array.isArray(message.content)) {
 		continue;
 	}
 	for (const content of message.content) {
 		if (content.type !== "text") {
 			continue;
 		}
-		prompt += content.text;
+		prompt += (prompt ? " " : "") + content.text;
 	}
 }

Or extract this logic to a shared helper function to reduce duplication.

Also applies to: 232-243

internal-packages/workflow-designer-ui/src/ui/constants.ts (2)

1-1: Consider camelCase + export a helper type for consumers.

To align with our TS naming guideline (camelCase for vars), consider thumbHeight. Also export a size key type to avoid magic strings at call sites.

-export const THUMB_HEIGHT = { sm: 220, md: 260 } as const;
+export const thumbHeight = { sm: 220, md: 260 } as const;
+export type ThumbSize = keyof typeof thumbHeight;

2-3: Remove commented-out exports or move them to design tokens.

Commented code adds noise and can drift. Either delete or re‑introduce as live tokens in a dedicated design tokens module.

internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/models/fal.tsx (1)

29-52: LGTM – consistent with SettingRow pattern.

Layout and a11y look good (label ↔ htmlFor/id). If Select supports generics, you can drop the cast by typing the option union.

- onValueChange={(value) =>
+ onValueChange={(value: FalLanguageModelData["configurations"]["size"]) =>
    updateConfiguration({ size: value })
 }
internal-packages/workflow-designer-ui/src/ui/hooks.ts (3)

3-4: Import React types explicitly.

Avoid referencing the global React namespace for types; import the type to keep TS happy across configs.

-import { useEffect, useState } from "react";
+import { useEffect, useState, type RefObject } from "react";

Then change parameter types to RefObject<HTMLElement>.


13-34: Handle ref element swaps; reduce initial layout flicker.

Current effect depends on ref identity, not the underlying ref.current. If the element changes, observers won’t reattach. Also, measure synchronously to avoid a frame of 0.

-export function useOverlayBottom(ref: React.RefObject<HTMLElement>) {
+export function useOverlayBottom(ref: RefObject<HTMLElement>) {
   const [overlayBottomPx, setOverlayBottomPx] = useState(0);

-  useEffect(() => {
+  useEffect(() => {
     const el = ref.current;
     if (!el) {
       setOverlayBottomPx(0);
       return;
     }
-    const update = () => setOverlayBottomPx(el.offsetHeight || 0);
+    const update = () => setOverlayBottomPx(el.offsetHeight || 0);
     update();
     const ro = new ResizeObserver(update);
     ro.observe(el);
     window.addEventListener("resize", update);
     return () => {
       ro.disconnect();
       window.removeEventListener("resize", update);
     };
-  }, [ref]);
+  // Re-run when the concrete element changes
+  }, [ref.current]);

36-64: Broaden container fallback and throttle scroll/resize updates.

Relying on .closest(".relative") may fail if the container lacks that class. Also, unthrottled scroll/resize can be chatty.

-export function useElementTopPx(ref: React.RefObject<HTMLElement>) {
+export function useElementTopPx(ref: RefObject<HTMLElement>) {
   const [topPx, setTopPx] = useState(0);

   useEffect(() => {
     const el = ref.current;
     if (!el) {
       setTopPx(0);
       return;
     }
-    const update = () => {
+    let raf = 0;
+    const getContainerRect = () => {
+      const c = el.closest(".relative") as HTMLElement | null;
+      // fallback to offsetParent if no .relative ancestor
+      const fallback = (el.offsetParent as HTMLElement | null) ?? null;
+      return (c ?? fallback)?.getBoundingClientRect();
+    };
+    const updateSync = () => {
       const rect = el.getBoundingClientRect();
-      const container = el.closest(".relative") as HTMLElement | null;
-      const containerRect = container?.getBoundingClientRect();
+      const containerRect = getContainerRect();
       setTopPx(containerRect ? rect.top - containerRect.top : 0);
-    };
-    update();
-    const ro = new ResizeObserver(update);
+    };
+    const update = () => {
+      cancelAnimationFrame(raf);
+      raf = requestAnimationFrame(updateSync);
+    };
+    updateSync();
+    const ro = new ResizeObserver(updateSync);
     ro.observe(el);
     window.addEventListener("resize", update);
     window.addEventListener("scroll", update, true);
     return () => {
       ro.disconnect();
+      cancelAnimationFrame(raf);
       window.removeEventListener("resize", update);
       window.removeEventListener("scroll", update, true);
     };
-  }, [ref]);
+  }, [ref.current]);
internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/generation-panel.tsx (1)

43-48: Remove unused _handleGenerate or wire it to the Empty state.

The handler is defined but never used. Either pass it into Empty (and add an onGenerate prop there) or delete the handler and the onClickGenerateButton prop from this component.

-const _handleGenerate = useCallback(() => {
-  if (onClickGenerateButton) {
-    onClickGenerateButton();
-  }
-}, [onClickGenerateButton]);
internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/prompt-panel.tsx (1)

46-53: Narrow the type after filter to avoid (Node | null)[].

Without a type guard, TS may retain null in the array, breaking nodes.map((n) => n.id).

-const nodes = useMemo(
-  () =>
-    connectedSources
-      .map((source) => Node.safeParse(source.node))
-      .map((parse) => (parse.success ? parse.data : null))
-      .filter((data) => data !== null),
-  [connectedSources],
-);
+const nodes = useMemo(
+  () =>
+    connectedSources
+      .map((source) => Node.safeParse(source.node))
+      .map((p) => (p.success ? p.data : null))
+      .filter((d): d is Node => d !== null),
+  [connectedSources],
+);
internal-packages/workflow-designer-ui/src/ui/image-card.tsx (3)

17-23: Add sizes prop to Next.js Image for better optimization.

The Next.js Image component should include a sizes prop to help the framework select appropriate image sizes for different viewports. Without it, Next.js may serve suboptimal image sizes.

Apply this diff:

 <Image
   src={src}
   alt="generated file"
   width={512}
   height={512}
+  sizes="(max-width: 768px) 100vw, 512px"
   className="h-full w-auto object-contain rounded-[8px]"
 />

19-19: Improve alt text descriptiveness.

The alt text "generated file" is generic. Consider making it more descriptive or allowing it to be customized via props for better accessibility.

For example:

-<Image
-  src={src}
-  alt="generated file"
+<Image
+  src={src}
+  alt="Generated image"

Or add an alt prop to the component if different descriptions are needed per usage.


26-42: Consider adding visible button hover states.

While the icons have hover effects (scale and translate), the buttons themselves lack visible hover/focus states, which may impact discoverability and accessibility.

Consider adding button hover styles:

 <button
   type="button"
   onClick={onDownload}
-  className="p-2"
+  className="p-2 rounded hover:bg-white/20 focus-visible:outline focus-visible:outline-2 focus-visible:outline-white transition-colors"
   title="Download"
 >
internal-packages/workflow-designer-ui/src/editor/properties-panel/ui/node-panel-header.tsx (2)

60-68: Consider adding delete confirmation.

The delete button triggers onDelete() immediately without confirmation. For a destructive action like node deletion, users might benefit from a confirmation dialog to prevent accidental deletions.

Consider adding a confirmation step:

import { useState } from "react";

// In component:
const [showConfirm, setShowConfirm] = useState(false);

// Replace direct onClick:
onClick={() => {
  if (window.confirm("Are you sure you want to delete this node?")) {
    onDelete();
  }
}}

Alternatively, implement a more sophisticated confirmation modal for better UX.


33-57: Consider extracting inline SVG to a reusable component.

The external link SVG icon is defined inline. If this icon is used elsewhere or might be reused, extracting it to a shared icon component would improve maintainability.

For example, create ExternalLinkIcon component:

// components/icons/external-link-icon.tsx
export function ExternalLinkIcon({ className }: { className?: string }) {
  return (
    <svg
      className={className}
      width="14"
      height="14"
      viewBox="0 0 24 24"
      // ... rest of SVG
    >
      {/* paths */}
    </svg>
  );
}

Then use: <ExternalLinkIcon className="size-[14px]" />

internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/models/openai.tsx (2)

72-75: Avoid iterator name shadowing imported symbols

Shadowing openaiImageQuality/openaiImageBackground/openaiImageModeration in map callbacks hurts readability and can confuse tooling.

- options={openaiImageQuality.options.map((openaiImageQuality) => ({
-   value: openaiImageQuality,
-   label: openaiImageQuality,
- }))} 
+ options={openaiImageQuality.options.map((opt) => ({
+   value: opt,
+   label: opt,
+ }))}

- options={openaiImageBackground.options.map((openaiImageBackground) => ({
-   value: openaiImageBackground,
-   label: openaiImageBackground,
- }))} 
+ options={openaiImageBackground.options.map((opt) => ({
+   value: opt,
+   label: opt,
+ }))}

- options={openaiImageModeration.options.map((openaiImageModeration) => ({
-   value: openaiImageModeration,
-   label: openaiImageModeration,
- }))} 
+ options={openaiImageModeration.options.map((opt) => ({
+   value: opt,
+   label: opt,
+ }))} 

Also applies to: 101-106, 132-137


23-31: Ensure label-for/id uniqueness per instance

Hard-coded ids ("size", "quality", etc.) can collide if multiple panels mount. Use useId to namespace.

+import { useId } from "react";
 ...
 export function OpenAIImageModelPanel({ languageModel, onModelChange }: { ... }) {
-  return (
+  const idBase = useId();
+  return (
     <div className="flex flex-col gap-[8px]">
       <div className="grid grid-cols-1 gap-[8px]">
         <SettingRow
-          label={<label htmlFor="size" className="text-text text-[14px]">Size</label>}
+          label={<label htmlFor={`${idBase}-size`} className="text-text text-[14px]">Size</label>}
         >
           <Select
-            id="size"
+            id={`${idBase}-size`}
 ...
-          label={<label htmlFor="quality" className="text-text text-[14px]">Quality</label>}
+          label={<label htmlFor={`${idBase}-quality`} className="text-text text-[14px]">Quality</label>}
           <Select
-            id="quality"
+            id={`${idBase}-quality`}
 ...
-          label={<label htmlFor="background" className="text-text text-[14px]">Background</label>}
+          label={<label htmlFor={`${idBase}-background`} className="text-text text-[14px]">Background</label>}
           <Select
-            id="background"
+            id={`${idBase}-background`}
 ...
-          label={<label htmlFor="moderation" className="text-text text-[14px]">Moderation</label>}
+          label={<label htmlFor={`${idBase}-moderation`} className="text-text text-[14px]">Moderation</label>}
           <Select
-            id="moderation"
+            id={`${idBase}-moderation`}

Also applies to: 52-60, 81-89, 112-120

internal-packages/workflow-designer-ui/src/ui/lightbox.tsx (1)

38-45: Prevent accidental close and add Image sizes/alt

Stop click bubbling on the content container and provide sizes for Next Image; allow custom alt.

-export function Lightbox({
-  src,
-  onClose,
-}: {
-  src: string;
-  onClose: () => void;
-}) {
+export function Lightbox({
+  src,
+  onClose,
+  alt = "Generated content",
+}: {
+  src: string;
+  onClose: () => void;
+  alt?: string;
+}) {
 ...
-  <div className="relative max-w-[95vw] max-h-[95vh] w-[95vw] h-[95vh]">
+  <div
+    className="relative max-w-[95vw] max-h-[95vh] w-[95vw] h-[95vh]"
+    onClick={(e) => e.stopPropagation()}
+  >
     <Image
       src={src}
-      alt="Generated content"
+      alt={alt}
       fill
+      sizes="95vw"
       style={{ objectFit: "contain" }}
     />
internal-packages/workflow-designer-ui/src/ui/editable-text.tsx (1)

43-47: Only emit changes when value actually changed; add Escape to cancel

Reduces unnecessary store updates and improves UX.

-const updateText = useCallback(() => {
-  setEdit(false);
-  const newTextValue = inputRef.current?.value ?? "";
-  onValueChange?.(newTextValue);
-}, [onValueChange]);
+const updateText = useCallback(() => {
+  setEdit(false);
+  const newTextValue = inputRef.current?.value ?? "";
+  const prev = text ?? "";
+  if (newTextValue !== prev) onValueChange?.(newTextValue);
+}, [onValueChange, text]);
 ...
 onKeyDown={(e) => {
   if (e.key === "Enter") {
     e.preventDefault();
     updateText();
   }
+  if (e.key === "Escape") {
+    e.preventDefault();
+    setEdit(false);
+    if (inputRef.current) inputRef.current.value = text ?? "";
+  }
 }}

Also applies to: 63-69

internal-packages/workflow-designer-ui/src/editor/properties-panel/web-page-node-properties-panel/index.tsx (2)

246-251: Omit undefined prop

Passing onDelete={undefined} is redundant; omit for clarity.

- <NodePanelHeader
-   node={node}
-   onChangeName={(name) => updateNodeData(node, { name })}
-   docsUrl="https://docs.giselles.ai/en/glossary/web-page-node"
-   onDelete={undefined}
- />
+ <NodePanelHeader
+   node={node}
+   onChangeName={(name) => updateNodeData(node, { name })}
+   docsUrl="https://docs.giselles.ai/en/glossary/web-page-node"
+ />

201-207: Propagate actual error message on fetch failure

Currently errorMessage is set to the URL, losing context. Capture the thrown error message.

-} catch (_err) {
+} catch (_err) {
+  const message =
+    _err instanceof Error ? _err.message : String(_err ?? "Unknown error");
   const failedWebPage: WebPage = {
     id: newWebPage.id,
     status: "failed",
     url: newWebPage.url,
-    errorMessage: newWebPage.url,
+    errorMessage: message,
   };
internal-packages/workflow-designer-ui/src/editor/properties-panel/ui/generate-cta-button.tsx (1)

23-37: Expose generating state to AT

Add aria-busy and better label when generating for screen readers.

-<button
+<button
   type="button"
   onClick={() => {
     onClick();
   }}
   disabled={!isGenerating && isEmpty}
+  aria-busy={isGenerating}
+  aria-live="polite"
+  aria-label={isGenerating ? "Stop generation" : (isEmpty ? emptyLabel : idleLabel)}

Also applies to: 43-49

internal-packages/workflow-designer-ui/src/editor/properties-panel/action-node-properties-panel/index.tsx (1)

63-67: Disable Run Action when inputs invalid

Prevents needless error flashes; handleClick can still guard.

- <Button type="button" onClick={handleClick} className="w-full">
+ <Button type="button" onClick={handleClick} className="w-full" disabled={!isValid}>
internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx (2)

172-236: Consider extracting repeated positioning logic into a custom hook.

Three similar useEffect blocks calculate overlay positions using ResizeObserver and window event listeners. This pattern could be extracted into a reusable hook (e.g., useElementPosition(ref)) to reduce duplication and improve maintainability.

Example:

function useElementTopPx(ref: RefObject<HTMLElement>) {
  const [topPx, setTopPx] = useState(0);
  useEffect(() => {
    const el = ref.current;
    if (!el) {
      setTopPx(0);
      return;
    }
    const update = () => {
      const rect = el.getBoundingClientRect();
      const container = el.closest(".relative");
      const containerRect = container?.getBoundingClientRect();
      setTopPx(containerRect ? rect.top - containerRect.top : 0);
    };
    update();
    const ro = new ResizeObserver(update);
    ro.observe(el);
    window.addEventListener("resize", update);
    window.addEventListener("scroll", update, true);
    return () => {
      ro.disconnect();
      window.removeEventListener("resize", update);
      window.removeEventListener("scroll", update, true);
    };
  }, []);
  return topPx;
}

286-343: Consider adding focus trap for overlay dialogs.

The expanded overlay dialogs (prompt and generation) have good ARIA attributes but might benefit from a focus trap to prevent keyboard navigation from escaping the modal. Consider using a library like focus-trap-react or ensuring Tab/Shift+Tab cycles within the dialog.

Also applies to: 346-383

internal-packages/workflow-designer-ui/src/editor/workspace-tour/workspace-tour.tsx (2)

561-567: Simplify arrow Image props to avoid redundancy.

Both arrow images specify width props that match the className values (w-[150px]), creating redundancy. For fixed-size decorative images, you can rely on the className for sizing or omit explicit dimensions when using static imports.

Apply this diff to simplify the step2Arrow usage:

 <Image
   src={step2Arrow}
   alt="Arrow pointing to toolbar"
-  width={150}
-  height={100}
   className="absolute bottom-[-100px] left-[calc(50%-200px)] translate-x-[-50%] z-[60] w-[150px] h-auto pointer-events-none arrow-animation"
 />

Apply this diff to simplify the step5Arrow usage:

 <Image
   src={step5Arrow}
   alt="Arrow pointing to tabs"
-  width={150}
-  height={100}
   className="absolute top-[-110px] left-[calc(50%-190px)] z-[60] w-[150px] h-auto pointer-events-none arrow-animation"
 />

Also applies to: 695-701


744-750: Consider omitting explicit width/height for consistency.

Similar to the TourCard image, the docsImage uses responsive sizing classes (w-full h-full object-cover) while also specifying explicit width={800} and height={300}. For static imports, these explicit dimensions are optional and may not match the actual image file dimensions.

Apply this diff for consistency and to let Next.js handle dimensions:

 <Image
   src={docsImage}
   alt="Tour step tutorial"
-  width={800}
-  height={300}
   className="w-full h-full object-cover object-top"
 />

Comment on lines +189 to +217
{group.models.map((m) => {
const isDisabled = Boolean(m.disabled);
return (
<button
key={`${group.provider}-${m.id}`}
type="button"
onClick={() => {
if (isDisabled) return;
onSelect(group.provider, m.id);
setOpen(false);
}}
aria-disabled={isDisabled}
className={clsx(
"flex gap-[12px] items-center p-[4px] rounded-[4px] text-left",
isDisabled
? "opacity-50 cursor-not-allowed"
: "hover:bg-white/5 focus:bg-white/5 cursor-pointer",
)}
>
<div className="flex items-center gap-[8px]">
<p className="text-[14px] text-left text-nowrap">
{m.label || m.id}
</p>
{m.badge}
</div>
{/* disabled reason text intentionally not shown (design: gray-out only) */}
</button>
);
})}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use disabled attribute instead of aria-disabled for better accessibility.

The current implementation uses aria-disabled while still keeping the button focusable and in the keyboard tab order. When isDisabled is true, keyboard users can focus the button but activation does nothing, creating a confusing experience.

According to WAI-ARIA best practices, aria-disabled should only be used when an element needs to remain in the tab order for context. For truly non-interactive items, the HTML disabled attribute is more appropriate as it removes the element from the tab order entirely.

Apply this diff to use the native disabled attribute:

 										{group.models.map((m) => {
 											const isDisabled = Boolean(m.disabled);
 											return (
 												<button
 													key={`${group.provider}-${m.id}`}
 													type="button"
+													disabled={isDisabled}
 													onClick={() => {
-														if (isDisabled) return;
 														onSelect(group.provider, m.id);
 														setOpen(false);
 													}}
-													aria-disabled={isDisabled}
 													className={clsx(
 														"flex gap-[12px] items-center p-[4px] rounded-[4px] text-left",
 														isDisabled
-															? "opacity-50 cursor-not-allowed"
-															: "hover:bg-white/5 focus:bg-white/5 cursor-pointer",
+															? "opacity-50 cursor-not-allowed disabled:pointer-events-none"
+															: "hover:bg-white/5 focus:bg-white/5 cursor-pointer enabled:hover:bg-white/5",
 													)}
 												>

Note: If you need disabled items to remain in tab order for specific UX reasons, please document that decision and ensure proper keyboard event handling (Enter/Space keys) is implemented.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{group.models.map((m) => {
const isDisabled = Boolean(m.disabled);
return (
<button
key={`${group.provider}-${m.id}`}
type="button"
onClick={() => {
if (isDisabled) return;
onSelect(group.provider, m.id);
setOpen(false);
}}
aria-disabled={isDisabled}
className={clsx(
"flex gap-[12px] items-center p-[4px] rounded-[4px] text-left",
isDisabled
? "opacity-50 cursor-not-allowed"
: "hover:bg-white/5 focus:bg-white/5 cursor-pointer",
)}
>
<div className="flex items-center gap-[8px]">
<p className="text-[14px] text-left text-nowrap">
{m.label || m.id}
</p>
{m.badge}
</div>
{/* disabled reason text intentionally not shown (design: gray-out only) */}
</button>
);
})}
{group.models.map((m) => {
const isDisabled = Boolean(m.disabled);
return (
<button
key={`${group.provider}-${m.id}`}
type="button"
disabled={isDisabled}
onClick={() => {
onSelect(group.provider, m.id);
setOpen(false);
}}
className={clsx(
"flex gap-[12px] items-center p-[4px] rounded-[4px] text-left",
isDisabled
? "opacity-50 cursor-not-allowed disabled:pointer-events-none"
: "hover:bg-white/5 focus:bg-white/5 cursor-pointer enabled:hover:bg-white/5",
)}
>
<div className="flex items-center gap-[8px]">
<p className="text-[14px] text-left text-nowrap">
{m.label || m.id}
</p>
{m.badge}
</div>
{/* disabled reason text intentionally not shown (design: gray-out only) */}
</button>
);
})}
🤖 Prompt for AI Agents
internal-packages/ui/components/model-picker.tsx lines 189-217: replace the use
of aria-disabled with the native disabled attribute on the <button> when
isDisabled is true so the button is removed from tab order and behaves natively
for keyboard users; keep the early return in onClick (or rely on the disabled
attribute) and adjust className logic so disabled state still applies opacity
and cursor-not-allowed while removing hover/focus styles for that state; ensure
no other code relies on the element remaining focusable (if it must remain
focusable, document and implement explicit keyboard handling instead).

Comment on lines 26 to 36
export function GenerationPanel({
node,
onClickGenerateButton,
onExpand,
isExpanded,
}: {
node: ImageGenerationNode;
onClickGenerateButton?: () => void;
onExpand?: () => void;
isExpanded?: boolean;
}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify: Is the generate CTA intentionally removed from the empty state?

onClickGenerateButton is no longer used and <Empty /> has no CTA. If generation is now triggered solely from elsewhere (e.g., a global GenerateCtaButton), all call sites should drop onClickGenerateButton. Otherwise, reintroduce the CTA.

Also applies to: 49-51


🏁 Script executed:

#!/bin/bash
# Find usages of GenerationPanel and check if onClickGenerateButton is still passed.
rg -nP --type=ts --type=tsx -C2 '\bGenerationPanel\s*\('
rg -nP --type=ts --type=tsx -C2 'onClickGenerateButton='

Length of output: 55


</script>

🏁 Script executed:

#!/bin/bash
# Search for GenerationPanel usages without --type flag
rg -nP '\bGenerationPanel\s*\(' -g '*.ts' -g '*.tsx' -C2

echo "---"

# Search for onClickGenerateButton parameter passing
rg -nP 'onClickGenerateButton=' -g '*.ts' -g '*.tsx' -C2

echo "---"

# Inspect the file to see lines 49-51
cat internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/generation-panel.tsx | sed -n '1,60p'

Length of output: 8156


</script>

🏁 Script executed:

#!/bin/bash
# Get full file content to see where _handleGenerate is used
wc -l internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/generation-panel.tsx

echo "---Full File---"
cat internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/generation-panel.tsx

echo "---"
# Search for _handleGenerate usage
rg '_handleGenerate' internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/generation-panel.tsx -n

echo "---"
# Check GenerationView component definition
fd GenerationView.tsx -g '*.tsx' | head -5

Length of output: 3004


Remove unused onClickGenerateButton callback or add CTA button in Empty state

The onClickGenerateButton parameter is still being passed by all call sites (generateImage, generateText, generate), but the _handleGenerate callback defined at line 43 is never invoked. The Empty() component (lines 13–22) has no CTA button, and neither does the rendered GenerationView. Either:

  1. Remove onClickGenerateButton from the function signature and all call sites, or
  2. Add a button in the Empty component that calls _handleGenerate() to trigger generation
🤖 Prompt for AI Agents
internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/generation-panel.tsx
lines 26-36: the onClickGenerateButton prop is passed from callers but never
used because the Empty view has no CTA; add a visible CTA button in the Empty()
component that calls the existing _handleGenerate (or invokes
onClickGenerateButton if you prefer using props) so generation actually
triggers, and ensure the button is accessible (label/aria) and styled
consistently with other panel buttons; alternatively, if you decide not to
expose a CTA, remove onClickGenerateButton from this component and all call
sites to avoid dead code.

Comment on lines +16 to +26
<div
role="dialog"
aria-label="Image viewer"
className="fixed inset-0 bg-background/95 z-[9999] flex items-center justify-center cursor-pointer"
onClick={onClose}
onKeyDown={(e) => {
if (e.key === "Escape" || e.key === "Enter" || e.key === " ") {
onClose();
}
}}
>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Modal a11y: add focus management and aria-modal

onKeyDown on a non-focusable div won’t fire reliably; also missing aria-modal. Focus the overlay and listen on window for Escape.

-import { createPortal } from "react-dom";
+import { createPortal } from "react-dom";
+import { useEffect, useRef } from "react";
 ...
 export function Lightbox({ src, onClose }: { src: string; onClose: () => void; }) {
   if (typeof document === "undefined") return null;
+  const overlayRef = useRef<HTMLDivElement>(null);
+  useEffect(() => {
+    overlayRef.current?.focus();
+    const onKey = (e: KeyboardEvent) => {
+      if (e.key === "Escape") onClose();
+    };
+    window.addEventListener("keydown", onKey);
+    return () => window.removeEventListener("keydown", onKey);
+  }, [onClose]);
   return createPortal(
-    <div
+    <div
       role="dialog"
+      aria-modal="true"
+      tabIndex={-1}
+      ref={overlayRef}
       aria-label="Image viewer"
       className="fixed inset-0 bg-background/95 z-[9999] flex items-center justify-center cursor-pointer"
       onClick={onClose}
-      onKeyDown={(e) => {
-        if (e.key === "Escape" || e.key === "Enter" || e.key === " ") {
-          onClose();
-        }
-      }}
     >

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In internal-packages/workflow-designer-ui/src/ui/lightbox.tsx around lines 16 to
26, the overlay div is not focusable and lacks aria-modal, and its onKeyDown
won't fire reliably; make the overlay focusable (add tabIndex={-1} or 0), set
aria-modal="true", move keyboard Escape handling to a window keydown listener
(mounted on open, removed on cleanup) to reliably catch Escape, keep or add a
key handler on the overlay for Enter/Space if you want activation via keyboard,
and implement focus management by saving document.activeElement before opening,
programmatically focusing the overlay when opened, and restoring focus when
closed.

- Remove console.log debugging statements
- Refactor to use useOverlayBottom and useElementTopPx hooks
- Remove unused variables: _uiState, _models, _disconnectInvalidConnections
- Remove unused imports: ImageGenerationLanguageModelData, isSupportedConnection, language models, useMemo, useModelEligibility, deleteConnection
- Fix TypeScript types in hooks to accept null refs

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx (1)

162-178: Missing required nodes prop in expanded PromptEditor.

The expanded PromptEditor is missing the nodes prop that is provided in the regular PromptPanel (see prompt-panel.tsx lines 30-188 in relevant snippets). This prop is required for proper text editor functionality with connected sources and could cause runtime errors or missing editor features.

Apply this diff to add the missing prop:

 		<div className="flex-1 min-h-0 flex flex-col overflow-hidden rounded-[8px] bg-background">
 			<PromptEditor
 				key={`expanded-${editorVersion}-${node.id}`}
 				value={livePrompt ?? node.content.prompt}
 				onValueChange={(value) => {
 					updateNodeDataContent(node, { prompt: value });
 				}}
+				nodes={connectedSources
+					.map((source) => source.node)
+					.filter((n): n is NonNullable<typeof n> => n !== null)}
 				connectedSources={connectedSources.map(({ node, output }) => ({
 					node,
 					output,
 				}))}

Alternatively, extract the nodes computation from PromptPanel and reuse it:

const nodes = useMemo(
	() =>
		connectedSources
			.map((source) => Node.safeParse(source.node))
			.map((parse) => (parse.success ? parse.data : null))
			.filter((data) => data !== null),
	[connectedSources],
);

Then pass it to both the regular and expanded editors.

🧹 Nitpick comments (3)
internal-packages/workflow-designer-ui/src/editor/properties-panel/ui/generate-cta-button.tsx (1)

1-63: LGTM! Clean implementation with proper state handling and styling.

The component is well-structured with appropriate TypeScript typing, follows coding guidelines (PascalCase component name, camelCase for props/variables), and implements correct logic for the disabled state and label switching. The inline animations and conditional shortcut hint are well executed.

Note: The past review comment flagging a debug console.log statement at lines 25-26 has been resolved—the current code correctly wires onClick and disabled props without any debug artifacts.

Optional enhancement: The keyboard shortcut hint currently displays ⌘ + Enter (Mac-specific). Consider detecting the user's OS and showing Ctrl + Enter on Windows/Linux for a more universal experience, though this is not essential for the current implementation.

internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx (2)

50-50: Misleading variable naming convention.

The _overlayBottomPx variable uses an underscore prefix, which typically indicates an unused or private variable. However, this variable is actively used in lines 156 and 216 for overlay positioning. Remove the underscore prefix to follow proper naming conventions.

Apply this diff:

-	const _overlayBottomPx = useOverlayBottom(generateCtaRef);
+	const overlayBottomPx = useOverlayBottom(generateCtaRef);

And update the usage sites:

 		style={{
 			top: 0,
-			bottom: _overlayBottomPx,
+			bottom: overlayBottomPx,
 			paddingBottom: 12,

144-242: Consider extracting common overlay pattern.

The expanded prompt overlay (lines 144-202) and expanded generation overlay (lines 204-242) share nearly identical structure: dialog wrapper, content area, minimize button, and backdrop. Consider extracting a reusable ExpandableOverlay component to reduce duplication and improve maintainability.

Example structure:

<ExpandableOverlay
  isExpanded={isPromptExpanded}
  onClose={() => {
    setIsPromptExpanded(false);
    setEditorVersion((v) => v + 1);
  }}
  transformOriginY={promptTopPx}
  bottomOffset={overlayBottomPx}
  ariaLabel="Expanded prompt editor"
>
  {/* Content */}
</ExpandableOverlay>

This would centralize the transition logic, ARIA attributes, and minimize/backdrop button patterns.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5712004 and 858ff1e.

📒 Files selected for processing (3)
  • internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx (4 hunks)
  • internal-packages/workflow-designer-ui/src/editor/properties-panel/ui/generate-cta-button.tsx (1 hunks)
  • internal-packages/workflow-designer-ui/src/ui/hooks.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal-packages/workflow-designer-ui/src/ui/hooks.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development-guide.mdc)

**/*.{ts,tsx}: Use Biome for formatting with tab indentation and double quotes
Follow organized imports pattern (enabled in biome.json)
Use TypeScript for type safety; avoid any types
Use Next.js patterns for web applications
Use async/await for asynchronous code rather than promises
Error handling: use try/catch blocks and propagate errors appropriately
Use kebab-case for all filenames (e.g., user-profile.ts)
Use camelCase for variables, functions, and methods (e.g., userEmail)
Use prefixes like is, has, can, should for boolean variables and functions for clarity
Use verbs or verb phrases that clearly indicate purpose for function naming (e.g., calculateTotalPrice(), not process())

If breaking changes are introduced in new AI SDK versions, update code to accommodate those changes

**/*.{ts,tsx}: Avoid using the any type in TypeScript
Use async/await for asynchronous code and include proper error handling
Variables and functions should be camelCase
Boolean variables and functions should use is/has/can/should prefixes where appropriate
Function names should clearly indicate their purpose

Files:

  • internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx
  • internal-packages/workflow-designer-ui/src/editor/properties-panel/ui/generate-cta-button.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/development-guide.mdc)

**/*.tsx: Use functional components with React hooks
Use PascalCase for React components and classes (e.g., UserProfile)

**/*.tsx: React components should use React hooks and Next.js patterns
Component identifiers (names) should be PascalCase

Files:

  • internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx
  • internal-packages/workflow-designer-ui/src/editor/properties-panel/ui/generate-cta-button.tsx
**/*

📄 CodeRabbit inference engine (.cursor/rules/naming-guide.mdc)

All filenames should use kebab-case (lowercase with hyphens)

Files:

  • internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx
  • internal-packages/workflow-designer-ui/src/editor/properties-panel/ui/generate-cta-button.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/naming-guide.mdc)

**/*.{js,jsx,ts,tsx}: React components and classes should use PascalCase
Variables, functions, and methods should use camelCase
Use verbs or verb phrases for function names; names should clearly indicate what the function does; avoid ambiguous names that could lead to misuse
Use nouns or noun phrases for variable names; names should describe what the variable represents; avoid single-letter variables except in very short scopes
Use prefixes like 'is', 'has', 'can', 'should' for both variables and functions returning boolean values; make the true/false meaning clear

Files:

  • internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx
  • internal-packages/workflow-designer-ui/src/editor/properties-panel/ui/generate-cta-button.tsx
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
PR: giselles-ai/giselle#0
File: .cursor/rules/edit-workspace-tour.mdc:0-0
Timestamp: 2025-07-21T22:29:07.662Z
Learning: Applies to internal-packages/workflow-designer-ui/src/editor/workspace-tour/workspace-tour.tsx : Update the `imageSrc` prop in the appropriate step component in `workspace-tour.tsx` to use a new image
Learnt from: CR
PR: giselles-ai/giselle#0
File: .cursor/rules/edit-workspace-tour.mdc:0-0
Timestamp: 2025-07-21T22:29:07.662Z
Learning: Applies to internal-packages/workflow-designer-ui/src/editor/workspace-tour/workspace-tour.tsx : Update imports and references to images in `workspace-tour.tsx` when changing tour visuals
Learnt from: CR
PR: giselles-ai/giselle#0
File: .cursor/rules/edit-workspace-tour.mdc:0-0
Timestamp: 2025-07-21T22:29:07.662Z
Learning: Applies to internal-packages/workflow-designer-ui/src/editor/workspace-tour/workspace-tour.tsx : Import new images at the top of `workspace-tour.tsx` when adding or changing step visuals
Learnt from: CR
PR: giselles-ai/giselle#0
File: .cursor/rules/edit-workspace-tour.mdc:0-0
Timestamp: 2025-07-21T22:29:07.662Z
Learning: Applies to internal-packages/workflow-designer-ui/src/editor/workspace-tour/assets/* : Replace GIFs and images in the `assets/` directory to change tour visuals
📚 Learning: 2025-07-21T22:29:07.662Z
Learnt from: CR
PR: giselles-ai/giselle#0
File: .cursor/rules/edit-workspace-tour.mdc:0-0
Timestamp: 2025-07-21T22:29:07.662Z
Learning: Applies to internal-packages/workflow-designer-ui/src/editor/workspace-tour/workspace-tour.tsx : Update the `imageSrc` prop in the appropriate step component in `workspace-tour.tsx` to use a new image

Applied to files:

  • internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx
📚 Learning: 2025-10-24T01:27:17.214Z
Learnt from: CR
PR: giselles-ai/giselle#0
File: internal-packages/workflow-designer-ui/src/editor/properties-panel/trigger-node-properties-panel/providers/github-trigger/AGENTS.md:0-0
Timestamp: 2025-10-24T01:27:17.214Z
Learning: Applies to internal-packages/workflow-designer-ui/src/editor/properties-panel/trigger-node-properties-panel/providers/github-trigger/github-trigger-properties-panel.tsx : Wrap state updates that transition between reconfiguring/configured in startTransition for consistent UI updates

Applied to files:

  • internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx
📚 Learning: 2025-07-21T22:29:07.662Z
Learnt from: CR
PR: giselles-ai/giselle#0
File: .cursor/rules/edit-workspace-tour.mdc:0-0
Timestamp: 2025-07-21T22:29:07.662Z
Learning: Applies to internal-packages/workflow-designer-ui/src/editor/workspace-tour/workspace-tour.tsx : Update imports and references to images in `workspace-tour.tsx` when changing tour visuals

Applied to files:

  • internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx
📚 Learning: 2025-09-02T05:50:06.317Z
Learnt from: CR
PR: giselles-ai/giselle#0
File: internal-packages/workflow-designer-ui/src/new-editor/AGENTS.md:0-0
Timestamp: 2025-09-02T05:50:06.317Z
Learning: Applies to internal-packages/workflow-designer-ui/src/new-editor/**/*.tsx : Avoid passing large props through multiple levels; prefer local selection from the store at leaf components.

Applied to files:

  • internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx
📚 Learning: 2025-09-02T05:50:06.317Z
Learnt from: CR
PR: giselles-ai/giselle#0
File: internal-packages/workflow-designer-ui/src/new-editor/AGENTS.md:0-0
Timestamp: 2025-09-02T05:50:06.317Z
Learning: Applies to internal-packages/workflow-designer-ui/src/new-editor/components/node/node.tsx : In Node component, select exactly the node data, derived connection ids, and UI flags needed; provide a custom equality function combining strict reference checks with shallow for arrays.

Applied to files:

  • internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx
📚 Learning: 2025-10-24T01:27:17.214Z
Learnt from: CR
PR: giselles-ai/giselle#0
File: internal-packages/workflow-designer-ui/src/editor/properties-panel/trigger-node-properties-panel/providers/github-trigger/AGENTS.md:0-0
Timestamp: 2025-10-24T01:27:17.214Z
Learning: Applies to internal-packages/workflow-designer-ui/src/editor/properties-panel/trigger-node-properties-panel/providers/github-trigger/github-trigger-properties-panel.tsx : Model node state as configured, reconfiguring, and implicit unconfigured; render GitHubTriggerConfiguredView for configured, GitHubTriggerReconfiguringView for reconfiguring, and integration-branching UI for unconfigured

Applied to files:

  • internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx
📚 Learning: 2025-09-02T05:50:06.317Z
Learnt from: CR
PR: giselles-ai/giselle#0
File: internal-packages/workflow-designer-ui/src/new-editor/AGENTS.md:0-0
Timestamp: 2025-09-02T05:50:06.317Z
Learning: Applies to internal-packages/workflow-designer-ui/src/new-editor/**/*.ts : Lift actions into the store (e.g., expose updateNode) and invoke them from components performing mutations.

Applied to files:

  • internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx
🧬 Code graph analysis (1)
internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx (8)
packages/giselle/src/react/flow/hooks/use-workflow-designer.ts (1)
  • useWorkflowDesigner (5-13)
internal-packages/workflow-designer-ui/src/ui/hooks.ts (3)
  • useOverlayBottom (13-34)
  • useElementTopPx (36-64)
  • useLivePrompt (6-11)
internal-packages/workflow-designer-ui/src/editor/properties-panel/ui/node-panel-header.tsx (1)
  • NodePanelHeader (8-73)
internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/prompt-panel.tsx (1)
  • PromptPanel (31-189)
internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/generation-panel.tsx (1)
  • GenerationPanel (26-93)
internal-packages/workflow-designer-ui/src/editor/properties-panel/ui/generate-cta-button.tsx (1)
  • GenerateCtaButton (5-63)
internal-packages/workflow-designer-ui/src/editor/lib/validate-prompt.ts (1)
  • isPromptEmpty (6-12)
internal-packages/ui/components/prompt-editor.tsx (1)
  • PromptEditor (28-97)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: codeql / languages (javascript) / Perform CodeQL for javascript
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (3)
internal-packages/workflow-designer-ui/src/editor/properties-panel/image-generation-node-properties-panel/index.tsx (3)

1-26: LGTM: Clean import organization.

The imports are well-organized and follow the project's conventions. All imported modules are used appropriately throughout the component.


63-94: LGTM: Well-structured generation callback.

The generateImage function properly validates usage limits and prompt content before triggering generation. Error handling via toasts and the dependencies array are correctly configured.


99-142: LGTM: Clean panel structure with proper state management.

The header integration with NodePanelHeader, the main content area layout, and the generate button are well-structured. The overlay expansion callbacks properly manage state transitions.

cursor[bot]

This comment was marked as outdated.

@kaochannel154
Copy link
Contributor Author

/pr-enrichment

@giselles-ai
Copy link

giselles-ai bot commented Oct 28, 2025

Finished running flow.

Step Status Updated(UTC)
1 Oct 28, 2025 12:36pm
2 Oct 28, 2025 12:39pm
3 Oct 28, 2025 12:40pm
4 Oct 28, 2025 12:40pm

@giselles-ai giselles-ai bot changed the title feat/image generation panel layout Redesign image generation node with unified headers and Next.js Image migration Oct 28, 2025
cursor[bot]

This comment was marked as outdated.

Copy link
Member

@shige shige left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! 🚀

@shige shige merged commit a96fb33 into main Oct 29, 2025
12 checks passed
@shige shige deleted the feat/image-generation-panel-layout branch October 29, 2025 05:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants