-
Notifications
You must be signed in to change notification settings - Fork 22
feat: implement premium Blog Writing Automation kit with multi-step orchestration #64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: implement premium Blog Writing Automation kit with multi-step orchestration #64
Conversation
📝 WalkthroughWalkthroughAdds a new Blog Writing Automation kit: a Next.js app, Lamatic orchestration (draft → SEO → publish), UI pages and components, flow definitions and configs, Lamatic client/utilities, theme and styling, and shadcn UI component library scaffolding for the kit. Changes
Sequence DiagramsequenceDiagram
participant User as User
participant Client as Browser UI<br/>(page.tsx)
participant Server as Next.js Server<br/>(actions/orchestrate.ts)
participant Lamatic as Lamatic API
participant CMS as CMS (WordPress)
User->>Client: Submit topic, keywords, instructions
Client->>Server: POST runBlogAutomation(topic, keywords, instructions)
activate Server
rect rgba(70,130,180,0.08)
Note over Server: Phase 1 — Drafting
Server->>Lamatic: Execute drafting flow (flowId drafting)
Lamatic-->>Server: draftContent
end
rect rgba(60,179,113,0.08)
Note over Server: Phase 2 — SEO Optimization
Server->>Lamatic: Execute SEO flow (draft → optimize)
Lamatic-->>Server: optimizedContent
end
rect rgba(255,165,0,0.08)
Note over Server: Phase 3 — Publishing
Server->>Lamatic: Execute publish flow (optimizedContent → CMS call)
Lamatic->>CMS: POST to WordPress (API)
CMS-->>Lamatic: publish response (url, status, id)
Lamatic-->>Server: publishResult
end
Server-->>Client: BlogAutomationResult { success, draft, optimizedContent, url, error? }
deactivate Server
Client->>User: Render results, show publish URL
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro Disabled knowledge base sources:
📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 14
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
♻️ Duplicate comments (1)
kits/automation/blog-automation/components/ui/use-toast.ts (1)
1-191: Duplicate file - consolidate with hooks/use-toast.ts.This file is a duplicate of
kits/automation/blog-automation/hooks/use-toast.ts. Consider consolidating to a single location and re-exporting if needed from the other path.
🟡 Minor comments (14)
kits/automation/blog-automation/components/ui/carousel.tsx-96-105 (1)
96-105: Missing cleanup forreInitevent listener.The effect subscribes to both
reInitandselectevents but only removes theselectlistener on cleanup. This causes event handler accumulation if the component re-renders with a newapioronSelectreference.🔎 Proposed fix
React.useEffect(() => { if (!api) return onSelect(api) api.on('reInit', onSelect) api.on('select', onSelect) return () => { + api?.off('reInit', onSelect) api?.off('select', onSelect) } }, [api, onSelect])kits/automation/blog-automation/components/ui/chart.tsx-235-239 (1)
235-239: Zero values will not be displayed due to falsy check.The condition
item.value &&treats0as falsy, so legitimate zero values won't render in the tooltip. This could hide important data points.🔎 Proposed fix
- {item.value && ( + {item.value !== undefined && item.value !== null && ( <span className="text-foreground font-mono font-medium tabular-nums"> {item.value.toLocaleString()} </span> )}Alternatively, if you want to show zeros explicitly:
- {item.value && ( + {item.value != null && (kits/automation/blog-automation/components/ui/collapsible.tsx-3-3 (1)
3-3: Update@radix-ui/react-collapsiblefrom 1.1.2 to 1.1.12.The dependency is secure with no known vulnerabilities, but is 11 patch versions behind the latest stable release. Update to 1.1.12 to benefit from bug fixes and improvements.
kits/automation/blog-automation/components/ui/slider.tsx-16-24 (1)
16-24: Verify the default thumb count behavior.The
_valuesfallback of[min, max]creates a two-thumb range slider by default when neithervaluenordefaultValueare provided. This may not align with typical user expectations for a slider component, which usually defaults to a single thumb.Consider changing the fallback to
[min]for a single-thumb slider, or document this two-thumb default behavior clearly if it's intentional.🔎 Proposed fix for single-thumb default
const _values = React.useMemo( () => Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue - : [min, max], + : [min], [value, defaultValue, min, max], )kits/automation/blog-automation/components/ui/textarea.tsx-10-10 (1)
10-10: Add feature detection or fallback forfield-sizing-content.The
field-sizing-contentCSS property is currently supported only in Chromium-based browsers (Chrome 123+, Edge 123+, Chrome Android 123+). Firefox and Safari do not yet support it. Use CSS feature detection (@supports field-sizing: contentorCSS.supports('field-sizing: content')) to provide fallback sizing behavior for unsupported browsers, or specify min/max height constraints as a baseline.kits/automation/blog-automation/orchestrate.js-21-37 (1)
21-37: Fix polling value type in SEO flow.Same issue as the drafting flow -
pollingshould be booleanfalseinstead of string"false".🔎 Proposed fix
"mode": "sync", - "polling": "false" + "polling": false },kits/automation/blog-automation/orchestrate.js-1-20 (1)
1-20: Fix polling value type.The
pollingproperty is set to string"false"instead of booleanfalse. This could cause issues if the orchestration engine expects a boolean type.🔎 Proposed fix for drafting flow
"mode": "sync", - "polling": "false" + "polling": false },kits/automation/blog-automation/orchestrate.js-38-55 (1)
38-55: Fix polling value type in publish flow.Same issue -
pollingshould be booleanfalseinstead of string"false".🔎 Proposed fix
"mode": "sync", - "polling": "false" + "polling": false }kits/automation/blog-automation/components/ui/kbd.tsx-18-26 (1)
18-26: Fix type mismatch in KbdGroup.KbdGroup accepts
React.ComponentProps<'div'>but renders a<kbd>element. This creates a type safety issue where div-specific attributes could be passed but won't be valid on a kbd element.🔎 Proposed fix
-function KbdGroup({ className, ...props }: React.ComponentProps<'div'>) { +function KbdGroup({ className, ...props }: React.ComponentProps<'kbd'>) { return ( <kbdkits/automation/blog-automation/app/page.tsx-51-68 (1)
51-68: Step state never progresses beyond "drafting" during execution.The
stepstate is set to"drafting"at the start but is never updated to"seo"or"publishing"during the automation. The loading text (Line 182) that shows different messages based on step will always display "AI Drafting..." regardless of actual progress.If step-based progress feedback is desired, you'll need to either:
- Update the step state during execution (requires callback/event mechanism from
runBlogAutomation)- Remove the step-based messaging if granular progress isn't available
kits/automation/blog-automation/hooks/use-toast.ts-174-182 (1)
174-182: Incorrect dependency array causes re-subscription on every state change.The
useEffectdepends onstate, but it only needs to subscribe once on mount. This causes the listener to be removed and re-added on every state update, which is inefficient and could cause missed updates.🔎 Proposed fix
React.useEffect(() => { listeners.push(setState) return () => { const index = listeners.indexOf(setState) if (index > -1) { listeners.splice(index, 1) } } - }, [state]) + }, [])kits/automation/blog-automation/components/ui/use-toast.ts-174-182 (1)
174-182: Incorrect dependency array causes re-subscription on every state change.Same issue as in
hooks/use-toast.ts- theuseEffectshould use an empty dependency array to subscribe once on mount.🔎 Proposed fix
React.useEffect(() => { listeners.push(setState) return () => { const index = listeners.indexOf(setState) if (index > -1) { listeners.splice(index, 1) } } - }, [state]) + }, [])kits/automation/blog-automation/components/ui/empty.tsx-71-81 (1)
71-81: Fix type/implementation mismatch in EmptyDescription.The component accepts
React.ComponentProps<'p'>but renders a<div>. This creates a type mismatch.🔎 Proposed fix
-function EmptyDescription({ className, ...props }: React.ComponentProps<'p'>) { +function EmptyDescription({ className, ...props }: React.ComponentProps<'div'>) { return ( <divAlternatively, if
<p>semantics are intended:function EmptyDescription({ className, ...props }: React.ComponentProps<'p'>) { return ( - <div + <p data-slot="empty-description"kits/automation/blog-automation/components/ui/form.tsx-45-66 (1)
45-66: Context validation check is ineffective.The check
if (!fieldContext)on line 52 will never evaluate totruebecauseuseContext(FormFieldContext)returns the default value{}(an empty object, which is truthy) when not inside aFormField. This means the error will never be thrown, and subsequent code accessingfieldContext.namewill silently returnundefined.🔎 Proposed fix
const useFormField = () => { const fieldContext = React.useContext(FormFieldContext) const itemContext = React.useContext(FormItemContext) const { getFieldState } = useFormContext() const formState = useFormState({ name: fieldContext.name }) const fieldState = getFieldState(fieldContext.name, formState) - if (!fieldContext) { + if (!fieldContext.name) { throw new Error('useFormField should be used within <FormField>') } const { id } = itemContext
🧹 Nitpick comments (33)
kits/automation/blog-automation/components/ui/alert-dialog.tsx (1)
121-143: Consider adding data-slot attributes for consistency.
AlertDialogActionandAlertDialogCancelare missing thedata-slotattribute that all other components in this file consistently include. Adding these attributes would maintain the pattern established throughout the component and could be useful for CSS selectors, testing utilities, or debugging tools.🔎 Suggested additions for consistency
function AlertDialogAction({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Action>) { return ( <AlertDialogPrimitive.Action + data-slot="alert-dialog-action" className={cn(buttonVariants(), className)} {...props} /> ) } function AlertDialogCancel({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) { return ( <AlertDialogPrimitive.Cancel + data-slot="alert-dialog-cancel" className={cn(buttonVariants({ variant: 'outline' }), className)} {...props} /> ) }kits/automation/blog-automation/components/ui/scroll-area.tsx (2)
8-29: Well-structured ScrollArea component.The implementation correctly wraps Radix UI primitives with proper prop spreading and type safety. The focus-visible styles provide custom focus indicators while maintaining accessibility.
Consider forwarding refs for enhanced composability, though this is optional and may depend on your library's design patterns.
🔎 Optional: Forward refs for better composability
-function ScrollArea({ +const ScrollArea = React.forwardRef< + React.ElementRef<typeof ScrollAreaPrimitive.Root>, + React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> +>(({ className, children, ...props -}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) { +}, ref) => { return ( <ScrollAreaPrimitive.Root + ref={ref} data-slot="scroll-area" className={cn('relative', className)} {...props} > <ScrollAreaPrimitive.Viewport data-slot="scroll-area-viewport" className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1" > {children} </ScrollAreaPrimitive.Viewport> <ScrollBar /> <ScrollAreaPrimitive.Corner /> </ScrollAreaPrimitive.Root> ) -} +}) +ScrollArea.displayName = 'ScrollArea'
31-58: Clean ScrollBar implementation with proper orientation handling.The component correctly implements conditional styling for vertical and horizontal orientations, with sensible defaults. The touch-none and select-none classes provide appropriate scrollbar behavior. Exports are properly defined.
Similar to ScrollArea, consider forwarding refs for enhanced composability (optional).
🔎 Optional: Forward refs for ScrollBar
-function ScrollBar({ +const ScrollBar = React.forwardRef< + React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>, + React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar> +>(({ className, orientation = 'vertical', ...props -}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) { +}, ref) => { return ( <ScrollAreaPrimitive.ScrollAreaScrollbar + ref={ref} data-slot="scroll-area-scrollbar" orientation={orientation} className={cn( 'flex touch-none p-px transition-colors select-none', orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent', orientation === 'horizontal' && 'h-2.5 flex-col border-t border-t-transparent', className, )} {...props} > <ScrollAreaPrimitive.ScrollAreaThumb data-slot="scroll-area-thumb" className="bg-border relative flex-1 rounded-full" /> </ScrollAreaPrimitive.ScrollAreaScrollbar> ) -} +}) +ScrollBar.displayName = 'ScrollBar'kits/automation/blog-automation/components/ui/radio-group.tsx (1)
35-40: Consider simplifying the centering approach.The Indicator uses flexbox centering (
flex items-center justify-center) while the CircleIcon uses absolute positioning with transforms. Since absolute positioning removes an element from the flex layout, only the absolute positioning is effective here, making the flex properties redundant.🔎 Suggested simplification
Option A: Use flexbox centering only (cleaner)
<RadioGroupPrimitive.Indicator data-slot="radio-group-indicator" className="relative flex items-center justify-center" > - <CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" /> + <CircleIcon className="fill-primary size-2" /> </RadioGroupPrimitive.Indicator>Option B: Use absolute positioning only
<RadioGroupPrimitive.Indicator data-slot="radio-group-indicator" - className="relative flex items-center justify-center" + className="relative" > <CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" /> </RadioGroupPrimitive.Indicator>kits/automation/blog-automation/components/ui/resizable.tsx (1)
31-54: Well-implemented handle with good flexibility.The component correctly handles both horizontal and vertical orientations, and the
withHandleprop provides useful control over the visual indicator.Consider adding an
aria-labelto the inner handle div for improved accessibility:🔎 Optional accessibility enhancement
{withHandle && ( - <div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border"> + <div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border" aria-label="Resize handle"> <GripVerticalIcon className="size-2.5" /> </div> )}kits/automation/blog-automation/components/ui/item.tsx (2)
33-72: Consider minor cleanups (optional).Two small refinements you could make:
- Line 34: The
transition-colorsclass appears both unconditionally and within the[a&]:selector, which seems redundant.- Line 43: Trailing space after
'gap-4 '.The
Itemcomponent implementation itself is excellent—good use of the RadixasChildpattern and proper TypeScript typing.🔎 Proposed cleanup
- 'group/item flex items-center border border-transparent text-sm rounded-md transition-colors [a&]:hover:bg-accent/50 [a&]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]', + 'group/item flex items-center border border-transparent text-sm rounded-md transition-colors [a&]:hover:bg-accent/50 duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]', { variants: { variant: { default: 'bg-transparent', outline: 'border-border', muted: 'bg-muted/50', }, size: { - default: 'p-4 gap-4 ', + default: 'p-4 gap-4', sm: 'py-3 px-4 gap-2.5', }, },
119-130: Consider semantic heading support for ItemTitle (optional).
ItemTitlecurrently uses a<div>, which provides flexibility but lacks semantic meaning. For better accessibility and SEO, you might consider making it polymorphic to support heading elements (h1-h6) when appropriate.💡 Example polymorphic approach
function ItemTitle({ className, as: Component = 'div', ...props }: React.ComponentProps<'div'> & { as?: 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' }) { return ( <Component data-slot="item-title" className={cn( 'flex w-fit items-center gap-2 text-sm leading-snug font-medium', className, )} {...props} /> ) }This allows consumers to specify
<ItemTitle as="h2">...</ItemTitle>when needed.kits/automation/blog-automation/components/ui/chart.tsx (2)
182-189: Potential duplicate React key whendataKeyis undefined.If
item.dataKeyisundefinedfor multiple items, React will warn about duplicate keys and may have issues with list reconciliation.🔎 Suggested fix using index as fallback
- key={item.dataKey} + key={item.dataKey ?? `item-${index}`}
278-301: Potential duplicate React key when legend items share the samevalue.Using
item.valueas the key could cause issues if multiple legend items have identical values (e.g., same label text).🔎 Suggested fix using dataKey or index
- <div - key={item.value} + <div + key={item.dataKey || item.value}kits/automation/blog-automation/components/ui/toggle.tsx (1)
31-45: Consider adding forwardRef for improved ref forwarding.The component is well-implemented but lacks ref forwarding, which is a common pattern in reusable UI libraries. Consumers may need ref access for focus management, animations, or measuring.
🔎 Suggested enhancement with forwardRef
-function Toggle({ +const Toggle = React.forwardRef< + React.ElementRef<typeof TogglePrimitive.Root>, + React.ComponentProps<typeof TogglePrimitive.Root> & + VariantProps<typeof toggleVariants> +>(({ className, variant, size, ...props -}: React.ComponentProps<typeof TogglePrimitive.Root> & - VariantProps<typeof toggleVariants>) { +}, ref) => { return ( <TogglePrimitive.Root data-slot="toggle" + ref={ref} className={cn(toggleVariants({ variant, size, className }))} {...props} /> ) -} +}) +Toggle.displayName = 'Toggle'kits/automation/blog-automation/components/ui/toggle-group.tsx (2)
17-41: Consider adding forwardRef to ToggleGroup.The component correctly implements the context provider pattern and styling. However, adding ref forwarding would improve flexibility for consumers who need DOM access.
🔎 Suggested enhancement with forwardRef
-function ToggleGroup({ +const ToggleGroup = React.forwardRef< + React.ElementRef<typeof ToggleGroupPrimitive.Root>, + React.ComponentProps<typeof ToggleGroupPrimitive.Root> & + VariantProps<typeof toggleVariants> +>(({ className, variant, size, children, ...props -}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> & - VariantProps<typeof toggleVariants>) { +}, ref) => { return ( <ToggleGroupPrimitive.Root data-slot="toggle-group" data-variant={variant} data-size={size} + ref={ref} className={cn( 'group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs', className, )} {...props} > <ToggleGroupContext.Provider value={{ variant, size }}> {children} </ToggleGroupContext.Provider> </ToggleGroupPrimitive.Root> ) -} +}) +ToggleGroup.displayName = 'ToggleGroup'
43-71: Consider adding forwardRef to ToggleGroupItem.The component correctly consumes context for variant/size fallback and applies appropriate styling for grouped items. The focus z-index management and border handling are well-implemented. Adding ref forwarding would complete the component API.
🔎 Suggested enhancement with forwardRef
-function ToggleGroupItem({ +const ToggleGroupItem = React.forwardRef< + React.ElementRef<typeof ToggleGroupPrimitive.Item>, + React.ComponentProps<typeof ToggleGroupPrimitive.Item> & + VariantProps<typeof toggleVariants> +>(({ className, children, variant, size, ...props -}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> & - VariantProps<typeof toggleVariants>) { +}, ref) => { const context = React.useContext(ToggleGroupContext) return ( <ToggleGroupPrimitive.Item data-slot="toggle-group-item" data-variant={context.variant || variant} data-size={context.size || size} + ref={ref} className={cn( toggleVariants({ variant: context.variant || variant, size: context.size || size, }), 'min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l', className, )} {...props} > {children} </ToggleGroupPrimitive.Item> ) -} +}) +ToggleGroupItem.displayName = 'ToggleGroupItem'kits/automation/blog-automation/components/ui/collapsible.tsx (1)
5-31: Consider adding explicit return types.While TypeScript correctly infers the return types, adding explicit
React.ReactElementorJSX.Elementreturn types would improve code maintainability and self-documentation.🔎 Proposed refactor with explicit return types
function Collapsible({ ...props -}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) { +}: React.ComponentProps<typeof CollapsiblePrimitive.Root>): React.ReactElement { return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} /> } function CollapsibleTrigger({ ...props -}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) { +}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>): React.ReactElement { return ( <CollapsiblePrimitive.CollapsibleTrigger data-slot="collapsible-trigger" {...props} /> ) } function CollapsibleContent({ ...props -}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) { +}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>): React.ReactElement { return ( <CollapsiblePrimitive.CollapsibleContent data-slot="collapsible-content" {...props} /> ) }kits/automation/blog-automation/components/ui/drawer.tsx (2)
48-73: Redundant data-slot prop on DrawerPortal.Line 54 passes
data-slot="drawer-portal"to<DrawerPortal>, butDrawerPortalalready sets this attribute internally (line 23). The prop is harmless but unnecessary.🔎 Proposed cleanup
- <DrawerPortal data-slot="drawer-portal"> + <DrawerPortal>
1-73: Test keyboard and focus behavior thoroughly—vaul has known edge cases.Vaul 0.9.9 is React-compatible and provides ARIA semantics, keyboard navigation (Escape, Tab), and focus management. However, test for reported issues: aria-hidden console errors when opening via keyboard, and focus becoming trapped when using
modal={false}withsnapPointsor always-open configurations. Include keyboard and screen reader testing (VoiceOver, NVDA, JAWS) in your validation before deployment.kits/automation/blog-automation/README.md (1)
15-15: Minor redundancy: "SEO optimization" is technically redundant.The phrase "SEO optimization" is redundant since SEO already stands for "Search Engine Optimization." Consider using simply "SEO" or "search engine optimization."
🔎 Proposed fix
-3. **AI Drafting & SEO**: The agent drafts a blog post using AI, ensuring deep SEO optimization, coherence, and technical accuracy. +3. **AI Drafting & SEO**: The agent drafts a blog post using AI, ensuring deep SEO, coherence, and technical accuracy.kits/automation/blog-automation/flows/blog-seo/README.md (2)
1-1: Consider simplifying the redundant title.The title "SEO Optimization Flow" is redundant since SEO already stands for "Search Engine Optimization." Consider simplifying to "SEO Flow" or "Search Engine Optimization Flow."
🔎 Proposed fix
-# SEO Optimization Flow +# SEO Flow
7-9: Specify language for the code block.The fenced code block should specify a language identifier for proper syntax highlighting and rendering.
🔎 Proposed fix
-``` +```mermaid [API Request] → [SEO Optimize Content (LLM)] → [API Response]Or if it's meant to be plain text: ```diff -``` +```text [API Request] → [SEO Optimize Content (LLM)] → [API Response]</details> </blockquote></details> <details> <summary>kits/automation/blog-automation/flows/blog-drafting/README.md (1)</summary><blockquote> `7-9`: **Add language identifier to the fenced code block.** The fenced code block should specify a language identifier for proper rendering and markdown compliance. <details> <summary>🔎 Proposed fix</summary> ```diff -``` +```text [API Request] → [Generate Blog Draft (LLM)] → [API Response]</details> </blockquote></details> <details> <summary>kits/automation/blog-automation/flows/blog-drafting/meta.json (1)</summary><blockquote> `15-17`: **Consider populating or removing empty URL fields.** The `githubUrl`, `documentationUrl`, and `deployUrl` fields are set to empty strings. If these won't be populated, consider removing them or setting them to `null` for clearer intent. </blockquote></details> <details> <summary>kits/automation/blog-automation/flows/blog-seo/meta.json (1)</summary><blockquote> `14-16`: **Consider populating or removing empty URL fields.** The `githubUrl`, `documentationUrl`, and `deployUrl` fields are set to empty strings. If these won't be populated, consider removing them or setting them to `null` for clearer intent. </blockquote></details> <details> <summary>kits/automation/blog-automation/flows/blog-publish/README.md (1)</summary><blockquote> `7-9`: **Add language specifier to fenced code block.** The flow diagram uses a fenced code block without a language identifier, which triggers linter warnings. <details> <summary>🔎 Proposed fix</summary> ```diff -``` +```text [API Request] → [Publish to WordPress (API)] → [API Response]</details> </blockquote></details> <details> <summary>kits/automation/blog-automation/app/layout.tsx (1)</summary><blockquote> `2-7`: **Unused font imports—apply or remove.** The `Geist` and `Geist_Mono` fonts are imported but never applied. The variables are prefixed with `_` (indicating intentional non-use), yet the body uses `font-sans` which doesn't reference them. If the fonts are meant to be used, apply them to the body element. Otherwise, remove the imports to reduce bundle size. <details> <summary>🔎 Option 1: Apply the fonts</summary> ```diff -const _geist = Geist({ subsets: ["latin"] }); -const _geistMono = Geist_Mono({ subsets: ["latin"] }); +const geist = Geist({ + subsets: ["latin"], + variable: "--font-sans", +}); +const geistMono = Geist_Mono({ + subsets: ["latin"], + variable: "--font-mono", +});- <body className={`font-sans antialiased`}> + <body className={`${geist.variable} ${geistMono.variable} font-sans antialiased`}>Option 2: Remove the unused imports
-import { Geist, Geist_Mono } from 'next/font/google' - -const _geist = Geist({ subsets: ["latin"] }); -const _geistMono = Geist_Mono({ subsets: ["latin"] }); -kits/automation/blog-automation/orchestrate.js (1)
57-61: Consider adding environment variable validation.The configuration relies on several environment variables (
AUTOMATION_BLOG_DRAFTING,AUTOMATION_BLOG_SEO,AUTOMATION_BLOG_PUBLISH,LAMATIC_API_URL,LAMATIC_PROJECT_ID,LAMATIC_API_KEY). Consider adding validation to fail fast with helpful error messages if any required variables are missing.🔎 Example validation approach
Add validation before the config export:
const requiredEnvVars = [ 'AUTOMATION_BLOG_DRAFTING', 'AUTOMATION_BLOG_SEO', 'AUTOMATION_BLOG_PUBLISH', 'LAMATIC_API_URL', 'LAMATIC_PROJECT_ID', 'LAMATIC_API_KEY' ]; const missingVars = requiredEnvVars.filter(v => !process.env[v]); if (missingVars.length > 0) { throw new Error(`Missing required environment variables: ${missingVars.join(', ')}`); } export const config = { // ... }kits/automation/blog-automation/components/ui/tooltip.tsx (1)
21-29: Consider using a single TooltipProvider at app level.Each
Tooltipinstance creates its ownTooltipProviderwrapper. If multiple tooltips are rendered on a page, this creates unnecessary provider instances. Radix UI recommends wrapping the entire app in a singleTooltipProviderfor better performance.🔎 Recommended approach
Option 1 (Recommended): Remove the provider from individual Tooltip instances and add it once at the app level:
function Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>) { return ( - <TooltipProvider> - <TooltipPrimitive.Root data-slot="tooltip" {...props} /> - </TooltipProvider> + <TooltipPrimitive.Root data-slot="tooltip" {...props} /> ) }Then in your root layout or app component:
<TooltipProvider> {/* your app */} </TooltipProvider>Option 2: Keep current pattern but document that it's intended for standalone use, and provide a separate composition-friendly export.
kits/automation/blog-automation/app/page.tsx (1)
319-323: Timestamp reflects render time, not actual execution time.
new Date().toLocaleTimeString()is evaluated on each render, so the "Last Execution" time will update if the component re-renders. Consider storing the execution timestamp in state when the automation completes.🔎 Proposed approach
+ const [executedAt, setExecutedAt] = useState<Date | null>(null) // In handleSubmit, after success: if (response.success) { setResult(response) + setExecutedAt(new Date()) } // In the timestamp display: - <p className="text-slate-500 dark:text-slate-400 font-medium">Today at {new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</p> + <p className="text-slate-500 dark:text-slate-400 font-medium">Today at {executedAt?.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</p>kits/automation/blog-automation/hooks/use-toast.ts (1)
1-191: Remove duplicate file and consolidate use-toast implementation.This file is identical to
kits/automation/blog-automation/components/ui/use-toast.ts. Having duplicate implementations creates maintenance burden and risk of divergence. Keep only one file and re-export from the other location, or remove the duplicate entirely.kits/automation/blog-automation/actions/orchestrate.ts (3)
23-23: Consider using a server-side environment variable for mock mode.The
NEXT_PUBLIC_MOCK_MODEprefix exposes this variable to the browser bundle. Since this is a server action ("use server"), prefer a non-prefixed environment variable likeMOCK_MODEto avoid unnecessary client exposure.🔎 Proposed fix
- if (process.env.NEXT_PUBLIC_MOCK_MODE === "true" || !process.env.LAMATIC_API_KEY) { + if (process.env.MOCK_MODE === "true" || !process.env.LAMATIC_API_KEY) {
39-47: Clarify duplicate key behavior in payloads.The payload includes both lowercase and capitalized versions of the same keys (e.g.,
topicandTopic). In JavaScript objects, duplicate keys result in the last value overwriting earlier ones, so only the capitalized versions will be retained. If this is intentional to support specific Lamatic flow input mappings, consider adding a comment explaining this behavior to prevent future confusion.
115-117: Consider stricter URL validation for successful publishes.The current logic allows a successful status without a URL. If a URL is always expected on successful publish, consider updating the validation:
Suggested stricter validation
- if (status !== "success" && !url) { + if (status !== "success" || !url) { throw new Error(`Publishing failed: Response status was '${rawStatus}' and no URL was found.`) }Only apply this if your CMS workflows always return a URL on success.
kits/automation/blog-automation/components/ui/pagination.tsx (1)
9-9: Unused import:Buttonis imported but never used.Only
buttonVariantsis utilized in this file. Remove the unused import to keep the code clean.🔎 Proposed fix
-import { Button, buttonVariants } from '@/components/ui/button' +import { buttonVariants } from '@/components/ui/button'kits/automation/blog-automation/components/ui/menubar.tsx (1)
219-241: Minor class inconsistency:outline-nonevsoutline-hidden.
MenubarSubTriggerusesoutline-none(line 232), while similar components incontext-menu.tsxuseoutline-hidden. This appears to be a minor inconsistency that doesn't affect functionality but could be aligned for consistency.🔎 Align with other components
className={cn( - 'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[inset]:pl-8', + 'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8', className, )}kits/automation/blog-automation/components/ui/sidebar.tsx (1)
8-8: Consolidate duplicateuseIsMobilehook across kits.The hook exists in duplicate form in both
hooks/use-mobile.tsandcomponents/ui/use-mobile.tsxthroughout the codebase. This pattern is systemic—all 8 kits (blog-automation, agentic/deep-search, agentic/generation, embed/chat, embed/search, embed/sheets, automation/hiring, sample/content-generation) contain identical implementations in both locations. Consider establishing a single source of truth at the hooks level and removing the components/ui copies to reduce maintenance overhead.
📜 Review details
Configuration used: defaults
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 (1)
kits/automation/blog-automation/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (89)
kits/automation/blog-automation/.env.examplekits/automation/blog-automation/.gitignorekits/automation/blog-automation/README.mdkits/automation/blog-automation/actions/orchestrate.tskits/automation/blog-automation/app/globals.csskits/automation/blog-automation/app/layout.tsxkits/automation/blog-automation/app/page.tsxkits/automation/blog-automation/components.jsonkits/automation/blog-automation/components/header.tsxkits/automation/blog-automation/components/theme-provider.tsxkits/automation/blog-automation/components/ui/accordion.tsxkits/automation/blog-automation/components/ui/alert-dialog.tsxkits/automation/blog-automation/components/ui/alert.tsxkits/automation/blog-automation/components/ui/aspect-ratio.tsxkits/automation/blog-automation/components/ui/avatar.tsxkits/automation/blog-automation/components/ui/badge.tsxkits/automation/blog-automation/components/ui/breadcrumb.tsxkits/automation/blog-automation/components/ui/button-group.tsxkits/automation/blog-automation/components/ui/button.tsxkits/automation/blog-automation/components/ui/calendar.tsxkits/automation/blog-automation/components/ui/card.tsxkits/automation/blog-automation/components/ui/carousel.tsxkits/automation/blog-automation/components/ui/chart.tsxkits/automation/blog-automation/components/ui/checkbox.tsxkits/automation/blog-automation/components/ui/collapsible.tsxkits/automation/blog-automation/components/ui/command.tsxkits/automation/blog-automation/components/ui/context-menu.tsxkits/automation/blog-automation/components/ui/dialog.tsxkits/automation/blog-automation/components/ui/drawer.tsxkits/automation/blog-automation/components/ui/dropdown-menu.tsxkits/automation/blog-automation/components/ui/empty.tsxkits/automation/blog-automation/components/ui/field.tsxkits/automation/blog-automation/components/ui/form.tsxkits/automation/blog-automation/components/ui/hover-card.tsxkits/automation/blog-automation/components/ui/input-group.tsxkits/automation/blog-automation/components/ui/input-otp.tsxkits/automation/blog-automation/components/ui/input.tsxkits/automation/blog-automation/components/ui/item.tsxkits/automation/blog-automation/components/ui/kbd.tsxkits/automation/blog-automation/components/ui/label.tsxkits/automation/blog-automation/components/ui/menubar.tsxkits/automation/blog-automation/components/ui/navigation-menu.tsxkits/automation/blog-automation/components/ui/pagination.tsxkits/automation/blog-automation/components/ui/popover.tsxkits/automation/blog-automation/components/ui/progress.tsxkits/automation/blog-automation/components/ui/radio-group.tsxkits/automation/blog-automation/components/ui/resizable.tsxkits/automation/blog-automation/components/ui/scroll-area.tsxkits/automation/blog-automation/components/ui/select.tsxkits/automation/blog-automation/components/ui/separator.tsxkits/automation/blog-automation/components/ui/sheet.tsxkits/automation/blog-automation/components/ui/sidebar.tsxkits/automation/blog-automation/components/ui/skeleton.tsxkits/automation/blog-automation/components/ui/slider.tsxkits/automation/blog-automation/components/ui/sonner.tsxkits/automation/blog-automation/components/ui/spinner.tsxkits/automation/blog-automation/components/ui/switch.tsxkits/automation/blog-automation/components/ui/table.tsxkits/automation/blog-automation/components/ui/tabs.tsxkits/automation/blog-automation/components/ui/textarea.tsxkits/automation/blog-automation/components/ui/toast.tsxkits/automation/blog-automation/components/ui/toaster.tsxkits/automation/blog-automation/components/ui/toggle-group.tsxkits/automation/blog-automation/components/ui/toggle.tsxkits/automation/blog-automation/components/ui/tooltip.tsxkits/automation/blog-automation/components/ui/use-mobile.tsxkits/automation/blog-automation/components/ui/use-toast.tskits/automation/blog-automation/config.jsonkits/automation/blog-automation/flows/blog-drafting/README.mdkits/automation/blog-automation/flows/blog-drafting/config.jsonkits/automation/blog-automation/flows/blog-drafting/inputs.jsonkits/automation/blog-automation/flows/blog-drafting/meta.jsonkits/automation/blog-automation/flows/blog-publish/README.mdkits/automation/blog-automation/flows/blog-publish/config.jsonkits/automation/blog-automation/flows/blog-publish/inputs.jsonkits/automation/blog-automation/flows/blog-publish/meta.jsonkits/automation/blog-automation/flows/blog-seo/README.mdkits/automation/blog-automation/flows/blog-seo/config.jsonkits/automation/blog-automation/flows/blog-seo/inputs.jsonkits/automation/blog-automation/flows/blog-seo/meta.jsonkits/automation/blog-automation/hooks/use-mobile.tskits/automation/blog-automation/hooks/use-toast.tskits/automation/blog-automation/lib/lamatic-client.tskits/automation/blog-automation/lib/utils.tskits/automation/blog-automation/next.config.mjskits/automation/blog-automation/orchestrate.jskits/automation/blog-automation/package.jsonkits/automation/blog-automation/postcss.config.mjskits/automation/blog-automation/tsconfig.json
🧰 Additional context used
🧬 Code graph analysis (56)
kits/automation/blog-automation/components/ui/alert.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/progress.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/switch.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/use-mobile.tsx (1)
kits/automation/blog-automation/hooks/use-mobile.ts (1)
useIsMobile(5-19)
kits/automation/blog-automation/components/ui/separator.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/toggle.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/checkbox.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/accordion.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/button.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/toggle-group.tsx (2)
kits/automation/blog-automation/components/ui/toggle.tsx (1)
toggleVariants(47-47)kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/spinner.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/textarea.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/scroll-area.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/postcss.config.mjs (1)
kits/automation/blog-automation/orchestrate.js (2)
config(1-62)config(1-62)
kits/automation/blog-automation/components/ui/badge.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/actions/orchestrate.ts (3)
kits/automation/blog-automation/orchestrate.js (2)
config(1-62)config(1-62)kits/automation/blog-automation/postcss.config.mjs (1)
config(2-6)kits/automation/blog-automation/lib/lamatic-client.ts (1)
lamaticClient(18-22)
kits/automation/blog-automation/components/ui/calendar.tsx (2)
kits/automation/blog-automation/components/ui/button.tsx (2)
Button(60-60)buttonVariants(60-60)kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/tooltip.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/hooks/use-mobile.ts (1)
kits/automation/blog-automation/components/ui/use-mobile.tsx (1)
useIsMobile(5-19)
kits/automation/blog-automation/components/ui/input.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/kbd.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/hover-card.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/navigation-menu.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/lib/lamatic-client.ts (1)
kits/automation/blog-automation/orchestrate.js (2)
config(1-62)config(1-62)
kits/automation/blog-automation/components/ui/breadcrumb.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/table.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/slider.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/app/page.tsx (2)
kits/automation/blog-automation/actions/orchestrate.ts (2)
BlogAutomationResult(6-12)runBlogAutomation(14-133)kits/automation/blog-automation/components/header.tsx (1)
Header(4-41)
kits/automation/blog-automation/components/ui/pagination.tsx (2)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)kits/automation/blog-automation/components/ui/button.tsx (2)
Button(60-60)buttonVariants(60-60)
kits/automation/blog-automation/components/ui/input-otp.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/avatar.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/button-group.tsx (2)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)kits/automation/blog-automation/components/ui/separator.tsx (1)
Separator(28-28)
kits/automation/blog-automation/components/ui/sonner.tsx (1)
kits/automation/blog-automation/components/ui/toaster.tsx (1)
Toaster(13-35)
kits/automation/blog-automation/components/ui/command.tsx (2)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)kits/automation/blog-automation/components/ui/dialog.tsx (5)
Dialog(133-133)DialogHeader(138-138)DialogTitle(141-141)DialogDescription(136-136)DialogContent(135-135)
kits/automation/blog-automation/components/ui/drawer.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/popover.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/label.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/radio-group.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/skeleton.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/empty.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/carousel.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/item.tsx (2)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)kits/automation/blog-automation/components/ui/separator.tsx (1)
Separator(28-28)
kits/automation/blog-automation/components/ui/dialog.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/form.tsx (2)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)kits/automation/blog-automation/components/ui/label.tsx (1)
Label(24-24)
kits/automation/blog-automation/components/ui/toast.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/select.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/tabs.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/context-menu.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/input-group.tsx (4)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)kits/automation/blog-automation/components/ui/button.tsx (1)
Button(60-60)kits/automation/blog-automation/components/ui/input.tsx (1)
Input(21-21)kits/automation/blog-automation/components/ui/textarea.tsx (1)
Textarea(18-18)
kits/automation/blog-automation/components/ui/resizable.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/toaster.tsx (4)
kits/automation/blog-automation/components/ui/sonner.tsx (1)
Toaster(25-25)kits/automation/blog-automation/components/ui/use-toast.ts (1)
useToast(191-191)kits/automation/blog-automation/hooks/use-toast.ts (1)
useToast(191-191)kits/automation/blog-automation/components/ui/toast.tsx (6)
ToastProvider(122-122)Toast(124-124)ToastTitle(125-125)ToastDescription(126-126)ToastClose(127-127)ToastViewport(123-123)
kits/automation/blog-automation/components/ui/use-toast.ts (1)
kits/automation/blog-automation/hooks/use-toast.ts (2)
reducer(74-127)toast(191-191)
kits/automation/blog-automation/components/ui/sidebar.tsx (8)
kits/automation/blog-automation/hooks/use-mobile.ts (1)
useIsMobile(5-19)kits/automation/blog-automation/components/ui/tooltip.tsx (4)
TooltipProvider(61-61)TooltipContent(61-61)Tooltip(61-61)TooltipTrigger(61-61)kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)kits/automation/blog-automation/components/ui/sheet.tsx (5)
Sheet(131-131)SheetContent(134-134)SheetHeader(135-135)SheetTitle(137-137)SheetDescription(138-138)kits/automation/blog-automation/components/ui/button.tsx (1)
Button(60-60)kits/automation/blog-automation/components/ui/input.tsx (1)
Input(21-21)kits/automation/blog-automation/components/ui/separator.tsx (1)
Separator(28-28)kits/automation/blog-automation/components/ui/skeleton.tsx (1)
Skeleton(13-13)
kits/automation/blog-automation/components/ui/menubar.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/sheet.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
kits/automation/blog-automation/components/ui/dropdown-menu.tsx (1)
kits/automation/blog-automation/lib/utils.ts (1)
cn(4-6)
🪛 ast-grep (0.40.3)
kits/automation/blog-automation/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)
kits/automation/blog-automation/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)
🪛 dotenv-linter (4.0.0)
kits/automation/blog-automation/.env.example
[warning] 2-2: [SpaceCharacter] The line has spaces around equal sign
(SpaceCharacter)
[warning] 3-3: [SpaceCharacter] The line has spaces around equal sign
(SpaceCharacter)
[warning] 4-4: [SpaceCharacter] The line has spaces around equal sign
(SpaceCharacter)
[warning] 4-4: [UnorderedKey] The AUTOMATION_BLOG_PUBLISH key should go before the AUTOMATION_BLOG_SEO key
(UnorderedKey)
[warning] 7-7: [SpaceCharacter] The line has spaces around equal sign
(SpaceCharacter)
[warning] 8-8: [SpaceCharacter] The line has spaces around equal sign
(SpaceCharacter)
[warning] 9-9: [SpaceCharacter] The line has spaces around equal sign
(SpaceCharacter)
[warning] 9-9: [UnorderedKey] The LAMATIC_API_KEY key should go before the LAMATIC_API_URL key
(UnorderedKey)
🪛 LanguageTool
kits/automation/blog-automation/flows/blog-seo/README.md
[style] ~1-~1: This phrase is redundant (‘O’ stands for ‘Optimization’). Use simply “SEO”.
Context: # SEO Optimization Flow This flow optimizes a blog post d...
(ACRONYM_TAUTOLOGY)
[style] ~16-~16: This phrase is redundant (‘O’ stands for ‘optimization’). Use simply “SEO”.
Context: ...s` | string | Yes | Target keywords for SEO optimization | ## Output Schema | Field | Type | D...
(ACRONYM_TAUTOLOGY)
kits/automation/blog-automation/README.md
[style] ~15-~15: This phrase is redundant (‘O’ stands for ‘optimization’). Use simply “SEO”.
Context: ...fts a blog post using AI, ensuring deep SEO optimization, coherence, and technical accuracy. 4. ...
(ACRONYM_TAUTOLOGY)
🪛 markdownlint-cli2 (0.18.1)
kits/automation/blog-automation/flows/blog-publish/README.md
7-7: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
kits/automation/blog-automation/flows/blog-seo/README.md
7-7: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
kits/automation/blog-automation/flows/blog-drafting/README.md
7-7: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (3)
kits/automation/blog-automation/flows/blog-publish/config.json (1)
33-42: LGTM: Environment variable interpolation correctly implemented.The WordPress credentials now use proper template syntax (
{{env.WORDPRESS_SITE_ID}}and{{env.WORDPRESS_TOKEN}}), addressing the previous review concern about hardcoded placeholders. This aligns with the environment variables defined in.env.example.kits/automation/blog-automation/.env.example (1)
2-13: LGTM: Environment variable syntax corrected.The spaces around equal signs have been removed, resolving the previous review concern. The format now follows the standard
KEY=valuepattern that.envparsers expect.Static analysis tools flag quote characters and key ordering, but these are minor style preferences that don't impact functionality.
kits/automation/blog-automation/lib/lamatic-client.ts (1)
1-14: LGTM: All previous review concerns addressed.The implementation now:
- Imports from the TypeScript
orchestrate.tsfile (providing proper type safety)- Validates credentials upfront and throws a descriptive error if missing
- Creates the Lamatic client only after validation succeeds
This addresses all three major concerns from previous reviews.
🧹 Nitpick comments (4)
kits/automation/blog-automation/next.config.mjs (1)
10-12: Verify if disabling image optimization is intentional.Setting
unoptimized: truedisables Next.js's automatic image optimization. For a blog automation tool that likely handles images in content, this could impact page load performance and user experience with larger image files.If this is intentional (e.g., your deployment platform handles optimization, or you're using external CDN processing), that's fine. Otherwise, consider enabling optimization for better performance.
kits/automation/blog-automation/orchestrate.ts (1)
1-22: Consider using literal types for better type safety.The
modeandpollingproperties are typed asstring, which allows any value. Consider using TypeScript literal types or enums to restrict these to valid values (e.g.,mode: "sync" | "async",polling: "true" | "false"orboolean).🔎 Suggested type refinements
export interface FlowConfig { name: string; type: string; workflowId: string | undefined; description: string; expectedOutput: string[]; inputSchema: Record<string, string>; outputSchema: Record<string, string>; - mode: string; - polling: string; + mode: "sync" | "async"; + polling: boolean; dependsOn?: string[]; }Then update the config values accordingly:
- "mode": "sync", - "polling": "false" + "mode": "sync", + "polling": falsekits/automation/blog-automation/actions/orchestrate.ts (2)
39-47: Consider standardizing flow input schemas to eliminate duplicate keys.The payloads include duplicate keys with different casings (e.g.,
topicandTopic,keywordsandKeywords) to work around inconsistent flow schemas. While this approach is pragmatic, it:
- Makes the payload structure unclear
- Relies on JavaScript object key overwriting behavior (the last key wins)
- Suggests the flow schemas themselves are inconsistent
💡 Recommended approach
Standardize the flow schemas in Lamatic Studio to use a consistent casing convention (e.g., all lowercase or camelCase), then remove the duplicates:
const payload = { topic, keywords, instructions, - // Common variations to ensure mapping - Topic: topic, - Keywords: keywords, - Instructions: instructions }This will make the code more maintainable and eliminate ambiguity about which keys the flows actually use.
Also applies to: 64-71, 88-95
57-58: Extensive output property fallbacks suggest schema inconsistency.The code checks multiple property names for each flow's output (e.g.,
generatedResponse || content || draft,url || post_url || link). This defensive approach handles variations but indicates the flow output schemas may be inconsistent or undocumented.💡 Suggestion
Document the actual output schema for each flow in the
outputSchemaconfiguration (inorchestrate.ts), then access properties directly using the documented schema. If flows genuinely return different schemas, add a comment explaining why these fallbacks are necessary.Alternatively, if you control the Lamatic flows, standardize their output property names to match the documented
outputSchema.Also applies to: 80-81, 105-108
📜 Review details
Configuration used: defaults
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 (1)
kits/automation/blog-automation/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (10)
kits/automation/blog-automation/.env.examplekits/automation/blog-automation/actions/orchestrate.tskits/automation/blog-automation/components/ui/breadcrumb.tsxkits/automation/blog-automation/components/ui/command.tsxkits/automation/blog-automation/components/ui/skeleton.tsxkits/automation/blog-automation/flows/blog-publish/config.jsonkits/automation/blog-automation/lib/lamatic-client.tskits/automation/blog-automation/next.config.mjskits/automation/blog-automation/orchestrate.tskits/automation/blog-automation/package.json
🚧 Files skipped from review as they are similar to previous changes (4)
- kits/automation/blog-automation/components/ui/skeleton.tsx
- kits/automation/blog-automation/package.json
- kits/automation/blog-automation/components/ui/command.tsx
- kits/automation/blog-automation/components/ui/breadcrumb.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
kits/automation/blog-automation/lib/lamatic-client.ts (1)
kits/automation/blog-automation/orchestrate.ts (1)
config(24-85)
🪛 dotenv-linter (4.0.0)
kits/automation/blog-automation/.env.example
[warning] 4-4: [UnorderedKey] The AUTOMATION_BLOG_PUBLISH key should go before the AUTOMATION_BLOG_SEO key
(UnorderedKey)
[warning] 7-7: [QuoteCharacter] The value has quote characters (', ")
(QuoteCharacter)
[warning] 8-8: [QuoteCharacter] The value has quote characters (', ")
(QuoteCharacter)
[warning] 9-9: [QuoteCharacter] The value has quote characters (', ")
(QuoteCharacter)
[warning] 9-9: [UnorderedKey] The LAMATIC_API_KEY key should go before the LAMATIC_API_URL key
(UnorderedKey)
[warning] 12-12: [QuoteCharacter] The value has quote characters (', ")
(QuoteCharacter)
[warning] 13-13: [QuoteCharacter] The value has quote characters (', ")
(QuoteCharacter)
🔇 Additional comments (5)
kits/automation/blog-automation/orchestrate.ts (2)
27-43: LGTM: Drafting flow configuration is well-defined.The drafting flow correctly specifies input schema (topic, keywords, instructions) and output schema (draft), with appropriate synchronous execution mode.
44-85: LGTM: Sequential flow dependencies and API configuration are correct.The dependency chain (drafting → SEO → publish) is properly defined, and API credentials correctly handle potentially undefined environment variables. The validation in
lamatic-client.tsensures these are set before use.kits/automation/blog-automation/actions/orchestrate.ts (3)
6-18: LGTM: Type definition and function signature are well-structured.The
BlogAutomationResulttype clearly defines success/failure paths with optional fields for each pipeline stage (draft, optimizedContent, url). The function signature is clean and focused.
110-117: LGTM: Robust error detection for publishing failures.The HTML detection (lines 111-113) effectively catches CMS 404 errors that might slip through as successful responses, and the validation logic (lines 115-117) ensures the publish operation genuinely succeeded with a URL. This defensive programming prevents silent failures.
126-132: LGTM: Error handling is comprehensive.The catch block properly logs errors and returns a standardized failure result with a clear error message. The type guard (
error instanceof Error) safely handles both Error objects and unexpected thrown values.
Blog Writing Automation AgentKit
What This Asset Does
The Blog Writing Automation AgentKit is an end-to-end AI-powered orchestration tool designed to automate the lifecycle of content creation and publishing. It solves the production of consistent blog content by following a high-quality pipeline:
The kit provides a premium glassmorphic dashboard for manual triggering and status monitoring.
Providers & Prerequisites
How to Run Locally (only for Kits)
/kits/automation/blog-automation.bun installornpm install..envfile using.env.exampleas a template.npm run dev.Issue: #27
Demo
Screen.Recording.2025-12-23.at.11.29.10.PM.1.mp4
Lamatic Flow
Flow configurations and schemas are maintained in the
/flowsdirectory of the kit.PR Checklist [Kit]
npm run dev.envcontains all required keys (no secrets in commits).env.examplehas no secrets, only placeholdersREADME.mddocuments setup, environment variables, and usagekits/automation/blog-automation/config.jsonandorchestrate.jsare present and validflows/folderSummary by CodeRabbit
New Features
Configuration
Chores
✏️ Tip: You can customize this high-level summary in your review settings.