Conversation
commit: |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a full-featured AI chat UI (Chat, ChatReasoning, ChatShimmer, ChatTool) and a persistent composable (useChat). Introduces a streaming AI server route at docs/server/api/ai.post.ts and removes the previous search POST route. Reworks theme handling and persistence (useTheme gains style, link, blackAsPrimary, applyThemeSettings; plugins and cssVariableDefaults updated). Adds a new Sidebar runtime component, theme module, many locale sidebar translations, several sidebar example pages, tests, and documentation updates. Multiple docs app templates and search integrations were adjusted to integrate the chat and theme changes. Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes 🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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: 11
🧹 Nitpick comments (11)
docs/app/components/chat/Chat.vue (1)
93-100: Automaticregenerate()on user message sync could cause unexpected behavior.When
messageschanges externally (e.g., fromuseSearch.tsadding a user message), this watch triggersregenerate()if the last message is from the user. This is likely intentional for the search → chat flow, but could cause issues if messages are modified for other reasons.Consider adding a comment explaining this behavior:
watch(messages, (newMessages) => { if (_skipSync) return chat.messages = newMessages + // When a new user message is added externally (e.g., from search), + // automatically trigger AI response generation if (chat.lastMessage?.role === 'user') { chat.regenerate() } })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/app/components/chat/Chat.vue` around lines 93 - 100, The watcher on messages (watch(messages, ...)) currently updates chat.messages and calls chat.regenerate() when chat.lastMessage?.role === 'user', which causes automatic regeneration whenever messages are changed externally; add a concise comment above this watch explaining that this behavior is intentional to support the search→chat flow (and to note potential side effects), and mention the _skipSync flag purpose (to avoid loops) so future maintainers understand why regenerate() is invoked on external user message sync; reference the watch, messages, _skipSync, chat.messages, chat.lastMessage, and chat.regenerate symbols in the comment.docs/app/plugins/theme.ts (2)
141-197: Server-side inline scripts duplicate client-side logic.The inline scripts for
chat-custom-colorsandchat-css-variablesduplicate the restoration logic from the client-side functions. This duplication increases maintenance burden.This is a common pattern for avoiding FOUC (Flash of Unstyled Content) with theme restoration, so the trade-off may be acceptable. Consider adding a comment explaining why both are needed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/app/plugins/theme.ts` around lines 141 - 197, The inline IIFEs that create 'chat-custom-colors' and 'chat-css-variables' duplicate client-side restoration logic and should be annotated: add a concise comment near the inline scripts that references the client-side theme restoration functions and explains that the duplication is intentional to prevent FOUC by restoring colors/variables on first paint (mention the 'chat-custom-colors' and 'chat-css-variables' script ids and the cssVariableDefaults/merge logic), so future readers know why both server-side inline scripts and client-side functions co-exist rather than removing the apparent duplication.
100-116: Type safety bypassed withas anycasts.The
as anycasts on lines 104 and 110 bypass TypeScript's type checking. While this may be necessary for dynamic property assignment from localStorage, it could mask type errors if the stored data structure changes.Consider adding runtime validation or type guards:
♻️ Optional: Add runtime type validation
function restoreAiTheme() { const raw = localStorage.getItem('nuxt-ui-ai-theme') if (!raw) return try { const extras = JSON.parse(raw) - if (extras.colors) { + if (extras.colors && typeof extras.colors === 'object') { for (const [key, value] of Object.entries(extras.colors)) { + if (typeof key === 'string' && typeof value === 'string') { (appConfig.ui.colors as any)[key] = value + } } } - if (extras.ui) { + if (extras.ui && typeof extras.ui === 'object') { for (const [key, value] of Object.entries(extras.ui)) { if (key === 'colors' || key === 'icons') continue + if (typeof key === 'string') { ;(appConfig.ui as any)[key] = value + } } } } catch { // ignore malformed localStorage } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/app/plugins/theme.ts` around lines 100 - 116, The code currently uses unsafe casts ((appConfig.ui.colors as any) and (appConfig.ui as any)) when applying parsed localStorage extras; replace these casts with runtime validation and controlled assignment: add type-guard functions (e.g., isValidColors(extras.colors) and isValidUI(extras.ui)) that check each key/value is an expected string/hex or allowed UI prop, then only copy validated entries into appConfig.ui.colors and appConfig.ui (or use a shallow merge like Object.assign with the validated object); keep the existing try/catch but ensure malformed or unexpected properties are rejected by the guards so TypeScript safety is preserved at runtime.docs/app/components/content/examples/sidebar/SidebarExample.vue (2)
127-127: Shortcuts registered once at setup time.
extractShortcuts(teamsItems.value)is evaluated once during component setup. IfteamsItemschanges (e.g., teams are added/removed), the shortcuts won't update. For this example this is likely fine since teams are static, but worth noting if the example is meant to demonstrate dynamic team lists.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/app/components/content/examples/sidebar/SidebarExample.vue` at line 127, The call defineShortcuts(extractShortcuts(teamsItems.value)) runs only once at setup so shortcuts won’t reflect later changes to teamsItems; update the example to recompute and re-register shortcuts when teamsItems changes (e.g., use a watch or watchEffect on teamsItems and call defineShortcuts(extractShortcuts(teamsItems.value)) inside it, or make a computed like const currentShortcuts = computed(() => extractShortcuts(teamsItems.value)) and watch currentShortcuts to call defineShortcuts), referencing defineShortcuts, extractShortcuts and teamsItems in your change.
93-116: Inconsistent event handlers between Light and Dark mode checkboxes.The Light mode checkbox only uses
onSelectwithe.preventDefault(), while Dark mode uses bothonUpdateCheckedandonSelect. This asymmetry could cause confusion:
- Light: sets preference immediately in
onSelect- Dark: sets preference in
onUpdateCheckedonly whencheckedis true, plus has a separateonSelectthat just prevents defaultConsider aligning the patterns for consistency:
♻️ Suggested unified approach
children: [{ label: 'Light', icon: 'i-lucide-sun', type: 'checkbox', checked: colorMode.value === 'light', - onSelect(e: Event) { - e.preventDefault() - - colorMode.preference = 'light' + onUpdateChecked(checked: boolean) { + if (checked) { + colorMode.preference = 'light' + } + }, + onSelect(e: Event) { + e.preventDefault() } }, { label: 'Dark', icon: 'i-lucide-moon', type: 'checkbox', checked: colorMode.value === 'dark', onUpdateChecked(checked: boolean) { if (checked) { colorMode.preference = 'dark' } }, onSelect(e: Event) { e.preventDefault() } }]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/app/components/content/examples/sidebar/SidebarExample.vue` around lines 93 - 116, The Light and Dark checkbox entries in the children array are inconsistent: the "Light" item sets colorMode.preference in onSelect while "Dark" sets it in onUpdateChecked and uses onSelect only to preventDefault; unify them by moving preference assignment to the same handler for both (prefer using onUpdateChecked(checked: boolean) to set colorMode.preference = 'light' or 'dark' when checked) and keep onSelect(e: Event) only to e.preventDefault(); update both entries (labels 'Light' and 'Dark', handlers onUpdateChecked and onSelect) so they follow the same pattern.docs/server/api/ai.post.ts (2)
342-349:onFinishandonErrorcallbacks should handle potential errors during cleanup.The
httpClient?.close()call in the lifecycle hooks could potentially throw. Whileevent.waitUntilhandles the promise, any synchronous error fromclose()would be unhandled.🛡️ Proposed fix to wrap cleanup in try-catch
onFinish: () => { - event.waitUntil(httpClient?.close()) + event.waitUntil( + httpClient?.close().catch(err => console.error('MCP client close error:', err)) + ) }, onError: (error) => { console.error('streamText error:', error) - event.waitUntil(httpClient?.close()) + event.waitUntil( + httpClient?.close().catch(err => console.error('MCP client close error:', err)) + ) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/server/api/ai.post.ts` around lines 342 - 349, Wrap the cleanup calls inside the onFinish and onError callbacks with try/catch so any synchronous exceptions from httpClient?.close() are caught, and pass a promise to event.waitUntil (e.g., call a small async wrapper that awaits httpClient?.close()). Update the onFinish and onError handlers (the callbacks passed to streamText) to use this wrapper and log or handle errors in the catch block so cleanup failures are not unhandled.
112-316: System prompt is comprehensive but extremely long.The system prompt (~200+ lines) provides detailed guidance for the AI assistant. While thorough, this significantly increases token usage per request. Consider:
- Moving static reference content (color options, CSS variable defaults) to a separate tool the AI can query when needed
- Condensing formatting rules and examples
This is acceptable for a docs site assistant but worth noting for cost optimization.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/server/api/ai.post.ts` around lines 112 - 316, The system prompt string assigned to the variable `system` is excessively long and inflates token usage; refactor by extracting large static reference sections (e.g., "color options", "CSS Variable defaults", full "LIVE THEME CUSTOMIZATION" details) into a separate retrievable resource or tool (e.g., a docsLookup/getThemeGuidelines helper) and replace those inline blocks in `system` with concise pointers or summaries and calls to that tool; update any code paths that rely on `system` to call the new helper when detailed guidance is needed and keep `system` focused on concise guardrails and critical formatting rules only.src/runtime/components/Sidebar.vue (2)
140-164: Complex watcher interactions may cause unexpected state synchronization.There are three watchers managing
isMobile,modelOpen, andopenMobilestate. The circular dependency between these watchers (lines 141-157, 153-157, 160-164) could lead to unexpected behavior:
watch(isMobile)setsmodelOpenwatch(modelOpen)setsopenMobilewhen mobilewatch(openMobile)setsmodelOpenwhen mobileThis creates potential for redundant updates. Consider consolidating or adding guards to prevent unnecessary re-triggers.
♻️ Consider adding a flag to prevent cascading updates
+const isTransitioning = ref(false) + // Handle viewport transitions and initial mobile state watch(isMobile, (mobile) => { + isTransitioning.value = true if (mobile) { // Save desktop state and align model to mobile (closed) desktopOpen.value = modelOpen.value modelOpen.value = false } else { // Restore desktop state modelOpen.value = desktopOpen.value } + nextTick(() => { isTransitioning.value = false }) }, { immediate: true }) // Sync model changes into mobile state watch(modelOpen, (value) => { - if (isMobile.value) { + if (isMobile.value && !isTransitioning.value) { openMobile.value = value } })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/runtime/components/Sidebar.vue` around lines 140 - 164, The three interdependent watchers (watch(isMobile), watch(modelOpen), watch(openMobile)) create circular updates; to fix, add a short-lived guard flag (e.g., syncing) or explicit equality checks inside the watcher callbacks to skip assignments when the target already equals the source, or consolidate into a single watcher that updates desktopOpen, modelOpen and openMobile atomically; update the watchers for isMobile, modelOpen, and openMobile to return early when syncing is true (or when value === target) and set/clear syncing only around the intentional state changes to prevent cascading re-triggers while keeping desktopOpen, modelOpen and openMobile in sync.
314-360: Duplicated template structure in mobile menu content.The mobile menu content slot (lines 316-359) largely duplicates the
DefineInnerTemplatestructure (lines 205-251). This creates maintenance burden - changes to the header/body/footer structure need to be made in two places.Consider extracting the shared structure into a single reusable template, or using
ReuseInnerTemplatewithin the mobile content slot with appropriate adaptations.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/runtime/components/Sidebar.vue` around lines 314 - 360, The mobile content slot duplicates the header/body/footer structure (the block rendering header with title/description, actions/close button, body slot and footer) so extract that shared markup into a single reusable template or small child component (e.g., DefineInnerTemplate or ReuseInnerTemplate) and replace the duplicated block inside the <template `#content`> with a single reference to it; ensure the new template/component accepts and forwards the same bindings/props used in the duplicated code (state, open, closeSidebar, ui, uiProp, title, description, canClose, closeIcon, props.close, appConfig, t and named slots like header/actions/footer) and preserve the UButton click handler (`@click`="closeSidebar") and slot bindings so behavior remains identical.docs/app/composables/useTheme.ts (2)
50-68:loadedFontsinitialized outside composable scope causes shared state across instances.The
loadedFontsSet is created at module level (outside theuseThemefunction), which means it's shared across all composable invocations. While this might be intentional for caching purposes, it can cause issues in SSR contexts where the module state persists across requests. Consider moving initialization inside the composable or using a more explicit singleton pattern.Additionally, the font loading lacks error handling for network failures.
♻️ Consider adding error handling for font loading
function loadFont(name: string) { const key = name.toLowerCase() if (loadedFonts.has(key)) return const linkId = `font-${key.replace(/\s+/g, '-')}` if (document.getElementById(linkId)) { loadedFonts.add(key) return } const link = document.createElement('link') link.id = linkId link.rel = 'stylesheet' link.href = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(name)}:wght@400;500;600;700&display=swap` + link.onerror = () => { + console.warn(`Failed to load font: ${name}`) + document.getElementById(linkId)?.remove() + loadedFonts.delete(key) + } document.head.appendChild(link) loadedFonts.add(key) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/app/composables/useTheme.ts` around lines 50 - 68, The module-level Set loadedFonts and the function loadFont should be moved/initialized inside the useTheme composable (or converted to a controlled singleton) to avoid sharing mutable state across SSR requests; update the definition of loadedFonts and loadFont within the useTheme function (or implement an explicit singleton accessor) and guard DOM operations with a typeof document check to avoid SSR errors. Also add basic error handling to loadFont by wiring link.onload and link.onerror (and a timeout fallback) so failures are logged/handled and ensure loadedFonts only marks a font as loaded on successful load (or on error if you want to avoid retry storms).
319-363:applyThemeSettingslacks input validation for critical properties.The function accepts arbitrary
settingsand applies them directly. For properties likeradius, there's no validation that the value is within the allowed range (radiusesarray: 0, 0.125, 0.25, 0.375, 0.5). Invalid values could produce unexpected UI behavior.🛡️ Proposed fix to add validation
function applyThemeSettings(settings: Record<string, any>) { if (settings.customColors && typeof settings.customColors === 'object') { injectCustomColors(settings.customColors) hasCustomColors.value = true } if (settings.cssVariables && typeof settings.cssVariables === 'object') { injectCSSVariables(settings.cssVariables) hasCSSVariables.value = true } - if (settings.primary) primary.value = settings.primary - if (settings.neutral) neutral.value = settings.neutral - if (settings.radius !== undefined) radius.value = settings.radius - if (settings.font) font.value = settings.font - if (settings.icons) icon.value = settings.icons + if (settings.primary && primaryColors.includes(settings.primary)) primary.value = settings.primary + if (settings.neutral && neutralColors.includes(settings.neutral)) neutral.value = settings.neutral + if (settings.radius !== undefined && radiuses.includes(settings.radius)) radius.value = settings.radius + if (settings.font) font.value = settings.font + if (settings.icons && icons.some(i => i.value === settings.icons)) icon.value = settings.icons if (settings.blackAsPrimary !== undefined) setBlackAsPrimary(settings.blackAsPrimary)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/app/composables/useTheme.ts` around lines 319 - 363, applyThemeSettings currently trusts arbitrary settings and doesn't validate critical fields (notably radius), so add input validation: in applyThemeSettings validate settings.radius against the allowed radiuses array (e.g. [0,0.125,0.25,0.375,0.5]) and only assign radius.value if it matches an allowed value (otherwise fallback to existing radius.value or a default), and similarly sanitize/validate any other critical fields before assigning (e.g., ensure settings.primary, settings.font, settings.icons are of expected types/values, and only call injectCustomColors/injectCSSVariables when their payloads meet expected shape); update savedExtras consistently only with validated values and keep track changes when calling setBlackAsPrimary so invalid booleans are ignored.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/app/components/chat/Chat.vue`:
- Around line 53-61: The onError handler currently attempts to JSON.parse
error.message when it begins with '{', which can throw on malformed JSON; update
the onError function to safely parse by wrapping JSON.parse in a try/catch (or
use a safeParse helper) and fall back to the original error or a default message
on parse failure before calling toast.add; ensure you keep the same toast.add
usage (description: message, icon: 'i-lucide-alert-circle', color: 'error',
duration: 0) so malformed JSON no longer causes unhandled exceptions.
In `@docs/app/components/content/examples/sidebar/SidebarChatExample.vue`:
- Around line 34-39: The UButton icon-only toggle in SidebarChatExample.vue (the
UButton with icon="i-lucide-panel-right" and `@click`="open = !open") lacks an
accessible name; add a localized accessible label by supplying an aria-label (or
aria-labelledby) prop bound to a translation string (e.g., use the component's
i18n key like $t('sidebar.toggle') or a computed prop) so screen readers
announce its purpose while keeping the visual icon-only button.
In `@docs/app/components/content/examples/sidebar/SidebarOpenExample.vue`:
- Around line 40-45: The UButton icon-only toggle lacks an accessible name;
update the UButton in SidebarOpenExample.vue (the icon button that toggles the
open reactive `open`) to include an accessible label (e.g., add a static
`aria-label` or a dynamic `:aria-label`/`label` binding) that reflects the
current state (open vs closed) so screen readers know the button’s purpose and
state; locate the UButton with `@click`="open = !open" and bind an appropriate
label such as "Open sidebar" / "Close sidebar" based on the `open` value.
In `@docs/app/components/content/examples/sidebar/SidebarPropsExample.vue`:
- Line 26: The conditional class using raw Tailwind colors ("variant === 'inset'
&& 'bg-neutral-50 dark:bg-neutral-950'") should be changed to use semantic color
utilities; update the expression in SidebarPropsExample.vue (the `variant ===
'inset'` conditional) to return the semantic class names (e.g., `bg-elevated`
and its dark counterpart such as `dark:bg-elevated` or the project's defined
semantic dark variant) instead of `bg-neutral-50 dark:bg-neutral-950`.
In `@docs/app/components/content/examples/sidebar/SidebarWidthExample.vue`:
- Around line 36-41: The icon-only UButton used to toggle the sidebar (the
<UButton ... `@click`="open = !open" /> in SidebarWidthExample.vue) needs an
accessible name so screen readers can announce its purpose; add an explicit
label by passing an aria-label or label prop (e.g., aria-label="Toggle sidebar"
or label="Toggle sidebar") to the UButton component, ensuring the text clearly
describes the action and keeping the `@click` and icon props unchanged.
In `@docs/app/composables/useTheme.ts`:
- Around line 264-281: The JSON.parse call in injectCustomColors can throw on
malformed localStorage; wrap retrieving and parsing
window.localStorage.getItem('nuxt-ui-custom-colors') in a try-catch, fallback to
an empty object if parse fails, and optionally clear the invalid localStorage
entry; then proceed to merge into merged and update the style element (styleEl)
as before so the function never throws due to bad JSON.
In `@docs/server/api/ai.post.ts`:
- Around line 67-68: The handler using defineEventHandler reads messages and
theme via readBody but does not validate inputs; add validation in the event
handler to ensure messages is present and is an array (and optionally non-empty)
before proceeding, returning a 400/Bad Request error if invalid; reference the
destructured variables (messages, theme), the defineEventHandler function, and
the readBody call so you add the validation immediately after const { messages,
theme } = await readBody(event) and short-circuit with an appropriate error
response when messages is missing or not an array.
In `@playgrounds/nuxt/app/pages/components/sidebar.vue`:
- Line 36: The hard-coded inset surface colors in the sidebar template (the
class string on the div using variant === 'inset') must use semantic tokens
instead of raw Tailwind palettes; replace "bg-neutral-50 dark:bg-neutral-950"
with the semantic background token used across the app (e.g., "bg-elevated" or
your theme's equivalent like "bg-elevated dark:bg-elevated-dark") so the
conditional class in the template (the div with :class="[variant === 'inset' &&
'bg-neutral-50 dark:bg-neutral-950']") becomes something like :class="[variant
=== 'inset' && 'bg-elevated']" (or include the explicit dark token if your token
requires it), ensuring the component follows theme overrides and the project
semantic color guidelines.
- Around line 60-73: The two icon-only UButton components (the ones toggling
openLeft and openRight) lack accessible names; add aria-label props to each
UButton (e.g., aria-label="Open left sidebar" and aria-label="Open right
sidebar" or similar localized/descriptive strings) so screen readers can
announce their purpose; update the UButton instances that use `@click`="openLeft =
!openLeft" and `@click`="openRight = !openRight" to include these aria-label
attributes (or provide visible text) while preserving the existing icon, color,
variant, and size props.
In `@src/runtime/components/Sidebar.vue`:
- Around line 293-300: The rail toggle button (the element with
data-slot="rail", :class="ui.rail({ class: uiProp?.rail })" and `@click`="open =
!open") currently has tabindex="-1" which removes it from keyboard focus; either
remove the tabindex attribute (so it is keyboard-focusable) or change it to
tabindex="0" and add keyboard handlers (e.g., `@keydown.enter` and `@keydown.space`
that toggle open = !open) or ensure another clearly labeled, keyboard-focusable
control exists that toggles the same sidebar state; update
aria-label/aria-controls as needed to maintain accessibility.
In `@src/runtime/types/locale.ts`:
- Around line 123-126: The change made the sidebar field on the public Messages
type required, which breaks existing user locale objects; revert sidebar to
optional on the Messages type and add a runtime fallback where those labels are
consumed (e.g., where sidebar.close and sidebar.toggle are read) to supply
sensible defaults if sidebar or its keys are undefined; ensure the type
signature for Messages keeps sidebar?: { close?: string; toggle?: string } and
update consumers (the code paths that access Messages.sidebar.close /
Messages.sidebar.toggle) to use a fallback string literal or helper function
when the value is missing.
---
Nitpick comments:
In `@docs/app/components/chat/Chat.vue`:
- Around line 93-100: The watcher on messages (watch(messages, ...)) currently
updates chat.messages and calls chat.regenerate() when chat.lastMessage?.role
=== 'user', which causes automatic regeneration whenever messages are changed
externally; add a concise comment above this watch explaining that this behavior
is intentional to support the search→chat flow (and to note potential side
effects), and mention the _skipSync flag purpose (to avoid loops) so future
maintainers understand why regenerate() is invoked on external user message
sync; reference the watch, messages, _skipSync, chat.messages, chat.lastMessage,
and chat.regenerate symbols in the comment.
In `@docs/app/components/content/examples/sidebar/SidebarExample.vue`:
- Line 127: The call defineShortcuts(extractShortcuts(teamsItems.value)) runs
only once at setup so shortcuts won’t reflect later changes to teamsItems;
update the example to recompute and re-register shortcuts when teamsItems
changes (e.g., use a watch or watchEffect on teamsItems and call
defineShortcuts(extractShortcuts(teamsItems.value)) inside it, or make a
computed like const currentShortcuts = computed(() =>
extractShortcuts(teamsItems.value)) and watch currentShortcuts to call
defineShortcuts), referencing defineShortcuts, extractShortcuts and teamsItems
in your change.
- Around line 93-116: The Light and Dark checkbox entries in the children array
are inconsistent: the "Light" item sets colorMode.preference in onSelect while
"Dark" sets it in onUpdateChecked and uses onSelect only to preventDefault;
unify them by moving preference assignment to the same handler for both (prefer
using onUpdateChecked(checked: boolean) to set colorMode.preference = 'light' or
'dark' when checked) and keep onSelect(e: Event) only to e.preventDefault();
update both entries (labels 'Light' and 'Dark', handlers onUpdateChecked and
onSelect) so they follow the same pattern.
In `@docs/app/composables/useTheme.ts`:
- Around line 50-68: The module-level Set loadedFonts and the function loadFont
should be moved/initialized inside the useTheme composable (or converted to a
controlled singleton) to avoid sharing mutable state across SSR requests; update
the definition of loadedFonts and loadFont within the useTheme function (or
implement an explicit singleton accessor) and guard DOM operations with a typeof
document check to avoid SSR errors. Also add basic error handling to loadFont by
wiring link.onload and link.onerror (and a timeout fallback) so failures are
logged/handled and ensure loadedFonts only marks a font as loaded on successful
load (or on error if you want to avoid retry storms).
- Around line 319-363: applyThemeSettings currently trusts arbitrary settings
and doesn't validate critical fields (notably radius), so add input validation:
in applyThemeSettings validate settings.radius against the allowed radiuses
array (e.g. [0,0.125,0.25,0.375,0.5]) and only assign radius.value if it matches
an allowed value (otherwise fallback to existing radius.value or a default), and
similarly sanitize/validate any other critical fields before assigning (e.g.,
ensure settings.primary, settings.font, settings.icons are of expected
types/values, and only call injectCustomColors/injectCSSVariables when their
payloads meet expected shape); update savedExtras consistently only with
validated values and keep track changes when calling setBlackAsPrimary so
invalid booleans are ignored.
In `@docs/app/plugins/theme.ts`:
- Around line 141-197: The inline IIFEs that create 'chat-custom-colors' and
'chat-css-variables' duplicate client-side restoration logic and should be
annotated: add a concise comment near the inline scripts that references the
client-side theme restoration functions and explains that the duplication is
intentional to prevent FOUC by restoring colors/variables on first paint
(mention the 'chat-custom-colors' and 'chat-css-variables' script ids and the
cssVariableDefaults/merge logic), so future readers know why both server-side
inline scripts and client-side functions co-exist rather than removing the
apparent duplication.
- Around line 100-116: The code currently uses unsafe casts
((appConfig.ui.colors as any) and (appConfig.ui as any)) when applying parsed
localStorage extras; replace these casts with runtime validation and controlled
assignment: add type-guard functions (e.g., isValidColors(extras.colors) and
isValidUI(extras.ui)) that check each key/value is an expected string/hex or
allowed UI prop, then only copy validated entries into appConfig.ui.colors and
appConfig.ui (or use a shallow merge like Object.assign with the validated
object); keep the existing try/catch but ensure malformed or unexpected
properties are rejected by the guards so TypeScript safety is preserved at
runtime.
In `@docs/server/api/ai.post.ts`:
- Around line 342-349: Wrap the cleanup calls inside the onFinish and onError
callbacks with try/catch so any synchronous exceptions from httpClient?.close()
are caught, and pass a promise to event.waitUntil (e.g., call a small async
wrapper that awaits httpClient?.close()). Update the onFinish and onError
handlers (the callbacks passed to streamText) to use this wrapper and log or
handle errors in the catch block so cleanup failures are not unhandled.
- Around line 112-316: The system prompt string assigned to the variable
`system` is excessively long and inflates token usage; refactor by extracting
large static reference sections (e.g., "color options", "CSS Variable defaults",
full "LIVE THEME CUSTOMIZATION" details) into a separate retrievable resource or
tool (e.g., a docsLookup/getThemeGuidelines helper) and replace those inline
blocks in `system` with concise pointers or summaries and calls to that tool;
update any code paths that rely on `system` to call the new helper when detailed
guidance is needed and keep `system` focused on concise guardrails and critical
formatting rules only.
In `@src/runtime/components/Sidebar.vue`:
- Around line 140-164: The three interdependent watchers (watch(isMobile),
watch(modelOpen), watch(openMobile)) create circular updates; to fix, add a
short-lived guard flag (e.g., syncing) or explicit equality checks inside the
watcher callbacks to skip assignments when the target already equals the source,
or consolidate into a single watcher that updates desktopOpen, modelOpen and
openMobile atomically; update the watchers for isMobile, modelOpen, and
openMobile to return early when syncing is true (or when value === target) and
set/clear syncing only around the intentional state changes to prevent cascading
re-triggers while keeping desktopOpen, modelOpen and openMobile in sync.
- Around line 314-360: The mobile content slot duplicates the header/body/footer
structure (the block rendering header with title/description, actions/close
button, body slot and footer) so extract that shared markup into a single
reusable template or small child component (e.g., DefineInnerTemplate or
ReuseInnerTemplate) and replace the duplicated block inside the <template
`#content`> with a single reference to it; ensure the new template/component
accepts and forwards the same bindings/props used in the duplicated code (state,
open, closeSidebar, ui, uiProp, title, description, canClose, closeIcon,
props.close, appConfig, t and named slots like header/actions/footer) and
preserve the UButton click handler (`@click`="closeSidebar") and slot bindings so
behavior remains identical.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b12eb8f5-4fa4-4407-9abf-65d5e784def4
⛔ Files ignored due to path filters (9)
docs/public/components/dark/sidebar.pngis excluded by!**/*.pngdocs/public/components/light/sidebar.pngis excluded by!**/*.pngpnpm-lock.yamlis excluded by!**/pnpm-lock.yamltest/components/__snapshots__/Modal-vue.spec.ts.snapis excluded by!**/*.snaptest/components/__snapshots__/Modal.spec.ts.snapis excluded by!**/*.snaptest/components/__snapshots__/Sidebar-vue.spec.ts.snapis excluded by!**/*.snaptest/components/__snapshots__/Sidebar.spec.ts.snapis excluded by!**/*.snaptest/components/__snapshots__/Slideover-vue.spec.ts.snapis excluded by!**/*.snaptest/components/__snapshots__/Slideover.spec.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (102)
docs/app/app.vuedocs/app/components/chat/Chat.vuedocs/app/components/chat/ChatReasoning.vuedocs/app/components/chat/ChatShimmer.vuedocs/app/components/chat/ChatTool.vuedocs/app/components/content/examples/sidebar/SidebarChatExample.vuedocs/app/components/content/examples/sidebar/SidebarExample.vuedocs/app/components/content/examples/sidebar/SidebarNavbarExample.vuedocs/app/components/content/examples/sidebar/SidebarOpenExample.vuedocs/app/components/content/examples/sidebar/SidebarPersistExample.vuedocs/app/components/content/examples/sidebar/SidebarPropsExample.vuedocs/app/components/content/examples/sidebar/SidebarWidthExample.vuedocs/app/components/search/Search.vuedocs/app/components/search/SearchChat.vuedocs/app/components/theme-picker/ThemePicker.vuedocs/app/composables/useChat.tsdocs/app/composables/useSearch.tsdocs/app/composables/useTheme.tsdocs/app/pages/docs/[...slug].vuedocs/app/pages/index.vuedocs/app/plugins/theme.tsdocs/app/utils/index.tsdocs/app/utils/theme.tsdocs/content/docs/1.getting-started/5.theme/2.css-variables.mddocs/content/docs/2.components/dashboard-sidebar.mddocs/content/docs/2.components/sidebar.mddocs/nuxt.config.tsdocs/package.jsondocs/server/api/ai.post.tsdocs/server/api/search.post.tsplaygrounds/nuxt/app/app.vueplaygrounds/nuxt/app/composables/useNavigation.tsplaygrounds/nuxt/app/pages/components/sidebar.vuesrc/runtime/components/Sidebar.vuesrc/runtime/index.csssrc/runtime/locale/ar.tssrc/runtime/locale/az.tssrc/runtime/locale/be.tssrc/runtime/locale/bg.tssrc/runtime/locale/bn.tssrc/runtime/locale/ca.tssrc/runtime/locale/ckb.tssrc/runtime/locale/cs.tssrc/runtime/locale/da.tssrc/runtime/locale/de.tssrc/runtime/locale/de_ch.tssrc/runtime/locale/el.tssrc/runtime/locale/en.tssrc/runtime/locale/es.tssrc/runtime/locale/et.tssrc/runtime/locale/eu.tssrc/runtime/locale/fa_ir.tssrc/runtime/locale/fi.tssrc/runtime/locale/fr.tssrc/runtime/locale/gl.tssrc/runtime/locale/he.tssrc/runtime/locale/hi.tssrc/runtime/locale/hr.tssrc/runtime/locale/hu.tssrc/runtime/locale/hy.tssrc/runtime/locale/id.tssrc/runtime/locale/is.tssrc/runtime/locale/it.tssrc/runtime/locale/ja.tssrc/runtime/locale/ka.tssrc/runtime/locale/kk.tssrc/runtime/locale/km.tssrc/runtime/locale/ko.tssrc/runtime/locale/ky.tssrc/runtime/locale/lb.tssrc/runtime/locale/lo.tssrc/runtime/locale/lt.tssrc/runtime/locale/mn.tssrc/runtime/locale/ms.tssrc/runtime/locale/nb_no.tssrc/runtime/locale/nl.tssrc/runtime/locale/pl.tssrc/runtime/locale/pt.tssrc/runtime/locale/pt_br.tssrc/runtime/locale/ro.tssrc/runtime/locale/ru.tssrc/runtime/locale/sk.tssrc/runtime/locale/sl.tssrc/runtime/locale/sq.tssrc/runtime/locale/sv.tssrc/runtime/locale/th.tssrc/runtime/locale/tj.tssrc/runtime/locale/tr.tssrc/runtime/locale/ug_cn.tssrc/runtime/locale/uk.tssrc/runtime/locale/ur.tssrc/runtime/locale/uz.tssrc/runtime/locale/vi.tssrc/runtime/locale/zh_cn.tssrc/runtime/locale/zh_tw.tssrc/runtime/types/index.tssrc/runtime/types/locale.tssrc/theme/index.tssrc/theme/modal.tssrc/theme/sidebar.tssrc/theme/slideover.tstest/components/Sidebar.spec.ts
💤 Files with no reviewable changes (2)
- docs/server/api/search.post.ts
- docs/app/components/search/SearchChat.vue
There was a problem hiding this comment.
🧹 Nitpick comments (3)
docs/app/plugins/theme.ts (2)
19-29: Unused_fallbackparameter inrestoreState.The
_fallbackparameter is declared but never used within the function body. Either remove it or use it as the fallback value when the key doesn't exist.♻️ Proposed fix
- function restoreState<T>(key: string, _fallback?: T) { + function restoreState<T>(key: string) { try { const raw = localStorage.getItem(key) if (raw) { const state = useState<T>(key) state.value = JSON.parse(raw) } } catch { // ignore malformed localStorage } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/app/plugins/theme.ts` around lines 19 - 29, The restoreState<T> function declares an unused _fallback parameter; either remove _fallback from the signature or use it as the fallback when localStorage has no value: inside restoreState (and referencing useState<T>(key)), if localStorage.getItem(key) returns null/undefined then assign state.value = _fallback (or JSON.parse(raw) when present); if you prefer removal, delete the _fallback parameter from the function signature and any callers accordingly to eliminate the unused variable.
31-52: Redundant localStorage parsing fornuxt-ui-ai-theme.The
nuxt-ui-ai-themevalue is parsed twice: once viarestoreState(line 31) which populates the useState, and again directly viaJSON.parse(line 36). Consider reusing the already-restored state value instead of re-parsing.♻️ Proposed optimization
restoreState('nuxt-ui-ai-theme', {}) restoreState('nuxt-ui-custom-colors', {}) restoreState('nuxt-ui-css-variables', {}) - try { - const extras = JSON.parse(localStorage.getItem('nuxt-ui-ai-theme') || '{}') + const extrasState = useState<Record<string, any>>('nuxt-ui-ai-theme') + try { + const extras = extrasState.value || {} if (extras.colors) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/app/plugins/theme.ts` around lines 31 - 52, The code redundantly parses localStorage for 'nuxt-ui-ai-theme' after calling restoreState; instead read the already-restored state (via useState('nuxt-ui-ai-theme') or the value restoreState populated) and use that object as extras to populate appConfig.ui.colors and extras.ui in the onNuxtReady block; remove the JSON.parse(localStorage.getItem('nuxt-ui-ai-theme')...) call and replace occurrences of extras with the restored state, keeping the existing loops that assign into (appConfig.ui.colors as any) and the onNuxtReady/defu logic for extras.ui.docs/app/composables/useTheme.ts (1)
155-163: Consider validating font names against an allowlist.While
encodeURIComponentprevents URL injection, arbitrary font names could load unexpected or unavailable fonts. Consider validating against a known list of Google Fonts or at minimum sanitizing the input to alphanumeric and spaces.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/app/composables/useTheme.ts` around lines 155 - 163, The computed link builder for fonts (link, _font, and fonts) currently accepts any font name and only uses encodeURIComponent; update it to validate the chosen font against an allowlist (or at minimum sanitize the string to only letters, numbers and spaces) before returning the Google Fonts href to prevent unexpected/invalid fonts being requested; if the font is not in the allowlist or fails the sanitize check, return [] or a safe default font id instead so link never references arbitrary input.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@docs/app/composables/useTheme.ts`:
- Around line 155-163: The computed link builder for fonts (link, _font, and
fonts) currently accepts any font name and only uses encodeURIComponent; update
it to validate the chosen font against an allowlist (or at minimum sanitize the
string to only letters, numbers and spaces) before returning the Google Fonts
href to prevent unexpected/invalid fonts being requested; if the font is not in
the allowlist or fails the sanitize check, return [] or a safe default font id
instead so link never references arbitrary input.
In `@docs/app/plugins/theme.ts`:
- Around line 19-29: The restoreState<T> function declares an unused _fallback
parameter; either remove _fallback from the signature or use it as the fallback
when localStorage has no value: inside restoreState (and referencing
useState<T>(key)), if localStorage.getItem(key) returns null/undefined then
assign state.value = _fallback (or JSON.parse(raw) when present); if you prefer
removal, delete the _fallback parameter from the function signature and any
callers accordingly to eliminate the unused variable.
- Around line 31-52: The code redundantly parses localStorage for
'nuxt-ui-ai-theme' after calling restoreState; instead read the already-restored
state (via useState('nuxt-ui-ai-theme') or the value restoreState populated) and
use that object as extras to populate appConfig.ui.colors and extras.ui in the
onNuxtReady block; remove the
JSON.parse(localStorage.getItem('nuxt-ui-ai-theme')...) call and replace
occurrences of extras with the restored state, keeping the existing loops that
assign into (appConfig.ui.colors as any) and the onNuxtReady/defu logic for
extras.ui.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f3f7c529-4870-4aaf-aae5-6ab74aaca252
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (9)
docs/app/app.config.tsdocs/app/app.vuedocs/app/components/chat/Chat.vuedocs/app/components/content/IconsTheme.vuedocs/app/components/theme-picker/ThemePicker.vuedocs/app/composables/useTheme.tsdocs/app/error.vuedocs/app/plugins/theme.tsdocs/package.json
💤 Files with no reviewable changes (1)
- docs/app/app.config.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- docs/package.json
- docs/app/components/theme-picker/ThemePicker.vue
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/app/composables/useTheme.ts`:
- Around line 291-305: The applyThemeSettings function currently writes settings
directly; validate and whitelist inputs before persisting: ensure
settings.customColors and settings.cssVariables remain objects before calling
injectCustomColors/injectCSSVariables, only accept known icon IDs for
settings.icons (do not overwrite icon.value or appConfig.ui.icons with an
unknown id — validate against the icon enum/list before assigning to
icon.value), only accept a real boolean for settings.blackAsPrimary
(coerce/parse and reject string values like 'false' and call setBlackAsPrimary
only for boolean true/false), and validate primitive types for primary, neutral,
radius and font before setting
primary.value/neutral.value/radius.value/font.value to avoid unintentionally
clearing state; use the unique symbols applyThemeSettings, injectCustomColors,
injectCSSVariables, icon (or appConfig.ui.icons), setBlackAsPrimary and
blackAsPrimaryStyle to locate and update the logic.
- Around line 323-325: The merge in applyThemeSettings is dropping existing
in-memory overrides because defu(value, savedExtras.ui[key] || {}) uses only the
savedExtras as base; change the merge to use the current in-memory config as the
base so existing appConfig.ui overrides are preserved (e.g., call defu(value as
Record<string, any>, (appConfig.ui as any)[key] || savedExtras.ui[key] || {})).
Update the logic around merged and the assignments to (appConfig.ui as any)[key]
and savedExtras.ui[key] so they remain consistent with the restore path used in
docs/app/plugins/theme.ts that references (appConfig.ui as any)[key].
- Around line 357-361: The reset logic for extras.ui currently sets
(appConfig.ui as any)[key] = undefined for each key (skipping 'colors' and
'icons'), which loses any preexisting baseline; update the flow to capture and
restore baseline UI values when applying and later resetting extras.ui: when
applying extras.ui store a shallow copy of the original appConfig.ui keys (e.g.,
baselineUi or similar) and on reset restore each key from baselineUi or fall
back to sensible defaults (similar to how defaultColors is used for colors) so
that functions/keys referenced as extras.ui and appConfig.ui revert correctly
instead of becoming undefined.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 81c6cd2e-fe75-4899-a252-84e290318309
📒 Files selected for processing (3)
docs/app/composables/useTheme.tsdocs/app/plugins/theme.tsdocs/nuxt.config.ts
💤 Files with no reviewable changes (1)
- docs/nuxt.config.ts
| function applyThemeSettings(settings: Record<string, any>) { | ||
| if (settings.customColors && typeof settings.customColors === 'object') { | ||
| injectCustomColors(settings.customColors) | ||
| } | ||
|
|
||
| if (settings.cssVariables && typeof settings.cssVariables === 'object') { | ||
| injectCSSVariables(settings.cssVariables) | ||
| } | ||
|
|
||
| if (settings.primary) primary.value = settings.primary | ||
| if (settings.neutral) neutral.value = settings.neutral | ||
| if (settings.radius !== undefined) radius.value = settings.radius | ||
| if (settings.font) font.value = settings.font | ||
| if (settings.icons) icon.value = settings.icons | ||
| if (settings.blackAsPrimary !== undefined) setBlackAsPrimary(settings.blackAsPrimary) |
There was a problem hiding this comment.
Validate applyThemeSettings() inputs before persisting them.
This API takes Record<string, any>, but these branches write values as-is. An unknown icons id ends up clearing appConfig.ui.icons, and 'false' passed as blackAsPrimary stays truthy later in blackAsPrimaryStyle. Please whitelist enum-like fields and require a real boolean here.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/app/composables/useTheme.ts` around lines 291 - 305, The
applyThemeSettings function currently writes settings directly; validate and
whitelist inputs before persisting: ensure settings.customColors and
settings.cssVariables remain objects before calling
injectCustomColors/injectCSSVariables, only accept known icon IDs for
settings.icons (do not overwrite icon.value or appConfig.ui.icons with an
unknown id — validate against the icon enum/list before assigning to
icon.value), only accept a real boolean for settings.blackAsPrimary
(coerce/parse and reject string values like 'false' and call setBlackAsPrimary
only for boolean true/false), and validate primitive types for primary, neutral,
radius and font before setting
primary.value/neutral.value/radius.value/font.value to avoid unintentionally
clearing state; use the unique symbols applyThemeSettings, injectCustomColors,
injectCSSVariables, icon (or appConfig.ui.icons), setBlackAsPrimary and
blackAsPrimaryStyle to locate and update the logic.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
docs/app/composables/useTheme.ts (1)
357-362:⚠️ Potential issue | 🟡 MinorReset logic for
extras.uikeys does not restore baseline values.Setting
(appConfig.ui as any)[key] = undefinedloses any preexisting baseline configuration fromapp.config.ts. Unlike the colors handling on line 354 which usesdefaultColors[key]as fallback, theuikeys have no restoration mechanism.Consider storing a baseline snapshot when applying
extras.ui, or define defaults for eachuikey similar to howdefaultColorsworks for color restoration.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/app/composables/useTheme.ts` around lines 357 - 362, The reset loop in useTheme.ts sets (appConfig.ui as any)[key] = undefined which drops any baseline values; change the logic so that when clearing extras.ui you restore a saved baseline or explicit defaults instead of undefined — e.g., capture a baseline snapshot (baselineUI) when extras are first applied and in the loop for each key (except 'colors'/'icons') assign (appConfig.ui as any)[key] = baselineUI[key] (or a defined default mapping similar to defaultColors) rather than undefined; update the code paths that apply extras.ui to create/store that baseline snapshot so restores work correctly.
🧹 Nitpick comments (2)
docs/app/plugins/theme.ts (1)
19-29: Unused_fallbackparameter inrestoreState.The
_fallbackparameter is defined but never used. Either remove it or use it as the fallback when localStorage is empty.♻️ Proposed fix to use or remove the fallback
Option 1: Remove unused parameter
- function restoreState<T>(key: string, _fallback?: T) { + function restoreState<T>(key: string) { try { const raw = localStorage.getItem(key) if (raw) { const state = useState<T>(key) state.value = JSON.parse(raw) } } catch { // ignore malformed localStorage } }Option 2: Use the fallback parameter
- function restoreState<T>(key: string, _fallback?: T) { + function restoreState<T>(key: string, fallback?: T) { try { const raw = localStorage.getItem(key) - if (raw) { - const state = useState<T>(key) - state.value = JSON.parse(raw) - } + const state = useState<T>(key, () => fallback as T) + if (raw) { + state.value = JSON.parse(raw) + } } catch { // ignore malformed localStorage } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/app/plugins/theme.ts` around lines 19 - 29, The restoreState function currently declares an unused _fallback parameter; either remove _fallback or wire it up as the fallback value when localStorage is empty: inside restoreState(key, _fallback?) get the state via useState<T>(key) and if raw exists parse and assign to state.value, otherwise assign _fallback (if provided) to state.value (keeping try/catch for malformed JSON). Update the function signature and logic in restoreState to reflect the chosen approach and remove the unused parameter if you opt not to use it.docs/server/api/ai.post.ts (1)
116-322: Consider externalizing the system prompt.The system prompt (~200 lines) is embedded inline, making this file harder to maintain and review. Consider moving it to a separate file or constant for better separation of concerns.
♻️ Proposed refactor to externalize prompt
Create a separate file for the prompt:
// docs/server/utils/ai-system-prompt.ts import { themeIcons, cssVariableDefaults } from '../../app/utils/theme' export function buildSystemPrompt(componentNames: string[]): string { return `You are a helpful assistant for Nuxt UI... // ... rest of prompt Available components: ${componentNames.join(', ')} ` }Then import and use it:
+import { buildSystemPrompt } from '../utils/ai-system-prompt' export default defineEventHandler(async (event) => { // ... - const system = `You are a helpful assistant...` + const system = buildSystemPrompt(componentNames)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/server/api/ai.post.ts` around lines 116 - 322, The large inline system prompt assigned to the system variable makes ai.post.ts hard to maintain; extract it into a new module (e.g., docs/server/utils/ai-system-prompt.ts) that exports a builder like buildSystemPrompt(componentNames: string[]) (and import any dependencies such as themeIcons or cssVariableDefaults there), then replace the inline multi-line string in ai.post.ts with a call to buildSystemPrompt(...) when constructing the system value; ensure ai.post.ts still passes the same componentNames and any required imports are adjusted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@playgrounds/nuxt/app/pages/components/sidebar.vue`:
- Around line 29-31: In onSubmit, guard against blank or whitespace-only
submissions by trimming input.value and returning early if the trimmed string is
empty; only call chat.sendMessage({ text: ... }) with the trimmed value and
clear input.value afterwards. Reference: function onSubmit, chat.sendMessage,
and input.value.
---
Duplicate comments:
In `@docs/app/composables/useTheme.ts`:
- Around line 357-362: The reset loop in useTheme.ts sets (appConfig.ui as
any)[key] = undefined which drops any baseline values; change the logic so that
when clearing extras.ui you restore a saved baseline or explicit defaults
instead of undefined — e.g., capture a baseline snapshot (baselineUI) when
extras are first applied and in the loop for each key (except 'colors'/'icons')
assign (appConfig.ui as any)[key] = baselineUI[key] (or a defined default
mapping similar to defaultColors) rather than undefined; update the code paths
that apply extras.ui to create/store that baseline snapshot so restores work
correctly.
---
Nitpick comments:
In `@docs/app/plugins/theme.ts`:
- Around line 19-29: The restoreState function currently declares an unused
_fallback parameter; either remove _fallback or wire it up as the fallback value
when localStorage is empty: inside restoreState(key, _fallback?) get the state
via useState<T>(key) and if raw exists parse and assign to state.value,
otherwise assign _fallback (if provided) to state.value (keeping try/catch for
malformed JSON). Update the function signature and logic in restoreState to
reflect the chosen approach and remove the unused parameter if you opt not to
use it.
In `@docs/server/api/ai.post.ts`:
- Around line 116-322: The large inline system prompt assigned to the system
variable makes ai.post.ts hard to maintain; extract it into a new module (e.g.,
docs/server/utils/ai-system-prompt.ts) that exports a builder like
buildSystemPrompt(componentNames: string[]) (and import any dependencies such as
themeIcons or cssVariableDefaults there), then replace the inline multi-line
string in ai.post.ts with a call to buildSystemPrompt(...) when constructing the
system value; ensure ai.post.ts still passes the same componentNames and any
required imports are adjusted.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4049a891-b681-4b42-9b0f-ad6e4dc8e1d1
📒 Files selected for processing (9)
docs/app/components/chat/Chat.vuedocs/app/components/content/examples/sidebar/SidebarChatExample.vuedocs/app/components/content/examples/sidebar/SidebarExample.vuedocs/app/components/content/examples/sidebar/SidebarOpenExample.vuedocs/app/components/content/examples/sidebar/SidebarWidthExample.vuedocs/app/composables/useTheme.tsdocs/app/plugins/theme.tsdocs/server/api/ai.post.tsplaygrounds/nuxt/app/pages/components/sidebar.vue
🚧 Files skipped from review as they are similar to previous changes (4)
- docs/app/components/content/examples/sidebar/SidebarOpenExample.vue
- docs/app/components/chat/Chat.vue
- docs/app/components/content/examples/sidebar/SidebarChatExample.vue
- docs/app/components/content/examples/sidebar/SidebarWidthExample.vue
🔗 Linked issue
❓ Type of change
📚 Description
📝 Checklist