Skip to content

Conversation

kaochannel154
Copy link
Contributor

@kaochannel154 kaochannel154 commented Aug 25, 2025

User description

Summary

Improved navigation rail sidebar functionality with z-index fixes, external link indicators, working display type synchronization, and code maintainability enhancements. The sidebar menu now properly displays above other UI elements and provides seamless display type switching between list and carousel views.

Related Issue

N/A - UI/UX improvements and bug fixes based on user feedback

Changes

  • Fixed z-index issue: Changed dropdown menu z-index from z-10 to z-50 to display above form containers
  • Added external link icons: Display ExternalLink icons for Docs, Terms, Privacy & Cookies, and Homepage items in Help submenu
  • Implemented URL-based display type synchronization:
    • Replace local state management with URL search params (?view=carousel)
    • Enable seamless sync between sidebar Display type dialog and main content view
    • Support browser history and bookmarkable display states
  • Improved Help submenu layout: Adjusted width from 250px to 180px for better text-icon spacing
  • Enhanced code maintainability:
    • Extract HELP_ITEMS constant to module level
    • Unify MENU_ITEM_CLASS constant to reduce CSS duplication across 6 usage points
    • Add type safety with as const assertions

Testing

  • ✅ All existing tests pass (pnpm test)
  • ✅ Type checking successful (pnpm check-types)
  • ✅ Build and SDK compilation successful (pnpm build-sdk)
  • ✅ No unused dependencies detected (pnpm tidy)
  • Manual testing verified:
    • Sidebar dropdown menu displays above form containers
    • External link icons appear in Help submenu items
    • Display type changes in sidebar immediately reflect in main content
    • URL updates correctly (e.g., /stage?view=carousel)
    • State persists across page reloads

Other Information

  • Minimal diff approach: Only 1 line reduction (344→343 lines) in main file
  • No breaking changes: All existing functionality preserved
  • Performance improvements: Reduced memory usage through constant extraction and CSS deduplication
  • Browser compatibility: URL-based state management works across all modern browsers
  • The Display type feature now works end-to-end, resolving the previous state synchronization issue between NavigationRail and main content components

PR Type

Enhancement


Description

  • Implement URL-based display type synchronization between sidebar and main content

  • Add user plan information display in navigation rail

  • Replace navigation icons with custom chevron design

  • Enhance dropdown menu with external link indicators and improved styling


Diagram Walkthrough

flowchart LR
  A["URL Search Params"] --> B["useUIState Hook"]
  B --> C["Display Type State"]
  C --> D["Navigation Rail Menu"]
  D --> E["Main Content View"]
  F["User Data"] --> G["Plan Information"]
  G --> D
  H["Custom Icons"] --> D
Loading

File Walkthrough

Relevant files
Enhancement
12 files
query.ts
Add user plan information to sidebar data                               
+5/-0     
types.ts
Add planName field to user data interface                               
+1/-0     
style.css
Add CSS variables for stage sidebar styling                           
+3/-0     
use-ui-state.tsx
Replace local state with URL-based display type management
+14/-1   
layout.tsx
Update background color to use CSS variable                           
+1/-1     
loading.tsx
Update loading page background colors                                       
+2/-2     
menu-button.tsx
Update button styling with CSS variables                                 
+1/-1     
navigation-list-item.tsx
Update navigation item text colors                                             
+1/-1     
navigation-rail-collapsed.tsx
Replace panel icon with custom chevron                                     
+4/-3     
navigation-rail-expanded.tsx
Replace panel icon and update text styling                             
+7/-4     
navigation-rail-footer-menu.tsx
Complete dropdown menu rewrite with display type dialog   
+310/-61
simple-chevron-icons.tsx
Create custom chevron icons for navigation                             
+45/-0   

Summary by CodeRabbit

  • New Features

    • Display Type dialog (List vs Carousel) with URL-backed view state for shareable/bookmarkable views.
    • Expanded account menu: plan display, Account settings, Help, Lobby/Homepage links, and Sign out.
    • New sidebar chevron icons and plan label shown in the sidebar.
  • Style

    • Stage and loading backgrounds plus sidebar text/hover colors now use new theme variables.
  • UI

    • Settings dialog action buttons removed; dialog overlay stacking adjusted for mobile/desktop.

- Fix z-index issue: Change dropdown menu z-index from z-10 to z-50 to display above form containers
- Add external link icons: Display ExternalLink icons for Docs, Terms, Privacy & Cookies, and Homepage items
- Add Display type dialog: Implement display type selection dialog matching the settings UI design
- Improve Help submenu width: Adjust width from 250px to 180px for better text-icon spacing
- Fix dropdown menu state management: Add proper state handling to prevent conflicts between dropdown and dialog

The navigation rail menu now properly displays above other UI elements and provides
a consistent experience with external link indicators and display type configuration.
- Replace local state with URL search params for display type management
- Add useRouter and useSearchParams to NavigationRailFooterMenu for URL state control
- Update useUIState hook to read/write display type from URL parameters
- Enable seamless sync between sidebar Display type dialog and main content view
- Support browser history and bookmarkable display states (?view=carousel)
- Remove localStorage dependency in favor of URL state management

The display type selection now works correctly across all components,
with changes in the navigation rail immediately reflected in the main content area.
- Extract HELP_ITEMS constant to module level for better organization
- Unify MENU_ITEM_CLASS constant to reduce CSS class duplication across 6 usage points
- Add as const assertion for type safety on help items array
- Improve code readability and maintainability without UI changes

This refactoring reduces redundancy, improves consistency, and makes future styling
changes easier to implement by centralizing repeated CSS class definitions.
@kaochannel154 kaochannel154 self-assigned this Aug 25, 2025
@Copilot Copilot AI review requested due to automatic review settings August 25, 2025 09:07
@kaochannel154 kaochannel154 requested a review from shige as a code owner August 25, 2025 09:07
Copy link

changeset-bot bot commented Aug 25, 2025

⚠️ No Changeset found

Latest commit: 97dbced

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.

Copy link

giselles-ai bot commented Aug 25, 2025

Finished running flow.

Step Status Updated(UTC)
1 Aug 25, 2025 9:07am
2 Aug 25, 2025 9:08am
3 Aug 25, 2025 9:08am
4 Aug 25, 2025 9:09am

Copy link
Contributor

coderabbitai bot commented Aug 25, 2025

Walkthrough

URL-driven view state: the carousel/list view is now derived from the view query param. The navigation-rail footer menu was rebuilt with Radix primitives and a Display Type dialog. Stage theming moved to CSS variables; sidebar data now includes planName. Settings dialog action buttons were removed and dialog z-indexes increased.

Changes

