fix(ChatMessage/ChatMessages): handle generic message and message duplication#6391
fix(ChatMessage/ChatMessages): handle generic message and message duplication#6391
message duplication#6391Conversation
Also mark `message` slot prop as deprecated. This is duplicate code that already comes from `UChatMessage` slot's data (which is the source of the types). Co-authored-by: Dany <alwe.dev@gmail.com>
📝 WalkthroughWalkthroughThe PR changes the ChatMessages content slot to surface message fields directly (destructuring Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 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: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
docs/content/docs/1.getting-started/3.migration/1.v4.md (1)
474-541:⚠️ Potential issue | 🟡 MinorAdd the release badge to the updated migration section.
These examples now document the unreleased flat
#contentslot payload, so the containing “AI SDK v5 migration” heading should be marked with the Soon badge.📝 Proposed docs update
-### AI SDK v5 migration (optional) +### AI SDK v5 migration (optional) :badge{label="Soon" class="align-text-top"}As per coding guidelines,
docs/**/*.md: Add:badge{label="Soon" class="align-text-top"}to docs headings when introducing new features or fixes, as the docs deploy on merge but features ship on next npm release.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/content/docs/1.getting-started/3.migration/1.v4.md` around lines 474 - 541, The "AI SDK v5 migration" heading needs the release badge added; update the markdown heading that introduces the "AI SDK v5 migration" section by appending :badge{label="Soon" class="align-text-top"} to the heading line so the section shows the Soon badge when documenting the unreleased flat `#content` slot payload.docs/content/blog/how-to-build-an-ai-chat.md (1)
582-601:⚠️ Potential issue | 🟡 MinorAdd Soon badges to the headings containing the updated slot examples.
These snippets now rely on the unreleased flat
UChatMessages#content`` slot payload, so readers need the release-status badge.📝 Proposed docs update
-## Creating the chat page +## Creating the chat page :badge{label="Soon" class="align-text-top"} -## Integrating history in the chat page +## Integrating history in the chat page :badge{label="Soon" class="align-text-top"} -### Integrating with the chat +### Integrating with the chat :badge{label="Soon" class="align-text-top"}As per coding guidelines,
docs/**/*.md: Add:badge{label="Soon" class="align-text-top"}to docs headings when introducing new features or fixes, as the docs deploy on merge but features ship on next npm release.Also applies to: 863-882, 1048-1067
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/content/blog/how-to-build-an-ai-chat.md` around lines 582 - 601, Add a "Soon" release-status badge to the headings that introduce the unreleased flat UChatMessages `#content` slot examples; locate the headings that precede the updated slot snippets (the sections referencing UChatReasoning and the UChatMessages `#content` payload) and append :badge{label="Soon" class="align-text-top"} to those headings so the docs show the Soon badge for readers; apply the same change to the other affected sections noted (the blocks around lines shown in the review: the examples at the UChatReasoning/ChatComark usage and the other two ranges mentioned).docs/content/docs/2.components/chat.md (1)
304-458:⚠️ Potential issue | 🟡 MinorMark this unreleased slot-contract example with the Soon badge.
These examples now show the new destructured
#contentslot scope. Since docs publish on merge before the npm release, mark the relevant heading so users know this API may not be available yet.Proposed fix
-## Client Setup +## Client Setup :badge{label="Soon" class="align-text-top"}As per coding guidelines,
docs/**/*.md: Add:badge{label="Soon" class="align-text-top"}to docs headings when introducing new features or fixes, as the docs deploy on merge but features ship on next npm release.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/content/docs/2.components/chat.md` around lines 304 - 458, The "Client Setup" heading introduces an unreleased API (destructured `#content` slot scope) and must be marked with the Soon badge; update the "Client Setup" heading in this doc to include :badge{label="Soon" class="align-text-top"} so readers see the "Soon" indicator for the new destructured `#content` slot usage (refer to the heading text "Client Setup" and the example using the `#content` slot and Chat class from `@ai-sdk/vue` to locate the section).src/runtime/components/ChatMessages.vue (1)
12-46:⚠️ Potential issue | 🟡 MinorPreserve message generics for list-level action callbacks.
messagesis generic overUIMessage[]butuserandassistantstill use the non-genericChatMessageProps, so action callbacks lose custom metadata/data/tool typings whenChatMessagesProps<CustomMessage[]>is used.The codebase already uses this extraction pattern in
SlotBase(line 63–66); apply the same approach here:Proposed fix
type ChatMessages = ComponentConfig<typeof theme, AppConfig, 'chatMessages'> +type ChatMessagePropsFor<T extends UIMessage[]> + = T[number] extends UIMessage<infer M, infer D, infer U> + ? ChatMessageProps<M, D, U> + : ChatMessageProps + export interface ChatMessagesProps<T extends UIMessage[] = UIMessage[]> { messages?: T @@ - user?: Pick<ChatMessageProps, 'icon' | 'avatar' | 'variant' | 'side' | 'actions' | 'ui'> + user?: Pick<ChatMessagePropsFor<T>, 'icon' | 'avatar' | 'variant' | 'side' | 'actions' | 'ui'> @@ - assistant?: Pick<ChatMessageProps, 'icon' | 'avatar' | 'variant' | 'side' | 'actions' | 'ui'> + assistant?: Pick<ChatMessagePropsFor<T>, 'icon' | 'avatar' | 'variant' | 'side' | 'actions' | 'ui'>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/runtime/components/ChatMessages.vue` around lines 12 - 46, The user and assistant props lose per-message generics because they reference non-generic ChatMessageProps; change their types to preserve the messages generic T by picking from ChatMessageProps<T[number]> instead of ChatMessageProps so action callbacks keep custom message typings. Specifically, in ChatMessagesProps<T extends UIMessage[] = UIMessage[]>, update user and assistant to use Pick<ChatMessageProps<T[number]>, 'icon' | 'avatar' | 'variant' | 'side' | 'actions' | 'ui'> (same keys) so the props inherit the message-level generic type.
🧹 Nitpick comments (1)
test/components/ChatMessages.spec.ts (1)
79-99: Tighten the actions slot assertions.This currently passes if only one message forwards
actions/message. Assert both role-specific invocations and the exact forwarded actions to make the regression test meaningful.🧪 Proposed test strengthening
it('forwards `message` to the actions slot', async () => { + const userActions = [{ icon: 'i-lucide-copy', label: 'Copy user' }] + const assistantActions = [{ icon: 'i-lucide-copy', label: 'Copy assistant' }] const captured: Parameters<Exclude<ChatMessagesSlots['actions'], undefined>>[0][] = [] await mountSuspended(ChatMessages, { props: { ...props, - user: { actions: [{ icon: 'i-lucide-copy', label: 'Copy' }] }, - assistant: { actions: [{ icon: 'i-lucide-copy', label: 'Copy' }] } + user: { actions: userActions }, + assistant: { actions: assistantActions } }, slots: { actions: (slotProps) => { captured.push(slotProps) return 'x' @@ } }) - expect(captured.length).toBeGreaterThan(0) - for (const p of captured) { - expect(p).toHaveProperty('message') - expect(p).toHaveProperty('actions') - } + expect(captured).toHaveLength(props.messages.length) + expect(captured[0]).toMatchObject({ message: props.messages[0], actions: userActions }) + expect(captured[1]).toMatchObject({ message: props.messages[1], actions: assistantActions }) })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/components/ChatMessages.spec.ts` around lines 79 - 99, The test currently only checks that the actions slot was called and that slotProps have message and actions; tighten it by asserting role-specific invocations and exact forwarded actions: when mounting ChatMessages with props.user.actions and props.assistant.actions, inspect the captured array (captured: Parameters<Exclude<ChatMessagesSlots['actions'], undefined>>[0][]) and assert there are calls for both message.role === 'user' and message.role === 'assistant', and for each call assert slotProps.actions deep-equals the corresponding props.user.actions or props.assistant.actions so the slot receives the exact arrays forwarded by ChatMessages.
🤖 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/content/docs/2.components/chat-messages.md`:
- Around line 423-426: The "Slots" heading in the updated slot API docs must be
marked unreleased: open docs/content/docs/2.components/chat-messages.md, locate
the section that documents the new flat slot payload (where the template uses
the slot signature template `#content`="{ id, parts }" and the v-for over
parts/:key uses `${id}-${part.type}-${index}`) and add the Soon badge to the
"Slots" heading by appending :badge{label="Soon" class="align-text-top"} so the
heading displays the Soon marker until the next npm release.
In `@src/runtime/components/ChatMessage.vue`:
- Line 35: The action handler is currently being called with the full component
props (leaking component-only fields like icon/avatar/actions/class/ui) instead
of the public UIMessage shape; change the invocation in ChatMessage.vue so
actions[].onClick is called with messageProps (the sanitized
UIMessage<TMetadata,TDataParts,TTools> object) rather than the component props
object, ensuring the callback signature for actions?.onClick(e, messageProps)
matches the declared UIMessage type and doesn't expose internal props.
In `@test/components/ChatMessage.spec.ts`:
- Line 5: The import currently mixes a runtime import and a type import; change
to two declarations by importing the component default (ChatMessage) normally
and importing the type separately using an explicit type import (import type {
ChatMessageSlots }) from the same module so the runtime symbol ChatMessage and
the type symbol ChatMessageSlots are on separate import lines.
In `@test/components/ChatMessages.spec.ts`:
- Line 5: The combined import pulls both the runtime component and a type; split
them so the runtime default export ChatMessages is imported normally and the
type ChatMessagesSlots is imported using a standalone "import type" statement;
update the existing import line to only import ChatMessages (default) and add a
separate "import type { ChatMessagesSlots }" line to satisfy the rule of
separate type imports.
---
Outside diff comments:
In `@docs/content/blog/how-to-build-an-ai-chat.md`:
- Around line 582-601: Add a "Soon" release-status badge to the headings that
introduce the unreleased flat UChatMessages `#content` slot examples; locate the
headings that precede the updated slot snippets (the sections referencing
UChatReasoning and the UChatMessages `#content` payload) and append
:badge{label="Soon" class="align-text-top"} to those headings so the docs show
the Soon badge for readers; apply the same change to the other affected sections
noted (the blocks around lines shown in the review: the examples at the
UChatReasoning/ChatComark usage and the other two ranges mentioned).
In `@docs/content/docs/1.getting-started/3.migration/1.v4.md`:
- Around line 474-541: The "AI SDK v5 migration" heading needs the release badge
added; update the markdown heading that introduces the "AI SDK v5 migration"
section by appending :badge{label="Soon" class="align-text-top"} to the heading
line so the section shows the Soon badge when documenting the unreleased flat
`#content` slot payload.
In `@docs/content/docs/2.components/chat.md`:
- Around line 304-458: The "Client Setup" heading introduces an unreleased API
(destructured `#content` slot scope) and must be marked with the Soon badge;
update the "Client Setup" heading in this doc to include :badge{label="Soon"
class="align-text-top"} so readers see the "Soon" indicator for the new
destructured `#content` slot usage (refer to the heading text "Client Setup" and
the example using the `#content` slot and Chat class from `@ai-sdk/vue` to locate
the section).
In `@src/runtime/components/ChatMessages.vue`:
- Around line 12-46: The user and assistant props lose per-message generics
because they reference non-generic ChatMessageProps; change their types to
preserve the messages generic T by picking from ChatMessageProps<T[number]>
instead of ChatMessageProps so action callbacks keep custom message typings.
Specifically, in ChatMessagesProps<T extends UIMessage[] = UIMessage[]>, update
user and assistant to use Pick<ChatMessageProps<T[number]>, 'icon' | 'avatar' |
'variant' | 'side' | 'actions' | 'ui'> (same keys) so the props inherit the
message-level generic type.
---
Nitpick comments:
In `@test/components/ChatMessages.spec.ts`:
- Around line 79-99: The test currently only checks that the actions slot was
called and that slotProps have message and actions; tighten it by asserting
role-specific invocations and exact forwarded actions: when mounting
ChatMessages with props.user.actions and props.assistant.actions, inspect the
captured array (captured: Parameters<Exclude<ChatMessagesSlots['actions'],
undefined>>[0][]) and assert there are calls for both message.role === 'user'
and message.role === 'assistant', and for each call assert slotProps.actions
deep-equals the corresponding props.user.actions or props.assistant.actions so
the slot receives the exact arrays forwarded by ChatMessages.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f997d2cb-36d0-4890-8d55-d586bb27b2eb
📒 Files selected for processing (14)
docs/app/components/chat/Chat.vuedocs/app/components/content/examples/chat/ChatPaletteContentSearchExample.vuedocs/app/components/content/examples/chat/ChatPaletteModalExample.vuedocs/app/components/content/examples/sidebar/SidebarChatExample.vuedocs/content/blog/how-to-build-an-ai-chat.mddocs/content/docs/1.getting-started/3.migration/1.v4.mddocs/content/docs/2.components/chat-messages.mddocs/content/docs/2.components/chat.mdplaygrounds/nuxt/app/pages/chat.vueskills/nuxt-ui/references/layouts/chat.mdsrc/runtime/components/ChatMessage.vuesrc/runtime/components/ChatMessages.vuetest/components/ChatMessage.spec.tstest/components/ChatMessages.spec.ts
commit: |
…ad of full props
…enerics in `actions`
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/runtime/components/ChatMessage.vue (1)
112-116: Remove redundant'text' in partguard.
part.type === 'text'narrowsparttoTextUIPartfrom theaiSDK, which guaranteestext: string. The additional&& 'text' in partis redundant at both type and runtime levels.♻️ Suggested change
- <template v-if="part.type === 'text' && 'text' in part"> + <template v-if="part.type === 'text'">🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/runtime/components/ChatMessage.vue` around lines 112 - 116, In the ChatMessage.vue template, remove the redundant runtime/type guard "&& 'text' in part" from the v-if because "part.type === 'text'" already narrows part to the TextUIPart shape; update the template conditional on the element that renders text (the v-if inside the v-for over parts) to only use part.type === 'text' so you rely on the existing discriminated union and eliminate the unnecessary check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/runtime/components/ChatMessage.vue`:
- Around line 112-116: In the ChatMessage.vue template, remove the redundant
runtime/type guard "&& 'text' in part" from the v-if because "part.type ===
'text'" already narrows part to the TextUIPart shape; update the template
conditional on the element that renders text (the v-if inside the v-for over
parts) to only use part.type === 'text' so you rely on the existing
discriminated union and eliminate the unnecessary check.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 39185ed7-ee26-4062-8bfa-004248f1d84d
📒 Files selected for processing (2)
src/runtime/components/ChatMessage.vuesrc/runtime/components/ChatMessages.vue
🔗 Linked issue
Replaces #5259
❓ Type of change
📚 Description
Bring
UChatMessage/UChatMessagesin line with the AI SDK'sUIMessageshape, propagate generics end to end, and deprecate the redundantmessageslot prop onUChatMessages.Type chain, and why message is now redundant
The typing flows in a straight line:
ChatMessageProps<TMetadata, TDataParts, TTools>extendsUIMessage<TMetadata, TDataParts, TTools>— every field ofUIMessage(id, role, parts, metadata) is a prop on<UChatMessage>.ChatMessageSlots.contentis narrowed toUIMessage<…> & { content?: string }and is bound in the template viav-bind="omit(props, <chat-ui keys>)"— so the slot exposes eachUIMessagefield directly, and any future field added toUIMessageupstream reaches the slot without a source change here.ChatMessagesSlots<T extends UIMessage[]>inheritsChatMessageSlots<M, D, U>per message (viaT[number] extends UIMessage<infer M, infer D, infer U>), so the forwardedleading/files/content/actionsslots receive exactly the same typed props the child produces.Because (1) and (2) already put every
UIMessagekey on the slot scope as top-level props, themessageobject passed toChatMessagesSlotsis by construction pure duplication — two supported paths to the same data, with the object form being the one that won't pick up futureUIMessageadditions cleanly.Why deprecate instead of remove
#content="{ message }"has been the pattern every first-party example taught since the v4 major shipped, so removing it outright would break a lot of existing code. The JSDoc @deprecated route mirrors the treatment AI SDK v5 itself gave to content → parts — which is already reflected onChatMessageProps.content: fully functional at runtime, flagged in IDE hover, canonical examples migrated to the destructured form.What's included
ChatMessage{Props,Slots}andChatMessages{Props,Slots}; narrowed + forward-compatible content slot; message field onChatMessagesSlotsmarked @deprecated with an @example pointing at the new destructure.metadatais actually delivered to thecontentslot, that no ChatMessage-specific layout prop leaks into it, and that message is still present on forwarded slots for backwards compatibility.#content="{ message }"migrated to#content="{ id, role, parts }"across the component showcase, component docs, v4 migration guide, blog post, playground, and the layout skill.Why not just make message the single source of truth?
Taking the opposite direction from v4 (making
messagea single nested object on every slot and dropping flat per-field props) is technically viable but meaningfully heavier.It requires reshaping all four
ChatMessageSlotssignatures (not justcontent, sinceleading/files/actionswould needmessageadded for consistency), commits every consumer tomessage.fooaccess with no path to flatten later, and still has to address the v4 gap wheremetadatawas advertised by the slot type but never bound at runtime. Going through @deprecated instead keeps flat access consistent across every slot at both component levels, preserves the existing#content="{ message }"pattern as a documented migration bridge, and mirrors how upstream AI SDK itself retiredcontentin favor ofparts.Use this playground link to understand the duplicate slot data (atm I cannot show you the type errors that it might rise)
📝 Checklist