Add Lamatic agentic travel planner kit#49
Add Lamatic agentic travel planner kit#49jeloskaisabel wants to merge 7 commits intoLamatic:mainfrom
Conversation
Feat/agentic travel planner
WalkthroughAdds a complete multi-agent travel planner template to Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Page as TravelPlannerPage
participant Form as Form Handler
participant Action as planTrip Action
participant Lamatic as Lamatic Flow
participant Parser as normalizeResponse
User->>Page: Fill form & submit
Page->>Form: Validate input (min 1 activity)
Form->>Action: Call planTrip(preferences)
Action->>Lamatic: Dispatch travelPlanner flow
Note over Lamatic: Multi-agent orchestration<br/>(routing, flights, stays, activities, budget)
Lamatic-->>Action: Return raw response
Action->>Parser: normalizeResponse(raw)
Parser->>Parser: Parse & extract<br/>itinerary, flights, stays, activities, budget, visualizations
Parser-->>Action: NormalizedTravelPlan
Action-->>Page: PlanTripResponse (success or error)
Page->>Page: Update state & render plan details
Page->>User: Display itinerary, flights, stays, activities, budget, tips, visualizations
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–90 minutes Areas requiring extra attention:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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.
Actionable comments posted: 27
🧹 Nitpick comments (40)
templates/agentic/travel-planner/components/ui/hover-card.tsx (1)
22-42: LGTM!The content component is properly wrapped in a Portal with sensible defaults and comprehensive animation classes. The Portal ensures the hover card renders in the correct layer, and the extensive Tailwind classes handle all animation states correctly.
Optional: Consider extracting the className for readability.
Line 35 is quite long. If desired, you could extract the base classes to a constant for easier maintenance:
+const hoverCardContentClasses = + "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden" + function HoverCardContent({ className, align = "center", sideOffset = 4, ...props }: React.ComponentProps<typeof HoverCardPrimitive.Content>) { return ( <HoverCardPrimitive.Portal data-slot="hover-card-portal"> <HoverCardPrimitive.Content data-slot="hover-card-content" align={align} sideOffset={sideOffset} - className={cn( - "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden", - className - )} + className={cn(hoverCardContentClasses, className)} {...props} /> </HoverCardPrimitive.Portal> ) }However, this is purely stylistic and the current inline approach is perfectly acceptable.
templates/agentic/travel-planner/components/ui/collapsible.tsx (1)
5-31: Consider simplifying the parameter destructuring pattern.The rest spread pattern
{ ...props }in the function parameters (lines 6, 12, 23) is unusual and creates unnecessary object spreading. The standard approach is to usepropsdirectly.For example, refactor the Collapsible component:
-function Collapsible({ - ...props -}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) { +function Collapsible( + props: React.ComponentProps<typeof CollapsiblePrimitive.Root> +) { return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} /> }Apply the same pattern to
CollapsibleTriggerandCollapsibleContent.templates/agentic/travel-planner/components/ui/progress.tsx (1)
25-25: Consider validating the value range.The transform calculation assumes
valueis between 0 and 100, but there's no validation. Values outside this range (e.g., 150 or -10) would produce unexpected visual results.You could clamp the value to ensure it stays within bounds:
- style={{ transform: `translateX(-${100 - (value || 0)}%)` }} + style={{ transform: `translateX(-${100 - Math.min(100, Math.max(0, value || 0))}%)` }}templates/agentic/travel-planner/components/ui/navigation-menu.tsx (2)
76-76: Consider CSS margin instead of text space for icon spacing.The
{" "}pattern adds a text space node between children and the icon. For cleaner markup and better maintainability, consider using a CSS margin class on the icon instead.Apply this diff:
- {children}{" "} + {children} <ChevronDownIcon - className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180" + className="relative top-[1px] ml-1.5 size-3 transition duration-300 group-data-[state=open]:rotate-180" aria-hidden="true" />
85-100: Consider breaking up the long className for readability.Lines 93-94 contain extremely long className strings that are difficult to read and maintain. While functionally correct, consider extracting repeated prefixes or breaking across multiple lines.
Note: The
**:data-[slot=navigation-menu-link]selector is a Tailwind universal descendant selector that targets all descendant elements with the specified data attribute.Example refactor option:
function NavigationMenuContent({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) { const baseClasses = "data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto" const viewportFalseClasses = "group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none" return ( <NavigationMenuPrimitive.Content data-slot="navigation-menu-content" className={cn(baseClasses, viewportFalseClasses, className)} {...props} /> ) }templates/agentic/travel-planner/components/ui/drawer.tsx (1)
48-73: <!-- [scratchpad_start]
[task review_file_1/1 verify_drawer_redundancy_and_handle_visibility 3/3]
Status: Verification complete[observations]
File: ./templates/agentic/travel-planner/components/ui/drawer.tsx (135 lines)Redundancy on Line 54:
- DrawerPortal component (lines 21-24) DOES set data-slot="drawer-portal" internally
- Line 54: passes same attribute as prop
- This creates duplicate attribute (harmless but unnecessary code noise)
- Review comment CLAIM VERIFIED AS CORRECT ✓
Handle visibility on Line 68:
- DrawerContent only shows handle for bottom drawers
- Component supports all 4 directions with full styling
- DrawerContent is NOT used anywhere in travel-planner template itself
- Component is exported as library component for potential external use
- Cannot verify impact without seeing actual usage in consuming code
[analysis]
Redundancy issue: DEFINITE - should remove data-slot from line 54
Handle visibility: DEBATABLE - component supports all directions, handle only for bottom
- Could be intentional design
- Could be incomplete implementation
- Since component is unused in travel-planner, cannot determine actual usage pattern
[conclusion]
- Line 54 redundancy: CONFIRMED - optional refactor to clean up
- Line 68 handle visibility: UNVERIFIABLE in this context - component definition supports all directions but is not actually used in travel-planner, so the UX implication cannot be assessed from this template alone
[done]
Ready to generate rewritten review comment with updated findings
</scratchpad_end] -->Remove redundant
data-slotprop; handle visibility design is intentional.The
data-slot="drawer-portal"on line 54 is redundant sinceDrawerPortalalready sets this attribute internally (line 23). Remove the duplicate prop.Regarding the handle visibility on line 68: the design showing the handle only for bottom drawers appears intentional. The
DrawerContentcomponent provides full responsive styling for all four directions (top, bottom, left, right), and the handle visibility can be customized by consumers based on their UX needs. This is acceptable for a reusable UI component.Apply this diff to clean up the redundancy:
- <DrawerPortal data-slot="drawer-portal"> + <DrawerPortal>templates/agentic/travel-planner/.gitignore (1)
37-37: Add trailing newline.The file ends without a trailing newline, which violates POSIX standards and may cause issues with some tools.
templates/agentic/travel-planner/components/ui/use-mobile.tsx (1)
5-19: Consider SSR-friendly initialization to prevent hydration mismatches.The hook initializes
isMobiletoundefinedbut returns!!isMobile(false) during SSR and initial client render. This can cause hydration mismatches in Next.js when components render differently based on the mobile state.Consider one of these approaches:
Option 1: Return undefined initially (preferred for SSR)
- return !!isMobile + return isMobileThen handle undefined in consuming components with optional chaining or fallback rendering.
Option 2: Initialize based on user agent (SSR-compatible)
-export function useIsMobile() { - const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined) +export function useIsMobile() { + const [isMobile, setIsMobile] = React.useState<boolean>(() => { + if (typeof window === 'undefined') return false + return window.innerWidth < MOBILE_BREAKPOINT + })Option 3: Suppress hydration for mobile-specific content
UsesuppressHydrationWarningon elements that differ between mobile/desktop, or wait forisMobile !== undefinedbefore rendering conditional content.templates/agentic/travel-planner/package.json (1)
47-47: Pin versions instead of using "latest".Three dependencies use
"latest"instead of specific versions, which can lead to non-deterministic builds and potential breaking changes:
@vercel/analyticsgeistreact-markdownPin these to specific versions to ensure reproducible builds. For example:
- "@vercel/analytics": "latest", + "@vercel/analytics": "^1.4.1",You can check the latest stable versions with:
#!/bin/bash # Check latest versions on npm npm view @vercel/analytics version npm view geist version npm view react-markdown versionAlso applies to: 54-54, 64-64
templates/agentic/travel-planner/components/ui/switch.tsx (1)
8-29: Consider extracting shared UI components.This Switch component is duplicated across multiple templates (reasoning, hiring, and now travel-planner). While this works, consider extracting shared UI components to a common location to reduce duplication and improve maintainability.
templates/agentic/travel-planner/components/ui/form.tsx (1)
45-66: Add validation for FormItem context.The
useFormFieldhook validates that it's used within aFormField(line 52-54) but doesn't validate that it's within aFormItem. Line 56 accessesitemContext.idwhich will throw a runtime error if the hook is used outsideFormItem.Add validation for itemContext:
if (!fieldContext) { throw new Error("useFormField should be used within <FormField>") } + if (!itemContext) { + throw new Error("useFormField should be used within <FormItem>") + } + const { id } = itemContexttemplates/agentic/travel-planner/tsconfig.json (1)
5-5: Consider updating the TypeScript target.The
ES6target is from 2015 and may limit access to modern JavaScript features. Modern Next.js projects typically useES2020orESNextfor better optimization and language feature support.Apply this diff to use a more modern target:
- "target": "ES6", + "target": "ES2020",templates/agentic/travel-planner/components/ui/skeleton.tsx (1)
3-11: Consider extracting shared UI components.This Skeleton component appears identical to the implementation in
templates/agentic/reasoning/components/ui/skeleton.tsx. If multiple templates share the same UI primitives, consider extracting them to a shared component library to improve maintainability and consistency.templates/agentic/travel-planner/hooks/use-mobile.ts (1)
1-19: Consider consolidating duplicate hook implementations.The relevant code snippets indicate this hook appears to be duplicated in both
hooks/use-mobile.tsandcomponents/ui/use-mobile.tsx. Consider removing the duplicate to maintain a single source of truth.templates/agentic/travel-planner/app/globals.css (1)
1-125: Consider consolidating duplicated theming across templates.The theming system defined here appears to be duplicated across multiple templates. Consider extracting the shared theming foundation into a shared utilities package or using a consistent theming configuration file that can be imported across templates.
templates/agentic/travel-planner/app/layout.tsx (1)
23-23: Consider a loading indicator instead of null fallback.The Suspense component uses
fallback={null}, which displays nothing during loading. This could result in a blank screen. Consider using a loading spinner or skeleton to improve perceived performance.Example:
-<Suspense fallback={null}>{children}</Suspense> +<Suspense fallback={<div className="flex h-screen items-center justify-center">Loading...</div>}>{children}</Suspense>templates/agentic/travel-planner/components/ui/separator.tsx (1)
8-26: Component implementation looks good.The Separator component correctly wraps the Radix UI primitive with appropriate styling and prop forwarding. Note that this component is identical to separators in other templates (reasoning, hiring, embed/chat), which presents an opportunity to consolidate shared UI components.
templates/agentic/travel-planner/components/ui/accordion.tsx (1)
9-13: Forward refs for Radix wrappers; hide decorative icon for a11y.
- Preserve ref semantics on Root/Item/Trigger/Content; current wrappers drop refs, which can break composition and focus management.
- Mark Chevron icon as decorative to avoid SR noise.
Apply this refactor:
@@ -function Accordion({ - ...props -}: React.ComponentProps<typeof AccordionPrimitive.Root>) { - return <AccordionPrimitive.Root data-slot="accordion" {...props} /> -} +const Accordion = React.forwardRef< + React.ElementRef<typeof AccordionPrimitive.Root>, + React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Root> +>(({ ...props }, ref) => ( + <AccordionPrimitive.Root ref={ref} data-slot="accordion" {...props} /> +)) +Accordion.displayName = "Accordion" @@ -function AccordionItem({ - className, - ...props -}: React.ComponentProps<typeof AccordionPrimitive.Item>) { - return ( - <AccordionPrimitive.Item - data-slot="accordion-item" - className={cn("border-b last:border-b-0", className)} - {...props} - /> - ) -} +const AccordionItem = React.forwardRef< + React.ElementRef<typeof AccordionPrimitive.Item>, + React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item> +>(({ className, ...props }, ref) => ( + <AccordionPrimitive.Item + ref={ref} + data-slot="accordion-item" + className={cn("border-b last:border-b-0", className)} + {...props} + /> +)) +AccordionItem.displayName = "AccordionItem" @@ -function AccordionTrigger({ - className, - children, - ...props -}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) { - return ( - <AccordionPrimitive.Header className="flex"> - <AccordionPrimitive.Trigger +const AccordionTrigger = React.forwardRef< + React.ElementRef<typeof AccordionPrimitive.Trigger>, + React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger> +>(({ className, children, ...props }, ref) => ( + <AccordionPrimitive.Header className="flex"> + <AccordionPrimitive.Trigger + ref={ref} data-slot="accordion-trigger" className={cn( "focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180", className )} {...props} > {children} - <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" /> - </AccordionPrimitive.Trigger> - </AccordionPrimitive.Header> - ) -} + <ChevronDownIcon + aria-hidden="true" + focusable="false" + className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" + /> + </AccordionPrimitive.Trigger> + </AccordionPrimitive.Header> +)) +AccordionTrigger.displayName = "AccordionTrigger" @@ -function AccordionContent({ - className, - children, - ...props -}: React.ComponentProps<typeof AccordionPrimitive.Content>) { - return ( - <AccordionPrimitive.Content - data-slot="accordion-content" - className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm" - {...props} - > - <div className={cn("pt-0 pb-4", className)}>{children}</div> - </AccordionPrimitive.Content> - ) -} +const AccordionContent = React.forwardRef< + React.ElementRef<typeof AccordionPrimitive.Content>, + React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content> +>(({ className, children, ...props }, ref) => ( + <AccordionPrimitive.Content + ref={ref} + data-slot="accordion-content" + className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm" + {...props} + > + <div className={cn("pt-0 pb-4", className)}>{children}</div> + </AccordionPrimitive.Content> +)) +AccordionContent.displayName = "AccordionContent"Also applies to: 15-26, 28-48, 50-64
templates/agentic/travel-planner/components/ui/calendar.tsx (1)
193-207: Prefer stable ISO date in data attribute.Locale-dependent toLocaleDateString() varies by user locale; use ISO 8601 for predictable selectors/debugging.
- data-day={day.date.toLocaleDateString()} + data-day={day.date.toISOString().slice(0, 10)}templates/agentic/travel-planner/components/ui/alert.tsx (1)
6-14: Fix destructive variant descendant selector; current pattern may not compile.The token
*:data-[slot=alert-description]:…isn’t a valid Tailwind arbitrary variant in most setups. Use a descendant arbitrary selector so the description text reliably inherits destructive color.- destructive: - "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", + destructive: + "text-destructive bg-card [&>svg]:text-current [&_*[data-slot=alert-description]]:text-destructive/90",If your Tailwind setup doesn’t support nested arbitrary variants, alternatively color the description directly in AlertDescription when variant === "destructive".
templates/agentic/travel-planner/components/ui/use-toast.ts (1)
11-13: Tighten toast ergonomics: effect deps, limits/delays, update typing.
- Effect depends on state, re-subscribing every change. Use [].
- TOAST_LIMIT=1 and TOAST_REMOVE_DELAY=1,000,000ms (~17m) are extreme for UX.
- update() shouldn’t require callers to pass id.
-const TOAST_LIMIT = 1 -const TOAST_REMOVE_DELAY = 1000000 +const TOAST_LIMIT = 3 +const TOAST_REMOVE_DELAY = 5000 @@ -type Toast = Omit<ToasterToast, "id"> +type Toast = Omit<ToasterToast, "id"> @@ -function toast({ ...props }: Toast) { +function toast({ ...props }: Toast) { const id = genId() - const update = (props: ToasterToast) => + const update = (next: Partial<Toast>) => dispatch({ type: "UPDATE_TOAST", - toast: { ...props, id }, + toast: { ...next, id }, }) @@ - React.useEffect(() => { + React.useEffect(() => { listeners.push(setState) return () => { const index = listeners.indexOf(setState) if (index > -1) { listeners.splice(index, 1) } } - }, [state]) + }, [])Also applies to: 174-185, 145-153
templates/agentic/travel-planner/app/page.tsx (3)
172-179: Fix date “today” calculation to local date; avoid UTC off‑by‑one.
toISOString().split("T")[0]uses UTC; users in US timezones can see “today” as tomorrow.- const today = useMemo(() => new Date().toISOString().split("T")[0], []); + const today = useMemo(() => { + const d = new Date(); + const yyyy = d.getFullYear(); + const mm = String(d.getMonth() + 1).padStart(2, "0"); + const dd = String(d.getDate()).padStart(2, "0"); + return `${yyyy}-${mm}-${dd}`; // local YYYY-MM-DD + }, []);No behavior change elsewhere; string compares still work.
Also applies to: 535-563, 571-585
30-31: Remove unused import.
ScrollAreais imported but never used.-import { ScrollArea } from "@/components/ui/scroll-area";
188-196: Remove unused computed value.
rawOutputis never rendered. Drop it to save work on large payloads.- const rawOutput = useMemo(() => { - if (!plan) return ""; - if (typeof plan.raw === "string") return plan.raw; - try { - return JSON.stringify(plan.raw, null, 2); - } catch { - return String(plan.raw); - } - }, [plan]);templates/agentic/travel-planner/components/ui/tabs.tsx (1)
8-19: Forward refs for Radix wrappers to preserve focus/a11y hooks.Without
forwardRef, consumers can’t attach refs that many Radix patterns rely on.-import * as React from "react" +import * as React from "react" - function Tabs({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Root>) { - return ( <TabsPrimitive.Root ... /> ) - } +const Tabs = React.forwardRef< + React.ElementRef<typeof TabsPrimitive.Root>, + React.ComponentProps<typeof TabsPrimitive.Root> +>(({ className, ...props }, ref) => ( + <TabsPrimitive.Root ref={ref} data-slot="tabs" className={cn("flex flex-col gap-2", className)} {...props} /> +)); +Tabs.displayName = "Tabs"; - function TabsList({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.List>) { +const TabsList = React.forwardRef< + React.ElementRef<typeof TabsPrimitive.List>, + React.ComponentProps<typeof TabsPrimitive.List> +>(({ className, ...props }, ref) => ( - <TabsPrimitive.List + <TabsPrimitive.List + ref={ref} data-slot="tabs-list" className={cn("bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]", className)} {...props} /> - } +)); +TabsList.displayName = "TabsList"; - function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>) { +const TabsTrigger = React.forwardRef< + React.ElementRef<typeof TabsPrimitive.Trigger>, + React.ComponentProps<typeof TabsPrimitive.Trigger> +>(({ className, ...props }, ref) => ( <TabsPrimitive.Trigger + ref={ref} data-slot="tabs-trigger" className={cn("data-[state=active]:bg-background ... [&_svg:not([class*='size-'])]:size-4", className)} {...props} /> - } +)); +TabsTrigger.displayName = "TabsTrigger"; - function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>) { +const TabsContent = React.forwardRef< + React.ElementRef<typeof TabsPrimitive.Content>, + React.ComponentProps<typeof TabsPrimitive.Content> +>(({ className, ...props }, ref) => ( <TabsPrimitive.Content + ref={ref} data-slot="tabs-content" className={cn("flex-1 outline-none", className)} {...props} /> - } +)); +TabsContent.displayName = "TabsContent";Also applies to: 21-35, 37-51, 53-64
templates/agentic/travel-planner/components/ui/carousel.tsx (1)
78-89: Keyboard UX: honor vertical orientation.ArrowLeft/ArrowRight are fine for horizontal. For vertical, also handle ArrowUp/ArrowDown.
- if (event.key === "ArrowLeft") { ... } else if (event.key === "ArrowRight") { ... } + if (orientation === "vertical") { + if (event.key === "ArrowUp") { event.preventDefault(); scrollPrev() } + if (event.key === "ArrowDown") { event.preventDefault(); scrollNext() } + } else { + if (event.key === "ArrowLeft") { event.preventDefault(); scrollPrev() } + if (event.key === "ArrowRight") { event.preventDefault(); scrollNext() } + }templates/agentic/travel-planner/components/ui/card.tsx (1)
31-39: Optional: allow semantic headings for accessibility.
CardTitlerenders adiv. Let consumers choose a heading element.-function CardTitle({ className, ...props }: React.ComponentProps<"div">) { +function CardTitle({ + as: As = "div", + className, + ...props +}: { as?: "h1"|"h2"|"h3"|"h4"|"h5"|"h6"|"div" } & React.ComponentProps<"div">) { return ( - <div + <As data-slot="card-title" className={cn("leading-none font-semibold", className)} {...props} - /> + /> ) }templates/agentic/travel-planner/components/ui/input-otp.tsx (1)
9-27: Consider ref forwarding for composition.Forwarding refs on
InputOTPandInputOTPSlotenables focus management and integrations. OTPInput supports refs and can be used with forwardRef, making the suggested refactor technically feasible.templates/agentic/travel-planner/components/ui/breadcrumb.tsx (1)
34-50: Forward refs for better interop (NextLink, focus mgmt, testing).Forwarding refs on BreadcrumbLink improves accessibility and composition.
-function BreadcrumbLink({ - asChild, - className, - ...props -}: React.ComponentProps<"a"> & { - asChild?: boolean -}) { - const Comp = asChild ? Slot : "a" - return ( - <Comp - data-slot="breadcrumb-link" - className={cn("hover:text-foreground transition-colors", className)} - {...props} - /> - ) -} +const BreadcrumbLink = React.forwardRef< + React.ElementRef<"a">, + React.ComponentPropsWithoutRef<"a"> & { asChild?: boolean } +>(({ asChild, className, ...props }, ref) => { + const Comp = asChild ? Slot : "a" + return ( + <Comp + ref={ref} + data-slot="breadcrumb-link" + className={cn("hover:text-foreground transition-colors", className)} + {...props} + /> + ) +}) +BreadcrumbLink.displayName = "BreadcrumbLink"templates/agentic/travel-planner/components/ui/select.tsx (1)
40-42: Replace non-standard selector variants and unify outline class.The “star” variants (e.g.,
*:data-[...],*:[span]:last:...) are not Tailwind defaults and likely won’t compile. Use bracketed descendant selectors. Also preferoutline-noneoveroutline-hidden.- "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 [&_*[data-slot=select-value]]:line-clamp-1 [&_*[data-slot=select-value]]:flex [&_*[data-slot=select-value]]:items-center [&_*[data-slot=select-value]]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",- "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", + "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&>span:last-child]:flex [&>span:last-child]:items-center [&>span:last-child]:gap-2",Also applies to: 108-113
templates/agentic/travel-planner/components/ui/dialog.tsx (1)
58-60: Small polish: avoid redundant data-slot; use standard outline class.
- No need to pass
data-slottoDialogPortalwrapper (it already sets it).- Prefer
outline-noneover non-standardoutline-hidden.- <DialogPortal data-slot="dialog-portal"> + <DialogPortal> <DialogOverlay /> <DialogPrimitive.Content @@ - <DialogPrimitive.Close + <DialogPrimitive.Close data-slot="dialog-close" - className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" + className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" >Also applies to: 70-76
templates/agentic/travel-planner/components/ui/menubar.tsx (2)
102-108: Replace non-standard star variant; also use standard outline class.
data-[variant=destructive]:*:[svg]:!text-destructiveis not a Tailwind variant. Use a bracketed descendant selector. Also preferoutline-none.- "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:[&_svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
56-63: Unify focus outline: useoutline-none(notoutline-hidden).Non-standard
outline-hiddenmay be dropped by Tailwind; keep consistency withMenubarSubTrigger.- className={cn( - "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-hidden select-none", + className={cn( + "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-none select-none",- className={cn( - "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + className={cn( + "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",- className={cn( - "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + className={cn( + "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",Also applies to: 121-137, 146-151
templates/agentic/travel-planner/components/ui/dropdown-menu.tsx (2)
76-80: Replace non-standard star variant and standardize outline.Switch to bracketed descendant selector; prefer
outline-none.- "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:[&_svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
94-99: Consistency: useoutline-none(notoutline-hidden).Ensure dropdown items/checkbox/radio use standard Tailwind outline utilities.
- className={cn( - "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + className={cn( + "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",- className={cn( - "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + className={cn( + "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",Also applies to: 130-134
templates/agentic/travel-planner/components/ui/context-menu.tsx (1)
9-13: Consider forwarding refs for all wrappersThese wrappers don’t use React.forwardRef, so consumers can’t focus/measure elements via refs. Forwarding preserves Radix primitives’ ref behavior.
Example pattern:
const ContextMenuItem = React.forwardRef< React.ElementRef<typeof ContextMenuPrimitive.Item>, React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & { inset?: boolean; variant?: "default" | "destructive" } >(({ className, inset, variant = "default", ...props }, ref) => ( <ContextMenuPrimitive.Item ref={ref} ... /> )); ContextMenuItem.displayName = "ContextMenuItem";templates/agentic/travel-planner/actions/orchestrate.ts (2)
9-11: Avoid import-time throw; guard inside planTripThrowing at module load breaks any import (including build/SSR). Prefer a runtime guard that returns a structured error.
Apply:
-if (!travelPlannerFlow) { - throw new Error("travelPlanner flow not found in lamatic-config.json"); -}And add near the start of planTrip:
export async function planTrip( preferences: TravelPreferences ): Promise<PlanTripResponse> { + if (!travelPlannerFlow) { + return { success: false, error: "travelPlanner flow not configured." }; + } try {
803-816: Minor: normalize/validate input earlyTrim strings and validate date order; clamp negative budgets/travelers.
Example:
if (new Date(preferences.startDate) > new Date(preferences.endDate)) { return { success: false, error: "startDate must be before endDate." }; } const budget = Number.isFinite(preferences.budget) && preferences.budget >= 0 ? preferences.budget : undefined;templates/agentic/travel-planner/components/ui/sidebar.tsx (2)
85-90: Harden and persist cookie correctly (samesite, secure; hydrate on mount)Current cookie lacks samesite/secure and the persisted state isn’t read back.
Apply:
- document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` + const secure = window.location.protocol === "https:" ? "; secure" : "" + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}; samesite=lax${secure}`Hydrate on mount when uncontrolled:
const [_open, _setOpen] = React.useState(defaultOpen) const open = openProp ?? _open + React.useEffect(() => { + if (openProp !== undefined) return + const m = document.cookie.match(new RegExp(`(?:^|; )${SIDEBAR_COOKIE_NAME}=([^;]*)`)) + if (m) _setOpen(m[1] === "true") + }, [openProp])
282-304: Optional a11y: SidebarRail is unfocusabletabIndex={-1} prevents keyboard access. Consider leaving it unfocusable (pure pointer affordance) or add a hidden label + keyboard toggle alternative.
You already provide SidebarTrigger; ensure parity for keyboard users is documented/available.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (3)
templates/agentic/travel-planner/app/icon.pngis excluded by!**/*.pngtemplates/agentic/travel-planner/public/lamatic-logo.pngis excluded by!**/*.pngtemplates/agentic/travel-planner/public/placeholder.svgis excluded by!**/*.svg
📒 Files selected for processing (73)
templates/agentic/travel-planner/.env.example(1 hunks)templates/agentic/travel-planner/.eslintrc.json(1 hunks)templates/agentic/travel-planner/.gitignore(1 hunks)templates/agentic/travel-planner/.npmrc(1 hunks)templates/agentic/travel-planner/LICENSE(1 hunks)templates/agentic/travel-planner/README.md(1 hunks)templates/agentic/travel-planner/actions/orchestrate.ts(1 hunks)templates/agentic/travel-planner/app/globals.css(1 hunks)templates/agentic/travel-planner/app/layout.tsx(1 hunks)templates/agentic/travel-planner/app/page.tsx(1 hunks)templates/agentic/travel-planner/components.json(1 hunks)templates/agentic/travel-planner/components/theme-provider.tsx(1 hunks)templates/agentic/travel-planner/components/ui/accordion.tsx(1 hunks)templates/agentic/travel-planner/components/ui/alert-dialog.tsx(1 hunks)templates/agentic/travel-planner/components/ui/alert.tsx(1 hunks)templates/agentic/travel-planner/components/ui/aspect-ratio.tsx(1 hunks)templates/agentic/travel-planner/components/ui/avatar.tsx(1 hunks)templates/agentic/travel-planner/components/ui/badge.tsx(1 hunks)templates/agentic/travel-planner/components/ui/breadcrumb.tsx(1 hunks)templates/agentic/travel-planner/components/ui/button.tsx(1 hunks)templates/agentic/travel-planner/components/ui/calendar.tsx(1 hunks)templates/agentic/travel-planner/components/ui/card.tsx(1 hunks)templates/agentic/travel-planner/components/ui/carousel.tsx(1 hunks)templates/agentic/travel-planner/components/ui/chart.tsx(1 hunks)templates/agentic/travel-planner/components/ui/checkbox.tsx(1 hunks)templates/agentic/travel-planner/components/ui/collapsible.tsx(1 hunks)templates/agentic/travel-planner/components/ui/command.tsx(1 hunks)templates/agentic/travel-planner/components/ui/context-menu.tsx(1 hunks)templates/agentic/travel-planner/components/ui/dialog.tsx(1 hunks)templates/agentic/travel-planner/components/ui/drawer.tsx(1 hunks)templates/agentic/travel-planner/components/ui/dropdown-menu.tsx(1 hunks)templates/agentic/travel-planner/components/ui/form.tsx(1 hunks)templates/agentic/travel-planner/components/ui/hover-card.tsx(1 hunks)templates/agentic/travel-planner/components/ui/input-otp.tsx(1 hunks)templates/agentic/travel-planner/components/ui/input.tsx(1 hunks)templates/agentic/travel-planner/components/ui/label.tsx(1 hunks)templates/agentic/travel-planner/components/ui/menubar.tsx(1 hunks)templates/agentic/travel-planner/components/ui/navigation-menu.tsx(1 hunks)templates/agentic/travel-planner/components/ui/pagination.tsx(1 hunks)templates/agentic/travel-planner/components/ui/popover.tsx(1 hunks)templates/agentic/travel-planner/components/ui/progress.tsx(1 hunks)templates/agentic/travel-planner/components/ui/radio-group.tsx(1 hunks)templates/agentic/travel-planner/components/ui/resizable.tsx(1 hunks)templates/agentic/travel-planner/components/ui/scroll-area.tsx(1 hunks)templates/agentic/travel-planner/components/ui/select.tsx(1 hunks)templates/agentic/travel-planner/components/ui/separator.tsx(1 hunks)templates/agentic/travel-planner/components/ui/sheet.tsx(1 hunks)templates/agentic/travel-planner/components/ui/sidebar.tsx(1 hunks)templates/agentic/travel-planner/components/ui/skeleton.tsx(1 hunks)templates/agentic/travel-planner/components/ui/slider.tsx(1 hunks)templates/agentic/travel-planner/components/ui/sonner.tsx(1 hunks)templates/agentic/travel-planner/components/ui/switch.tsx(1 hunks)templates/agentic/travel-planner/components/ui/table.tsx(1 hunks)templates/agentic/travel-planner/components/ui/tabs.tsx(1 hunks)templates/agentic/travel-planner/components/ui/textarea.tsx(1 hunks)templates/agentic/travel-planner/components/ui/toast.tsx(1 hunks)templates/agentic/travel-planner/components/ui/toaster.tsx(1 hunks)templates/agentic/travel-planner/components/ui/toggle-group.tsx(1 hunks)templates/agentic/travel-planner/components/ui/toggle.tsx(1 hunks)templates/agentic/travel-planner/components/ui/tooltip.tsx(1 hunks)templates/agentic/travel-planner/components/ui/use-mobile.tsx(1 hunks)templates/agentic/travel-planner/components/ui/use-toast.ts(1 hunks)templates/agentic/travel-planner/hooks/use-mobile.ts(1 hunks)templates/agentic/travel-planner/hooks/use-toast.ts(1 hunks)templates/agentic/travel-planner/lamatic-config.json(1 hunks)templates/agentic/travel-planner/lib/lamatic-client.ts(1 hunks)templates/agentic/travel-planner/lib/utils.ts(1 hunks)templates/agentic/travel-planner/next.config.mjs(1 hunks)templates/agentic/travel-planner/package.json(1 hunks)templates/agentic/travel-planner/postcss.config.mjs(1 hunks)templates/agentic/travel-planner/scripts/deploy.sh(1 hunks)templates/agentic/travel-planner/styles/globals.css(1 hunks)templates/agentic/travel-planner/tsconfig.json(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (23)
templates/agentic/travel-planner/components/theme-provider.tsx (2)
templates/embed/chat/components/theme-provider.tsx (1)
ThemeProvider(9-11)templates/agentic/reasoning/components/theme-provider.tsx (1)
ThemeProvider(9-11)
templates/agentic/travel-planner/components/ui/toaster.tsx (4)
templates/agentic/travel-planner/components/ui/sonner.tsx (1)
Toaster(25-25)templates/agentic/travel-planner/components/ui/use-toast.ts (1)
useToast(194-194)templates/agentic/travel-planner/hooks/use-toast.ts (1)
useToast(194-194)templates/agentic/travel-planner/components/ui/toast.tsx (6)
ToastProvider(122-122)Toast(124-124)ToastTitle(125-125)ToastDescription(126-126)ToastClose(127-127)ToastViewport(123-123)
templates/agentic/travel-planner/components/ui/skeleton.tsx (1)
templates/agentic/reasoning/components/ui/skeleton.tsx (1)
Skeleton(3-11)
templates/agentic/travel-planner/components/ui/textarea.tsx (3)
templates/agentic/reasoning/components/ui/textarea.tsx (1)
Textarea(5-16)templates/automation/hiring/components/ui/textarea.tsx (1)
Textarea(5-16)templates/embed/chat/components/ui/textarea.tsx (1)
Textarea(5-16)
templates/agentic/travel-planner/components/ui/sonner.tsx (2)
templates/agentic/travel-planner/components/ui/toaster.tsx (1)
Toaster(13-35)templates/agentic/reasoning/components/ui/sonner.tsx (1)
props(6-23)
templates/agentic/travel-planner/components/ui/badge.tsx (2)
templates/agentic/reasoning/components/ui/badge.tsx (1)
Badge(28-44)templates/automation/hiring/components/ui/badge.tsx (1)
Badge(28-44)
templates/agentic/travel-planner/components/ui/switch.tsx (2)
templates/agentic/reasoning/components/ui/switch.tsx (1)
Switch(8-29)templates/automation/hiring/components/ui/switch.tsx (1)
Switch(8-29)
templates/agentic/travel-planner/components/ui/separator.tsx (3)
templates/agentic/reasoning/components/ui/separator.tsx (1)
Separator(8-26)templates/automation/hiring/components/ui/separator.tsx (1)
Separator(8-26)templates/embed/chat/components/ui/separator.tsx (1)
Separator(8-26)
templates/agentic/travel-planner/components/ui/avatar.tsx (1)
templates/agentic/reasoning/components/ui/avatar.tsx (1)
Avatar(8-22)
templates/agentic/travel-planner/components/ui/command.tsx (1)
templates/agentic/travel-planner/components/ui/dialog.tsx (5)
Dialog(133-133)DialogHeader(138-138)DialogTitle(141-141)DialogDescription(136-136)DialogContent(135-135)
templates/agentic/travel-planner/components/ui/scroll-area.tsx (1)
templates/agentic/reasoning/components/ui/scroll-area.tsx (1)
ScrollArea(8-29)
templates/agentic/travel-planner/hooks/use-mobile.ts (1)
templates/agentic/travel-planner/components/ui/use-mobile.tsx (1)
useIsMobile(5-19)
templates/agentic/travel-planner/components/ui/calendar.tsx (1)
templates/agentic/reasoning/components/ui/calendar.tsx (1)
Calendar(14-173)
templates/agentic/travel-planner/components/ui/toggle.tsx (1)
templates/agentic/reasoning/components/ui/toggle.tsx (1)
Toggle(31-45)
templates/agentic/travel-planner/hooks/use-toast.ts (1)
templates/agentic/travel-planner/components/ui/toast.tsx (3)
ToastProps(120-120)ToastActionElement(121-121)Toast(124-124)
templates/agentic/travel-planner/components/ui/use-toast.ts (1)
templates/agentic/travel-planner/components/ui/toast.tsx (3)
ToastProps(120-120)ToastActionElement(121-121)Toast(124-124)
templates/agentic/travel-planner/app/page.tsx (1)
templates/agentic/travel-planner/actions/orchestrate.ts (3)
NormalizedTravelPlan(124-141)planTrip(793-852)FlightLeg(113-117)
templates/agentic/travel-planner/components/ui/alert-dialog.tsx (1)
templates/agentic/travel-planner/components/ui/button.tsx (1)
buttonVariants(59-59)
templates/agentic/travel-planner/components/ui/chart.tsx (1)
templates/agentic/travel-planner/postcss.config.mjs (1)
config(2-6)
templates/agentic/travel-planner/components/ui/button.tsx (2)
templates/embed/sheets/components/ui/button.tsx (1)
Button(39-58)templates/embed/chat/components/ui/button.tsx (1)
Button(38-57)
templates/agentic/travel-planner/components/ui/pagination.tsx (1)
templates/agentic/reasoning/components/ui/pagination.tsx (1)
Pagination(11-21)
templates/agentic/travel-planner/actions/orchestrate.ts (1)
templates/agentic/travel-planner/lib/lamatic-client.ts (1)
lamaticClient(10-14)
templates/agentic/travel-planner/components/ui/sidebar.tsx (1)
templates/agentic/travel-planner/hooks/use-mobile.ts (1)
useIsMobile(5-19)
🪛 ast-grep (0.39.6)
templates/agentic/travel-planner/app/page.tsx
[warning] 1422-1422: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
templates/agentic/travel-planner/components/ui/chart.tsx
[warning] 82-82: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 Biome (2.1.2)
templates/agentic/travel-planner/app/page.tsx
[error] 1423-1424: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
templates/agentic/travel-planner/components/ui/chart.tsx
[error] 83-83: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
| # package manager | ||
| package-lock.json | ||
| yarn.lock | ||
| pnpm-lock.yaml | ||
| bun.lockb |
There was a problem hiding this comment.
Do not ignore package manager lockfiles.
Ignoring lockfiles (package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb) breaks build reproducibility. Lockfiles ensure consistent dependency versions across development, CI/CD, and production environments. Without them, different environments may resolve to different package versions, leading to "works on my machine" issues and potential bugs in production.
Apply this diff to commit lockfiles:
# package manager
-package-lock.json
-yarn.lock
-pnpm-lock.yaml
-bun.lockbAfter removing these entries, commit the appropriate lockfile(s) for the package manager(s) you're using.
📝 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.
| # package manager | |
| package-lock.json | |
| yarn.lock | |
| pnpm-lock.yaml | |
| bun.lockb | |
| # package manager |
🤖 Prompt for AI Agents
templates/agentic/travel-planner/.gitignore lines 30-34 currently ignore package
manager lockfiles (package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb);
remove those entries from .gitignore and commit the relevant lockfile(s) for the
package manager(s) you use (e.g., commit package-lock.json for npm, yarn.lock
for Yarn, etc.) so dependency resolution is reproducible across environments.
| @@ -0,0 +1 @@ | |||
| force=true No newline at end of file | |||
There was a problem hiding this comment.
Remove force=true to prevent unsafe dependency installations.
The force=true flag bypasses npm's safety checks and conflict resolution, which can lead to corrupted node_modules, silently ignored dependency conflicts, and difficult-to-debug production issues. Dependency conflicts should be resolved explicitly in package.json rather than forcing installations.
Apply this diff to remove the unsafe configuration:
-force=trueAlternatively, if there are specific dependency conflicts, address them explicitly in package.json with resolutions or overrides.
📝 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.
| force=true |
🤖 Prompt for AI Agents
templates/agentic/travel-planner/.npmrc lines 1-1: remove the unsafe
"force=true" setting from .npmrc (or change it to "force=false") so npm no
longer bypasses safety checks; instead resolve any dependency conflicts
explicitly in package.json using "resolutions" or "overrides" or by updating
package versions, then re-run install to verify the lockfile and node_modules
are consistent.
| export interface NormalizedTravelPlan { | ||
| overview?: string; | ||
| keyHighlights: string[]; | ||
| itinerary: TravelDayPlan[]; | ||
| flights: FlightOption[]; | ||
| stays: StayOption[]; | ||
| activities: ActivityOption[]; | ||
| budget?: BudgetSummary; | ||
| visualizations: VisualizationResource[]; | ||
| bookingTasks: BookingTask[]; | ||
| references: string[]; | ||
| travelTips: string[]; | ||
| nextSteps: string[]; | ||
| tripSummary?: TripSummary; | ||
| flightItinerary?: FlightItinerary; | ||
| accommodation?: StayOption; | ||
| raw: unknown; | ||
| } |
There was a problem hiding this comment.
Don’t expose raw provider response to clients by default
NormalizedTravelPlan.raw may contain unintended data. Gate it to dev or make it optional and strip before returning.
Apply:
-export interface NormalizedTravelPlan {
+export interface NormalizedTravelPlan {
...
- raw: unknown;
+ raw?: unknown;
}And sanitize in planTrip:
- const plan = normalizeTravelPlan(rawResult);
-
- return {
+ const plan = normalizeTravelPlan(rawResult);
+ const safePlan: NormalizedTravelPlan =
+ process.env.NODE_ENV === "development" ? plan : { ...plan, raw: undefined };
+ return {
success: true,
status,
- plan,
+ plan: safePlan,
};Also applies to: 830-836
🤖 Prompt for AI Agents
In templates/agentic/travel-planner/actions/orchestrate.ts around lines 124–141
(and also address related usage at lines 830–836), the NormalizedTravelPlan
currently exposes a raw provider response; make the property optional (raw?:
unknown) and ensure planTrip does not return raw to clients by default — only
attach raw when running in a development environment (e.g., NODE_ENV ===
"development") or when an explicit debug flag is passed; additionally,
sanitize/strip the raw property before sending the plan response (remove or
clone the object without raw), and update any code at lines 830–836 that
constructs or returns the plan to follow this rule so production responses never
include raw provider data.
| --destructive: oklch(0.577 0.245 27.325); | ||
| --destructive-foreground: oklch(0.577 0.245 27.325); |
There was a problem hiding this comment.
Fix duplicate color value for destructive foreground.
Lines 21-22 assign the same color value oklch(0.577 0.245 27.325) to both --destructive and --destructive-foreground. This would result in red text on a red background, making destructive messages unreadable. The dark mode theme (lines 57-58) correctly uses different values.
Apply this diff to fix the foreground color:
--destructive: oklch(0.577 0.245 27.325);
- --destructive-foreground: oklch(0.577 0.245 27.325);
+ --destructive-foreground: oklch(0.985 0 0);📝 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.
| --destructive: oklch(0.577 0.245 27.325); | |
| --destructive-foreground: oklch(0.577 0.245 27.325); | |
| --destructive: oklch(0.577 0.245 27.325); | |
| --destructive-foreground: oklch(0.985 0 0); |
🤖 Prompt for AI Agents
In templates/agentic/travel-planner/app/globals.css around lines 21-22, the CSS
custom property --destructive and --destructive-foreground are set to the same
oklch value causing unreadable red-on-red text; change --destructive-foreground
to a contrasting (lighter) foreground color (use the same foreground value
pattern as the dark-mode block at lines ~57-58) so destructive text remains
readable against the destructive background.
| <div | ||
| className="aspect-video overflow-hidden rounded-md border" | ||
| dangerouslySetInnerHTML={{ | ||
| __html: viz.embedHtml, | ||
| }} | ||
| /> |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
XSS risk: sanitize HTML before using dangerouslySetInnerHTML.
viz.embedHtml is untrusted; injects raw HTML into the DOM.
Apply a sanitizer (DOMPurify or isomorphic-dompurify) before rendering.
+import DOMPurify from "isomorphic-dompurify";
...
{viz.embedHtml && (
<div
className="aspect-video overflow-hidden rounded-md border"
dangerouslySetInnerHTML={{
- __html: viz.embedHtml,
+ __html: DOMPurify.sanitize(viz.embedHtml, { ADD_ATTR: ["target","rel"] }),
}}
/>
)}Based on static analysis hints.
Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 ast-grep (0.39.6)
[warning] 1422-1422: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 Biome (2.1.2)
[error] 1423-1424: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
| const TOAST_LIMIT = 1 | ||
| const TOAST_REMOVE_DELAY = 1000000 |
There was a problem hiding this comment.
Review the toast removal delay configuration.
The TOAST_REMOVE_DELAY is set to 1,000,000ms (~16.7 minutes), which is unusually long for toast notifications. Typical toast removal delays range from 3-10 seconds. This extended delay may result in excessive memory usage as dismissed toasts remain in the removal queue for an extended period.
Consider reducing the delay to a more typical value:
const TOAST_LIMIT = 1
-const TOAST_REMOVE_DELAY = 1000000
+const TOAST_REMOVE_DELAY = 5000📝 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.
| const TOAST_LIMIT = 1 | |
| const TOAST_REMOVE_DELAY = 1000000 | |
| const TOAST_LIMIT = 1 | |
| const TOAST_REMOVE_DELAY = 5000 |
🤖 Prompt for AI Agents
In templates/agentic/travel-planner/hooks/use-toast.ts around lines 11 to 12,
the TOAST_REMOVE_DELAY is set to 1,000,000ms which is far too long for toast
notifications; change the constant to a typical short duration (e.g., 3000–7000
ms, recommended 5000 ms) so toasts are removed promptly to avoid memory buildup,
and keep TOAST_LIMIT as-is or adjust if needed.
| React.useEffect(() => { | ||
| listeners.push(setState) | ||
| return () => { | ||
| const index = listeners.indexOf(setState) | ||
| if (index > -1) { | ||
| listeners.splice(index, 1) | ||
| } | ||
| } | ||
| }, [state]) |
There was a problem hiding this comment.
Remove state from the useEffect dependency array.
Including state in the dependency array causes the listener to be re-registered on every state update, leading to unnecessary re-renders and potential memory issues. The listener should only be registered once on mount and cleaned up on unmount.
Apply this diff:
React.useEffect(() => {
listeners.push(setState)
return () => {
const index = listeners.indexOf(setState)
if (index > -1) {
listeners.splice(index, 1)
}
}
- }, [state])
+ }, [])📝 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.
| React.useEffect(() => { | |
| listeners.push(setState) | |
| return () => { | |
| const index = listeners.indexOf(setState) | |
| if (index > -1) { | |
| listeners.splice(index, 1) | |
| } | |
| } | |
| }, [state]) | |
| React.useEffect(() => { | |
| listeners.push(setState) | |
| return () => { | |
| const index = listeners.indexOf(setState) | |
| if (index > -1) { | |
| listeners.splice(index, 1) | |
| } | |
| } | |
| }, []) |
🤖 Prompt for AI Agents
In templates/agentic/travel-planner/hooks/use-toast.ts around lines 177 to 185,
the useEffect currently lists `state` as a dependency which causes the listener
to be re-registered on every state change; remove `state` from the dependency
array so the effect runs only once on mount and cleans up on unmount (i.e.,
replace `[state]` with `[]`), ensuring the listener is registered a single time
and spliced out in the cleanup.
| if (!process.env.LAMATIC_API_KEY) { | ||
| throw new Error( | ||
| "LAMATIC_API_KEY environment variable is not set. Please add it to your .env.local file." | ||
| ); | ||
| } | ||
|
|
||
| export const lamaticClient = new Lamatic({ | ||
| endpoint: config.api.endpoint, | ||
| projectId: config.api.projectId, | ||
| apiKey: process.env.LAMATIC_API_KEY, | ||
| }); |
There was a problem hiding this comment.
Critical: Module initialization prevents building without API key.
The environment variable check and client instantiation occur at module initialization (lines 4-14). In Next.js, when this module is imported by server components or actions, it's evaluated at build time. This means:
- The build will fail if
LAMATIC_API_KEYis not set during build - You cannot build once and deploy with different environment variables
- This breaks standard CI/CD patterns where you build in one environment and deploy to multiple environments with different credentials
Refactor to use lazy initialization:
import { Lamatic } from "lamatic";
import config from "@/lamatic-config.json";
-if (!process.env.LAMATIC_API_KEY) {
- throw new Error(
- "LAMATIC_API_KEY environment variable is not set. Please add it to your .env.local file."
- );
-}
-
-export const lamaticClient = new Lamatic({
- endpoint: config.api.endpoint,
- projectId: config.api.projectId,
- apiKey: process.env.LAMATIC_API_KEY,
-});
+let lamaticClient: Lamatic | null = null;
+
+export function getLamaticClient(): Lamatic {
+ if (!lamaticClient) {
+ if (!process.env.LAMATIC_API_KEY) {
+ throw new Error(
+ "LAMATIC_API_KEY environment variable is not set. Please add it to your .env.local file."
+ );
+ }
+ lamaticClient = new Lamatic({
+ endpoint: config.api.endpoint,
+ projectId: config.api.projectId,
+ apiKey: process.env.LAMATIC_API_KEY,
+ });
+ }
+ return lamaticClient;
+}Then update imports to use getLamaticClient() instead of lamaticClient.
📝 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.
| if (!process.env.LAMATIC_API_KEY) { | |
| throw new Error( | |
| "LAMATIC_API_KEY environment variable is not set. Please add it to your .env.local file." | |
| ); | |
| } | |
| export const lamaticClient = new Lamatic({ | |
| endpoint: config.api.endpoint, | |
| projectId: config.api.projectId, | |
| apiKey: process.env.LAMATIC_API_KEY, | |
| }); | |
| import { Lamatic } from "lamatic"; | |
| import config from "@/lamatic-config.json"; | |
| let lamaticClient: Lamatic | null = null; | |
| export function getLamaticClient(): Lamatic { | |
| if (!lamaticClient) { | |
| if (!process.env.LAMATIC_API_KEY) { | |
| throw new Error( | |
| "LAMATIC_API_KEY environment variable is not set. Please add it to your .env.local file." | |
| ); | |
| } | |
| lamaticClient = new Lamatic({ | |
| endpoint: config.api.endpoint, | |
| projectId: config.api.projectId, | |
| apiKey: process.env.LAMATIC_API_KEY, | |
| }); | |
| } | |
| return lamaticClient; | |
| } |
🤖 Prompt for AI Agents
In templates/agentic/travel-planner/lib/lamatic-client.ts around lines 4 to 14,
the current module-level env check and new Lamatic(...) call causes the module
to throw at build-time if LAMATIC_API_KEY is missing; replace this with a lazy
initializer: export a getLamaticClient() function that reads
process.env.LAMATIC_API_KEY at call time, throws only when actually invoked
without a key (not at module import), and memoizes/returns a single Lamatic
instance (or accepts an optional apiKey param) so imports use getLamaticClient()
instead of a module-level lamaticClient; update callers to call
getLamaticClient() where needed.
| "url": "https://github.com/Lamatic/AgentKit" | ||
| }, | ||
| "version": "0.1.0", | ||
| "private": false, |
There was a problem hiding this comment.
🧩 Analysis chain
Verify if the package should be public.
The package is marked as "private": false, which means it could be published to npm. For a template within the AgentKit repository, this should typically be "private": true to prevent accidental publication.
Apply this diff if the package should remain private:
- "private": false,
+ "private": true,I'll verify this review comment by examining the repository context and comparing with similar files.
🏁 Script executed:
#!/bin/bash
# Find all package.json files in templates directory and check "private" field
echo "=== Checking package.json files in templates directory ==="
find templates -name "package.json" -type f | head -20 | while read file; do
echo "File: $file"
jq -r '.private // "not set"' "$file" 2>/dev/null || echo " (parsing error)"
done
echo -e "\n=== Specifically checking agentic/travel-planner/package.json ==="
if [ -f "templates/agentic/travel-planner/package.json" ]; then
jq '.private, .name, .description' templates/agentic/travel-planner/package.json 2>/dev/null
else
echo "File not found"
fi
echo -e "\n=== Checking for publishConfig or other publish-related fields ==="
find templates -name "package.json" -type f | head -10 | xargs jq -r 'select(.publishConfig != null) | .name + ": " + (.publishConfig | tostring)' 2>/dev/nullLength of output: 721
🏁 Script executed:
#!/bin/bash
# Check repository root for publishing guidelines and CI workflows
echo "=== Checking for publishing documentation ==="
ls -la README* PUBLISHING* .npmrc .npmignore 2>/dev/null | head -10
echo -e "\n=== Checking for publish workflows in .github/workflows ==="
find .github/workflows -name "*.yml" -o -name "*.yaml" 2>/dev/null | xargs grep -l "publish\|npm publish" 2>/dev/null || echo "No publish workflows found"
echo -e "\n=== Checking root package.json for workspace config ==="
jq '.private, .workspaces, .publishConfig' package.json 2>/dev/null || echo "No root package.json or error parsing"Length of output: 287
🏁 Script executed:
#!/bin/bash
# Check README for information about templates and publishing intent
echo "=== README.md content (first 100 lines) ==="
head -100 README.md
echo -e "\n=== Searching for template-related guidance in README ==="
grep -i -A5 -B5 "template\|private\|publish\|package.json" README.md | head -50Length of output: 10618
Set "private": true to align with template convention.
The travel-planner template is marked as "private": false, making it publishable to npm. This is inconsistent with other templates in the repository (automation/hiring, embed/*, special/halloween-costume) which set "private": true. Templates are starter kits meant for individual cloning and deployment, not separate npm packages.
- "private": false,
+ "private": true,📝 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.
| "private": false, | |
| "private": true, |
🤖 Prompt for AI Agents
In templates/agentic/travel-planner/package.json around line 10, the package is
incorrectly marked "private": false; update this to "private": true to match
repository template convention and prevent accidental npm publishing; ensure the
JSON remains valid (keep trailing comma rules consistent with surrounding
entries) and run a quick lint/format check after the change.
| #!/bin/bash | ||
|
|
||
| npm install | ||
|
|
||
| # Run the dev server | ||
| npm run dev No newline at end of file |
There was a problem hiding this comment.
Rename script and add error handling.
The script name "deploy.sh" is misleading since it runs the development server (npm run dev) rather than performing an actual deployment. Additionally, the script lacks error handling—if npm install fails, npm run dev will still execute.
Apply this diff to improve the script:
#!/bin/bash
+set -e # Exit on error
npm install
# Run the dev server
npm run devConsider renaming the file to dev.sh or start-dev.sh to better reflect its purpose.
📝 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.
| #!/bin/bash | |
| npm install | |
| # Run the dev server | |
| npm run dev | |
| #!/bin/bash | |
| set -e # Exit on error | |
| npm install | |
| # Run the dev server | |
| npm run dev |
🤖 Prompt for AI Agents
In templates/agentic/travel-planner/scripts/deploy.sh (lines 1-6): the script
name is misleading and it lacks error handling so `npm run dev` will run even if
`npm install` fails; rename the file to dev.sh or start-dev.sh to reflect its
purpose, make the script fail fast by adding `set -e` (or explicitly check the
exit status of `npm install` and exit with a non-zero code on failure), and keep
the script content to run `npm install` then `npm run dev` only if install
succeeds; also ensure the new file is executable (chmod +x).
What: Introduces a multi-agent travel researcher and booking assistant that coordinates Lamatic flight, hotel, and activity agents to deliver a shareable itinerary, budget, and booking checklist for any destination.
Prerequisites: Lamatic account with a valid LAMATIC_API_KEY.
Steps to run locally:
Live preview: https://agent-kit-vert.vercel.app/
Lamatic flow ID: 3cc791a2-ca33-4e27-8791-ff386cef14b2
Summary by CodeRabbit
New Features
Chores