Cohort / File(s) Summary
URL-driven view state
apps/studio.giselles.ai/app/stage/(top)/hooks/use-ui-state.tsx
isCarouselView moved from local state to being derived from useSearchParams; added internal setIsCarouselView that updates view query via router.push (no-scroll).
Footer menu overhaul & Display dialog
apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx
Replaced previous dropdown with Radix primitives and explicit open-state; added Display Type dialog (List vs Carousel radio cards); updated menu structure (account, help, links, sign out); wires URL-backed view state and shows planName in expanded trigger.
Sidebar data & types
apps/studio.giselles.ai/app/stage/query.ts, apps/studio.giselles.ai/app/stage/ui/navigation-rail/types.ts
Fetch currentTeam and evaluate isProPlan; return planName ("Pro plan"/"Free plan"); added `planName: string
Stage theming (CSS variables)
apps/studio.giselles.ai/app/stage/layout.tsx, apps/studio.giselles.ai/app/stage/loading.tsx, internal-packages/ui/style.css
Replaced hard-coded background classes with bg-[var(--color-stage-background)]; added CSS tokens --color-stage-sidebar-text, --color-stage-sidebar-text-hover, --color-stage-accent.
Navigation rail styling & icons
apps/studio.giselles.ai/app/stage/ui/navigation-rail/menu-button.tsx, apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-list-item.tsx, apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-collapsed.tsx, apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.tsx, apps/studio.giselles.ai/app/stage/ui/navigation-rail/simple-chevron-icons.tsx
Swapped muted/hover-bg classes for CSS variable-based text colors; replaced lucide chevrons with new SimpleChevronLeft/SimpleChevronRight icons; adjusted group-hover behavior and classnames; no control-flow changes.
Settings dialog & Dialog primitive
apps/studio.giselles.ai/app/stage/(top)/settings-dialog.tsx, internal-packages/ui/components/dialog.tsx
Removed mobile and desktop action buttons from SettingsDialog; increased Dialog overlay/content z-index to z-[9999]/z-[10000].

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Footer as NavigationRail Footer Menu
  participant Dialog as Display Type Dialog
  participant Hook as useUIState
  participant Router as Next.js Router
  participant UI as Stage View

  User->>Footer: open menu
  Footer->>Dialog: open "Display type"
  User->>Dialog: select "Carousel" or "List"
  Dialog->>Hook: setIsCarouselView(value)
  alt value == carousel
    Hook->>Router: push ?view=carousel (no-scroll)
  else
    Hook->>Router: push ? (remove view)
  end
  Router-->>Hook: updated searchParams
  Hook-->>UI: isCarouselView derived from URL
  UI-->>User: render selected view
Loading
sequenceDiagram
  autonumber
  participant Server as getSidebarData
  participant Teams as teams service
  Server->>Teams: fetchCurrentTeam()
  Teams-->>Server: currentTeam
  Server->>Teams: isProPlan(currentTeam)
  Teams-->>Server: boolean
  Server-->>Caller: sidebar data { ..., planName: "Pro plan"/"Free plan" }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • shige
  • toyamarinyon

Poem

A rabbit tweaks the query line,
hops between List and Carousel fine.
Chevrons shimmy, sidebar glows,
plans whisper Pro or Free as it goes.
Hop, click — the stage now feels divine. 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/sidebar-adjustments

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

vercel bot commented Aug 25, 2025

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

Project Deployment Preview Comments Updated (UTC)
giselle Ready Ready Preview Comment Aug 25, 2025 1:34pm

Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

URL Sync Logic

The hook now derives isCarouselView from useSearchParams and pushes updates on setter. Verify that components using this hook re-render when the search params change and that back/forward navigation stays consistent. Consider debouncing rapid toggles and ensuring no hydration mismatches between server and client.

import { useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";

export function useUIState() {
	const [isMobile, setIsMobile] = useState(false);
	const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
	const searchParams = useSearchParams();
	const router = useRouter();
	const isCarouselView = searchParams.get("view") === "carousel";

	useEffect(() => {
		const checkMobile = () => {
			setIsMobile(window.innerWidth < 768);
		};

		checkMobile();
		window.addEventListener("resize", checkMobile);

		return () => window.removeEventListener("resize", checkMobile);
	}, []);

	const setIsCarouselView = (value: boolean) => {
		const params = new URLSearchParams(searchParams.toString());
		if (value) {
			params.set("view", "carousel");
		} else {
			params.delete("view");
		}
		router.push(`?${params.toString()}`, { scroll: false });
	};

	return {
		isMobile,
		isCarouselView,
		setIsCarouselView,
Dropdown/Dialog State

Coordinating dropdownOpen with isDisplayDialogOpen can lead to tricky focus/closing behaviors. Validate that opening the Display dialog reliably closes the dropdown, focus is trapped in the dialog, and that ESC/blur restores the previous state without leaving the dropdown stuck closed or open.

import {
	Dialog,
	DialogContent,
	DialogFooter,
	DialogTitle,
} from "@giselle-internal/ui/dialog";
import { PopoverContent } from "@giselle-internal/ui/popover";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import clsx from "clsx/lite";
import { ChevronRight, ExternalLink, X } from "lucide-react";
import Link from "next/link";
import { useRouter, useSearchParams } from "next/navigation";
import { use, useCallback, useState } from "react";
import {
	Card,
	CardDescription,
	CardHeader,
	CardTitle,
} from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { cn } from "@/lib/utils";
import { AvatarImage } from "@/services/accounts/components/user-button/avatar-image";
import { SignOutButton } from "@/services/accounts/components/user-button/sign-out-button";
import { buttonVariants } from "../../../(main)/settings/components/button";
import type { NavigationRailState, UserDataForNavigationRail } from "./types";

const HELP_ITEMS = [
	{
		label: "Docs",
		href: "https://docs.giselles.ai/guides/introduction",
		external: true,
	},
	{
		label: "Terms",
		href: "https://giselles.ai/legal/terms",
		external: true,
	},
	{
		label: "Privacy & Cookies",
		href: "https://giselles.ai/legal/privacy",
		external: true,
	},
	{
		label: "Contact Us",
		href: "mailto:support@giselles.ai",
		external: true,
	},
] as const;

const MENU_ITEM_CLASS =
	"text-text outline-none cursor-pointer hover:bg-ghost-element-hover rounded-[4px] px-[8px] py-[6px] text-[14px]";

export function NavigationRailFooterMenu({
	user: userPromise,
	variant,
}: {
	user: Promise<UserDataForNavigationRail>;
	variant: NavigationRailState;
}) {
	const user = use(userPromise);
	const router = useRouter();
	const searchParams = useSearchParams();
	const isCarouselView = searchParams.get("view") === "carousel";
	const [isDisplayDialogOpen, setIsDisplayDialogOpen] = useState(false);
	const [dropdownOpen, setDropdownOpen] = useState(false);

	const setIsCarouselView = useCallback(
		(value: boolean) => {
			const params = new URLSearchParams(searchParams.toString());
			if (value) {
				params.set("view", "carousel");
			} else {
				params.delete("view");
			}
			router.push(`?${params.toString()}`, { scroll: false });
		},
		[router, searchParams],
	);

	return (
		<DropdownMenuPrimitive.Root
			open={dropdownOpen && !isDisplayDialogOpen}
			onOpenChange={(open) => {
				if (!isDisplayDialogOpen) {
					setDropdownOpen(open);
				}
			}}
		>
			<DropdownMenuPrimitive.Trigger asChild>
				<button
					className="w-full hover:bg-ghost-element-hover h-full rounded-md cursor-pointer outline-none p-1.5 flex items-center gap-2"
					type="button"
				>
					<div className="size-8 flex items-center justify-center shrink-0">
						<AvatarImage
							className="rounded-full"
							avatarUrl={user.avatarUrl ?? null}
							width={24}
							height={24}
							alt={user.displayName || user.email || "User"}
						/>
					</div>
					{variant === "expanded" && (
						<div className="flex flex-col min-w-0 flex-1 text-left">
							<p className="truncate text-text-muted text-sm">
								{user.displayName ?? user.email}
							</p>
							<p className="truncate text-[var(--color-stage-key)] text-[10px]">
								{user.planName}
							</p>
						</div>
					)}
				</button>
			</DropdownMenuPrimitive.Trigger>
			<DropdownMenuPrimitive.Portal>
				<DropdownMenuPrimitive.Content
					align={variant === "expanded" ? "center" : "start"}
					className={`z-50 ${
						variant === "expanded"
							? "w-[var(--radix-dropdown-menu-trigger-width)]"
							: ""
					}`}
				>
					<PopoverContent>
						{/* Account Settings */}
						<DropdownMenuPrimitive.Item
							className={`${MENU_ITEM_CLASS} flex items-center justify-between`}
							asChild
						>
							<Link href="/settings/account" className="w-full">
								Account settings
							</Link>
						</DropdownMenuPrimitive.Item>

						{/* Display type */}
						<DropdownMenuPrimitive.Item
							className={MENU_ITEM_CLASS}
							onClick={() => {
								setDropdownOpen(false);
								setIsDisplayDialogOpen(true);
							}}
						>
							Display type
						</DropdownMenuPrimitive.Item>

						{/* Help with Submenu */}
						<DropdownMenuPrimitive.Sub>
							<DropdownMenuPrimitive.SubTrigger
								className={`${MENU_ITEM_CLASS} flex items-center justify-between w-full`}
							>
								Help
								<ChevronRight className="w-3 h-3" />
							</DropdownMenuPrimitive.SubTrigger>
							<DropdownMenuPrimitive.Portal>
								<DropdownMenuPrimitive.SubContent
									className="z-50 w-[180px]"
									sideOffset={4}
								>
									<PopoverContent>
										{HELP_ITEMS.map((item) => (
											<DropdownMenuPrimitive.Item
												key={item.label}
												className={MENU_ITEM_CLASS}
												asChild
											>
												{item.external ? (
													<a
														href={item.href}
														target="_blank"
														rel="noopener"
														className="w-full flex items-center justify-between"
													>
														{item.label}
														<ExternalLink className="w-3 h-3" />
													</a>
												) : (
													<Link href={item.href} className="w-full block">
														{item.label}
													</Link>
												)}
											</DropdownMenuPrimitive.Item>
										))}
									</PopoverContent>
								</DropdownMenuPrimitive.SubContent>
							</DropdownMenuPrimitive.Portal>
						</DropdownMenuPrimitive.Sub>

						{/* Separator */}
						<DropdownMenuPrimitive.Separator className="h-px bg-white/10 my-1" />

						{/* Lobby */}
						<DropdownMenuPrimitive.Item className={MENU_ITEM_CLASS} asChild>
							<Link href="/apps" className="w-full block">
								Lobby
							</Link>
						</DropdownMenuPrimitive.Item>

						{/* Homepage */}
						<DropdownMenuPrimitive.Item className={MENU_ITEM_CLASS} asChild>
							<a
								href="https://giselles.ai"
								target="_blank"
								rel="noopener"
								className="w-full flex items-center justify-between"
							>
								Homepage
								<ExternalLink className="w-3 h-3" />
							</a>
						</DropdownMenuPrimitive.Item>

						{/* Separator */}
						<DropdownMenuPrimitive.Separator className="h-px bg-white/10 my-1" />

						{/* Logout */}
						<DropdownMenuPrimitive.Item className={MENU_ITEM_CLASS}>
							<SignOutButton className="text-[14px]">Log out</SignOutButton>
						</DropdownMenuPrimitive.Item>
					</PopoverContent>
				</DropdownMenuPrimitive.Content>
			</DropdownMenuPrimitive.Portal>

			{/* Display Type Dialog */}
			<Dialog open={isDisplayDialogOpen} onOpenChange={setIsDisplayDialogOpen}>
				<DialogContent>
					<div className="flex items-center justify-between mb-6">
						<DialogTitle className="text-[20px] font-medium text-white-400 tracking-tight font-sans">
							View Style
						</DialogTitle>
						<button
							type="button"
							onClick={() => setIsDisplayDialogOpen(false)}
							className="p-1 rounded-lg hover:bg-white/10 transition-colors"
						>
							<X className="w-5 h-5 text-white-400" />
						</button>
					</div>

					{/* View Type Selection */}
					<div className="mb-6">
						<Label className="text-white-800 font-medium text-[12px] leading-[20.4px] font-geist">
							Display Type
						</Label>
						<RadioGroup
							value={isCarouselView ? "carousel" : "list"}
							onValueChange={(value) => setIsCarouselView(value === "carousel")}
							className="grid grid-cols-2 gap-4 mt-2"
						>
							<Card
								className={clsx(
									"cursor-pointer border-[1px]",
									!isCarouselView ? "border-blue-500" : "border-white/10",
								)}
							>
								<label htmlFor="list">
									<CardHeader>
										<div className="flex flex-col gap-2">
											<CardTitle className="text-white-400 text-[16px] leading-[27.2px] tracking-normal font-sans">
												List
											</CardTitle>
											<div className="flex items-center mb-2">
												<RadioGroupItem
													value="list"
													id="list"
													className="text-blue-500 data-[state=checked]:border-[1.5px] data-[state=checked]:border-blue-500"
												/>
											</div>
											<CardDescription className="text-black-400 font-medium text-[12px] leading-[20.4px] font-geist">
												Simple vertical list
											</CardDescription>
										</div>
									</CardHeader>
								</label>
							</Card>
							<Card
								className={clsx(
									"cursor-pointer border-[1px]",
									isCarouselView ? "border-blue-500" : "border-white/10",
								)}
							>
								<label htmlFor="carousel">
									<CardHeader>
										<div className="flex flex-col gap-2">
											<CardTitle className="text-white-400 text-[16px] leading-[27.2px] tracking-normal font-sans">
												Carousel
											</CardTitle>
											<div className="flex items-center mb-2">
												<RadioGroupItem
													value="carousel"
													id="carousel"
													className="text-blue-500 data-[state=checked]:border-[1.5px] data-[state=checked]:border-blue-500"
												/>
											</div>
											<CardDescription className="text-black-400 font-medium text-[12px] leading-[20.4px] font-geist">
												Interactive circular layout
											</CardDescription>
										</div>
									</CardHeader>
								</label>
							</Card>
						</RadioGroup>
					</div>

					{/* Font Options */}
					<div className="mb-6">
						<label
							htmlFor="font-select"
							className="block text-white-400 text-sm font-medium mb-3"
						>
							Font
						</label>
						<select
							id="font-select"
							disabled
							className="w-full p-3 bg-white/5 border border-white/10 rounded-lg text-white-100 text-sm opacity-50 cursor-not-allowed"
						>
							<option className="bg-gray-900">Coming Soon</option>
						</select>
					</div>

					<DialogFooter>
						<div className="flex justify-end gap-x-3">
							<button
								type="button"
								onClick={() => setIsDisplayDialogOpen(false)}
								className={cn(buttonVariants({ variant: "link" }))}
							>
								Cancel
							</button>
							<button
								type="button"
								onClick={() => setIsDisplayDialogOpen(false)}
								className={cn(buttonVariants({ variant: "primary" }))}
							>
								Continue
							</button>
						</div>
					</DialogFooter>
				</DialogContent>
			</Dialog>
		</DropdownMenuPrimitive.Root>
	);
Plan Detection

isProPlan(currentTeam) is assumed to be synchronous and currentTeam non-null. Confirm behavior for unauthenticated users or missing team to avoid crashes and ensure a sensible default for planName.

import { fetchCurrentTeam, isProPlan } from "@/services/teams";
import { getAccountInfo } from "../(main)/settings/account/actions";

export async function getSidebarData() {
	const accountInfo = await getAccountInfo();
	const currentTeam = await fetchCurrentTeam();
	const isPro = isProPlan(currentTeam);

	return {
		displayName: accountInfo.displayName ?? undefined,
		email: accountInfo.email ?? undefined,
		avatarUrl: accountInfo.avatarUrl ?? undefined,
		planName: isPro ? "Pro plan" : "Free plan",
	};
}

This comment was marked as outdated.

Copy link

qodo-merge-for-open-source bot commented Aug 25, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Learned
best practice
Add safe service fallbacks

Wrap calls to external services in try/catch and return a safe fallback payload
when they fail. This prevents layout rendering from breaking if team or account
queries error.

apps/studio.giselles.ai/app/stage/query.ts [1-15]

 import { fetchCurrentTeam, isProPlan } from "@/services/teams";
 import { getAccountInfo } from "../(main)/settings/account/actions";
 
 export async function getSidebarData() {
-	const accountInfo = await getAccountInfo();
-	const currentTeam = await fetchCurrentTeam();
-	const isPro = isProPlan(currentTeam);
+	let accountInfo: { displayName?: string | null; email?: string | null; avatarUrl?: string | null } = {};
+	let currentTeam: unknown = null;
+	try {
+		accountInfo = await getAccountInfo();
+	} catch {}
+	try {
+		currentTeam = await fetchCurrentTeam();
+	} catch {}
+	let isPro = false;
+	try {
+		isPro = isProPlan(currentTeam as any);
+	} catch {}
 
 	return {
-		displayName: accountInfo.displayName ?? undefined,
-		email: accountInfo.email ?? undefined,
-		avatarUrl: accountInfo.avatarUrl ?? undefined,
+		displayName: accountInfo?.displayName ?? undefined,
+		email: accountInfo?.email ?? undefined,
+		avatarUrl: accountInfo?.avatarUrl ?? undefined,
 		planName: isPro ? "Pro plan" : "Free plan",
 	};
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Guard external service access with try/catch and provide safe fallbacks so UI doesn’t crash if network/services fail.

Low
Possible issue
Preserve path and avoid history spam

Avoid pushing a bare search string which drops the current pathname. Use
router.replace to prevent adding history entries for a UI-only toggle, and
include the current path to avoid unintended navigation. This prevents URL
resets and history bloat when toggling the view.

apps/studio.giselles.ai/app/stage/(top)/hooks/use-ui-state.tsx [9-30]

+const pathname = typeof window !== "undefined" ? window.location.pathname : "/";
 const isCarouselView = searchParams.get("view") === "carousel";
 ...
 const setIsCarouselView = (value: boolean) => {
 	const params = new URLSearchParams(searchParams.toString());
 	if (value) {
 		params.set("view", "carousel");
 	} else {
 		params.delete("view");
 	}
-	router.push(`?${params.toString()}`, { scroll: false });
+	router.replace(`${pathname}?${params.toString()}`, { scroll: false });
 };

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 5

__

Why: The suggestion to use router.replace is a good UX improvement to avoid polluting browser history, but its claim that the pathname is dropped is incorrect for next/navigation.

Low
Preserve path and replace history entry

Keep the current pathname when updating query params to prevent navigation to
the root and use replace to avoid polluting history for a non-navigational
setting. This mirrors the fix in your hook and ensures consistent behavior from
the menu as well.

apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx [68-79]

 const setIsCarouselView = useCallback(
 	(value: boolean) => {
 		const params = new URLSearchParams(searchParams.toString());
 		if (value) {
 			params.set("view", "carousel");
 		} else {
 			params.delete("view");
 		}
-		router.push(`?${params.toString()}`, { scroll: false });
+		const pathname = typeof window !== "undefined" ? window.location.pathname : "/";
+		router.replace(`${pathname}?${params.toString()}`, { scroll: false });
 	},
 	[router, searchParams],
 );
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: This suggestion correctly points out that router.replace is better for UX than router.push here, but its reasoning about the pathname being dropped is incorrect for next/navigation.

Low
  • Update

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: 4

🧹 Nitpick comments (18)
apps/studio.giselles.ai/app/stage/loading.tsx (1)

5-5: Good move to theme variables; consider dvh + reduced-motion for skeletons

  • Using var(--color-stage-background) is spot on. On mobile Safari, h-screen can cause viewport jump; consider min-h-[100dvh] to account for dynamic toolbars.
  • For motion-sensitive users, prefer motion-safe:animate-pulse over unconditional animate-pulse.
  • Optional a11y: mark the region as loading with aria-busy (and optionally aria-live="polite").

Apply:

-    <div className="flex h-screen bg-[var(--color-stage-background)]">
+    <div className="flex min-h-[100dvh] bg-[var(--color-stage-background)]" aria-busy="true" aria-live="polite">
-      <div className="w-[200px] bg-[var(--color-stage-background)] border-r border-white/10 animate-pulse">
+      <div className="w-[200px] bg-[var(--color-stage-background)] border-r border-white/10 motion-safe:animate-pulse">

Also applies to: 7-7

apps/studio.giselles.ai/app/stage/ui/navigation-rail/menu-button.tsx (1)

18-18: Adopts stage sidebar tokens — add focus-visible ring and allow aria-label passthrough

The color tokens look good. Two improvements:

  • Add a visible keyboard focus outline using the new stage key color.
  • The component currently blocks aria-label (and most native button props). Allowing passthrough props improves a11y for icon-only buttons.

Apply focus styles:

-        className={clsx(
-          "group size-8 text-[var(--color-stage-sidebar-text)] hover:text-[var(--color-stage-sidebar-text-hover)] transition-colors rounded flex items-center justify-center",
-          className,
-        )}
+        className={clsx(
+          "group size-8 text-[var(--color-stage-sidebar-text)] hover:text-[var(--color-stage-sidebar-text-hover)] transition-colors rounded flex items-center justify-center",
+          "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-stage-key)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-stage-background)]",
+          className,
+        )}

For prop passthrough (outside the selected lines), a minimal revision:

export function MenuButton({
  onClick,
  children,
  className,
  ...rest
}: React.ButtonHTMLAttributes<HTMLButtonElement>) {
  return (
    <button
      type="button"
      onClick={onClick}
      className={/* same clsx as above */}
      {...rest} // enables aria-label, title, disabled, etc.
    >
      {children}
    </button>
  );
}
apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-collapsed.tsx (2)

29-30: Icon color theming looks correct; consider keyboard parity for hover swap

Using --color-stage-sidebar-text(-hover) is consistent with the new tokens. Optional: mirror the hover swap for keyboard focus so non-pointer users see the same cue.

Apply:

- <GiselleIcon className="size-6 text-[var(--color-stage-sidebar-text-hover)] stroke-1 group-hover:hidden" />
- <PanelLeftOpenIcon className="size-6 text-[var(--color-stage-sidebar-text)] stroke-1 hidden group-hover:block" />
+ <GiselleIcon className="size-6 text-[var(--color-stage-sidebar-text-hover)] stroke-1 group-hover:hidden group-focus-within:hidden" />
+ <PanelLeftOpenIcon className="size-6 text-[var(--color-stage-sidebar-text)] stroke-1 hidden group-hover:block group-focus-within:block" />

48-49: Unify fallback skeleton background with stage theme

The Suspense fallback still uses bg-black-800. For consistency with the rest of the PR (and custom themes), use bg-[var(--color-stage-background)].

Apply:

- <div className="w-full bg-black-800 animate-pulse h-full rounded-md" />
+ <div className="w-full bg-[var(--color-stage-background)] motion-safe:animate-pulse h-full rounded-md" />
apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.tsx (4)

27-27: Verify group-hover:hidden actually has a .group ancestor.

The GiselleIcon uses group-hover:hidden, but I don't see a .group ancestor in this header block. If no ancestor has group, the hover style won’t trigger.

Proposed minimal fix (add .group to the wrapper on Line 25):

-<div className="flex items-center justify-start w-full">
+<div className="group flex items-center justify-start w-full">

29-31: Use the base text token for non-interactive label; reserve “hover” token for hover states.

Stage label uses text-[var(--color-stage-sidebar-text-hover)]. If the label isn’t interactive, prefer the base token to match the theming model and only shift to the hover token on interactive elements.

-<p className="text-[var(--color-stage-sidebar-text-hover)] text-[13px] font-semibold">
+<p className="text-[var(--color-stage-sidebar-text)] text-[13px] font-semibold">

34-37: Cursor affordance: use cursor-pointer for a clickable collapse button.

cursor-w-resize suggests drag-resizing. If the action is a click-to-collapse, cursor-pointer is clearer.

- className="cursor-w-resize"
+ className="cursor-pointer"

34-39: Add an accessible label to the collapse button.

Improves screen-reader UX.

- <MenuButton
-   onClick={() => onCollapseButtonClick()}
-   className="cursor-w-resize"
- >
+ <MenuButton
+   onClick={() => onCollapseButtonClick()}
+   className="cursor-pointer"
+   aria-label="Collapse sidebar"
+ >
apps/studio.giselles.ai/app/stage/query.ts (1)

6-8: Handle team fetch failures gracefully and fetch in parallel to reduce latency.

  • If fetchCurrentTeam() throws, the current code would crash this call path.
  • Fetch account/team concurrently to shave a round-trip.
 export async function getSidebarData() {
-  const accountInfo = await getAccountInfo();
-  const currentTeam = await fetchCurrentTeam();
-  const isPro = isProPlan(currentTeam);
+  const [accountInfo, currentTeam] = await Promise.all([
+    getAccountInfo(),
+    // Tolerate errors: treat plan as unknown instead of failing the whole request.
+    fetchCurrentTeam().catch(() => null as const),
+  ]);
+  const isPro = currentTeam ? isProPlan(currentTeam) : false;
 
   return {
     displayName: accountInfo.displayName ?? undefined,
     email: accountInfo.email ?? undefined,
     avatarUrl: accountInfo.avatarUrl ?? undefined,
-    planName: isPro ? "Pro plan" : "Free plan",
+    // If team is unavailable, leave undefined to avoid misreporting the plan.
+    planName: currentTeam ? (isPro ? "Pro plan" : "Free plan") : undefined,
   };
 }

Also applies to: 13-13

apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-list-item.tsx (1)

11-19: Add accessible name when collapsed (icon-only).

When variant === "collapsed", the link renders only an icon; without an accessible name, screen readers may announce an unlabeled link.

 <Link
   href={props.href}
-  className="text-[var(--color-stage-sidebar-text)] text-sm flex items-center py-0.5 hover:text-[var(--color-stage-sidebar-text-hover)] rounded-lg px-1"
+  className="text-[var(--color-stage-sidebar-text)] text-sm flex items-center py-0.5 hover:text-[var(--color-stage-sidebar-text-hover)] rounded-lg px-1"
+  aria-label={props.variant === "collapsed" ? props.label : undefined}
+  title={props.variant === "collapsed" ? props.label : undefined}
 >

Optional: add a focus-visible style for keyboard users.

- className="text-[var(--color-stage-sidebar-text)] text-sm flex items-center py-0.5 hover:text-[var(--color-stage-sidebar-text-hover)] rounded-lg px-1"
+ className="text-[var(--color-stage-sidebar-text)] text-sm flex items-center py-0.5 hover:text-[var(--color-stage-sidebar-text-hover)] rounded-lg px-1 focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--color-stage-key)] focus-visible:outline-offset-2"
apps/studio.giselles.ai/app/stage/ui/navigation-rail/types.ts (1)

7-8: Narrow planName to a union for type-safety.

Since only “Pro plan”/“Free plan” are produced today, consider a tagged type to avoid typos and ease future additions.

+export type PlanName = "Pro plan" | "Free plan";
+
 export interface UserDataForNavigationRail {
   displayName: string | undefined;
   email: string | undefined;
   avatarUrl: string | undefined;
-  planName: string | undefined;
+  planName: PlanName | undefined;
 }
apps/studio.giselles.ai/app/stage/(top)/hooks/use-ui-state.tsx (2)

22-30: Preserve pathname when updating query params.

Pushing only ?query relies on relative resolution; using usePathname() avoids edge cases (nested paths, basePath). Also keeps intent explicit.

-import { useRouter, useSearchParams } from "next/navigation";
+import { usePathname, useRouter, useSearchParams } from "next/navigation";
@@
- const setIsCarouselView = (value: boolean) => {
+ const setIsCarouselView = (value: boolean) => {
   const params = new URLSearchParams(searchParams.toString());
   if (value) {
     params.set("view", "carousel");
   } else {
     params.delete("view");
   }
-  router.push(`?${params.toString()}`, { scroll: false });
+  const pathname = usePathname();
+  router.push(`${pathname}?${params.toString()}`, { scroll: false });
 };

22-30: Optional: avoid fragment-only updates creating duplicate history entries.

If users toggle frequently, consider router.replace to avoid history spam. You’re intentionally supporting history, so keep push if that’s desired.

- router.push(`${pathname}?${params.toString()}`, { scroll: false });
+ router.push(`${pathname}?${params.toString()}`, { scroll: false }); // or router.replace(...)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (5)

68-79: Preserve path/basePath when pushing query params

Using router.push("?…") relies on implicit resolution against the current URL. Using usePathname() is more robust (keeps basePath, locale, and nested routes intact).

Apply these diffs:

  1. Imports
- import { useRouter, useSearchParams } from "next/navigation";
+ import { usePathname, useRouter, useSearchParams } from "next/navigation";
  1. Initialize pathname
- const router = useRouter();
+ const router = useRouter();
+ const pathname = usePathname();
  1. Push with pathname and update deps
- router.push(`?${params.toString()}`, { scroll: false });
+ router.push(`${pathname}?${params.toString()}`, { scroll: false });
- [router, searchParams],
+ [pathname, router, searchParams],

167-176: Add noreferrer to external links for privacy and consistency

You already use rel="noopener". Append noreferrer to prevent referrer leakage to external destinations.

Apply these diffs:

- rel="noopener"
+ rel="noopener noreferrer"

Also applies to: 200-209


216-218: Avoid nested interactive elements; render SignOutButton as the item via asChild

Placing a button inside a non-asChild DropdownMenu.Item nests interactive elements and can confuse focus/ARIA and event handling. Let the Item render your button directly.

- {/* Logout */}
- <DropdownMenuPrimitive.Item className={MENU_ITEM_CLASS}>
-   <SignOutButton className="text-[14px]">Log out</SignOutButton>
- </DropdownMenuPrimitive.Item>
+ {/* Logout */}
+ <DropdownMenuPrimitive.Item className={MENU_ITEM_CLASS} asChild>
+   <SignOutButton className="w-full text-[14px]">Log out</SignOutButton>
+ </DropdownMenuPrimitive.Item>

244-301: Potential UI lag: RadioGroup value derives from URL state

Because value comes from useSearchParams(), the visual selection may briefly lag until the router updates. If you notice this, keep a local selectedView state for instant feedback while still pushing the URL on change.

Example approach (minimal):

// Local state next to dialog open state
const [selectedView, setSelectedView] = useState<"list" | "carousel">(isCarouselView ? "carousel" : "list");

// Keep in sync when URL changes or dialog opens
useEffect(() => {
  setSelectedView(isCarouselView ? "carousel" : "list");
}, [isCarouselView, isDisplayDialogOpen]);

// RadioGroup
<RadioGroup
  value={selectedView}
  onValueChange={(value) => {
    setSelectedView(value as "list" | "carousel");
    setIsCarouselView(value === "carousel"); // keep immediate URL sync if desired
  }}
/>

331-336: Microcopy nit: “Continue” might imply deferred apply

The selection applies immediately on radio change. Consider “Done” or “Close” to better reflect the action.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3a59839 and 4154fa7.

📒 Files selected for processing (11)
  • apps/studio.giselles.ai/app/stage/(top)/hooks/use-ui-state.tsx (2 hunks)
  • apps/studio.giselles.ai/app/stage/layout.tsx (1 hunks)
  • apps/studio.giselles.ai/app/stage/loading.tsx (1 hunks)
  • apps/studio.giselles.ai/app/stage/query.ts (1 hunks)
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/menu-button.tsx (1 hunks)
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-list-item.tsx (1 hunks)
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-collapsed.tsx (1 hunks)
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.tsx (1 hunks)
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (3 hunks)
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/types.ts (1 hunks)
  • internal-packages/ui/style.css (1 hunks)
🧰 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

Files:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/menu-button.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-collapsed.tsx
  • apps/studio.giselles.ai/app/stage/layout.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-list-item.tsx
  • apps/studio.giselles.ai/app/stage/query.ts
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/types.ts
  • apps/studio.giselles.ai/app/stage/(top)/hooks/use-ui-state.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.tsx
  • apps/studio.giselles.ai/app/stage/loading.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.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)

Files:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/menu-button.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-collapsed.tsx
  • apps/studio.giselles.ai/app/stage/layout.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-list-item.tsx
  • apps/studio.giselles.ai/app/stage/(top)/hooks/use-ui-state.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.tsx
  • apps/studio.giselles.ai/app/stage/loading.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx
**/*

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

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

Files:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/menu-button.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-collapsed.tsx
  • apps/studio.giselles.ai/app/stage/layout.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-list-item.tsx
  • apps/studio.giselles.ai/app/stage/query.ts
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/types.ts
  • apps/studio.giselles.ai/app/stage/(top)/hooks/use-ui-state.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.tsx
  • apps/studio.giselles.ai/app/stage/loading.tsx
  • internal-packages/ui/style.css
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.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:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/menu-button.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-collapsed.tsx
  • apps/studio.giselles.ai/app/stage/layout.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-list-item.tsx
  • apps/studio.giselles.ai/app/stage/query.ts
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/types.ts
  • apps/studio.giselles.ai/app/stage/(top)/hooks/use-ui-state.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.tsx
  • apps/studio.giselles.ai/app/stage/loading.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx
🧠 Learnings (3)
📚 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 `TourGlobalStyles` component in `workspace-tour.tsx` for animation changes

Applied to files:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/menu-button.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-collapsed.tsx
  • apps/studio.giselles.ai/app/stage/layout.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-list-item.tsx
  • apps/studio.giselles.ai/app/stage/(top)/hooks/use-ui-state.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.tsx
  • apps/studio.giselles.ai/app/stage/loading.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 : Modify CSS positioning in step components in `workspace-tour.tsx` to adjust step positioning

Applied to files:

  • apps/studio.giselles.ai/app/stage/layout.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 : Modify the `CARD_STYLES` constants in `workspace-tour.tsx` to change step styling

Applied to files:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.tsx
🧬 Code graph analysis (4)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-collapsed.tsx (1)
internal-packages/workflow-designer-ui/src/icons/giselle-icon.tsx (1)
  • GiselleIcon (3-34)
apps/studio.giselles.ai/app/stage/query.ts (2)
apps/studio.giselles.ai/app/(main)/settings/account/actions.ts (1)
  • getAccountInfo (53-75)
apps/studio.giselles.ai/services/teams/utils.ts (1)
  • isProPlan (4-8)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.tsx (2)
internal-packages/workflow-designer-ui/src/icons/giselle-icon.tsx (1)
  • GiselleIcon (3-34)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/menu-button.tsx (1)
  • MenuButton (3-26)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (3)
internal-packages/ui/components/popover.tsx (1)
  • PopoverContent (4-15)
internal-packages/ui/components/dialog.tsx (1)
  • DialogFooter (59-69)
apps/studio.giselles.ai/lib/utils.ts (1)
  • cn (4-6)
⏰ 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 (4)
apps/studio.giselles.ai/app/stage/layout.tsx (1)

9-9: LGTM — background now sourced from stage theme

Consistent with the rest of the PR and keeps layout and loading aligned. No functional concerns.

apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (3)

118-124: z-index bump and width behavior look correct

z-50 addresses the submenu stacking issue, and using Radix Portal ensures the layer escapes overflow contexts. The trigger-width binding is a nice touch for the expanded variant.


28-53: Good extraction of HELP_ITEMS and shared MENU_ITEM_CLASS

Moving these to module scope reduces duplication and increases consistency. The as const assertion provides stronger typing of item shapes.


54-62: Verify React/Next versions for use() support

I wasn’t able to determine exact React or Next versions from your manifests (both are using workspace:/catalog: protocols in package.json and the lock file didn’t expose explicit versions). React 19+ is required to use the experimental use() hook inside a Client Component, and your Next version must bundle that support. Please confirm you’re on React ≥ 19.0.0 (and a compatible Next.js release). If not, you’ll need to resolve the promise in a Server Component and pass the data down as a plain prop.

Call sites using use() on a Promise—ensure each is either in a Server Component or you’re on React 19+:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (lines 61–62)
  • apps/studio.giselles.ai/app/stage/acts/[actId]/ui/sidebar.tsx (line 41)
  • apps/studio.giselles.ai/app/(main)/apps/page.tsx (line 14)

Suggested fallback diff if you need to switch to resolved props:

-export function NavigationRailFooterMenu({
-  user: userPromise,
-  variant,
-}: {
-  user: Promise<UserDataForNavigationRail>;
-  variant: NavigationRailState;
-}) {
-  const user = use(userPromise);
+export function NavigationRailFooterMenu({
+  user,
+  variant,
+}: {
+  user: UserDataForNavigationRail;
+  variant: NavigationRailState;
+}) {
+  // user is resolved upstream in a Server Component

@@ -1,10 +1,15 @@
import { fetchCurrentTeam, isProPlan } from "@/services/teams";
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Confirm barrel exports exist for @/services/teams.

Importing both fetchCurrentTeam and isProPlan from the package root implies a barrel. Please verify the index exports to avoid runtime/import errors.

Run from repo root:


🏁 Script executed:

#!/bin/bash
# Verify exports in services/teams
rg -nP 'export\s+(?:\{[^}]*\}|const|function|type|interface)\s+isProPlan|fetchCurrentTeam' -- apps/studio.giselles.ai/services
rg -nP 'from\s+"@/services/teams"' -C2

Length of output: 13617


Add missing barrel exports in services/teams

It looks like index.ts in apps/studio.giselles.ai/services/teams only re-exports fetchCurrentTeam, but you’re importing both fetchCurrentTeam and isProPlan (and elsewhere also fetchUserTeams) from "@/services/teams". That will cause unresolved-module errors at runtime.

Please update apps/studio.giselles.ai/services/teams/index.ts to include:

--- a/apps/studio.giselles.ai/services/teams/index.ts
+++ b/apps/studio.giselles.ai/services/teams/index.ts
 export { fetchCurrentTeam } from "./fetch-current-team";
+export { isProPlan } from "./utils";
+export { fetchUserTeams } from "./fetch-user-teams";

• Verify that ./utils.ts exports isProPlan.
• Verify that there is a fetch-user-teams.ts (or equivalent) exporting fetchUserTeams.
• After adding these barrel exports, confirm all imports from "@/services/teams" resolve without errors.

📝 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
import { fetchCurrentTeam, isProPlan } from "@/services/teams";
// apps/studio.giselles.ai/services/teams/index.ts
export { fetchCurrentTeam } from "./fetch-current-team";
export { isProPlan } from "./utils";
export { fetchUserTeams } from "./fetch-user-teams";
🤖 Prompt for AI Agents
In apps/studio.giselles.ai/services/teams/index.ts around the current exports
(affecting imports in apps/studio.giselles.ai/app/stage/query.ts line 1), the
barrel file only re-exports fetchCurrentTeam but callers import isProPlan and
fetchUserTeams as well; update the barrel to export isProPlan (verify it is
exported from ./utils.ts) and export fetchUserTeams from its module (e.g.,
./fetch-user-teams.ts or the actual filename), and run a TypeScript build or IDE
import resolution to confirm all imports from "@/services/teams" resolve without
errors.

@kaochannel154 kaochannel154 marked this pull request as draft August 25, 2025 09:19
- Create custom SimpleChevronLeft/Right icons with 1.5px stroke width
- Replace PanelLeftCloseIcon and PanelLeftOpenIcon with minimal double chevron design
- Reduce icon size from size-6 to size-5 for better visual balance
- Add proper accessibility attributes (role, aria-label)
- Maintain existing hover behavior and cursor styles
@kaochannel154 kaochannel154 marked this pull request as ready for review August 25, 2025 09:32
Copy link

giselles-ai bot commented Aug 25, 2025

Finished running flow.

Step Status Updated(UTC)
1 Aug 25, 2025 9:32am
2 Aug 25, 2025 9:33am
3 Aug 25, 2025 9:33am
4 Aug 25, 2025 9:33am

Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

URL Sync Logic

The new URL-based state helper setIsCarouselView depends on useSearchParams() and pushes a new URL each toggle. Verify it handles existing unrelated query params correctly, avoids unnecessary history entries on repeated selections, and behaves well in server/client transitions.

const setIsCarouselView = (value: boolean) => {
	const params = new URLSearchParams(searchParams.toString());
	if (value) {
		params.set("view", "carousel");
	} else {
		params.delete("view");
	}
	router.push(`?${params.toString()}`, { scroll: false });
};
Dialog Dropdown State

The dropdown open state is coupled with dialog open state (open={dropdownOpen && !isDisplayDialogOpen}) and custom onOpenChange. Validate no focus traps or stuck states occur, and that keyboard navigation/escape correctly closes both layers per Radix a11y expectations.

return (
	<DropdownMenuPrimitive.Root
		open={dropdownOpen && !isDisplayDialogOpen}
		onOpenChange={(open) => {
			if (!isDisplayDialogOpen) {
				setDropdownOpen(open);
			}
		}}
	>
		<DropdownMenuPrimitive.Trigger asChild>
			<button
Plan Name Fallback

isProPlan(currentTeam) result is mapped to a hardcoded planName string. Confirm this won’t conflict with future plan tiers and that currentTeam null/undefined is handled gracefully by isProPlan.

const currentTeam = await fetchCurrentTeam();
const isPro = isProPlan(currentTeam);

return {
	displayName: accountInfo.displayName ?? undefined,
	email: accountInfo.email ?? undefined,
	avatarUrl: accountInfo.avatarUrl ?? undefined,
	planName: isPro ? "Pro plan" : "Free plan",
};

Copy link

giselles-ai bot commented Aug 25, 2025

🔍 QA Testing Assistant by Giselle

📋 Manual QA Checklist

Based on the changes in this PR, here are the key areas to test manually:

  • 1. Verify User Information Display: Open the sidebar and confirm the user's display name is correctly shown in the footer menu button.
  • 2. Verify Plan Information Display: In the sidebar footer menu button, confirm the current user plan (e.g., "Pro plan" or "Free plan") is accurately displayed below the name.
  • 3. Change View from List to Carousel via URL: Navigate to /stage, open the user menu, select "Display type", choose "Carousel", and verify the main content changes to carousel view and the URL updates to include ?view=carousel.
  • 4. Change View from Carousel to List via URL: With ?view=carousel in the URL, open the "View Style" dialog, select "List", and verify the main content changes to list view and the ?view=carousel parameter is removed.
  • 5. Verify State Persistence on Reload: Set the view to "Carousel", reload the page, and confirm the carousel view persists. Also, check that "Carousel" remains selected in the "View Style" dialog.
  • 6. Verify Browser History Navigation (Back/Forward): Switch between List and Carousel views, then use the browser's back and forward buttons to confirm the view and URL parameters update correctly.
  • 7. Verify Menu Appears on Top (z-index fix): On a page with other UI elements (like a form), open the user menu and confirm it appears layered above all other content.
  • 8. Verify Help Submenu Contents and Icons: Open the user menu, access the "Help" submenu, and verify it contains "Docs", "Terms", "Privacy & Cookies", "Contact Us", and that "Docs", "Terms", and "Privacy & Cookies" have external link icons.
  • 9. Verify External Link Navigation: From the user menu, click "Homepage", and from the "Help" submenu, click "Docs", "Terms", and "Privacy & Cookies", ensuring each opens in a new browser tab with the correct URL.
  • 10. Verify Internal Link Navigation: Click "Account settings" and verify navigation to /settings/account. Click "Lobby" and verify navigation to /apps.
  • 11. Verify Sign Out Functionality: Click "Log out" and confirm successful logout.
  • 12. Verify New Sidebar Collapse/Expand Icons: Check that the sidebar collapse/expand buttons display the correct double-chevron icons (<< and >>) and that the functionality works.
  • 13. Verify New Color Scheme: Visually inspect the sidebar for updated text colors, hover states, and background colors, specifically noting the --color-stage-key for the user plan name.

✨ Prompt for AI Agents

Use the following prompts with Cursor or Claude Code to automate E2E testing:

📝 E2E Test Generation Prompt
## Prompt for AI Agent: Generate E2E Tests for Sidebar Adjustments

### **AI Agent Instructions:**

You are an expert QA engineer. Your task is to generate a comprehensive E2E test suite using **Playwright** for a new feature involving sidebar adjustments. The feature refactors the user account menu in the navigation sidebar, introduces URL-based state management for content display types (List vs. Carousel), and fixes a z-index issue.

Please follow the instructions below to create a robust, maintainable, and CI-ready test file.

---

### 1. Context Summary

The Pull Request introduces significant enhancements to the application's main navigation sidebar, specifically the user account footer menu.

*   **What changed:** The sidebar's user menu has been completely redesigned. A simple dropdown is replaced with a more advanced menu containing submenus and a new dialog. State management for the content view (List vs. Carousel) has been moved from local component state to URL search parameters (`?view=carousel`). A z-index bug was fixed, ensuring the menu appears above all other page content. User plan information (e.g., "Pro plan") is now displayed in the menu trigger.

*   **Key user flows affected:**
    1.  Accessing user account options (Account settings, Sign out).
    2.  Changing the main content's display style between "List" and "Carousel".
    3.  Navigating to external documentation and legal pages from the "Help" submenu.
    4.  Viewing user-specific information like plan type.

*   **Critical paths to test:**
    1.  **Display Type Synchronization:** The end-to-end flow of a user opening the menu, launching the "Display type" dialog, changing the view, and verifying that both the URL and the main content area update correctly. This must also work on page reload and with browser history (back/forward).
    2.  **Menu Visibility (z-index):** The user menu dropdown must render on top of all other UI elements on the page.
    3.  **External Link Navigation:** All links in the "Help" submenu marked as external should open in a new browser tab.
    4.  **User Data Display:** The user's plan name should be visible in the expanded sidebar menu.

---

### 2. Test Scenarios

Create tests covering the following scenarios. Organize them within a `describe('Sidebar Footer Menu', ...)` block.

#### **Happy Paths:**

1.  **User Info Display:**
    *   **Given** a logged-in user with a "Pro plan".
    *   **When** the sidebar is in its expanded state.
    *   **Then** the user's display name and "Pro plan" text are visible in the sidebar footer.
2.  **Display Type Change (List to Carousel):**
    *   **Given** the page is loaded with the default "List" view.
    *   **When** the user opens the account menu, clicks "Display type", selects "Carousel" in the dialog, and closes the dialog.
    *   **Then** the URL should be updated to include `?view=carousel`.
    *   **And** the main content area should switch to the carousel layout.
3.  **Display Type Change (Carousel to List):**
    *   **Given** the page is loaded with `?view=carousel`.
    *   **When** the user opens the account menu, clicks "Display type", selects "List" in the dialog.
    *   **Then** the `view` search parameter should be removed from the URL.
    *   **And** the main content area should switch back to the list layout.
4.  **URL State Persistence:**
    *   **Given** the user navigates directly to a URL with `?view=carousel`.
    *   **Then** the main content should render the carousel view on initial load.
    *   **And** the "Carousel" option in the "Display type" dialog should be pre-selected.
5.  **Browser History Navigation:**
    *   **Given** the user starts on the List view, then switches to the Carousel view.
    *   **When** the user clicks the browser's "Back" button.
    *   **Then** the URL should revert to not having the `view` parameter, and the content should display the List view.
6.  **Help Submenu and External Links:**
    *   **When** the user opens the account menu and hovers over/clicks "Help".
    *   **Then** a submenu with "Docs", "Terms", etc., should appear.
    *   **And** the "Docs" item should have an external link icon.
    *   **When** the user clicks "Docs".
    *   **Then** a new browser tab/page should be opened with the URL `https://docs.giselles.ai/guides/introduction`.
