Skip to content

feat(Sidebar): new component#6038

Open
benjamincanac wants to merge 57 commits intov4from
feat/sidebar
Open

feat(Sidebar): new component#6038
benjamincanac wants to merge 57 commits intov4from
feat/sidebar

Conversation

@benjamincanac
Copy link
Member

🔗 Linked issue

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@github-actions github-actions bot added the v4 #4488 label Feb 14, 2026
@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 20, 2026

npm i https://pkg.pr.new/@nuxt/ui@6038

commit: 1814784

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 6, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds 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)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description contains only the PR template/checklist with no actual implementation details, rationale, or context about the Sidebar component being added. Provide a meaningful description explaining what the Sidebar component does, why it was added, and any relevant context or usage guidance.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(Sidebar): new component' directly and clearly describes the main change—the addition of a new Sidebar component.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/sidebar

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🧹 Nitpick comments (11)
docs/app/components/chat/Chat.vue (1)

93-100: Automatic regenerate() on user message sync could cause unexpected behavior.

When messages changes externally (e.g., from useSearch.ts adding a user message), this watch triggers regenerate() 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-colors and chat-css-variables duplicate 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 with as any casts.

The as any casts 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. If teamsItems changes (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 onSelect with e.preventDefault(), while Dark mode uses both onUpdateChecked and onSelect. This asymmetry could cause confusion:

  • Light: sets preference immediately in onSelect
  • Dark: sets preference in onUpdateChecked only when checked is true, plus has a separate onSelect that just prevents default

Consider 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: onFinish and onError callbacks should handle potential errors during cleanup.

The httpClient?.close() call in the lifecycle hooks could potentially throw. While event.waitUntil handles the promise, any synchronous error from close() 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:

  1. Moving static reference content (color options, CSS variable defaults) to a separate tool the AI can query when needed
  2. 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, and openMobile state. The circular dependency between these watchers (lines 141-157, 153-157, 160-164) could lead to unexpected behavior:

  1. watch(isMobile) sets modelOpen
  2. watch(modelOpen) sets openMobile when mobile
  3. watch(openMobile) sets modelOpen when mobile

This 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 DefineInnerTemplate structure (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 ReuseInnerTemplate within 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: loadedFonts initialized outside composable scope causes shared state across instances.

The loadedFonts Set is created at module level (outside the useTheme function), 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: applyThemeSettings lacks input validation for critical properties.

The function accepts arbitrary settings and applies them directly. For properties like radius, there's no validation that the value is within the allowed range (radiuses array: 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

📥 Commits

Reviewing files that changed from the base of the PR and between ee8a248 and 7e56978.

⛔ Files ignored due to path filters (9)
  • docs/public/components/dark/sidebar.png is excluded by !**/*.png
  • docs/public/components/light/sidebar.png is excluded by !**/*.png
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • test/components/__snapshots__/Modal-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Modal.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Sidebar-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Sidebar.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Slideover-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Slideover.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (102)
  • docs/app/app.vue
  • docs/app/components/chat/Chat.vue
  • docs/app/components/chat/ChatReasoning.vue
  • docs/app/components/chat/ChatShimmer.vue
  • docs/app/components/chat/ChatTool.vue
  • docs/app/components/content/examples/sidebar/SidebarChatExample.vue
  • docs/app/components/content/examples/sidebar/SidebarExample.vue
  • docs/app/components/content/examples/sidebar/SidebarNavbarExample.vue
  • docs/app/components/content/examples/sidebar/SidebarOpenExample.vue
  • docs/app/components/content/examples/sidebar/SidebarPersistExample.vue
  • docs/app/components/content/examples/sidebar/SidebarPropsExample.vue
  • docs/app/components/content/examples/sidebar/SidebarWidthExample.vue
  • docs/app/components/search/Search.vue
  • docs/app/components/search/SearchChat.vue
  • docs/app/components/theme-picker/ThemePicker.vue
  • docs/app/composables/useChat.ts
  • docs/app/composables/useSearch.ts
  • docs/app/composables/useTheme.ts
  • docs/app/pages/docs/[...slug].vue
  • docs/app/pages/index.vue
  • docs/app/plugins/theme.ts
  • docs/app/utils/index.ts
  • docs/app/utils/theme.ts
  • docs/content/docs/1.getting-started/5.theme/2.css-variables.md
  • docs/content/docs/2.components/dashboard-sidebar.md
  • docs/content/docs/2.components/sidebar.md
  • docs/nuxt.config.ts
  • docs/package.json
  • docs/server/api/ai.post.ts
  • docs/server/api/search.post.ts
  • playgrounds/nuxt/app/app.vue
  • playgrounds/nuxt/app/composables/useNavigation.ts
  • playgrounds/nuxt/app/pages/components/sidebar.vue
  • src/runtime/components/Sidebar.vue
  • src/runtime/index.css
  • src/runtime/locale/ar.ts
  • src/runtime/locale/az.ts
  • src/runtime/locale/be.ts
  • src/runtime/locale/bg.ts
  • src/runtime/locale/bn.ts
  • src/runtime/locale/ca.ts
  • src/runtime/locale/ckb.ts
  • src/runtime/locale/cs.ts
  • src/runtime/locale/da.ts
  • src/runtime/locale/de.ts
  • src/runtime/locale/de_ch.ts
  • src/runtime/locale/el.ts
  • src/runtime/locale/en.ts
  • src/runtime/locale/es.ts
  • src/runtime/locale/et.ts
  • src/runtime/locale/eu.ts
  • src/runtime/locale/fa_ir.ts
  • src/runtime/locale/fi.ts
  • src/runtime/locale/fr.ts
  • src/runtime/locale/gl.ts
  • src/runtime/locale/he.ts
  • src/runtime/locale/hi.ts
  • src/runtime/locale/hr.ts
  • src/runtime/locale/hu.ts
  • src/runtime/locale/hy.ts
  • src/runtime/locale/id.ts
  • src/runtime/locale/is.ts
  • src/runtime/locale/it.ts
  • src/runtime/locale/ja.ts
  • src/runtime/locale/ka.ts
  • src/runtime/locale/kk.ts
  • src/runtime/locale/km.ts
  • src/runtime/locale/ko.ts
  • src/runtime/locale/ky.ts
  • src/runtime/locale/lb.ts
  • src/runtime/locale/lo.ts
  • src/runtime/locale/lt.ts
  • src/runtime/locale/mn.ts
  • src/runtime/locale/ms.ts
  • src/runtime/locale/nb_no.ts
  • src/runtime/locale/nl.ts
  • src/runtime/locale/pl.ts
  • src/runtime/locale/pt.ts
  • src/runtime/locale/pt_br.ts
  • src/runtime/locale/ro.ts
  • src/runtime/locale/ru.ts
  • src/runtime/locale/sk.ts
  • src/runtime/locale/sl.ts
  • src/runtime/locale/sq.ts
  • src/runtime/locale/sv.ts
  • src/runtime/locale/th.ts
  • src/runtime/locale/tj.ts
  • src/runtime/locale/tr.ts
  • src/runtime/locale/ug_cn.ts
  • src/runtime/locale/uk.ts
  • src/runtime/locale/ur.ts
  • src/runtime/locale/uz.ts
  • src/runtime/locale/vi.ts
  • src/runtime/locale/zh_cn.ts
  • src/runtime/locale/zh_tw.ts
  • src/runtime/types/index.ts
  • src/runtime/types/locale.ts
  • src/theme/index.ts
  • src/theme/modal.ts
  • src/theme/sidebar.ts
  • src/theme/slideover.ts
  • test/components/Sidebar.spec.ts
💤 Files with no reviewable changes (2)
  • docs/server/api/search.post.ts
  • docs/app/components/search/SearchChat.vue

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
docs/app/plugins/theme.ts (2)

19-29: Unused _fallback parameter in restoreState.

The _fallback parameter 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 for nuxt-ui-ai-theme.

The nuxt-ui-ai-theme value is parsed twice: once via restoreState (line 31) which populates the useState, and again directly via JSON.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 encodeURIComponent prevents 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7e56978 and 27faaf5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • docs/app/app.config.ts
  • docs/app/app.vue
  • docs/app/components/chat/Chat.vue
  • docs/app/components/content/IconsTheme.vue
  • docs/app/components/theme-picker/ThemePicker.vue
  • docs/app/composables/useTheme.ts
  • docs/app/error.vue
  • docs/app/plugins/theme.ts
  • docs/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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 27faaf5 and a413db2.

📒 Files selected for processing (3)
  • docs/app/composables/useTheme.ts
  • docs/app/plugins/theme.ts
  • docs/nuxt.config.ts
💤 Files with no reviewable changes (1)
  • docs/nuxt.config.ts

Comment on lines +291 to +305
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)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
docs/app/composables/useTheme.ts (1)

357-362: ⚠️ Potential issue | 🟡 Minor

Reset logic for extras.ui keys does not restore baseline values.

Setting (appConfig.ui as any)[key] = undefined loses any preexisting baseline configuration from app.config.ts. Unlike the colors handling on line 354 which uses defaultColors[key] as fallback, the ui keys have no restoration mechanism.

Consider storing a baseline snapshot when applying extras.ui, or define defaults for each ui key similar to how defaultColors works 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 _fallback parameter in restoreState.

The _fallback parameter 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

📥 Commits

Reviewing files that changed from the base of the PR and between a413db2 and 02b1c05.

📒 Files selected for processing (9)
  • docs/app/components/chat/Chat.vue
  • docs/app/components/content/examples/sidebar/SidebarChatExample.vue
  • docs/app/components/content/examples/sidebar/SidebarExample.vue
  • docs/app/components/content/examples/sidebar/SidebarOpenExample.vue
  • docs/app/components/content/examples/sidebar/SidebarWidthExample.vue
  • docs/app/composables/useTheme.ts
  • docs/app/plugins/theme.ts
  • docs/server/api/ai.post.ts
  • playgrounds/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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v4 #4488

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant