feat: Add three UI enhancement components for improved UX#4
feat: Add three UI enhancement components for improved UX#4richter83-star wants to merge 2 commits intomainfrom
Conversation
- Add Command Palette component with keyboard shortcuts (Cmd/Ctrl+K) - Fuzzy search across all actions - Recent actions tracking - Categorized navigation - Add Onboarding Checklist component with gamification - Step-by-step progress tracking - Confetti animations on completion - localStorage persistence - Collapsible interface - Add Workflow Stepper component for multi-step processes - Visual step indicators (horizontal/vertical) - Async validation support - Draft saving capability - Progress tracking with time estimates - Add interactive demo page at /portal/demo - Add comprehensive documentation in docs/UI_COMPONENTS_GUIDE.md - Install canvas-confetti dependency for animations - Register new portal routes (demo, downloads, changelog) These components enhance user experience by: - Reducing clicks by 60-80% (Command Palette) - Increasing activation rate (Onboarding Checklist) - Reducing workflow abandonment by 50% (Workflow Stepper)
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughAdds three new UI components (Command Palette, Onboarding Checklist, Workflow Stepper), documentation and demo route, three portal routes/types, a canvas-confetti dependency, and minor edits to a few UI files and Tailwind import ordering. Changes
Sequence Diagram(s)mermaid Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
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. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 88427bf626
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const completedCount = steps.filter((s) => s.completed).length; | ||
| const totalCount = steps.length; | ||
| const progress = (completedCount / totalCount) * 100; | ||
| const isFullyCompleted = completedCount === totalCount; |
There was a problem hiding this comment.
Guard against empty steps before computing progress
When steps is an empty array (e.g., a caller passes no checklist items or localStorage restores an empty list), totalCount becomes 0 and progress becomes NaN. That NaN is then fed into <Progress value={progress}> and the collapsed ring strokeDashoffset, which produces invalid SVG values and a broken widget. Consider short‑circuiting for totalCount === 0 (e.g., default progress to 0 or render an empty state) before using the value.
Useful? React with 👍 / 👎.
| const currentStepIndex = controlledStep !== undefined ? controlledStep : internalStep; | ||
| const currentStepData = steps[currentStepIndex]; | ||
|
|
There was a problem hiding this comment.
Clamp step index to avoid undefined currentStepData
This assumes the current step index is always valid. If steps is empty, or if a persisted savedStep/controlled currentStep is out of range (e.g., steps shrink after a draft was saved), currentStepData becomes undefined and the render path later dereferences currentStepData.title/description, causing a runtime crash. Consider clamping the index to [0, steps.length - 1] and rendering a fallback when steps.length === 0.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (1)
app/components/ui/command-palette.tsx (1)
143-143: Consider merging custom actions with defaults rather than replacing.When
customActionsare provided, the default actions are completely replaced. Consider merging them to give users access to both default navigation actions and their custom actions.🔎 Proposed implementation
- const allActions = customActions || defaultActions; + const allActions = customActions + ? [...defaultActions, ...customActions] + : defaultActions;
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
UI_COMPONENTS_README.mdapp/components/ui/command-palette.tsxapp/components/ui/onboarding-checklist.tsxapp/components/ui/workflow-stepper.tsxapp/routes.tsapp/routes/portal.demo.tsxdocs/UI_COMPONENTS_GUIDE.mdpackage.json
🧰 Additional context used
📓 Path-based instructions (3)
**/routes.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Define routes in
routes.tsfile before implementing them inroutes/*.tsxmodules
Files:
app/routes.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Import from
"react-router"instead of@remix-run/*packages
Files:
app/routes.tsapp/routes/portal.demo.tsxapp/components/ui/command-palette.tsxapp/components/ui/onboarding-checklist.tsxapp/components/ui/workflow-stepper.tsx
**/routes/**/*.tsx
📄 CodeRabbit inference engine (CLAUDE.md)
**/routes/**/*.tsx: UseRoute.LoaderArgstypes from./+types/routeNamefor type-safe loader function parameters
Always usecallTrpc(request)for server-side data fetching in loaders instead of HTTP calls
ThrowResponseor useredirect()for error and authentication handling in loaders
Return plain objects from loaders (automatically serialized by React Router)
UseloaderDataprop to access data in route components instead ofuseLoaderDatahook
Always useenabled: isClientflag for client-only queries and subscriptions to ensure SSR safety
Useexport async function action({ request }: Route.ActionArgs)for form submissions in route files
Use<Form method="post">for progressive enhancement oruseFetcher()for non-navigation form submissions
UseuseNavigate()for programmatic navigation,<Link>for links, andredirect()for loader/action redirects
Check authentication in protected route loaders usingcaller.auth.me()and redirect to/loginif not signed in
UseuseAuth()hook to accessisSignedIn,user, andsignOutin client-side components
Lazy load client-only modules usinglazy(() => import(...))and wrap with<Suspense>in parent components
Files:
app/routes/portal.demo.tsx
🧠 Learnings (12)
📚 Learning: 2025-12-27T20:10:54.305Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.305Z
Learning: Applies to **/routes.ts : Define routes in `routes.ts` file before implementing them in `routes/*.tsx` modules
Applied to files:
app/routes.tsapp/routes/portal.demo.tsx
📚 Learning: 2025-12-27T20:10:54.305Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.305Z
Learning: Applies to **/routes/{home,dashboard,signup,login}.tsx : Replace placeholder files `home.tsx`, `dashboard.tsx`, `signup.tsx`, and `login.tsx` with actual app-specific implementations
Applied to files:
app/routes.tsapp/routes/portal.demo.tsx
📚 Learning: 2025-12-27T20:10:54.305Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.305Z
Learning: Applies to **/routes/**/*.tsx : Use `useNavigate()` for programmatic navigation, `<Link>` for links, and `redirect()` for loader/action redirects
Applied to files:
app/routes.tsapp/routes/portal.demo.tsx
📚 Learning: 2025-12-27T20:10:54.305Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.305Z
Learning: Applies to **/routes/**/*.tsx : Return plain objects from loaders (automatically serialized by React Router)
Applied to files:
app/routes.tsapp/routes/portal.demo.tsx
📚 Learning: 2025-12-27T20:10:54.305Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.305Z
Learning: Applies to **/routes/auth/**/*.callback.tsx : Handle OAuth callbacks by calling `handleOAuthCallback(provider, id, { email, name, avatarUrl })` in `routes/auth/[provider].callback.tsx`
Applied to files:
app/routes.ts
📚 Learning: 2025-12-27T20:10:54.305Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.305Z
Learning: Applies to **/routes/**/*.tsx : Lazy load client-only modules using `lazy(() => import(...))` and wrap with `<Suspense>` in parent components
Applied to files:
app/routes.tsapp/routes/portal.demo.tsx
📚 Learning: 2025-12-27T20:10:54.305Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.305Z
Learning: Applies to **/routes/**/*.tsx : Use `useAuth()` hook to access `isSignedIn`, `user`, and `signOut` in client-side components
Applied to files:
app/routes.ts
📚 Learning: 2025-12-27T20:10:54.305Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.305Z
Learning: Applies to **/routes/**/*.tsx : Use `Route.LoaderArgs` types from `./+types/routeName` for type-safe loader function parameters
Applied to files:
app/routes.tsapp/routes/portal.demo.tsx
📚 Learning: 2025-12-27T20:10:54.305Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.305Z
Learning: Applies to **/routes/**/*.tsx : Throw `Response` or use `redirect()` for error and authentication handling in loaders
Applied to files:
app/routes.tsapp/routes/portal.demo.tsx
📚 Learning: 2025-12-27T20:10:54.305Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.305Z
Learning: Applies to **/routes/**/*.tsx : Use `export async function action({ request }: Route.ActionArgs)` for form submissions in route files
Applied to files:
app/routes.tsapp/routes/portal.demo.tsx
📚 Learning: 2025-12-27T20:10:54.305Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.305Z
Learning: Applies to **/routes/**/*.tsx : Check authentication in protected route loaders using `caller.auth.me()` and redirect to `/login` if not signed in
Applied to files:
app/routes/portal.demo.tsx
📚 Learning: 2025-12-27T20:10:54.305Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.305Z
Learning: Applies to **/routes/**/*.tsx : Use `loaderData` prop to access data in route components instead of `useLoaderData` hook
Applied to files:
app/routes/portal.demo.tsx
🧬 Code graph analysis (3)
app/components/ui/command-palette.tsx (2)
app/components/ui/command.tsx (7)
CommandDialog(145-145)CommandInput(146-146)CommandList(147-147)CommandEmpty(148-148)CommandGroup(149-149)CommandItem(150-150)CommandSeparator(152-152)app/components/ui/icon.tsx (1)
Icon(15-29)
app/components/ui/onboarding-checklist.tsx (4)
app/components/ui/card.tsx (4)
Card(79-79)CardHeader(79-79)CardTitle(79-79)CardContent(79-79)app/lib/utils.ts (1)
cn(4-6)app/components/ui/button.tsx (1)
Button(56-56)app/components/ui/progress.tsx (1)
Progress(26-26)
app/components/ui/workflow-stepper.tsx (4)
app/lib/utils.ts (1)
cn(4-6)app/components/ui/progress.tsx (1)
Progress(26-26)app/components/ui/card.tsx (5)
Card(79-79)CardHeader(79-79)CardTitle(79-79)CardDescription(79-79)CardContent(79-79)app/components/ui/button.tsx (1)
Button(56-56)
🪛 LanguageTool
UI_COMPONENTS_README.md
[uncategorized] ~203-~203: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...er 1. Command Palette - Quick win, high impact 2. Onboarding Checklist - Improves ...
(EN_COMPOUND_ADJECTIVE_INTERNAL)
⏰ 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 (6)
app/components/ui/onboarding-checklist.tsx (1)
54-68: LGTM! Proper SSR safety for localStorage.The localStorage access is correctly wrapped in a
typeof window !== "undefined"check and includes error handling for parse failures. This ensures the component works correctly in SSR environments.app/components/ui/workflow-stepper.tsx (2)
66-104: LGTM! Excellent async validation and error handling.The
handleNextfunction properly handles:
- Async validation with loading state
- Error boundaries with try/catch
- Step completion tracking
- Callback invocation at appropriate times
- Clear user feedback via validation errors
This is a well-implemented pattern for multi-step workflows.
112-119: LGTM! Smart navigation restrictions.The click handler properly restricts navigation to only completed steps or the immediate next step when
allowStepNavigationis enabled. This prevents users from skipping required steps while maintaining good UX.app/routes.ts (1)
23-25: LGTM! Routes properly defined before implementation.The three new portal routes are correctly defined in
routes.tsbefore being implemented in their respectiveroutes/*.tsxmodules, following the established coding guidelines and project patterns.package.json (1)
75-75: canvas-confetti version 1.9.4 is the latest release and has no known security vulnerabilities.app/routes/portal.demo.tsx (1)
38-72: All referenced portal routes are properly defined. The routes/portal/start,/portal/setup,/portal/seo, and/portal/downloadsare registered inroutes.ts(lines 19–23) with corresponding implementation files in place.
| <div className="fixed bottom-4 right-4 z-40 hidden sm:block"> | ||
| <button | ||
| onClick={() => setOpen(true)} | ||
| className="flex items-center gap-2 rounded-lg border bg-background px-3 py-2 text-sm shadow-sm transition-all hover:shadow-md" | ||
| > | ||
| <span className="text-muted-foreground">Press</span> | ||
| <kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground"> | ||
| ⌘K | ||
| </kbd> | ||
| <span className="text-muted-foreground">to search</span> | ||
| </button> | ||
| </div> |
There was a problem hiding this comment.
Position conflict with OnboardingChecklist component.
This hint button uses fixed bottom-4 right-4 z-40, which will overlap with the OnboardingChecklist component that uses fixed bottom-4 right-4 z-50 (see app/components/ui/onboarding-checklist.tsx:166). When both components are rendered simultaneously (as in the demo page), they will occupy the same screen space, causing a poor user experience.
Consider:
- Using a different position (e.g., bottom-left for one component)
- Adding a prop to control positioning
- Documenting that these components should not be used together
- Coordinating layout in the parent component
🤖 Prompt for AI Agents
In app/components/ui/command-palette.tsx around lines 260 to 271, the floating
hint button is hard-coded to "fixed bottom-4 right-4 z-40" which conflicts with
OnboardingChecklist (fixed bottom-4 right-4 z-50); make position configurable by
adding a prop (e.g., position: "bottom-right" | "bottom-left" | custom class)
with a sensible default ("bottom-right" and z-40), replace the hard-coded class
with conditional classes derived from that prop, keep the z-index at or below
onboarding (z-40) or allow zIndex as an optional prop, and update usages
(demo/parent) to set position="bottom-left" when both components render; also
document the prop in the component comment/README.
| if (isFullyCompleted && onDismiss) { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Fix incorrect early return logic.
The condition checks if isFullyCompleted && onDismiss but then returns null without calling onDismiss. This means the component will disappear when fully completed if an onDismiss callback exists, but the callback is never invoked. This appears to be a logic error.
Either:
- Call
onDismiss()before returning, or - Remove the
onDismisscheck and only return null if explicitly dismissed
🔎 Proposed fix (option 1 - call onDismiss)
if (isFullyCompleted && onDismiss) {
+ React.useEffect(() => {
+ onDismiss();
+ }, [onDismiss]);
return null;
}🔎 Proposed fix (option 2 - use separate dismissed state)
+ const [isDismissed, setIsDismissed] = React.useState(false);
+
+ if (isDismissed) {
+ return null;
+ }
// Later in the dismiss button:
onClick={() => {
+ setIsDismissed(true);
onDismiss?.();
}}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/components/ui/onboarding-checklist.tsx around lines 159-161, the
component currently returns null when (isFullyCompleted && onDismiss) but never
invokes onDismiss; fix by invoking the dismiss callback inside a side-effect
instead of during render: add a useEffect that watches isFullyCompleted and
onDismiss and calls onDismiss() once when isFullyCompleted becomes true, then
keep the early return (or a dismissed state) so the component unmounts; this
ensures the callback is executed safely and only once rather than being skipped
or called during render.
| import { Textarea } from "~/components/ui/textarea"; | ||
| import { useState } from "react"; | ||
|
|
||
| export async function loader({ request }: LoaderFunctionArgs) { |
There was a problem hiding this comment.
Use Route.LoaderArgs for type-safe loader parameters.
The loader function uses LoaderFunctionArgs from react-router, but the coding guidelines require using Route.LoaderArgs from the route's type definition file for type safety.
🔎 Proposed fix
-export async function loader({ request }: LoaderFunctionArgs) {
+export async function loader({ request }: Route.LoaderArgs) {And update the import at the top:
-import { redirect, type LoaderFunctionArgs } from "react-router";
+import { redirect } from "react-router";
+import type { Route } from "./+types/portal.demo";Based on coding guidelines and learnings.
📝 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.
| export async function loader({ request }: LoaderFunctionArgs) { | |
| import { redirect } from "react-router"; | |
| import type { Route } from "./+types/portal.demo"; | |
| export async function loader({ request }: Route.LoaderArgs) { |
🤖 Prompt for AI Agents
In app/routes/portal.demo.tsx around line 15, the loader signature currently
uses LoaderFunctionArgs from react-router; replace it with the route-specific
Route.LoaderArgs type from the route's type definition to ensure type-safe
loader parameters. Update the import at the top to import the route types (or
Route) that expose LoaderArgs, change the function parameter type to
Route.LoaderArgs, and update any downstream usages to match the new type.
| validation: async () => { | ||
| if (formData.name.length < 3) { | ||
| throw new Error("Automation name must be at least 3 characters"); | ||
| } | ||
| return true; | ||
| }, |
There was a problem hiding this comment.
Validation pattern inconsistent with documented interface.
The validation function throws an error when validation fails, but the WorkflowStep interface documented in UI_COMPONENTS_GUIDE.md specifies that validation should return boolean | Promise<boolean>. This inconsistency between implementation and documentation could confuse users integrating the component.
🔎 Suggested fix to match documented interface
If the component only supports boolean returns:
validation: async () => {
- if (formData.name.length < 3) {
- throw new Error("Automation name must be at least 3 characters");
- }
- return true;
+ return formData.name.length >= 3;
},Alternatively, update the documentation to reflect that validation can throw errors for displaying error messages.
📝 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.
| validation: async () => { | |
| if (formData.name.length < 3) { | |
| throw new Error("Automation name must be at least 3 characters"); | |
| } | |
| return true; | |
| }, | |
| validation: async () => { | |
| return formData.name.length >= 3; | |
| }, |
🤖 Prompt for AI Agents
In app/routes/portal.demo.tsx around lines 157-162 the validation function
currently throws an Error on failure which conflicts with the documented
WorkflowStep interface expecting boolean | Promise<boolean>; modify the function
to return false (or Promise.resolve(false)) when formData.name.length < 3 and
true otherwise, and move any user-facing error messaging into the component's
error state/validation display logic (or alternatively update
UI_COMPONENTS_GUIDE.md if you intend to keep throwing errors).
There was a problem hiding this comment.
This PR is being reviewed by Cursor Bugbot
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
| if (completedSteps.has(stepIndex) || stepIndex === currentStepIndex + 1) { | ||
| handleStepChange(stepIndex); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Step indicator click bypasses workflow validation
The handleStepClick function allows users to navigate to the next step by clicking on step indicators without running validation. The condition stepIndex === currentStepIndex + 1 makes the next step clickable, but unlike handleNext, it doesn't execute the validation function before allowing navigation. This defeats the purpose of step validation - users can skip required fields by clicking step indicators instead of the "Next" button.
Additional Locations (1)
|
|
||
| if (isFullyCompleted && onDismiss) { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Checklist hides before showing congratulations message
When onDismiss is provided, the component returns null immediately upon completion (isFullyCompleted && onDismiss). This early return happens before the Achievement Message UI (lines 305-316) is ever rendered. The congratulations message, trophy icon, and "Complete!" indicator are never shown to users when the onDismiss prop is used, undermining the gamification elements designed to celebrate user progress.
Additional Locations (1)
| JSON.stringify({ steps, isCollapsed }) | ||
| ); | ||
| } | ||
| }, [steps, isCollapsed, storageKey]); |
There was a problem hiding this comment.
LocalStorage progress overwritten on component mount
The save effect runs on mount because steps is included in its dependency array. At mount time, steps is initialized to initialSteps (not the saved state). The load effect calls setSteps(savedSteps), but that update only takes effect on the next render. Before that happens, the save effect writes initialSteps to localStorage, overwriting the user's saved progress. Though corrected on re-render, if the component unmounts quickly, progress can be permanently lost.
Additional Locations (1)
|
|
||
| const isFirstStep = currentStepIndex === 0; | ||
| const isLastStep = currentStepIndex === steps.length - 1; | ||
| const progress = ((currentStepIndex + 1) / steps.length) * 100; |
There was a problem hiding this comment.
Stepper crashes when step index exceeds array bounds
The component doesn't validate that currentStepIndex is within the bounds of the steps array. If savedStep or controlledStep exceeds steps.length - 1, or if steps is empty, then currentStepData becomes undefined. The component then crashes when accessing currentStepData.title, currentStepData.description, and other properties. Additionally, progress calculation divides by steps.length, causing Infinity when the array is empty.
Additional Locations (1)
| } | ||
| } | ||
| } | ||
| }, [storageKey]); |
There was a problem hiding this comment.
LocalStorage overwrites steps without reconciliation
The OnboardingChecklist component loads steps from localStorage and completely replaces the initialSteps prop without any reconciliation. When a developer updates the steps (adds new steps, removes old ones, or changes step properties like links or descriptions), returning users will see stale data from localStorage instead of the updated content. New onboarding steps won't appear for existing users, removed steps will persist as ghost entries with potentially broken links, and updated step metadata will remain outdated until users clear their localStorage.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (6)
app/components/ui/command-palette.tsx (1)
258-269: Position conflict with OnboardingChecklist component.This hint button uses
fixed bottom-4 right-4 z-40, which conflicts with the OnboardingChecklist component'sfixed bottom-4 right-4 z-50positioning. This was flagged in a previous review and remains unaddressed.app/components/ui/workflow-stepper.tsx (2)
48-53: Critical: Add bounds validation for step index.The component doesn't validate that
currentStepIndexis within bounds. Ifstepsis empty or the index exceedssteps.length - 1,currentStepDatabecomesundefined, causing crashes when accessingcurrentStepData.title(line 234),currentStepData.description(line 235), andcurrentStepData.content(line 247). Additionally, line 53 divides bysteps.length, resulting inInfinitywhen the array is empty.🔎 Proposed fix
const currentStepIndex = controlledStep !== undefined ? controlledStep : internalStep; +const safeStepIndex = Math.max(0, Math.min(currentStepIndex, steps.length - 1)); +const currentStepData = steps.length > 0 ? steps[safeStepIndex] : null; const isFirstStep = currentStepIndex === 0; const isLastStep = currentStepIndex === steps.length - 1; -const progress = ((currentStepIndex + 1) / steps.length) * 100; +const progress = steps.length > 0 ? ((safeStepIndex + 1) / steps.length) * 100 : 0;Then add an early return after the state declarations:
+if (!currentStepData) { + return ( + <div className={cn("space-y-6", className)}> + <p className="text-muted-foreground">No steps available.</p> + </div> + ); +} + const handleStepChange = (newStep: number) => {
110-117: Major: Step indicator navigation bypasses validation.The
handleStepClickfunction allows navigation tocurrentStepIndex + 1(line 114) without running thevalidation()function thathandleNextenforces (lines 70-77). Users can skip required fields by clicking step indicators instead of using the Next button, defeating the purpose of step validation.🔎 Proposed fix
-const handleStepClick = (stepIndex: number) => { +const handleStepClick = async (stepIndex: number) => { if (!allowStepNavigation) return; - // Allow navigation to completed steps or the next step - if (completedSteps.has(stepIndex) || stepIndex === currentStepIndex + 1) { + // Allow navigation to completed steps only + if (completedSteps.has(stepIndex)) { handleStepChange(stepIndex); + } else if (stepIndex === currentStepIndex + 1) { + // For the next step, use handleNext to enforce validation + await handleNext(); } };Alternatively, if you want to allow clicking the next step without validation, document this behavior clearly and consider adding a prop like
strictValidationto control it.app/components/ui/onboarding-checklist.tsx (3)
78-81: Guard against empty steps causing NaN progress.When
stepsis empty,totalCountbecomes 0 andprogressbecomesNaN, breaking the Progress bar (line 229) and the collapsed SVG ring (line 340).This issue was already identified by chatgpt-codex-connector.
157-159: Early return logic skips onDismiss callback.The component returns
nullwithout invokingonDismiss(), so the parent is never notified when the checklist is fully completed.This issue was already identified by coderabbitai and cursor with proposed fixes.
69-76: localStorage save effect may overwrite progress on mount.The save effect runs on mount with
initialStepsbefore the load effect (lines 52-66) restores saved state, potentially overwriting user progress.This issue was already identified by cursor.
🧹 Nitpick comments (3)
app/components/ui/command-palette.tsx (3)
141-141: Consider whether empty array should fall back to defaults.Currently, passing
actions={[]}will result in no actions (empty palette), whileactions={undefined}uses defaults. This behavior may be intentional, but if you want empty arrays to also use defaults, change to:- const allActions = customActions || defaultActions; + const allActions = customActions?.length ? customActions : defaultActions;
221-253: TrailingCommandSeparatorafter the last group.The
CommandSeparatoris rendered after every group, including the last one, which may create an unnecessary visual separator at the bottom of the list.🔎 Proposed fix
- {Object.entries(groupedActions).map(([category, actions]) => ( - <React.Fragment key={category}> + {Object.entries(groupedActions).map(([category, actions], index, arr) => ( + <React.Fragment key={category}> <CommandGroup heading={category}> {actions.map((action) => { const Icon = action.icon; return ( <CommandItem key={action.id} onSelect={() => handleActionSelect(action)} className="flex items-center gap-2" keywords={action.keywords} > {Icon && <Icon className="h-4 w-4" />} <div className="flex-1"> <div className="font-medium">{action.label}</div> {action.description && ( <div className="text-xs text-muted-foreground"> {action.description} </div> )} </div> {action.shortcut && ( <kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100"> {action.shortcut} </kbd> )} </CommandItem> ); })} </CommandGroup> - <CommandSeparator /> + {index < arr.length - 1 && <CommandSeparator />} </React.Fragment> ))}
264-266: Shortcut hint shows macOS symbol only.The
⌘Khint is macOS-specific, but the keyboard handler (line 146) supports bothmetaKey(Cmd) andctrlKey(Ctrl). Consider showing the appropriate shortcut based on the user's OS for better UX on Windows/Linux.🔎 Proposed fix
Add OS detection and conditionally render the shortcut:
// Add at component level or as a utility const isMac = typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform); // Then in the JSX: <kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground"> {isMac ? '⌘' : 'Ctrl+'}K </kbd>
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
.react-router/types/+routes.ts.react-router/types/app/routes/+types/portal.changelog.ts.react-router/types/app/routes/+types/portal.demo.ts.react-router/types/app/routes/+types/portal.downloads.tsapp/components/ui/command-palette.tsxapp/components/ui/field.tsxapp/components/ui/onboarding-checklist.tsxapp/components/ui/sidebar.tsxapp/components/ui/workflow-stepper.tsxapp/tailwind.css
💤 Files with no reviewable changes (2)
- app/components/ui/sidebar.tsx
- app/components/ui/field.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Import from
"react-router"instead of@remix-run/*packages
Files:
app/components/ui/command-palette.tsxapp/components/ui/workflow-stepper.tsxapp/components/ui/onboarding-checklist.tsx
🧠 Learnings (16)
📚 Learning: 2025-12-27T20:10:54.314Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.314Z
Learning: Applies to **/routes/**/*.tsx : Always use `enabled: isClient` flag for client-only queries and subscriptions to ensure SSR safety
Applied to files:
app/components/ui/command-palette.tsxapp/components/ui/workflow-stepper.tsxapp/components/ui/onboarding-checklist.tsx
📚 Learning: 2025-12-27T20:10:54.314Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.314Z
Learning: Applies to **/routes/**/*.tsx : Use `useAuth()` hook to access `isSignedIn`, `user`, and `signOut` in client-side components
Applied to files:
app/components/ui/command-palette.tsxapp/components/ui/workflow-stepper.tsx.react-router/types/+routes.tsapp/components/ui/onboarding-checklist.tsx
📚 Learning: 2025-12-27T20:10:54.314Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.314Z
Learning: Applies to **/*.{ts,tsx} : Import from `"react-router"` instead of `remix-run/*` packages
Applied to files:
app/components/ui/command-palette.tsxapp/components/ui/workflow-stepper.tsx.react-router/types/app/routes/+types/portal.changelog.ts.react-router/types/app/routes/+types/portal.downloads.ts.react-router/types/app/routes/+types/portal.demo.ts.react-router/types/+routes.tsapp/components/ui/onboarding-checklist.tsx
📚 Learning: 2025-12-27T20:10:54.314Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.314Z
Learning: Applies to **/routes/**/*.tsx : Use `useNavigate()` for programmatic navigation, `<Link>` for links, and `redirect()` for loader/action redirects
Applied to files:
app/components/ui/command-palette.tsxapp/components/ui/workflow-stepper.tsx.react-router/types/app/routes/+types/portal.changelog.ts.react-router/types/app/routes/+types/portal.downloads.ts.react-router/types/app/routes/+types/portal.demo.ts.react-router/types/+routes.tsapp/components/ui/onboarding-checklist.tsx
📚 Learning: 2025-12-27T20:10:54.314Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.314Z
Learning: Applies to **/*.client.{ts,tsx} : Mark client-only files with `.client.ts` or `.client.tsx` suffix or nest in `.client/` directories to prevent SSR issues
Applied to files:
app/components/ui/command-palette.tsxapp/components/ui/workflow-stepper.tsxapp/components/ui/onboarding-checklist.tsx
📚 Learning: 2025-12-27T20:10:54.314Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.314Z
Learning: Applies to **/routes/**/*.tsx : Lazy load client-only modules using `lazy(() => import(...))` and wrap with `<Suspense>` in parent components
Applied to files:
app/components/ui/command-palette.tsxapp/components/ui/workflow-stepper.tsx.react-router/types/app/routes/+types/portal.changelog.ts.react-router/types/app/routes/+types/portal.downloads.ts.react-router/types/app/routes/+types/portal.demo.ts.react-router/types/+routes.tsapp/components/ui/onboarding-checklist.tsx
📚 Learning: 2025-12-27T20:10:54.314Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.314Z
Learning: Applies to **/routes/**/*.tsx : Use `export async function action({ request }: Route.ActionArgs)` for form submissions in route files
Applied to files:
app/components/ui/workflow-stepper.tsx.react-router/types/app/routes/+types/portal.changelog.ts.react-router/types/app/routes/+types/portal.downloads.ts.react-router/types/app/routes/+types/portal.demo.ts.react-router/types/+routes.tsapp/components/ui/onboarding-checklist.tsx
📚 Learning: 2025-12-27T20:10:54.314Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.314Z
Learning: Applies to **/routes/**/*.tsx : Always use `callTrpc(request)` for server-side data fetching in loaders instead of HTTP calls
Applied to files:
app/components/ui/workflow-stepper.tsxapp/components/ui/onboarding-checklist.tsx
📚 Learning: 2025-12-27T20:10:54.314Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.314Z
Learning: Applies to **/routes/{home,dashboard,signup,login}.tsx : Replace placeholder files `home.tsx`, `dashboard.tsx`, `signup.tsx`, and `login.tsx` with actual app-specific implementations
Applied to files:
app/components/ui/workflow-stepper.tsx.react-router/types/app/routes/+types/portal.changelog.ts.react-router/types/app/routes/+types/portal.demo.ts.react-router/types/+routes.tsapp/components/ui/onboarding-checklist.tsx
📚 Learning: 2025-12-27T20:10:54.314Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.314Z
Learning: Applies to **/routes/**/*.tsx : Throw `Response` or use `redirect()` for error and authentication handling in loaders
Applied to files:
app/components/ui/workflow-stepper.tsx.react-router/types/app/routes/+types/portal.changelog.ts.react-router/types/app/routes/+types/portal.downloads.ts.react-router/types/+routes.tsapp/components/ui/onboarding-checklist.tsx
📚 Learning: 2025-12-27T20:10:54.314Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.314Z
Learning: Applies to **/routes/**/*.tsx : Use `Route.LoaderArgs` types from `./+types/routeName` for type-safe loader function parameters
Applied to files:
.react-router/types/app/routes/+types/portal.changelog.ts.react-router/types/app/routes/+types/portal.downloads.ts.react-router/types/app/routes/+types/portal.demo.ts.react-router/types/+routes.ts
📚 Learning: 2025-12-27T20:10:54.314Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.314Z
Learning: Applies to **/routes.ts : Define routes in `routes.ts` file before implementing them in `routes/*.tsx` modules
Applied to files:
.react-router/types/app/routes/+types/portal.changelog.ts.react-router/types/app/routes/+types/portal.downloads.ts.react-router/types/app/routes/+types/portal.demo.ts.react-router/types/+routes.ts
📚 Learning: 2025-12-27T20:10:54.314Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.314Z
Learning: Applies to **/routes/**/*.tsx : Return plain objects from loaders (automatically serialized by React Router)
Applied to files:
.react-router/types/app/routes/+types/portal.changelog.ts.react-router/types/app/routes/+types/portal.downloads.ts.react-router/types/app/routes/+types/portal.demo.ts.react-router/types/+routes.ts
📚 Learning: 2025-12-27T20:10:54.314Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.314Z
Learning: Applies to **/routes/**/*.tsx : Use `loaderData` prop to access data in route components instead of `useLoaderData` hook
Applied to files:
.react-router/types/app/routes/+types/portal.changelog.ts.react-router/types/app/routes/+types/portal.downloads.ts
📚 Learning: 2025-12-27T20:10:54.314Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.314Z
Learning: Applies to **/server/trpc/routers/**/*.ts : Define tRPC procedures in `server/trpc/routers/` and register them in `root.ts`
Applied to files:
.react-router/types/+routes.ts
📚 Learning: 2025-12-27T20:10:54.314Z
Learnt from: CR
Repo: richter83-star/buildflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T20:10:54.314Z
Learning: Applies to **/routes/auth/**/*.callback.tsx : Handle OAuth callbacks by calling `handleOAuthCallback(provider, id, { email, name, avatarUrl })` in `routes/auth/[provider].callback.tsx`
Applied to files:
.react-router/types/+routes.ts
🧬 Code graph analysis (4)
app/components/ui/command-palette.tsx (2)
app/components/ui/command.tsx (6)
CommandDialog(145-145)CommandInput(146-146)CommandList(147-147)CommandEmpty(148-148)CommandGroup(149-149)CommandSeparator(152-152)app/components/ui/icon.tsx (1)
Icon(15-29)
app/components/ui/workflow-stepper.tsx (1)
app/lib/utils.ts (1)
cn(4-6)
.react-router/types/app/routes/+types/portal.changelog.ts (2)
.react-router/types/app/routes/+types/portal.demo.ts (16)
LinkDescriptors(24-24)LinksFunction(25-25)MetaArgs(28-28)MetaDescriptors(29-29)MetaFunction(30-30)HeadersArgs(33-33)HeadersFunction(34-34)MiddlewareFunction(37-37)ClientMiddlewareFunction(40-40)LoaderArgs(43-43)ClientLoaderArgs(46-46)ActionArgs(49-49)ClientActionArgs(52-52)HydrateFallbackProps(55-55)ComponentProps(58-58)ErrorBoundaryProps(61-61).react-router/types/app/routes/+types/portal.downloads.ts (16)
LinkDescriptors(24-24)LinksFunction(25-25)MetaArgs(28-28)MetaDescriptors(29-29)MetaFunction(30-30)HeadersArgs(33-33)HeadersFunction(34-34)MiddlewareFunction(37-37)ClientMiddlewareFunction(40-40)LoaderArgs(43-43)ClientLoaderArgs(46-46)ActionArgs(49-49)ClientActionArgs(52-52)HydrateFallbackProps(55-55)ComponentProps(58-58)ErrorBoundaryProps(61-61)
app/components/ui/onboarding-checklist.tsx (4)
app/components/ui/card.tsx (4)
Card(79-79)CardHeader(79-79)CardTitle(79-79)CardContent(79-79)app/lib/utils.ts (1)
cn(4-6)app/components/ui/button.tsx (1)
Button(56-56)app/components/ui/progress.tsx (1)
Progress(26-26)
⏰ 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 (17)
.react-router/types/+routes.ts (1)
59-67: LGTM - Generated route types are properly structured.The three new portal routes (
/portal/downloads,/portal/changelog,/portal/demo) have been correctly added to all required type definitions (Pages, RouteFiles, and RouteModules). The structure is consistent with existing routes.Also applies to: 155-166, 210-212
.react-router/types/app/routes/+types/portal.demo.ts (1)
1-62: LGTM - Auto-generated route types are correct.This generated type file follows the standard React Router pattern and correctly defines all type aliases for the portal.demo route.
.react-router/types/app/routes/+types/portal.changelog.ts (1)
1-62: LGTM - Auto-generated route types are correct.This generated type file follows the standard React Router pattern and correctly defines all type aliases for the portal.changelog route.
.react-router/types/app/routes/+types/portal.downloads.ts (1)
1-62: LGTM - Auto-generated route types are correct.This generated type file follows the standard React Router pattern and correctly defines all type aliases for the portal.downloads route.
app/components/ui/command-palette.tsx (4)
1-26: LGTM!Imports are correctly structured. Using
react-routerfor navigation aligns with the coding guidelines.
28-43: LGTM!The interfaces are well-typed and provide good flexibility for customization while maintaining type safety.
143-154: LGTM!Keyboard shortcut handler is correctly implemented with proper cleanup on unmount.
274-274: LGTM!Type export allows consumers to properly type their custom actions.
app/tailwind.css (1)
1-3: Directive order is correct. The @import statement correctly precedes @config, which aligns with Tailwind v4 documentation requirements for CSS specification compliance when using postcss-import.app/components/ui/workflow-stepper.tsx (8)
1-6: LGTM!Imports are clean and appropriate. The component uses standard React and internal UI primitives without any framework-specific directives.
8-28: LGTM!Type definitions are well-structured. The WorkflowStep interface provides comprehensive configuration options, and WorkflowStepperProps supports both controlled and uncontrolled usage patterns with appropriate callbacks.
41-44: Verify the completedSteps initialization logic for your use case.The initialization assumes all steps before
savedStepare completed (lines 42-44). This is a reasonable default for linear workflows, but if your application allows non-linear navigation or draft saving before completing prior steps, you may need a more sophisticated initialization strategy (e.g., persisting the actual completed steps set).
55-108: LGTM!Navigation handlers are well-implemented. The
handleNextfunction properly validates the current step before advancing (lines 70-77), marks steps as completed, and invokes callbacks in the correct sequence. Error handling is also appropriate.
119-133: LGTM!Helper functions are straightforward and correct. The time estimation calculations gracefully handle empty arrays and missing
estimatedMinutesproperties.
135-187: LGTM with noted validation concern.The StepIndicator component is well-structured with appropriate conditional styling and responsive layout support. Note that the clickability logic (line 139) is part of the validation bypass issue flagged in the
handleStepClickreview comment—fixing that handler will also address the behavior here.
189-308: LGTM with noted bounds concern.The render structure is well-organized with clear sections for progress tracking, step indicators, content display, and upcoming step previews. The UI progressively discloses information and provides good user feedback with validation errors and loading states. Note that lines 234, 235, and 247 dereference
currentStepDataproperties, which relates to the bounds validation issue already flagged in the earlier review comment.
311-311: LGTM!Type exports are appropriate, providing a clean public API for consumers of this component.
| import { Button } from "~/components/ui/button"; | ||
| import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; | ||
| import { Progress } from "~/components/ui/progress"; | ||
| import confetti from "canvas-confetti"; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, find the onboarding-checklist.tsx file
fd -t f "onboarding-checklist\." --extension tsx --extension tsRepository: richter83-star/buildflow
Length of output: 110
🏁 Script executed:
# Check the repository structure
git ls-files | head -20Repository: richter83-star/buildflow
Length of output: 1022
🏁 Script executed:
# Search for canvas-confetti usage in the codebase
rg "canvas-confetti" --type ts --type tsxRepository: richter83-star/buildflow
Length of output: 95
🏁 Script executed:
# Check if .client.tsx pattern is used in this codebase
fd -t f "\.client\.(ts|tsx)$"Repository: richter83-star/buildflow
Length of output: 88
🏁 Script executed:
# Search for where onboarding-checklist is imported
rg "onboarding-checklist" --type ts --type tsxRepository: richter83-star/buildflow
Length of output: 95
🏁 Script executed:
# Read the onboarding-checklist.tsx file to see the import and usage
cat -n app/components/ui/onboarding-checklist.tsx | head -120Repository: richter83-star/buildflow
Length of output: 3987
🏁 Script executed:
# Search for canvas-confetti without file type
rg "canvas-confetti"Repository: richter83-star/buildflow
Length of output: 660
🏁 Script executed:
# Search for where onboarding-checklist is imported
rg "onboarding-checklist"Repository: richter83-star/buildflow
Length of output: 1274
🏁 Script executed:
# Check React Router config to understand SSR behavior
fd -t f "react-router" --max-results 5Repository: richter83-star/buildflow
Length of output: 50
🌐 Web query:
canvas-confetti SSR compatibility Node.js
💡 Result:
Short answer: No — canvas-confetti is a browser-only library that depends on window/document/canvas and will not run on Node during server-side rendering. Use it only on the client (e.g., dynamic import / client-only component or guard with typeof window !== 'undefined'). [1][2]
Sources:
- canvas-confetti README (explicitly: "this is a client component, and will not run in Node"). [1]
- Example Next.js / client-only usage patterns (dynamic import or "use client" component). [2]
Guard client-only canvas-confetti from SSR.
The canvas-confetti library is a browser-only library that depends on window, document, and canvas APIs. It cannot run in Node.js and will fail during server-side rendering. Currently, it's imported at the module level (line 17) without any SSR guard, which will break SSR rendering of any route that imports this component (including portal.demo.tsx).
Fix this by either:
- Renaming the file to
onboarding-checklist.client.tsxso the component is only loaded on the client, or - Lazy-loading the confetti library dynamically with
import()inside an event handler
Option 1: Rename to client-only file
git mv app/components/ui/onboarding-checklist.tsx app/components/ui/onboarding-checklist.client.tsx
Then update the import in app/routes/portal.demo.tsx.
Option 2: Lazy-load canvas-confetti
Replace the module-level import with dynamic loading inside the triggerConfetti function:
-import confetti from "canvas-confetti";
+
const triggerConfetti = async () => {
+ const { default: confetti } = await import("canvas-confetti");
const count = 200;
// ... rest of functionUpdate the call in handleStepToggle to handle the async function.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/components/ui/onboarding-checklist.tsx around line 17 the module-level
import of canvas-confetti breaks SSR because it uses browser-only APIs; fix by
either renaming the component file to onboarding-checklist.client.tsx and
updating any imports (e.g. in app/routes/portal.demo.tsx) so the module is only
loaded on the client, OR remove the top-level import and lazy-load the package
with dynamic import() inside triggerConfetti (make triggerConfetti async, await
import('canvas-confetti') and call the exported function), then update
handleStepToggle to await or handle the async triggerConfetti call; ensure no
other top-level references to window/document remain.
Summary
This PR adds three new UI components designed to significantly enhance user experience:
Components Added
Command Palette (�pp/components/ui/command-palette.tsx)
Onboarding Checklist (�pp/components/ui/onboarding-checklist.tsx)
Workflow Stepper (�pp/components/ui/workflow-stepper.tsx)
Files Changed
Testing
Documentation
Usage
See the demo page at /portal/demo\ (requires authentication) or refer to the comprehensive documentation for integration examples.
Note: These components are ready for production use and follow all existing code standards and design patterns.
Note
Adds three reusable UI components to improve navigation and onboarding, plus routes and docs.
CommandPalette,OnboardingChecklist(usescanvas-confetti), andWorkflowStepperunderapp/components/ui/*'/portal/downloads','/portal/changelog','/portal/demo'; registered inapp/routes.tsand generated.react-router/types/*routes/portal.demo.tsx(auth + entitlement gated) showcasing componentsdocs/UI_COMPONENTS_GUIDE.mdandUI_COMPONENTS_README.mdcanvas-confetti; minor Tailwind config import order tweak inapp/tailwind.cssWritten by Cursor Bugbot for commit e572e95. This will update automatically on new commits. Configure here.
Summary by CodeRabbit
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.