7.  **Z-Index Verification:**
    *   **Given** there is a form or another UI element present on the main stage.
    *   **When** the user opens the account menu.
    *   **Then** the dropdown menu should be visible and should overlay the form element.

#### **Edge Cases & Regressions:**

1.  **Dialog Cancellation:**
    *   **Given** the current view is "List".
    *   **When** the user opens the "Display type" dialog, selects "Carousel", but then clicks "Cancel" or the "X" close button.
    *   **Then** the URL and the content view should remain unchanged (still "List").
2.  **Menu in Collapsed Sidebar:**
    *   **When** the sidebar is in its collapsed state and the user clicks the account icon.
    *   **Then** the menu should open correctly, aligned to the collapsed sidebar.
3.  **Standard Navigation:**
    *   **When** the user clicks "Account settings".
    *   **Then** the page should navigate to the `/settings/account` route.
4.  **Sign Out Functionality:**
    *   **When** the user clicks "Log out".
    *   **Then** the user should be signed out and redirected appropriately.

---

### 3. Playwright Implementation Instructions

#### **Setup:**

*   Use `test.beforeEach` to handle user login and navigation to the main stage page (e.g., `/stage`).
*   Mock the API response for `getSidebarData` to control the user's `planName` (e.g., "Pro plan" or "Free plan") for deterministic tests. Example using `page.route()`:
    ```typescript
    await page.route('**/api/getSidebarData', route => { // Adjust URL as needed
      route.fulfill({
        status: 200,
        body: JSON.stringify({
          displayName: 'Test User',
          email: 'test@example.com',
          avatarUrl: 'some-url',
          planName: 'Pro plan',
        }),
      });
    });
    ```

#### **Selectors to Target:**

*   **Account Menu Trigger:** The `button` element within the sidebar footer. A selector like `page.getByRole('button', { name: /Test User/ })` or a more stable `data-testid` is preferred. For now, you can use a structural selector based on the diff: `page.locator('footer button:has(img[alt="User Avatar"])')`.
*   **Menu Item:** `page.getByRole('menuitem', { name: 'Display type' })`
*   **Help Submenu Trigger:** `page.getByRole('menuitem', { name: 'Help' })`
*   **External Link (Docs):** `page.getByRole('menuitem', { name: 'Docs' })`
*   **Display Type Dialog:** `page.getByRole('dialog', { name: 'View Style' })`
*   **Carousel Radio Button:** `page.getByRole('radio', { name: 'Carousel' })` or `page.locator('label[for="carousel"]')`
*   **List Radio Button:** `page.getByRole('radio', { name: 'List' })` or `page.locator('label[for="list"]')`
*   **Dialog "Continue" Button:** `page.getByRole('button', { name: 'Continue' })`
*   **External Link Icon:** Within a menu item, target the `ExternalLink` component's SVG. You can assert its presence with `locator.locator('svg')` and check its class or path data if needed.
*   **Main Content Area:** Define locators for the list and carousel view containers to verify which one is visible. (e.g., `page.locator('[data-testid="content-list-view"]')`, `page.locator('[data-testid="content-carousel-view"]')`). **Note:** If these test IDs don't exist, please add a comment in the generated code suggesting their addition for test stability.

#### **User Interactions and Assertions:**

*   **Changing View Type:**
    ```typescript
    // Interaction
    await page.getByRole('button', { name: /Test User/ }).click();
    await page.getByRole('menuitem', { name: 'Display type' }).click();
    await expect(page.getByRole('dialog', { name: 'View Style' })).toBeVisible();
    await page.getByRole('radio', { name: 'Carousel' }).click();
    await page.getByRole('button', { name: 'Continue' }).click();

    // Assertion
    await expect(page).toHaveURL(/.*view=carousel/);
    await expect(page.locator('[data-testid="content-carousel-view"]')).toBeVisible();
    await expect(page.locator('[data-testid="content-list-view"]')).not.toBeVisible();
    ```

*   **Verifying External Links:** Use Playwright's multi-page handling.
    ```typescript
    const newPagePromise = page.context().waitForEvent('page');
    await page.getByRole('menuitem', { name: 'Docs' }).click();
    const newPage = await newPagePromise;
    await newPage.waitForLoadState();
    
    // Assertions
    expect(newPage.url()).toBe('https://docs.giselles.ai/guides/introduction');
    await newPage.close();
    ```

*   **Verifying z-index:**
    ```typescript
    // Assuming a locator for an element that should be behind the menu
    const backgroundElement = page.locator('[data-testid="background-form"]');
    await expect(backgroundElement).toBeVisible();

    // Open the menu
    await page.getByRole('button', { name: /Test User/ }).click();
    const menuContent = page.getByRole('menu');
    await expect(menuContent).toBeVisible();

    // A simple check is to ensure the menu's bounding box overlaps the background element.
    // A visual regression snapshot test would be the most effective way to confirm this.
    // For a functional test, we can check a computed style property.
    const menuZIndex = await menuContent.evaluate(el => window.getComputedStyle(el).zIndex);
    expect(parseInt(menuZIndex, 10)).toBeGreaterThanOrEqual(50);
    ```

---

### 4. MCP Integration Guidelines

Structuring commands for Playwright MCP is straightforward. The AI agent executing this should know how to run Playwright, but here are the specifics.

*   **Execution Command:** The test suite can be executed with a standard Playwright command.
    ```bash
    # Run the newly created test file specifically
    mcp playwright test tests/sidebar-footer-menu.spec.ts

    # Run all tests in the project
    mcp playwright test
    ```
*   **Environment Configuration:** Ensure any required environment variables (e.g., `BASE_URL`, user credentials for `test.beforeEach`) are configured in the `.env` file or passed via the command line.

---

### 5. CI-Ready Code Requirements

*   **Test Organization:** Place the generated test file at `tests/e2e/sidebar-footer-menu.spec.ts`.
*   **Naming Conventions:**
    *   Test file: `sidebar-footer-menu.spec.ts`
    *   Main suite: `describe('Sidebar Footer Menu', () => { ... })`
    *   Test cases: Use clear, descriptive names with the `test()` function, e.g., `test('should update URL and view when switching to carousel mode', async ({ page }) => { ... })`.
*   **Atomicity & Independence:** Each `test()` block must be fully independent and able to run on its own without relying on the state from a previous test. Use `test.beforeEach` for shared setup (like logging in) and `test.afterEach` for cleanup.
*   **Error Handling:** Rely on Playwright's built-in assertions (`expect`) which provide clear error messages. Avoid generic `try/catch` blocks unless necessary for complex cleanup logic.
*   **Parallelization:** Since all tests are atomic, they are ready for parallel execution by default in Playwright. No special configuration is needed in the test file itself.

Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Client-router usage risk

You’re invoking Next.js client navigation APIs (useRouter/useSearchParams,
router.push) from components that may render during server layout/suspense
resolution (e.g., footer menu using use(userPromise) and layout-sourced data).
Ensure all such components are definitively client components ("use client") and
not rendered within server-only trees; otherwise hydration/runtime errors can
occur. If mixed, move router/searchParam logic into a clearly marked client
boundary or a dedicated client provider to avoid SSR/client mismatches.

Examples:

apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx [62-79]
	const router = useRouter();
	const searchParams = useSearchParams();
	const isCarouselView = searchParams.get("view") === "carousel";
	const [isDisplayDialogOpen, setIsDisplayDialogOpen] = useState(false);
	const [dropdownOpen, setDropdownOpen] = useState(false);

	const setIsCarouselView = useCallback(
		(value: boolean) => {
			const params = new URLSearchParams(searchParams.toString());
			if (value) {

 ... (clipped 8 lines)

Solution Walkthrough:

Before:

// apps/.../navigation-rail-footer-menu.tsx
// Missing "use client" directive
import { useRouter, useSearchParams } from "next/navigation";
import { use, useCallback, useState } from "react";

export function NavigationRailFooterMenu({ user: userPromise, ... }) {
    const user = use(userPromise);
    const router = useRouter(); // Client hook
    const searchParams = useSearchParams(); // Client hook
    const [isDisplayDialogOpen, setIsDisplayDialogOpen] = useState(false); // Client hook

    // ... component logic using client hooks
}

After:

// apps/.../navigation-rail-footer-menu.tsx
"use client"; // Directive added to define a client boundary

import { useRouter, useSearchParams } from "next/navigation";
import { use, useCallback, useState } from "react";

export function NavigationRailFooterMenu({ user: userPromise, ... }) {
    const user = use(userPromise);
    const router = useRouter();
    const searchParams = useSearchParams();
    const [isDisplayDialogOpen, setIsDisplayDialogOpen] = useState(false);

    // ... component logic now correctly runs on the client
}
Suggestion importance[1-10]: 10

__

Why: This suggestion correctly identifies a critical architectural error in navigation-rail-footer-menu.tsx, which uses client-side hooks without the mandatory "use client" directive, leading to a guaranteed runtime crash.

High
General
Make logout item a proper child

Prevent the dropdown from closing prematurely or swallowing clicks when
triggering logout. Convert the item to asChild and render the button as the
direct item child so Radix can handle focus and prevent default correctly.

apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx [216-218]

-<DropdownMenuPrimitive.Item className={MENU_ITEM_CLASS}>
-	<SignOutButton className="text-[14px]">Log out</SignOutButton>
+<DropdownMenuPrimitive.Item className={MENU_ITEM_CLASS} asChild>
+	<SignOutButton className="w-full text-left text-[14px]">Log out</SignOutButton>
 </DropdownMenuPrimitive.Item>
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: This suggestion correctly identifies a common issue when using Radix UI primitives by proposing the use of asChild, which improves component composition, event handling, and accessibility.

Low
Use single-chevron icons

Replace the double-chevron paths with single chevrons to better match the
collapse/expand affordance and avoid ambiguous icons. The current paths draw two
arrows that may confuse users and clash visually at small sizes.

apps/studio.giselles.ai/app/stage/ui/navigation-rail/simple-chevron-icons.tsx [7-45]

 export function SimpleChevronLeft({ className, ...props }: IconProps) {
 	return (
 		<svg
 			className={className}
 			viewBox="0 0 24 24"
 			fill="none"
 			stroke="currentColor"
 			strokeWidth="1.5"
 			strokeLinecap="round"
 			strokeLinejoin="round"
 			role="img"
 			aria-label="Collapse sidebar"
 			{...props}
 		>
-			<path d="M11 17l-5-5 5-5" />
-			<path d="M18 17l-5-5 5-5" />
+			<path d="M15 18l-6-6 6-6" />
 		</svg>
 	);
 }
 
 export function SimpleChevronRight({ className, ...props }: IconProps) {
 	return (
 		<svg
 			className={className}
 			viewBox="0 0 24 24"
 			fill="none"
 			stroke="currentColor"
 			strokeWidth="1.5"
 			strokeLinecap="round"
 			strokeLinejoin="round"
 			role="img"
 			aria-label="Expand sidebar"
 			{...props}
 		>
-			<path d="M13 17l5-5-5-5" />
-			<path d="M6 17l5-5-5-5" />
+			<path d="M9 6l6 6-6 6" />
 		</svg>
 	);
 }
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion offers a valid UI improvement by changing the double-chevron SVG paths to single chevrons, which provides a clearer and more standard visual cue for expand/collapse actions.

Low
  • More

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: 1

🧹 Nitpick comments (8)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/simple-chevron-icons.tsx (4)

17-19: Make icons decorative by default; move the accessible name to the button.

Placing aria-label on the SVG ties these icons to a specific meaning (“Collapse/Expand sidebar”). It’s more robust to:

  • Mark SVGs as decorative (aria-hidden) and let the parent control (button) provide the accessible name, or
  • Accept an overridable aria-label via props (which you already do by spreading props last), but don’t set a semantic default here.

Proposed change (keeps consumer override possible):

-      role="img"
-      aria-label="Collapse sidebar"
+      aria-hidden="true"
+      focusable="false"
       {...props}

Apply to both icons at the indicated lines.

Also applies to: 37-39


21-22: Optional: Keep stroke width visually consistent when scaled.

If these icons are frequently sized via CSS (e.g., size-4 to size-7), consider non-scaling strokes for crisper rendering:

-      <path d="M11 17l-5-5 5-5" />
-      <path d="M18 17l-5-5 5-5" />
+      <path vectorEffect="non-scaling-stroke" d="M11 17l-5-5 5-5" />
+      <path vectorEffect="non-scaling-stroke" d="M18 17l-5-5 5-5" />

Repeat for the right icon’s paths.

Also applies to: 41-42


3-5: Type nit: className is already in SVG props.

IconProps extends ComponentProps<"svg">, which already includes className?: string. You can drop the explicit className to avoid redundancy:

-interface IconProps extends ComponentProps<"svg"> {
-  className?: string;
-}
+type IconProps = ComponentProps<"svg">;

This keeps the surface area minimal.


7-9: (Optional) forwardRef for better interoperability.

If these icons might be measured/focused/animated by parent components, consider forwardRef<SVGSVGElement, IconProps>. No behavior change otherwise.

Also applies to: 27-29

apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.tsx (4)

36-37: Pass the handler directly; avoid the no-op lambda.

Slightly cleaner and avoids re-creating a function on each render:

-          onClick={() => onCollapseButtonClick()}
+          onClick={onCollapseButtonClick}

35-41: Give the collapse button an explicit accessible name.

Per the icon comment, label the interactive control instead of the SVG. Once MenuButton forwards standard button props (see suggested change below), set an aria-label here:

           <MenuButton
-            onClick={onCollapseButtonClick}
+            onClick={onCollapseButtonClick}
+            aria-label="Collapse sidebar"
             className="cursor-w-resize"
           >
            <SimpleChevronLeft className="size-5 text-[var(--color-stage-sidebar-text)]" />
           </MenuButton>

Support change in MenuButton (outside this file) to forward native props:

// apps/studio.giselles.ai/app/stage/ui/navigation-rail/menu-button.tsx
export function MenuButton({
  onClick,
  children,
  className,
  ...rest
}: React.ButtonHTMLAttributes<HTMLButtonElement>) {
  return (
    <button
      type="button"
      className={clsx(
        "group size-8 text-[var(--color-stage-sidebar-text)] hover:text-[var(--color-stage-sidebar-text-hover)] transition-colors rounded flex items-center justify-center",
        className,
      )}
      onClick={onClick}
      {...rest}
    >
      {children}
    </button>
  );
}

28-28: Minor: stroke-1 has no effect on a fill-only SVG.

GiselleIcon fills paths with currentColor; stroke-1 won’t affect rendering. You can drop it:

- <GiselleIcon className="size-6 text-[var(--color-stage-sidebar-text-hover)] stroke-1 group-hover:hidden shrink-0" />
+ <GiselleIcon className="size-6 text-[var(--color-stage-sidebar-text-hover)] group-hover:hidden shrink-0" />

37-37: Cursor style nit.

cursor-w-resize implies a resize affordance; this is a toggle button. cursor-pointer is clearer:

-            className="cursor-w-resize"
+            className="cursor-pointer"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4154fa7 and f01ccc5.

📒 Files selected for processing (3)
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-collapsed.tsx (3 hunks)
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.tsx (3 hunks)
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/simple-chevron-icons.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-collapsed.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

Files:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/simple-chevron-icons.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.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)

Files:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/simple-chevron-icons.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.tsx
**/*

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

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

Files:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/simple-chevron-icons.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.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:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/simple-chevron-icons.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.tsx
🧠 Learnings (1)
📚 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 `TourGlobalStyles` component in `workspace-tour.tsx` for animation changes

Applied to files:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.tsx
🧬 Code graph analysis (1)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.tsx (3)
internal-packages/workflow-designer-ui/src/icons/giselle-icon.tsx (1)
  • GiselleIcon (3-34)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/menu-button.tsx (1)
  • MenuButton (3-26)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/simple-chevron-icons.tsx (1)
  • SimpleChevronLeft (7-25)
⏰ 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)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/simple-chevron-icons.tsx (1)

7-25: Solid, reusable chevron icons with sensible defaults.

Clean SVGs, correct viewBox, currentColor usage, and props-spread order lets consumer overrides win. Naming and file casing follow the guidelines.

Also applies to: 27-45

apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.tsx (1)

13-13: Swap to internal chevron icons: LGTM.

Importing and using SimpleChevronLeft aligns with the new shared icon set and theming tokens.

Also applies to: 39-39

- Add 'use client' directives to client components using Next.js hooks
- Improve color contrast for stage-sidebar-text to meet WCAG AA standards
- Rename --color-stage-key to --color-stage-accent for consistency
- Add group class to enable group-hover functionality

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
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)
apps/studio.giselles.ai/app/stage/(top)/hooks/use-ui-state.tsx (1)

1-1: "use client" added — correct and necessary for App Router hooks

Marking this module as a Client Component is required when using useRouter/useSearchParams. Good catch.

🧹 Nitpick comments (3)
apps/studio.giselles.ai/app/stage/(top)/hooks/use-ui-state.tsx (3)

3-3: Import usePathname to build full, stable URLs on navigation

To avoid edge cases with relative ?query pushes and to preserve the current pathname/basePath/locale, import usePathname for use in the setter below.

-import { useRouter, useSearchParams } from "next/navigation";
+import { useRouter, useSearchParams, usePathname } from "next/navigation";

9-11: Deriving isCarouselView from the URL is a solid move; also capture pathname

URL-driven state makes the view shareable and history-friendly. Add pathname now so the setter can construct absolute URLs (and avoid pushing just "?...").

 const searchParams = useSearchParams();
 const router = useRouter();
-const isCarouselView = searchParams.get("view") === "carousel";
+const pathname = usePathname();
+const isCarouselView = searchParams.get("view") === "carousel";

24-32: Avoid redundant history entries and trailing “?”; push full path + query

Two improvements:

  • No-op when the requested value matches the current one (prevents extra history entries).
  • Build the URL using pathname and only append ? when there are params, avoiding URLs like /stage?.
-const setIsCarouselView = (value: boolean) => {
-  const params = new URLSearchParams(searchParams.toString());
-  if (value) {
-    params.set("view", "carousel");
-  } else {
-    params.delete("view");
-  }
-  router.push(`?${params.toString()}`, { scroll: false });
-};
+const setIsCarouselView = (value: boolean) => {
+  // No-op if nothing changes; avoids redundant history entries
+  if (isCarouselView === value) return;
+
+  const params = new URLSearchParams(searchParams.toString());
+  if (value) {
+    params.set("view", "carousel");
+  } else {
+    params.delete("view");
+  }
+  const qs = params.toString();
+  router.push(qs ? `${pathname}?${qs}` : pathname, { scroll: false });
+};

Optional: if your page uses hash fragments, consider preserving them by appending window.location.hash when present.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f01ccc5 and 413c444.

📒 Files selected for processing (4)
  • apps/studio.giselles.ai/app/stage/(top)/hooks/use-ui-state.tsx (2 hunks)
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.tsx (3 hunks)
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (3 hunks)
  • internal-packages/ui/style.css (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-expanded.tsx
  • internal-packages/ui/style.css
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.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

Files:

  • apps/studio.giselles.ai/app/stage/(top)/hooks/use-ui-state.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)

Files:

  • apps/studio.giselles.ai/app/stage/(top)/hooks/use-ui-state.tsx
**/*

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

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

Files:

  • apps/studio.giselles.ai/app/stage/(top)/hooks/use-ui-state.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:

  • apps/studio.giselles.ai/app/stage/(top)/hooks/use-ui-state.tsx
🧠 Learnings (1)
📚 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 `TourGlobalStyles` component in `workspace-tour.tsx` for animation changes

Applied to files:

  • apps/studio.giselles.ai/app/stage/(top)/hooks/use-ui-state.tsx
⏰ 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

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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.

@kaochannel154 When I checked the operation, it seemed that I couldn't change the display format by selecting the radio button in the View Style modal.

Could you please check if you can reproduce this issue locally?

cannot-click-carousel.mp4

@kaochannel154
Copy link
Contributor Author

@shige It's going to take a while to load, but it should work...! I know it's bad UX, but do you know the reason??

2025-08-25.21.24.14.mov

@shige
Copy link
Member

shige commented Aug 25, 2025

@kaochannel154 I understand that this has an impact on the speed at which the app loads!

If the View Style changes in the background when you switch the radio button, I thought that the "Continue" button might not be necessary.I thought that showing only an X or Close button would be better instead.

I was confused because I mistakenly thought that the View Style changed when you pressed the Continue button.

- Removed Continue button from both PC and mobile View Style dialogs
- Removed X close icon from dialog headers
- Simplified dialog footer to only show Cancel button
- Applied changes to both settings-dialog.tsx and navigation-rail-footer-menu.tsx

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

Co-Authored-By: Claude <noreply@anthropic.com>
@kaochannel154
Copy link
Contributor Author

I removed the continue button and the X button!

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

Caution

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

⚠️ Outside diff range comments (1)
apps/studio.giselles.ai/app/stage/(top)/settings-dialog.tsx (1)

41-52: Mobile modal lacks dialog semantics and focus management—screen readers will miss this dialog

The custom mobile overlay is not an actual dialog (no role="dialog", no aria-modal, no labelledby, no focus trap, no Escape handling). This impacts accessibility and keyboard users. If keeping the custom overlay, minimally add dialog semantics and basic close interactions; ideally, reuse the same Dialog component as desktop for consistency and a11y.

Apply this minimal patch for semantics and basic interactions:

-        <div className="fixed inset-0 z-50 bg-black/60 flex items-center justify-center p-4">
+        <div
+          className="fixed inset-0 z-50 bg-black/60 flex items-center justify-center p-4"
+          role="dialog"
+          aria-modal="true"
+          aria-labelledby="settings-dialog-title"
+          onKeyDown={(e) => e.key === "Escape" && handleClose()}
+          tabIndex={-1}
+          onClick={handleClose}
+        >
-          <div className="relative z-10 w-[90vw] max-w-[500px] max-h-[90vh] overflow-y-auto rounded-[12px] p-6 shadow-xl focus:outline-none">
+          <div
+            className="relative z-10 w-[90vw] max-w-[500px] max-h-[90vh] overflow-y-auto rounded-[12px] p-6 shadow-xl focus:outline-none"
+            onClick={(e) => e.stopPropagation()}
+          >
@@
-            <div className="mb-6">
-              <h2 className="text-[20px] font-medium text-white-400 tracking-tight font-sans">
+            <div className="mb-6">
+              <h2
+                id="settings-dialog-title"
+                className="text-[20px] font-medium text-white-400 tracking-tight font-sans"
+              >
                 View Style
               </h2>
             </div>
@@
-            <div className="mt-6 flex justify-end">
+            <div className="mt-6 flex justify-end">
               <button
                 type="button"
                 onClick={handleClose}
                 className={cn(buttonVariants({ variant: "link" }))}
                 aria-label="Cancel"
+                autoFocus
               >
                 Cancel
               </button>
             </div>

If you want, I can refactor this to use the shared for mobile (full-screen variant) so focus trapping, aria, and Escape handling are handled for you.

Also applies to: 59-136, 137-146

🧹 Nitpick comments (8)
apps/studio.giselles.ai/app/stage/(top)/settings-dialog.tsx (2)

53-57: Reinstate a close (X) control in headers for both mobile and desktop

Per the UX feedback in the PR comments, removing “Continue” is good, but users still expect an explicit close affordance in the header. Add a close button (X) next to the title.

Apply this patch to add a close button (mobile):

-            <div className="mb-6">
-              <h2
-                id="settings-dialog-title"
-                className="text-[20px] font-medium text-white-400 tracking-tight font-sans"
-              >
-                View Style
-              </h2>
-            </div>
+            <div className="mb-6 flex items-center justify-between">
+              <h2
+                id="settings-dialog-title"
+                className="text-[20px] font-medium text-white-400 tracking-tight font-sans"
+              >
+                View Style
+              </h2>
+              <button
+                type="button"
+                onClick={handleClose}
+                aria-label="Close"
+                className="inline-flex items-center justify-center rounded-md p-1 text-white-400 hover:text-white-200 hover:bg-white/10"
+              >
+                <X className="h-4 w-4" />
+              </button>
+            </div>

And similarly for desktop:

-        <div className="mb-6">
-          <DialogTitle className="text-[20px] font-medium text-white-400 tracking-tight font-sans">
-            View Style
-          </DialogTitle>
-        </div>
+        <div className="mb-6 flex items-center justify-between">
+          <DialogTitle className="text-[20px] font-medium text-white-400 tracking-tight font-sans">
+            View Style
+          </DialogTitle>
+          <button
+            type="button"
+            onClick={handleClose}
+            aria-label="Close"
+            className="inline-flex items-center justify-center rounded-md p-1 text-white-400 hover:text-white-200 hover:bg-white/10"
+          >
+            <X className="h-4 w-4" />
+          </button>
+        </div>

Add the missing import:

import { X } from "lucide-react";

Confirm this aligns with shige’s suggestion (close-only, no Continue). I can also wire this to your design system’s DialogClose if available.

Also applies to: 157-161


62-71: Associate section label with its radio group for better a11y

Tie the “Display Type” label to the RadioGroup via aria-labelledby so screen readers announce the group properly.

Apply for mobile:

-                <Label className="text-white-800 font-medium text-[12px] leading-[20.4px] font-geist">
+                <Label id="mobile-display-type-label" className="text-white-800 font-medium text-[12px] leading-[20.4px] font-geist">
                   Display Type
                 </Label>
-                <RadioGroup
+                <RadioGroup
+                  aria-labelledby="mobile-display-type-label"
                   value={isCarouselView ? "carousel" : "list"}
                   onValueChange={(value) =>
                     setIsCarouselView(value === "carousel")
                   }

Apply for desktop:

-          <Label className="text-white-800 font-medium text-[12px] leading-[20.4px] font-geist">
+          <Label id="desktop-display-type-label" className="text-white-800 font-medium text-[12px] leading-[20.4px] font-geist">
             Display Type
           </Label>
-          <RadioGroup
+          <RadioGroup
+            aria-labelledby="desktop-display-type-label"
             value={isCarouselView ? "carousel" : "list"}
             onValueChange={(value) => setIsCarouselView(value === "carousel")}

Also applies to: 165-171

apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (6)

93-116: Add an accessible name to the trigger button (especially when collapsed)

When variant is "collapsed", the trigger shows only an avatar—no text. Provide an aria-label so screen readers announce what this button does.

Apply:

-      <button
-        className="w-full hover:bg-ghost-element-hover h-full rounded-md cursor-pointer outline-none p-1.5 flex items-center gap-2"
-        type="button"
-      >
+      <button
+        className="w-full hover:bg-ghost-element-hover h-full rounded-md cursor-pointer outline-none p-1.5 flex items-center gap-2"
+        type="button"
+        aria-label={
+          variant === "expanded"
+            ? "Open user menu"
+            : `Open user menu for ${user.displayName ?? user.email ?? "account"}`
+        }
+      >

170-178: Use rel="noopener noreferrer" for external links

You already have target="_blank" with rel="noopener". Add “noreferrer” for privacy and to cover older browsers.

Apply:

-                <a
+                <a
                   href={item.href}
                   target="_blank"
-                  rel="noopener"
+                  rel="noopener noreferrer"
                   className="w-full flex items-center justify-between"
                 >
@@
-      <a
+      <a
         href="https://giselles.ai"
         target="_blank"
-        rel="noopener"
+        rel="noopener noreferrer"
         className="w-full flex items-center justify-between"
       >

Also applies to: 203-211


217-220: Avoid nested interactive elements inside a menu item

DropdownMenuPrimitive.Item renders a focusable element. Nesting a button inside can create conflicting interaction semantics. Use asChild and move classes to the child.

Apply:

-            <DropdownMenuPrimitive.Item className={MENU_ITEM_CLASS}>
-              <SignOutButton className="text-[14px]">Log out</SignOutButton>
-            </DropdownMenuPrimitive.Item>
+            <DropdownMenuPrimitive.Item asChild>
+              <SignOutButton className={cn(MENU_ITEM_CLASS, "text-[14px]")}>
+                Log out
+              </SignOutButton>
+            </DropdownMenuPrimitive.Item>

70-81: Preserve pathname (and avoid dropping fragments) when updating the query param

Pushing only ?query can drop the URL hash and is a bit implicit. Prefer composing with pathname for clarity and stability.

Apply:

-import { useRouter, useSearchParams } from "next/navigation";
+import { usePathname, useRouter, useSearchParams } from "next/navigation";
@@
-  const router = useRouter();
-  const searchParams = useSearchParams();
+  const router = useRouter();
+  const pathname = usePathname();
+  const searchParams = useSearchParams();
@@
-      router.push(`?${params.toString()}`, { scroll: false });
+      router.push(`${pathname}?${params.toString()}`, { scroll: false });

Optional (if you want to retain hash as well):

// after computing params
const hash = typeof window !== "undefined" ? window.location.hash : "";
router.push(`${pathname}?${params.toString()}${hash}`, { scroll: false });

Also applies to: 14-15, 65-67


53-55: Keyboard highlight state for menu items

Consider styling the Radix-highlighted state for keyboard users in addition to hover.

Apply:

-const MENU_ITEM_CLASS =
-  "text-text outline-none cursor-pointer hover:bg-ghost-element-hover rounded-[4px] px-[8px] py-[6px] text-[14px]";
+const MENU_ITEM_CLASS =
+  "text-text outline-none cursor-pointer hover:bg-ghost-element-hover data-[highlighted]:bg-ghost-element-hover rounded-[4px] px-[8px] py-[6px] text-[14px]";

107-114: Guard empty planName to avoid an empty second line

When planName is undefined, the second line renders empty space. Only show the plan line if present.

Apply:

-            <div className="flex flex-col min-w-0 flex-1 text-left">
+            <div className="flex flex-col min-w-0 flex-1 text-left">
               <p className="truncate text-text-muted text-sm">
                 {user.displayName ?? user.email}
               </p>
-              <p className="truncate text-[var(--color-stage-accent)] text-[10px]">
-                {user.planName}
-              </p>
+              {user.planName ? (
+                <p className="truncate text-[var(--color-stage-accent)] text-[10px]">
+                  {user.planName}
+                </p>
+              ) : null}
             </div>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 413c444 and bde54f0.

📒 Files selected for processing (2)
  • apps/studio.giselles.ai/app/stage/(top)/settings-dialog.tsx (4 hunks)
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (3 hunks)
🧰 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

Files:

  • apps/studio.giselles.ai/app/stage/(top)/settings-dialog.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.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)

Files:

  • apps/studio.giselles.ai/app/stage/(top)/settings-dialog.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx
**/*

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

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

Files:

  • apps/studio.giselles.ai/app/stage/(top)/settings-dialog.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.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:

  • apps/studio.giselles.ai/app/stage/(top)/settings-dialog.tsx
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx
🧬 Code graph analysis (1)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (3)
internal-packages/ui/components/popover.tsx (1)
  • PopoverContent (4-15)
internal-packages/ui/components/dialog.tsx (1)
  • DialogFooter (59-69)
apps/studio.giselles.ai/lib/utils.ts (1)
  • cn (4-6)
⏰ 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 (4)
apps/studio.giselles.ai/app/stage/(top)/settings-dialog.tsx (1)

246-255: Cancel-only footer matches the new URL-synced behavior—LGTM

Removing “Continue” is consistent with immediate application on radio change and reduces confusion. The sticky DialogFooter keeps the affordance visible.

apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (3)

84-91: Open-state gating between dropdown and dialog is solid

Disabling the dropdown while the dialog is open avoids focus and layering conflicts; this is a clean solution.


121-126: z-index bump on menu content fixes stacking—LGTM

Using z-50 on Content addresses the reported stacking issue over form containers and aligns with the PR objective.


63-63: Verified: Project uses React 19.1.1 and Next 15.3.4, which support use() in Client Components

  • The pnpm lockfile shows React v19.1.1 and React-DOM v19.1.1.
  • Next.js is pinned at v15.3.4.
  • React 19 includes the new use() hook (allowing reading promises or contexts in client components) and integrates with Suspense and error boundaries (blog.saeloun.com)

No further action required—keeping const user = use(userPromise); in a Client Component is safe with these versions.

kaochannel154 and others added 2 commits August 25, 2025 22:09
…dialogs

- Removed Cancel button from DialogFooter in both dialogs
- Kept X close button in dialog header for closing
- Applied changes to both settings-dialog.tsx and navigation-rail-footer-menu.tsx

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

Co-Authored-By: Claude <noreply@anthropic.com>
…tent

- Changed dialog overlay z-index from z-50 to z-[9999]
- Changed dialog content z-index from z-50 to z-[10000]
- Applied to both shared dialog component and mobile settings dialog
- Ensures dialogs appear above all other page elements

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

Co-Authored-By: Claude <noreply@anthropic.com>
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)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (1)

1-1: Client directive added — good catch and resolution

The "use client" directive addresses the prior build-time issue flagged in earlier review comments. No further action needed here.

🧹 Nitpick comments (6)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (6)

93-116: Add an accessible name to the trigger button

In collapsed mode the trigger shows only an avatar, so screen readers have no accessible name. Add aria-label to the button to improve a11y.

-				<button
+				<button
+					aria-label="Open user menu"
 					className="w-full hover:bg-ghost-element-hover h-full rounded-md cursor-pointer outline-none p-1.5 flex items-center gap-2"
 					type="button"
 				>

163-185: Harden external links, avoid blank tab on mailto

  • Use rel="noopener noreferrer" for external links to prevent reverse-tabnabbing and avoid leaking referrer.
  • For mailto links, opening a new tab is undesirable; render without target/rel.
-												{item.external ? (
+												{item.external ? (
 													<a
 														href={item.href}
-														target="_blank"
-														rel="noopener"
+														target={item.href.startsWith("mailto:") ? undefined : "_blank"}
+														rel={item.href.startsWith("mailto:") ? undefined : "noopener noreferrer"}
 														className="w-full flex items-center justify-between"
 													>

202-212: Add noreferrer to Homepage external link

Small security/privacy tweak for consistency with the Help items.

 							<a
 								href="https://giselles.ai"
 								target="_blank"
-								rel="noopener"
+								rel="noopener noreferrer"
 								className="w-full flex items-center justify-between"
 							>

218-220: Avoid nested interactive elements inside Radix Item

Wrap SignOutButton with asChild to delegate focus/keyboard handling to your button and avoid nested interactive semantics.

-						<DropdownMenuPrimitive.Item className={MENU_ITEM_CLASS}>
-							<SignOutButton className="text-[14px]">Log out</SignOutButton>
-						</DropdownMenuPrimitive.Item>
+						<DropdownMenuPrimitive.Item className={MENU_ITEM_CLASS} asChild>
+							<SignOutButton className="w-full text-[14px]">Log out</SignOutButton>
+						</DropdownMenuPrimitive.Item>

232-238: Add an accessible name to the dialog close button

Screen readers should announce the intent of the X button.

-						<button
+						<button
 							type="button"
+							aria-label="Close dialog"
 							onClick={() => setIsDisplayDialogOpen(false)}
 							className="p-1 rounded-lg hover:bg-white/10 transition-colors"
 						>

70-81: Wrap router.push in a transition to reduce perceived latency

Updating search params triggers a navigation and can feel janky. Mark the update as a transition so React keeps the UI responsive while the route refreshes. This may address the “takes a while to load” UX comment without changing your URL-driven state design.

-const [isDisplayDialogOpen, setIsDisplayDialogOpen] = useState(false);
-const [dropdownOpen, setDropdownOpen] = useState(false);
+const [isDisplayDialogOpen, setIsDisplayDialogOpen] = useState(false);
+const [dropdownOpen, setDropdownOpen] = useState(false);
+const [isPending, startTransition] = useTransition();
-			router.push(`?${params.toString()}`, { scroll: false });
+			startTransition(() => {
+				router.push(`?${params.toString()}`, { scroll: false });
+			});

And add the import:

-import { use, useCallback, useState } from "react";
+import { use, useCallback, useState, useTransition } from "react";

Also applies to: 67-69, 15-15

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bde54f0 and e03c935.

📒 Files selected for processing (2)
  • apps/studio.giselles.ai/app/stage/(top)/settings-dialog.tsx (0 hunks)
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (3 hunks)
💤 Files with no reviewable changes (1)
  • apps/studio.giselles.ai/app/stage/(top)/settings-dialog.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

Files:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.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)

Files:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx
**/*

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

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

Files:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.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:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx
🧬 Code graph analysis (1)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (1)
internal-packages/ui/components/popover.tsx (1)
  • PopoverContent (4-15)
⏰ 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: Cursor Bugbot
  • GitHub Check: check
🔇 Additional comments (6)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (6)

30-55: Good extraction and reuse of menu constants

Pulling HELP_ITEMS and MENU_ITEM_CLASS to module scope with as const improves readability, type safety, and consistency across the menu. Nice cleanup.


119-126: z-index bump resolves stacking conflicts

Switching the dropdown content to z-50 is the right fix for the overlap with form containers. Verified alignment logic per variant looks sane.


138-148: Nice interplay: closing dropdown before opening dialog

Explicitly closing the menu before opening the Display Type dialog prevents focus traps and interaction conflicts. Solid UX detail.


241-304: URL-backed View Style selection looks correct

  • Radio reflects isCarouselView.
  • onValueChange updates the URL immediately via setIsCarouselView.
  • Border highlight logic matches the current selection.
    This achieves the PR goal of synchronizing the dialog with main content and browser history.

226-239: Note on product decision: dialog close control

The code currently includes an explicit X close button, which differs from the PR thread that mentioned removing the X. If keeping the X is intentional after reconsideration, ignore this; otherwise confirm alignment with the latest UX decision.


63-67: Action Required: Verify React Version and Suspense Boundary

  • apps/studio.giselles.ai/package.json
    – React dependency resolves to “catalog:” (no concrete version)
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx
    – Uses "use client" and use(userPromise) but no <Suspense> wrapper was detected around this component or its consumers

Please confirm that your workspace is running React 19 or newer (required for use() in Client Components) and that a <Suspense> boundary surrounds the tree where <NavigationRailFooterMenu> is rendered.

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: 1

🧹 Nitpick comments (2)
internal-packages/ui/components/dialog.tsx (2)

21-22: Optional: use viewport-relative max-heights for more predictable sizing

Percentages on a fixed-position element are relative to the viewport in most cases, but vh communicates intent more clearly and avoids surprises in nested contexts.

-          "data-[size=default]:w-[500px] data-[size=default]:max-h-[75%]",
-          "data-[size=wide]:w-[800px] data-[size=wide]:max-h-[85%]",
+          "data-[size=default]:w-[500px] data-[size=default]:max-h-[75vh]",
+          "data-[size=wide]:w-[800px] data-[size=wide]:max-h-[85vh]",

16-16: Centralize z-index layering to replace magic values

We ran an inventory of z-index usages across TS/TSX/CSS and found numerous hard-coded values—e.g. z-0, z-10, z-50, z-[9999], z-[10000], and even z-2147483647—leading to inconsistent stacking and potential conflicts (see script output).

Rather than bumping to z-[9999] for one overlay, it’s safer to define a shared set of z-index tokens in your Tailwind config so every layer is predictable:

• In tailwind.config.js under theme.extend.zIndex:

module.exports = {
  theme: {
    extend: {
      zIndex: {
        navigation: 50,
        popover:    60,
        dropdown:   70,
        'dialog-overlay': 80,
        dialog:     90,
      }
    }
  }
}

• Update the dialog component (internal-packages/ui/components/dialog.tsx):

- <DialogPrimitive.Overlay className="fixed inset-0 bg-black/60 z-[9999]" />
+ <DialogPrimitive.Overlay className="fixed inset-0 bg-black/60 z-dialog-overlay" />

- <DialogPrimitive.Content
-   className="fixed left-[50%] top-[50%] … z-[10000] overflow-y-auto …"
- >
+ <DialogPrimitive.Content
+   className="fixed left-[50%] top-[50%] … z-dialog overflow-y-auto …"
+ >

• (Optional) If you prefer CSS variables, you can mirror these tokens with --z-dialog-overlay and --z-dialog, but Tailwind theme tokens integrate more naturally with existing classnames.

By centralizing z-index values, you’ll prevent future “stacking wars” (e.g. tooltips vs. dropdowns vs. modals) and make it trivial to adjust layering globally. I’m happy to help migrate this across the codebase or open a follow-up PR—just let me know!

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e03c935 and cc91ce6.

📒 Files selected for processing (2)
  • apps/studio.giselles.ai/app/stage/(top)/settings-dialog.tsx (1 hunks)
  • internal-packages/ui/components/dialog.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/studio.giselles.ai/app/stage/(top)/settings-dialog.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

Files:

  • internal-packages/ui/components/dialog.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)

Files:

  • internal-packages/ui/components/dialog.tsx
**/*

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

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

Files:

  • internal-packages/ui/components/dialog.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/ui/components/dialog.tsx

Comment on lines +20 to 22
"fixed left-[50%] top-[50%] translate-y-[-50%] translate-x-[-50%] z-[10000] overflow-y-auto overflow-x-hidden outline-none",
"data-[size=default]:w-[500px] data-[size=default]:max-h-[75%]",
"data-[size=wide]:w-[800px] data-[size=default]:max-h-[85%]",
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

Bug: data-[size=default] mistakenly used twice; wide max-height is never applied

On Line 22, data-[size=default]:max-h-[85%] looks like a typo. It overrides the default’s earlier max-h-[75%] and leaves the wide variant without its intended max-h. This will change the default dialog’s height and make wide behave the same as default.

Apply this diff to fix the variant:

-          "data-[size=default]:w-[500px] data-[size=default]:max-h-[75%]",
-          "data-[size=wide]:w-[800px] data-[size=default]:max-h-[85%]",
+          "data-[size=default]:w-[500px] data-[size=default]:max-h-[75%]",
+          "data-[size=wide]:w-[800px] data-[size=wide]:max-h-[85%]",
📝 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
"fixed left-[50%] top-[50%] translate-y-[-50%] translate-x-[-50%] z-[10000] overflow-y-auto overflow-x-hidden outline-none",
"data-[size=default]:w-[500px] data-[size=default]:max-h-[75%]",
"data-[size=wide]:w-[800px] data-[size=default]:max-h-[85%]",
"fixed left-[50%] top-[50%] translate-y-[-50%] translate-x-[-50%] z-[10000] overflow-y-auto overflow-x-hidden outline-none",
"data-[size=default]:w-[500px] data-[size=default]:max-h-[75%]",
"data-[size=wide]:w-[800px] data-[size=wide]:max-h-[85%]",
🤖 Prompt for AI Agents
In internal-packages/ui/components/dialog.tsx around lines 20 to 22, the
tailwind variant on line 22 mistakenly uses data-[size=default]:max-h-[85%]
which overrides the earlier default max-h and prevents the wide variant from
getting its intended max-h; change that token to data-[size=wide]:max-h-[85%] so
default keeps max-h-[75%] and wide gets max-h-[85%].

- Removed unused DialogFooter imports from both dialog files
- Removed unused cn utility and buttonVariants imports
- Fixed formatting by removing extra blank lines
- No design changes, only linting fixes

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

Co-Authored-By: Claude <noreply@anthropic.com>
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

Caution

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

⚠️ Outside diff range comments (1)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (1)

53-60: Avoid suspending the entire footer on userPromise; move use(userPromise) under Suspense to fix slow load UX

Calling use(userPromise) at the top-level suspends the whole menu until user resolves, which aligns with the “slow load/bad UX” comment. Wrap only the trigger’s user-dependent UI in a Suspense boundary and resolve the promise inside that subtree. This keeps the rest (menu/open-state, z-index, etc.) interactive immediately.

Apply these diffs:

-import { use, useCallback, useState } from "react";
+import { Suspense, use, useCallback, useState } from "react";
-  const user = use(userPromise);
-      <DropdownMenuPrimitive.Trigger asChild>
-        <button
-          className="w-full hover:bg-ghost-element-hover h-full rounded-md cursor-pointer outline-none p-1.5 flex items-center gap-2"
-          type="button"
-        >
-          <div className="size-8 flex items-center justify-center shrink-0">
-            <AvatarImage
-              className="rounded-full"
-              avatarUrl={user.avatarUrl ?? null}
-              width={24}
-              height={24}
-              alt={user.displayName || user.email || "User"}
-            />
-          </div>
-          {variant === "expanded" && (
-            <div className="flex flex-col min-w-0 flex-1 text-left">
-              <p className="truncate text-text-muted text-sm">
-                {user.displayName ?? user.email}
-              </p>
-              <p className="truncate text-[var(--color-stage-accent)] text-[10px]">
-                {user.planName}
-              </p>
-            </div>
-          )}
-        </button>
-      </DropdownMenuPrimitive.Trigger>
+      <DropdownMenuPrimitive.Trigger asChild>
+        <Suspense
+          fallback={
+            <button
+              className="w-full hover:bg-ghost-element-hover h-full rounded-md cursor-pointer outline-none p-1.5 flex items-center gap-2"
+              type="button"
+            >
+              <div className="size-8 rounded-full bg-white/10 animate-pulse" />
+              {variant === "expanded" && (
+                <div className="flex flex-col min-w-0 flex-1 text-left">
+                  <div className="h-4 w-24 bg-white/10 rounded animate-pulse mb-1" />
+                  <div className="h-3 w-12 bg-white/10 rounded animate-pulse" />
+                </div>
+              )}
+            </button>
+          }
+        >
+          <UserTrigger userPromise={userPromise} variant={variant} />
+        </Suspense>
+      </DropdownMenuPrimitive.Trigger>

Add this small internal component (anywhere in the file, e.g., below the export):

function UserTrigger({
  userPromise,
  variant,
}: {
  userPromise: Promise<UserDataForNavigationRail>;
  variant: NavigationRailState;
}) {
  const user = use(userPromise);
  return (
    <button
      className="w-full hover:bg-ghost-element-hover h-full rounded-md cursor-pointer outline-none p-1.5 flex items-center gap-2"
      type="button"
    >
      <div className="size-8 flex items-center justify-center shrink-0">
        <AvatarImage
          className="rounded-full"
          avatarUrl={user.avatarUrl ?? null}
          width={24}
          height={24}
          alt={user.displayName || user.email || "User"}
        />
      </div>
      {variant === "expanded" && (
        <div className="flex flex-col min-w-0 flex-1 text-left">
          <p className="truncate text-text-muted text-sm">
            {user.displayName ?? user.email}
          </p>
          {user.planName ? (
            <p className="truncate text-[var(--color-stage-accent)] text-[10px]">
              {user.planName}
            </p>
          ) : null}
        </div>
      )}
    </button>
  );
}

Also applies to: 90-114

♻️ Duplicate comments (1)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (1)

1-1: Good catch: Client Component directive is present

The prior issue is resolved; the file correctly opts into client mode.

🧹 Nitpick comments (5)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (5)

27-52: Nice extraction of constants; minor naming nit

Pulling HELP_ITEMS and MENU_ITEM_CLASS up improves readability and reuse. Small naming nit: per our guidelines, variables should use camelCase. Consider renaming MENU_ITEM_CLASS -> menuItemClass for consistency.

Apply this minimal change and update references:

-const MENU_ITEM_CLASS =
+const menuItemClass =
 	"text-text outline-none cursor-pointer hover:bg-ghost-element-hover rounded-[4px] px-[8px] py-[6px] text-[14px]";

Example usage updates:

- className={`${MENU_ITEM_CLASS} flex items-center justify-between`}
+ className={`${menuItemClass} flex items-center justify-between`}

67-77: Avoid pushing a bare “?” when removing the view param; include pathname

When params become empty, router.push(\?${params}`)` yields the path with a trailing “?”. Use pathname to push a clean URL and preserve any future hash.

-import { useRouter, useSearchParams } from "next/navigation";
+import { usePathname, useRouter, useSearchParams } from "next/navigation";
-      router.push(`?${params.toString()}`, { scroll: false });
+      const pathname = usePathname();
+      const qs = params.toString();
+      router.push(qs ? `${pathname}?${qs}` : pathname, { scroll: false });

167-176: Harden external links: add noreferrer

For target="_blank" links, include rel="noopener noreferrer" to prevent referrer leakage and strengthen security.

-  rel="noopener"
+  rel="noopener noreferrer"

Also applies to: 199-209


215-217: Avoid interactive-in-interactive; prefer asChild or handle via onSelect

Wrapping a button inside a Radix Menu.Item (role=menuitem) can produce nested interactive semantics. Prefer making the Item adopt the button via asChild, provided SignOutButton forwards refs and passes props, or handle the action from the Item’s onSelect.

Option A (preferred, if SignOutButton supports it):

-<DropdownMenuPrimitive.Item className={MENU_ITEM_CLASS}>
-  <SignOutButton className="text-[14px]">Log out</SignOutButton>
-</DropdownMenuPrimitive.Item>
+<DropdownMenuPrimitive.Item className={menuItemClass} asChild>
+  <SignOutButton className="text-[14px]">Log out</SignOutButton>
+</DropdownMenuPrimitive.Item>

Option B (fallback):

<DropdownMenuPrimitive.Item
  className={menuItemClass}
  onSelect={(e) => {
    e.preventDefault();
    // call sign-out here or expose a callback prop from SignOutButton
  }}
>
  Log out
</DropdownMenuPrimitive.Item>

303-318: Disabled Font selector: add a short hint or tooltip?

Minor UX polish: consider a helper text or title attribute explaining “Coming Soon” so keyboard/AT users get feedback.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cc91ce6 and 97dbced.

📒 Files selected for processing (2)
  • apps/studio.giselles.ai/app/stage/(top)/settings-dialog.tsx (1 hunks)
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/studio.giselles.ai/app/stage/(top)/settings-dialog.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

Files:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.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)

Files:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx
**/*

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

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

Files:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.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:

  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx
🧬 Code graph analysis (1)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (1)
internal-packages/ui/components/popover.tsx (1)
  • PopoverContent (4-15)
⏰ 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 (5)
apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (5)

81-88: Dropdown/Dialog coordination looks solid

Guarding open state with !isDisplayDialogOpen prevents focus/interaction conflicts between the menu and dialog.


118-123: Layering and submenu width addressed

Raising z-index to 50 and constraining Help submenu to 180px should resolve prior overlay and spacing issues.

Also applies to: 156-158


225-236: Inconsistent with PR comments: Close (X) still present in the dialog header

PR thread says both “Continue” and “Close (X)” were removed. The X button is still rendered. If you intend to remove manual close, rely on outside click/Esc via onOpenChange.

-  <div className="flex items-center justify-between mb-6">
-    <DialogTitle className="text-[20px] font-medium text-white-400 tracking-tight font-sans">
-      View Style
-    </DialogTitle>
-    <button
-      type="button"
-      onClick={() => setIsDisplayDialogOpen(false)}
-      className="p-1 rounded-lg hover:bg-white/10 transition-colors"
-    >
-      <X className="w-5 h-5 text-white-400" />
-    </button>
-  </div>
+  <div className="flex items-center justify-between mb-6">
+    <DialogTitle className="text-[20px] font-medium text-white-400 tracking-tight font-sans">
+      View Style
+    </DialogTitle>
+  </div>

243-301: URL-backed View Style selection LGTM

RadioGroup reflects the view param and updates it immediately. Borders toggle correctly and removal of action buttons simplifies UX.


12-14: Verify React 19+ & Next.js compatibility for use() in Client Components

The app’s package.json under apps/studio.giselles.ai uses catalog: placeholders for both React and Next.js versions, so the actual installed versions aren’t visible here. The new use() hook in Client Components is only supported starting in React 19+ and requires a matching Next.js version (e.g. Next 14+). Please confirm your effective versions by inspecting the lockfile (e.g. pnpm-lock.yaml) or running:

pnpm list react next

If you’re not on React 19+ and a compatible Next.js release, these imports will break at runtime.

Review all occurrences of use() imports in your Client Components and adjust as needed:

  • apps/studio.giselles.ai/app/(main)/apps/page.tsx (line 4):
    import { Suspense, use } from "react";
  • apps/studio.giselles.ai/app/stage/acts/[actId]/ui/sidebar.tsx (line 23):
    import { use, useCallback, useEffect, useState } from "react";
  • apps/studio.giselles.ai/app/stage/ui/navigation-rail/navigation-rail-footer-menu.tsx (line 14):
    import { use, useCallback, useState } from "react";

If necessary, upgrade React/Next or refactor these hooks out until you’re on a supported version.

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! 🎨

Copy link
Contributor

@toyamarinyon toyamarinyon left a comment

Choose a reason for hiding this comment

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

Note

I've also written here the details of the synchronous communication.

Thank you for implementing various features into the Sidebar (Navigation Rail)! The Stage top page with the Sidebar requires quite complex data streaming and state management, and the current implementation is causing unintended delays, so I will rewrite the UI implemented in this pull request to make it work. I will create a new pull request, and close this one once it's merged.

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.

3 participants