-
-
Notifications
You must be signed in to change notification settings - Fork 72
vue-ai-devtools and ts-vue-start-chat #143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughAdds a new Vue 3 example app (examples/ts-vue-start-chat) with routing, model selection, client/server chat (streaming /api/chat), guitar tools, UI components, and TanStack Vue AI devtools; also adds a new Changes
Sequence DiagramsequenceDiagram
participant User as User
participant UI as ChatView / Messages
participant Client as useChat Hook
participant Server as /api/chat Route
participant Provider as AI Provider
participant Tools as Tool Executor
User->>UI: Select model & send message
UI->>Client: sendMessage(text)
Client->>Server: POST /api/chat { provider, model, conversationId }
Server->>Provider: Create adapter & run agent with tools
loop Agent loop (<=20)
Provider-->>Server: Response (text or tool call)
alt Tool call requires approval
Server-->>Client: Stream approval request
Client->>UI: Render approve/deny
User->>UI: Approve/deny
UI->>Client: addToolApprovalResponse(decision)
Client->>Server: Send approval decision
Server->>Tools: Execute tool with approval
Tools-->>Server: Tool result
else Tool call (no approval)
Server->>Tools: Execute tool
Tools-->>Server: Tool result
end
Server-->>Client: Stream response parts
Client->>UI: Update messages
end
UI->>User: Render conversation and recommendations
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
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 |
|
View your CI Pipeline Execution ↗ for commit 1e5bdc3
☁️ Nx Cloud last updated this comment at |
@tanstack/ai
@tanstack/ai-anthropic
@tanstack/ai-client
@tanstack/ai-devtools-core
@tanstack/ai-gemini
@tanstack/ai-ollama
@tanstack/ai-openai
@tanstack/ai-react
@tanstack/ai-react-ui
@tanstack/ai-solid
@tanstack/ai-solid-ui
@tanstack/ai-svelte
@tanstack/ai-vue
@tanstack/ai-vue-ui
@tanstack/react-ai-devtools
@tanstack/solid-ai-devtools
@tanstack/vue-ai-devtools
commit: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 18
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
examples/ts-vue-chat/package.json (1)
12-27:@tanstack/vue-devtoolsis not a valid npm package. The official TanStack Vue devtools package is@tanstack/vue-query-devtools(currently at v5.x–6.x). This import inApp.vuewill fail at build time. Replace@tanstack/vue-devtools: ^0.1.2with the correct package@tanstack/vue-query-devtoolsand verify the component API (propsconfig,plugins,eventBusConfig) matches the actual library version.
🟡 Minor comments (7)
examples/ts-vue-start-chat/README.md-38-65 (1)
38-65: Add a language to the fenced “Project Structure” block (MD040).markdownlint flags the fence at Line 38 as missing a language.
-``` +```text src/ ├── routes/ # File-based routes (TanStack Router/Start) ... └── views/ # Page components ├── ChatView.vue └── GuitarDetailView.vue -``` +```Also worth a quick check that the listed filenames/extensions (e.g.
__root.tsx) match what’s actually in the example.examples/ts-vue-start-chat/CHANGELOG.md-1-2 (1)
1-2: Changelog title doesn’t match the package/example name.This looks like a copy/paste from the other example:
-# ts-vue-chat +# ts-vue-start-chatpackages/typescript/vue-ai-devtools/README.md-7-40 (1)
7-40: Fix hard tabs in code fence + remove dangling##header.// in jsx - <TanStackDevtools - config={{ - position: 'bottom-right', - }} - plugins={[ - { - name: 'Tanstack Router', - id: 'tanstack-router', - component: TanStackRouterDevtoolsPanel, - }, - aiDevtoolsPlugin(), - ]} - eventBusConfig={{ - connectToServerBus: true, - }} - /> + <TanStackDevtools + config={{ + position: 'bottom-right', + }} + plugins={[ + { + name: 'Tanstack Router', + id: 'tanstack-router', + component: TanStackRouterDevtoolsPanel, + }, + aiDevtoolsPlugin(), + ]} + eventBusConfig={{ + connectToServerBus: true, + }} + />
-##
If you intend to keep a section after this, give it a real title (e.g. “## Notes”) and content. </blockquote></details> <details> <summary>packages/typescript/vue-ai-devtools/vite.config.ts-1-38 (1)</summary><blockquote> `1-38`: **Remove unsupported `cjs: false` option from tanstackViteConfig call.** The `@tanstack/vite-config` function does not support a `cjs` option. Dual ESM/CJS output is handled via the `package.json` exports layout, not configuration parameters. Remove line 36 (`cjs: false,`) as it has no effect and creates confusion about the actual build output strategy. </blockquote></details> <details> <summary>examples/ts-vue-start-chat/package.json-1-47 (1)</summary><blockquote> `1-47`: **Use official `vue-router` or document community adapter limitation** The example uses `@tanstack/vue-router@^1.140.5`, which is a community-maintained adapter—TanStack Router v1 only provides first-class support for React and Solid frameworks. While functional, this creates a maintenance/stability risk versus using the official `vue-router@^4.x` (as in the comparable `ts-vue-chat` example). Either switch to the standard Vue Router or add a note to the README explaining this is a community adapter. Express 5.1.0 ESM and Node 24.8.0 satisfy all other compatibility requirements. </blockquote></details> <details> <summary>examples/ts-vue-start-chat/src/views/ChatView.vue-59-72 (1)</summary><blockquote> `59-72`: **Handle `selectedIndex === -1` to avoid a “no selection” `<select>` state.** ```diff -const selectedIndex = computed(() => - MODEL_OPTIONS.findIndex( +const selectedIndex = computed(() => { + const idx = MODEL_OPTIONS.findIndex( (opt) => opt.provider === selectedModel.value.provider && opt.model === selectedModel.value.model, - ), -) + ) + return idx === -1 ? 0 : idx +})Also applies to: 107-122
examples/ts-vue-start-chat/src/components/ChatInput.vue-14-31 (1)
14-31: Keydown handler should also respectisLoadingto avoid blocking Enter behavior while loading.
Right now, Enter is prevented even though send is blocked inhandleSend().-const handleKeydown = (e: KeyboardEvent) => { - if (e.key === 'Enter' && !e.shiftKey && input.value.trim()) { +const handleKeydown = (e: KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey && input.value.trim() && !props.isLoading) { e.preventDefault() handleSend() } }
🧹 Nitpick comments (15)
examples/ts-vue-start-chat/.gitignore (1)
1-22: LGTM; standard.gitignorefor a Node/Vue example.Optional: consider aligning with other examples by also ignoring common tooling outputs (e.g.,
coverage/,.turbo/,.cache/,.vite/) if those are generated in this example.examples/ts-vue-start-chat/public/manifest.json (1)
4-20: Icon files exist; consider addingpurposefield for enhanced PWA icon support.All three icon files (favicon.ico, logo192.png, logo512.png) are present in the public directory. However, the manifest would benefit from the
purposefield on icon definitions to indicate how they should be used (e.g., "maskable" for adaptive icons in modern PWA implementations). Consider updating the icons to include this field:{ "src": "logo192.png", "type": "image/png", "sizes": "192x192", "purpose": "any maskable" }packages/typescript/vue-ai-devtools/src/server.ts (1)
3-5: Avoid duplicatingAiDevtoolsVueInitacross entrypoints (type drift risk).
IfAiDevtoolsVueInitis also declared in another module (e.g., panel/plugin entry), re-export it from a single source instead of redefining here.Also consider renaming
_props→propssince it’s used.Also applies to: 18-27
examples/ts-vue-start-chat/src/views/GuitarDetailView.vue (1)
6-16: Harden route param parsing (parseIntradix + NaN handling); consider back fallback.const guitar = computed(() => { - const id = parseInt(params.value.id as string) - return guitars.find((g) => g.id === id) + const rawId = params.value.id + const id = typeof rawId === 'string' ? Number.parseInt(rawId, 10) : Number.NaN + return Number.isFinite(id) ? guitars.find((g) => g.id === id) : undefined }) const handleBack = () => { router.history.back() }Optional: if history can’t go back, navigate to the list route instead.
examples/ts-vue-start-chat/src/views/GuitarsView.vue (1)
1-40: Prefer typed navigation (to: '/guitars/$id'+params) + lazy-load images
This avoids manual string building/encoding and keeps navigation consistent with the file-route (/guitars/$id). Also considerloading="lazy"for the grid images.- <Link + <Link v-for="guitar in guitars" :key="guitar.id" - :to="`/guitars/${guitar.id}`" + :to="{ to: '/guitars/$id', params: { id: String(guitar.id) } }" class="block rounded-lg overflow-hidden border border-orange-500/20 bg-gray-800/50 hover:border-orange-500/40 transition-colors" > ... <img :src="guitar.image" :alt="guitar.name" + loading="lazy" + decoding="async" class="w-full h-full object-cover" />examples/ts-vue-start-chat/tsconfig.json (1)
1-32: Consider a separate Node tsconfig (or includevite.config.ts) for full typecheck coverage
Right nowcheckwon’t typecheck non-src/TS (e.g.,vite.config.ts, server entrypoints), which is fine if you intentionally have a separate config—otherwise you may miss regressions.examples/ts-vue-start-chat/src/components/Header.vue (1)
1-147: Add basic a11y hooks for the off-canvas menu (aria-expanded/controls + ESC close)
This improves keyboard/screen-reader behavior with minimal code.<script setup lang="ts"> import { ref } from 'vue' import { Link } from '@tanstack/vue-router' const isOpen = ref(false) +const navId = 'app-offcanvas-nav' </script> <template> <header class="p-4 flex items-center bg-gray-800 text-white shadow-lg"> <button @click="isOpen = true" class="p-2 hover:bg-gray-700 rounded-lg transition-colors" aria-label="Open menu" + :aria-expanded="isOpen" + :aria-controls="navId" > ... <!-- Sidebar --> <aside + :id="navId" :class="[ ... ]" + @keydown.esc="isOpen = false" >(If you want to go one step further later: focus trap + preventing body scroll while open.)
examples/ts-vue-start-chat/src/components/GuitarRecommendation.vue (1)
1-54: Make id normalization explicit + addtype="button"
AvoidsNaNsurprises and prevents accidental form submission when embedded.const props = defineProps<{ id: string | number }>() const navigate = useNavigate() -const guitar = computed(() => guitars.find((g) => g.id === +props.id)) +const guitarId = computed(() => + typeof props.id === 'number' ? props.id : Number.parseInt(props.id, 10), +) +const guitar = computed(() => guitars.find((g) => g.id === guitarId.value)) ... <button @click="handleViewDetails" + type="button" class="bg-gradient-to-r from-orange-500 to-red-600 text-white px-4 py-1.5 rounded-lg text-sm hover:opacity-90 transition-opacity" >packages/typescript/vue-ai-devtools/src/plugin.ts (1)
5-10: Consider reusing a sharedNullComponent(dedupe withsrc/index.ts/src/server.ts).
Not a correctness issue, but it’ll reduce drift between env entrypoints.examples/ts-vue-start-chat/src/data/guitars.ts (1)
1-8: Type looks good; considerGuitar[]overArray<Guitar>for consistency (optional).examples/ts-vue-start-chat/src/components/ChatInput.vue (1)
13-15: Prefernull-initialized DOM refs for correctness/clarity.-const textareaRef = ref<HTMLTextAreaElement>() +const textareaRef = ref<HTMLTextAreaElement | null>(null)examples/ts-vue-start-chat/src/components/Messages.vue (1)
9-29: Consider improving message typing and “only autoscroll if already near bottom”.
Current{ deep: true }+ always-scroll will fight the user when they scroll up.packages/typescript/vue-ai-devtools/src/index.ts (1)
14-19:AiDevtoolsPanelInProd = DevToolsPanelComponentnaming is slightly confusing.
SinceAiDevtoolsPanelis the one that becomes no-op outside dev, maybe rename to something likeAiDevtoolsPanelActual(optional).examples/ts-vue-start-chat/src/routes/__root.tsx (1)
51-66: GateconnectToServerBusto development to prevent unexpected production connections.
Although devtools are tree-shaken by default and excluded from production builds, explicitly settingconnectToServerBus: trueunconditionally creates a maintenance risk—especially in example code that developers copy into production applications. If devtools are ever intentionally included in production, this would cause unexpected connection attempts to a devtools server.<TanStackDevtools config={{ position: 'bottom-right', }} plugins={[ { name: 'Tanstack Router', id: 'tanstack-router', component: TanStackRouterDevtoolsPanel, }, aiDevtoolsPlugin(), ]} eventBusConfig={{ - connectToServerBus: true, + connectToServerBus: import.meta.env.DEV, }} />examples/ts-vue-start-chat/src/views/VueUIView.vue (1)
58-64: Prefer stable<option>keys (provider+model) over array index
Index keys can cause odd DOM reuse if options reorder.<option v-for="(option, index) in MODEL_OPTIONS" - :key="index" + :key="`${option.provider}:${option.model}`" :value="index" >Also applies to: 93-100
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (14)
examples/ts-vue-start-chat/public/example-guitar-flowers.jpgis excluded by!**/*.jpgexamples/ts-vue-start-chat/public/example-guitar-motherboard.jpgis excluded by!**/*.jpgexamples/ts-vue-start-chat/public/example-guitar-racing.jpgis excluded by!**/*.jpgexamples/ts-vue-start-chat/public/example-guitar-steamer-trunk.jpgis excluded by!**/*.jpgexamples/ts-vue-start-chat/public/example-guitar-superhero.jpgis excluded by!**/*.jpgexamples/ts-vue-start-chat/public/example-guitar-traveling.jpgis excluded by!**/*.jpgexamples/ts-vue-start-chat/public/example-guitar-video-games.jpgis excluded by!**/*.jpgexamples/ts-vue-start-chat/public/example-ukelele-tanstack.jpgis excluded by!**/*.jpgexamples/ts-vue-start-chat/public/favicon.icois excluded by!**/*.icoexamples/ts-vue-start-chat/public/logo192.pngis excluded by!**/*.pngexamples/ts-vue-start-chat/public/logo512.pngis excluded by!**/*.pngexamples/ts-vue-start-chat/public/tanstack-circle-logo.pngis excluded by!**/*.pngexamples/ts-vue-start-chat/public/tanstack-word-logo-white.svgis excluded by!**/*.svgpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (41)
examples/ts-vue-chat/package.json(1 hunks)examples/ts-vue-chat/src/App.vue(1 hunks)examples/ts-vue-start-chat/.gitignore(1 hunks)examples/ts-vue-start-chat/CHANGELOG.md(1 hunks)examples/ts-vue-start-chat/README.md(1 hunks)examples/ts-vue-start-chat/package.json(1 hunks)examples/ts-vue-start-chat/public/manifest.json(1 hunks)examples/ts-vue-start-chat/public/robots.txt(1 hunks)examples/ts-vue-start-chat/src/components/ChatInput.vue(1 hunks)examples/ts-vue-start-chat/src/components/GuitarRecommendation.vue(1 hunks)examples/ts-vue-start-chat/src/components/Header.vue(1 hunks)examples/ts-vue-start-chat/src/components/Messages.vue(1 hunks)examples/ts-vue-start-chat/src/data/guitars.ts(1 hunks)examples/ts-vue-start-chat/src/lib/guitar-tools.ts(1 hunks)examples/ts-vue-start-chat/src/lib/model-selection.ts(1 hunks)examples/ts-vue-start-chat/src/routeTree.gen.ts(1 hunks)examples/ts-vue-start-chat/src/router.ts(1 hunks)examples/ts-vue-start-chat/src/routes/__root.tsx(1 hunks)examples/ts-vue-start-chat/src/routes/api.chat.ts(1 hunks)examples/ts-vue-start-chat/src/routes/guitars/$id.ts(1 hunks)examples/ts-vue-start-chat/src/routes/guitars/index.ts(1 hunks)examples/ts-vue-start-chat/src/routes/index.ts(1 hunks)examples/ts-vue-start-chat/src/routes/vue-ui.ts(1 hunks)examples/ts-vue-start-chat/src/styles.css(1 hunks)examples/ts-vue-start-chat/src/views/ChatView.vue(1 hunks)examples/ts-vue-start-chat/src/views/GuitarDetailView.vue(1 hunks)examples/ts-vue-start-chat/src/views/GuitarsView.vue(1 hunks)examples/ts-vue-start-chat/src/views/VueUIView.vue(1 hunks)examples/ts-vue-start-chat/tsconfig.json(1 hunks)examples/ts-vue-start-chat/vite.config.ts(1 hunks)packages/typescript/vue-ai-devtools/CHANGELOG.md(1 hunks)packages/typescript/vue-ai-devtools/README.md(1 hunks)packages/typescript/vue-ai-devtools/package.json(1 hunks)packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.ts(1 hunks)packages/typescript/vue-ai-devtools/src/createVuePanel.ts(1 hunks)packages/typescript/vue-ai-devtools/src/index.ts(1 hunks)packages/typescript/vue-ai-devtools/src/plugin.ts(1 hunks)packages/typescript/vue-ai-devtools/src/production.ts(1 hunks)packages/typescript/vue-ai-devtools/src/server.ts(1 hunks)packages/typescript/vue-ai-devtools/tsconfig.json(1 hunks)packages/typescript/vue-ai-devtools/vite.config.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
examples/ts-vue-start-chat/src/routes/api.chat.ts (1)
examples/ts-solid-chat/src/lib/guitar-tools.ts (1)
addToCartToolServer(109-117)
examples/ts-vue-start-chat/src/routes/guitars/$id.ts (5)
examples/ts-vue-start-chat/src/routes/guitars/index.ts (1)
Route(4-6)examples/ts-vue-start-chat/src/routes/index.ts (1)
Route(4-6)examples/ts-vue-start-chat/src/routes/vue-ui.ts (1)
Route(4-6)examples/ts-vue-start-chat/src/routes/api.chat.ts (1)
Route(45-130)examples/ts-vue-start-chat/src/routes/__root.tsx (1)
Route(18-40)
examples/ts-vue-start-chat/src/routes/guitars/index.ts (5)
examples/ts-vue-start-chat/src/routes/guitars/$id.ts (1)
Route(4-6)examples/ts-vue-start-chat/src/routes/index.ts (1)
Route(4-6)examples/ts-vue-start-chat/src/routes/vue-ui.ts (1)
Route(4-6)examples/ts-vue-start-chat/src/routes/api.chat.ts (1)
Route(45-130)examples/ts-vue-start-chat/src/routes/__root.tsx (1)
Route(18-40)
examples/ts-vue-start-chat/src/routeTree.gen.ts (1)
examples/ts-vue-start-chat/src/router.ts (1)
getRouter(5-11)
examples/ts-vue-start-chat/src/routes/__root.tsx (3)
packages/typescript/vue-ai-devtools/src/index.ts (1)
aiDevtoolsPlugin(21-24)packages/typescript/vue-ai-devtools/src/plugin.ts (1)
aiDevtoolsPlugin(16-23)packages/typescript/vue-ai-devtools/src/server.ts (1)
aiDevtoolsPlugin(18-25)
packages/typescript/vue-ai-devtools/src/plugin.ts (3)
packages/typescript/vue-ai-devtools/src/index.ts (4)
aiDevtoolsPlugin(21-24)AiDevtoolsVueInit(5-5)AiDevtoolsVueInit(26-26)AiDevtoolsPanel(14-17)packages/typescript/vue-ai-devtools/src/production.ts (3)
aiDevtoolsPlugin(4-4)AiDevtoolsVueInit(2-2)AiDevtoolsPanel(1-1)packages/typescript/vue-ai-devtools/src/server.ts (4)
aiDevtoolsPlugin(18-25)AiDevtoolsVueInit(3-5)AiDevtoolsPanel(15-15)aiDevtoolsNoOpPlugin(27-27)
examples/ts-vue-start-chat/src/routes/index.ts (5)
examples/ts-vue-start-chat/src/routes/guitars/$id.ts (1)
Route(4-6)examples/ts-vue-start-chat/src/routes/guitars/index.ts (1)
Route(4-6)examples/ts-vue-start-chat/src/routes/vue-ui.ts (1)
Route(4-6)examples/ts-vue-start-chat/src/routes/api.chat.ts (1)
Route(45-130)examples/ts-vue-start-chat/src/routes/__root.tsx (1)
Route(18-40)
packages/typescript/vue-ai-devtools/src/index.ts (5)
packages/typescript/vue-ai-devtools/src/production.ts (2)
AiDevtoolsPanel(1-1)aiDevtoolsPlugin(4-4)packages/typescript/vue-ai-devtools/src/server.ts (3)
AiDevtoolsPanel(15-15)AiDevtoolsPanelInProd(16-16)aiDevtoolsPlugin(18-25)packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.ts (1)
AiDevtoolsPanel(22-40)packages/typescript/ai/src/stream/processor.ts (1)
process(390-413)packages/typescript/vue-ai-devtools/src/plugin.ts (1)
aiDevtoolsPlugin(16-23)
🪛 LanguageTool
packages/typescript/vue-ai-devtools/README.md
[grammar] ~5-~5: Il y a peut-être une erreur ici
Context: ... Vue Devtools for TanStack AI. ## Usage ts import { aiDevtoolsPlugin } from '@tanstack/vue-ai-devtools' import { TanStackDevtools } from '@tanstack/vue-devtools' const devtoolsPlugins = [aiDevtoolsPlugin()] // in .vue file <TanStackDevtools :config="devtoolsConfig" :plugins="devtoolsPlugins" :eventBusConfig="devtoolsEventBusConfig" /> // in jsx <TanStackDevtools config={{ position: 'bottom-right', }} plugins={[ { name: 'Tanstack Router', id: 'tanstack-router', component: TanStackRouterDevtoolsPanel, }, aiDevtoolsPlugin(), ]} eventBusConfig={{ connectToServerBus: true, }} /> ##
(QB_NEW_FR)
🪛 markdownlint-cli2 (0.18.1)
packages/typescript/vue-ai-devtools/README.md
21-21: Hard tabs
Column: 1
(MD010, no-hard-tabs)
22-22: Hard tabs
Column: 1
(MD010, no-hard-tabs)
23-23: Hard tabs
Column: 1
(MD010, no-hard-tabs)
24-24: Hard tabs
Column: 1
(MD010, no-hard-tabs)
25-25: Hard tabs
Column: 1
(MD010, no-hard-tabs)
26-26: Hard tabs
Column: 1
(MD010, no-hard-tabs)
27-27: Hard tabs
Column: 1
(MD010, no-hard-tabs)
28-28: Hard tabs
Column: 1
(MD010, no-hard-tabs)
29-29: Hard tabs
Column: 1
(MD010, no-hard-tabs)
30-30: Hard tabs
Column: 1
(MD010, no-hard-tabs)
31-31: Hard tabs
Column: 1
(MD010, no-hard-tabs)
32-32: Hard tabs
Column: 1
(MD010, no-hard-tabs)
33-33: Hard tabs
Column: 1
(MD010, no-hard-tabs)
34-34: Hard tabs
Column: 1
(MD010, no-hard-tabs)
35-35: Hard tabs
Column: 1
(MD010, no-hard-tabs)
36-36: Hard tabs
Column: 1
(MD010, no-hard-tabs)
examples/ts-vue-start-chat/README.md
38-38: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (22)
packages/typescript/vue-ai-devtools/CHANGELOG.md (1)
1-5: Verify release version + align with repo changelog conventions
## 0.0.3with “Initial release” reads like a first publish, but the version suggests prior releases—please confirm it matches the package’s actualpackage.jsonversion and intended publish/tag history, and consider adding minimal “Added” details (or conforming to the repo’s standard changelog/changesets format) for future readers.examples/ts-vue-start-chat/src/styles.css (1)
1-95: Well-structured Tailwind CSS with a cohesive dark theme design.The CSS file demonstrates solid Tailwind best practices: proper use of
@layerdirectives to organize styles, consistent application of utilities via@apply, and a thoughtfully designed dark color scheme (gray-900 base with orange-400/500 accents). The prose/markdown styling (lines 12–77) comprehensively covers rendered content elements, and the button components (lines 79–95) provide four semantic variants with consistent padding, rounding, and hover behavior.examples/ts-vue-start-chat/public/robots.txt (1)
1-3: The robots.txt file in examples is boilerplate that follows the pattern used across all example applications. Since these are reference implementations in the repository rather than deployed services, the robots.txt configuration does not require review or modification. Developers deploying an example to a live service would adjust robots.txt as needed for their hosting context.examples/ts-vue-start-chat/vite.config.ts (1)
1-28: Verify Vite plugin order with@tanstack/vue-start+ Vue plugins.Order can be sensitive here (especially
tanstackStart()relative tovue()/vueJsx()). Please confirm the example builds and runs (dev + prod build), and adjust ordering if Start expects to wrap/precede Vue transforms.packages/typescript/vue-ai-devtools/src/server.ts (1)
7-17: SSR no-op surface looks solid.
Null component returningnullis a clean way to ensure devtools are inert on server/SSR.packages/typescript/vue-ai-devtools/tsconfig.json (1)
1-14: TS project config looks reasonable for a publishable package (declarations + composite).
No immediate issues spotted.examples/ts-vue-start-chat/src/routes/vue-ui.ts (1)
1-6: Simple route wiring looks good.examples/ts-vue-start-chat/src/routes/index.ts (1)
1-6: LGTM.examples/ts-vue-start-chat/src/routes/guitars/index.ts (1)
1-6: LGTM.packages/typescript/vue-ai-devtools/src/production.ts (1)
1-4: Clean production re-export surface
Exports look consistent and the type-only export avoids runtime coupling.examples/ts-vue-start-chat/src/router.ts (1)
1-11: [rewritten comment]
[classification tag]examples/ts-vue-start-chat/src/routes/__root.tsx (1)
18-40: Roothead()looks fine; stylesheet URL import is appropriate for Vite.examples/ts-vue-start-chat/src/components/ChatInput.vue (1)
42-92: Template wiring looks solid (disabled state + stop button).examples/ts-vue-start-chat/src/views/ChatView.vue (1)
75-86:get body()reactive getter is a nice way to keep provider/model in sync.examples/ts-vue-start-chat/src/data/guitars.ts (1)
10-93: No action needed. All referenced images exist inexamples/ts-vue-start-chat/public/and will render correctly. The image paths in the code correctly reference the public assets directory.packages/typescript/vue-ai-devtools/src/plugin.ts (1)
16-32: Add an explicit return type for the plugin descriptor if available from @tanstack/vue-devtools.
This helps catch shape regressions (e.g., missingprops) early. If no exported type exists, consider defining a local type to document the expected shape.examples/ts-vue-start-chat/src/lib/model-selection.ts (1)
79-113: Storage helpers look solid for SSR
Goodwindowguard + try/catch;getStoredModelPreference()also correctly normalizes to an entry fromMODEL_OPTIONS.examples/ts-vue-start-chat/src/routeTree.gen.ts (2)
11-43: Otherwise LGTM for a generated route tree
No manual edits recommended.Also applies to: 44-140
1-10: Generated file: verify it's excluded from lint/format in repo configThe file header indicates it should be excluded from linting and formatting, but
.prettierignoreandeslint.config.jsdon't reference this file or theexamples/directory. The file relies oneslint-disableandts-nocheckheaders as workarounds, but these don't prevent prettier from formatting it if the format task runs. Consider adding a pattern likeexamples/**/*.gen.tsto.prettierignoreand ensuring eslint also skips it via config rather than relying on file-level directives alone.examples/ts-vue-start-chat/src/lib/guitar-tools.ts (1)
1-3: No action needed. TheneedsApprovaloption is fully supported in @tanstack/ai and is actively tested in the codebase. Approvals will trigger correctly when set totrue. The option name and behavior remain stable.examples/ts-vue-start-chat/src/views/VueUIView.vue (1)
44-46: The client tool output contract is correct as implementedThe
recommendGuitarToolDefexplicitly defines its output schema asz.object({ id: z.number() })(lines 33-35 in guitar-tools.ts), and the client implementation returns{ id: +id }which matches this contract exactly. The outputSchema is the definitive specification of the tool's output contract, not a minimum requirement. The @tanstack/ai-vue-ui Chat component accepts tool outputs asoutput?: anywith no additional field validation, meaning the tool will execute correctly with the schema-defined fields only.Likely an incorrect or invalid review comment.
examples/ts-vue-start-chat/src/routes/api.chat.ts (1)
92-107: This pattern is intentional and fully supported—no issue here.The framework explicitly handles mixing
.server()tools with rawtoolDefinitions. Tools withoutexecutefunctions are automatically treated as client-side tools: the framework detects them, emitstool-input-availableevents to the client, and waits for the client to execute them via their.client()implementations defined inVueUIView.vue. Tools withneedsApprovalwork correctly in this flow. The code matches the documented pattern in thetoolDefinitionAPI documentation.
examples/ts-vue-chat/src/App.vue
Outdated
| import { markRaw } from 'vue' | ||
| import { aiDevtoolsPlugin } from '@tanstack/vue-ai-devtools' | ||
| import { TanStackDevtools } from '@tanstack/vue-devtools' | ||
| const devtoolsConfig = { position: 'bottom-right' as const } | ||
| const devtoolsPlugins = [aiDevtoolsPlugin()] | ||
| const devtoolsEventBusConfig = { connectToServerBus: true } | ||
| </script> | ||
|
|
||
| <template> | ||
| <div class="min-h-screen bg-gray-900"> | ||
| <Header /> | ||
| <router-view /> | ||
|
|
||
| <TanStackDevtools | ||
| :config="devtoolsConfig" | ||
| :plugins="devtoolsPlugins" | ||
| :eventBusConfig="devtoolsEventBusConfig" | ||
| /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cd examples/ts-vue-chat && find . -name "App.vue" -type fRepository: TanStack/ai
Length of output: 68
🏁 Script executed:
cat -n examples/ts-vue-chat/src/App.vue | head -40Repository: TanStack/ai
Length of output: 885
Gate devtools to import.meta.env.DEV and wrap plugin with markRaw.
The TanStackDevtools component is currently rendered unconditionally, which means the devtools UI and server bus connection will ship in production builds of this example. Additionally, markRaw is imported but not applied to the plugin descriptor.
const devtoolsConfig = { position: 'bottom-right' as const }
-const devtoolsPlugins = [aiDevtoolsPlugin()]
+const devtoolsPlugins = [markRaw(aiDevtoolsPlugin())]
const devtoolsEventBusConfig = { connectToServerBus: true }
</script>
<template>
<div class="min-h-screen bg-gray-900">
<Header />
<router-view />
- <TanStackDevtools
+ <TanStackDevtools v-if="import.meta.env.DEV"
:config="devtoolsConfig"
:plugins="devtoolsPlugins"
:eventBusConfig="devtoolsEventBusConfig"
/>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { markRaw } from 'vue' | |
| import { aiDevtoolsPlugin } from '@tanstack/vue-ai-devtools' | |
| import { TanStackDevtools } from '@tanstack/vue-devtools' | |
| const devtoolsConfig = { position: 'bottom-right' as const } | |
| const devtoolsPlugins = [aiDevtoolsPlugin()] | |
| const devtoolsEventBusConfig = { connectToServerBus: true } | |
| </script> | |
| <template> | |
| <div class="min-h-screen bg-gray-900"> | |
| <Header /> | |
| <router-view /> | |
| <TanStackDevtools | |
| :config="devtoolsConfig" | |
| :plugins="devtoolsPlugins" | |
| :eventBusConfig="devtoolsEventBusConfig" | |
| /> | |
| import { markRaw } from 'vue' | |
| import { aiDevtoolsPlugin } from '@tanstack/vue-ai-devtools' | |
| import { TanStackDevtools } from '@tanstack/vue-devtools' | |
| const devtoolsConfig = { position: 'bottom-right' as const } | |
| const devtoolsPlugins = [markRaw(aiDevtoolsPlugin())] | |
| const devtoolsEventBusConfig = { connectToServerBus: true } | |
| </script> | |
| <template> | |
| <div class="min-h-screen bg-gray-900"> | |
| <Header /> | |
| <router-view /> | |
| <TanStackDevtools v-if="import.meta.env.DEV" | |
| :config="devtoolsConfig" | |
| :plugins="devtoolsPlugins" | |
| :eventBusConfig="devtoolsEventBusConfig" | |
| /> |
🤖 Prompt for AI Agents
In examples/ts-vue-chat/src/App.vue around lines 4 to 24, the TanStackDevtools
is rendered unconditionally and the aiDevtoolsPlugin isn't wrapped with markRaw;
change the module to gate devtools to development only by creating a const isDev
= import.meta.env.DEV and only build
devtoolsConfig/devtoolsPlugins/devtoolsEventBusConfig when isDev is true, wrap
the plugin descriptor as markRaw(aiDevtoolsPlugin()) when populating
devtoolsPlugins, and conditionally render the <TanStackDevtools> in the template
with a v-if="isDev" so no devtools UI or server bus is included in production
builds.
| const renderMarkdown = (content: string) => { | ||
| return marked(content, { async: false }) as string | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
marked library sanitize output default behavior XSS security
💡 Result:
Short answer: marked does NOT sanitize output by default. The built-in sanitize/sanitizer options were deprecated/removed (marked recommends using an external sanitizer such as DOMPurify, sanitize‑html, or insane), so you must run the HTML output through a sanitizer yourself to prevent XSS. [1][2]
What to do (brief):
- Treat marked output as unsafe if it includes user input.
- Pipe marked’s HTML through a proven sanitizer (DOMPurify recommended) before inserting into the DOM. [1][3]
Sources:
- marked docs — sanitize/sanitizer removed; use a sanitize library. [1]
- Security writeups on marked XSS history and the need for external sanitization. [2]
- Example project wrapping marked+DOMPurify (safe-by-default approach). [3]
🏁 Script executed:
find . -name "Messages.vue" -type fRepository: TanStack/ai
Length of output: 162
🏁 Script executed:
cat -n ./examples/ts-vue-start-chat/src/components/Messages.vue | head -100Repository: TanStack/ai
Length of output: 3823
🏁 Script executed:
grep -n "DOMPurify\|sanitize\|xss" ./examples/ts-vue-start-chat/src/components/Messages.vueRepository: TanStack/ai
Length of output: 37
Unsanitized v-html from marked() is an XSS risk; sanitize output before rendering.
The marked library does not sanitize HTML by default. Run the rendered output through a sanitizer such as DOMPurify before injecting into the DOM:
import DOMPurify from 'dompurify'
const renderMarkdown = (content: string) => {
return DOMPurify.sanitize(marked(content, { async: false }))
}This applies to lines 31–33 and 84–88.
🤖 Prompt for AI Agents
In examples/ts-vue-start-chat/src/components/Messages.vue around lines 31–33 and
84–88, the output of marked() is injected via v-html without sanitization which
allows XSS; import a sanitizer (e.g., DOMPurify) at the top of the file and pass
the marked() result through DOMPurify.sanitize before returning or binding it so
the HTML is cleaned (ensure the sanitized value is typed/returned as string and
replace direct marked(...) usage in both places with the sanitized result).
| <div | ||
| v-else-if=" | ||
| part.type === 'tool-call' && | ||
| part.state === 'approval-requested' && | ||
| part.approval | ||
| " | ||
| class="p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-lg mt-2" | ||
| > | ||
| <p class="text-white font-medium mb-2"> | ||
| 🔒 Approval Required: {{ part.name }} | ||
| </p> | ||
| <div class="text-gray-300 text-sm mb-3"> | ||
| <pre class="bg-gray-800 p-2 rounded text-xs overflow-x-auto">{{ | ||
| JSON.stringify(JSON.parse(part.arguments), null, 2) | ||
| }}</pre> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid JSON.parse inside the template (can throw and break the whole message list).
Move parsing into a helper that returns a safe fallback string.
<script setup lang="ts">
@@
const renderMarkdown = (content: string) => {
return marked(content, { async: false }) as string
}
+
+const safePrettyJson = (value: string) => {
+ try {
+ return JSON.stringify(JSON.parse(value), null, 2)
+ } catch {
+ return value
+ }
+}
</script>
@@
- <pre class="bg-gray-800 p-2 rounded text-xs overflow-x-auto">{{
- JSON.stringify(JSON.parse(part.arguments), null, 2)
- }}</pre>
+ <pre class="bg-gray-800 p-2 rounded text-xs overflow-x-auto">{{
+ safePrettyJson(part.arguments)
+ }}</pre>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div | |
| v-else-if=" | |
| part.type === 'tool-call' && | |
| part.state === 'approval-requested' && | |
| part.approval | |
| " | |
| class="p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-lg mt-2" | |
| > | |
| <p class="text-white font-medium mb-2"> | |
| 🔒 Approval Required: {{ part.name }} | |
| </p> | |
| <div class="text-gray-300 text-sm mb-3"> | |
| <pre class="bg-gray-800 p-2 rounded text-xs overflow-x-auto">{{ | |
| JSON.stringify(JSON.parse(part.arguments), null, 2) | |
| }}</pre> | |
| </div> | |
| <div | |
| v-else-if=" | |
| part.type === 'tool-call' && | |
| part.state === 'approval-requested' && | |
| part.approval | |
| " | |
| class="p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-lg mt-2" | |
| > | |
| <p class="text-white font-medium mb-2"> | |
| 🔒 Approval Required: {{ part.name }} | |
| </p> | |
| <div class="text-gray-300 text-sm mb-3"> | |
| <pre class="bg-gray-800 p-2 rounded text-xs overflow-x-auto">{{ | |
| safePrettyJson(part.arguments) | |
| }}</pre> | |
| </div> |
| <div | |
| v-else-if=" | |
| part.type === 'tool-call' && | |
| part.state === 'approval-requested' && | |
| part.approval | |
| " | |
| class="p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-lg mt-2" | |
| > | |
| <p class="text-white font-medium mb-2"> | |
| 🔒 Approval Required: {{ part.name }} | |
| </p> | |
| <div class="text-gray-300 text-sm mb-3"> | |
| <pre class="bg-gray-800 p-2 rounded text-xs overflow-x-auto">{{ | |
| JSON.stringify(JSON.parse(part.arguments), null, 2) | |
| }}</pre> | |
| </div> | |
| const safePrettyJson = (value: string) => { | |
| try { | |
| return JSON.stringify(JSON.parse(value), null, 2) | |
| } catch { | |
| return value | |
| } | |
| } |
🤖 Prompt for AI Agents
In examples/ts-vue-start-chat/src/components/Messages.vue around lines 91 to
106, the template currently calls JSON.parse(JSON.stringify(...)) directly which
can throw and break rendering; move the parsing/formatting into a component
method or computed property that wraps JSON.parse in a try/catch and returns a
safe pretty-printed string fallback (e.g., original part.arguments or '{}' or an
error note) if parsing fails, then update the template to bind to that helper
result instead of calling JSON.parse in-template.
| export const addToWishListToolDef = toolDefinition({ | ||
| name: 'addToWishList', | ||
| description: "Add a guitar to the user's wish list (requires approval)", | ||
| inputSchema: z.object({ | ||
| guitarId: z.string(), | ||
| }), | ||
| outputSchema: z.object({ | ||
| success: z.boolean(), | ||
| guitarId: z.string(), | ||
| totalItems: z.number(), | ||
| }), | ||
| needsApproval: true, | ||
| }) | ||
|
|
||
| // Tool definition for add to cart (server + client) | ||
| export const addToCartToolDef = toolDefinition({ | ||
| name: 'addToCart', | ||
| description: 'Add a guitar to the shopping cart (requires approval)', | ||
| inputSchema: z.object({ | ||
| guitarId: z.string(), | ||
| quantity: z.number(), | ||
| }), | ||
| outputSchema: z.object({ | ||
| success: z.boolean(), | ||
| cartId: z.string(), | ||
| guitarId: z.string(), | ||
| quantity: z.number(), | ||
| totalItems: z.number(), | ||
| }), | ||
| needsApproval: true, | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix guitarId type mismatch (inventory uses number ids, tools require string)
getGuitars returns id: number, and the system prompt tells the model to use that ID—yet addToWishList/addToCart demand guitarId: string. This can cause Zod validation failures during tool calls.
export const addToWishListToolDef = toolDefinition({
@@
inputSchema: z.object({
- guitarId: z.string(),
+ guitarId: z.union([z.string(), z.number()]).transform((v) => String(v)),
}),
@@
export const addToCartToolDef = toolDefinition({
@@
inputSchema: z.object({
- guitarId: z.string(),
- quantity: z.number(),
+ guitarId: z.union([z.string(), z.number()]).transform((v) => String(v)),
+ quantity: z.number().int().positive(),
}),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const addToWishListToolDef = toolDefinition({ | |
| name: 'addToWishList', | |
| description: "Add a guitar to the user's wish list (requires approval)", | |
| inputSchema: z.object({ | |
| guitarId: z.string(), | |
| }), | |
| outputSchema: z.object({ | |
| success: z.boolean(), | |
| guitarId: z.string(), | |
| totalItems: z.number(), | |
| }), | |
| needsApproval: true, | |
| }) | |
| // Tool definition for add to cart (server + client) | |
| export const addToCartToolDef = toolDefinition({ | |
| name: 'addToCart', | |
| description: 'Add a guitar to the shopping cart (requires approval)', | |
| inputSchema: z.object({ | |
| guitarId: z.string(), | |
| quantity: z.number(), | |
| }), | |
| outputSchema: z.object({ | |
| success: z.boolean(), | |
| cartId: z.string(), | |
| guitarId: z.string(), | |
| quantity: z.number(), | |
| totalItems: z.number(), | |
| }), | |
| needsApproval: true, | |
| }) | |
| export const addToWishListToolDef = toolDefinition({ | |
| name: 'addToWishList', | |
| description: "Add a guitar to the user's wish list (requires approval)", | |
| inputSchema: z.object({ | |
| guitarId: z.union([z.string(), z.number()]).transform((v) => String(v)), | |
| }), | |
| outputSchema: z.object({ | |
| success: z.boolean(), | |
| guitarId: z.string(), | |
| totalItems: z.number(), | |
| }), | |
| needsApproval: true, | |
| }) | |
| // Tool definition for add to cart (server + client) | |
| export const addToCartToolDef = toolDefinition({ | |
| name: 'addToCart', | |
| description: 'Add a guitar to the shopping cart (requires approval)', | |
| inputSchema: z.object({ | |
| guitarId: z.union([z.string(), z.number()]).transform((v) => String(v)), | |
| quantity: z.number().int().positive(), | |
| }), | |
| outputSchema: z.object({ | |
| success: z.boolean(), | |
| cartId: z.string(), | |
| guitarId: z.string(), | |
| quantity: z.number(), | |
| totalItems: z.number(), | |
| }), | |
| needsApproval: true, | |
| }) |
🤖 Prompt for AI Agents
In examples/ts-vue-start-chat/src/lib/guitar-tools.ts around lines 50 to 80, the
tool input schemas declare guitarId as z.string() but the app uses numeric IDs
(getGuitars returns id: number); change the inputSchema for both addToWishList
and addToCart to use z.number() for guitarId (and adjust any callers to pass
numbers if they currently pass strings) so Zod validation matches the inventory
ID type.
| export const MODEL_OPTIONS: Array<ModelOption> = [ | ||
| // OpenAI | ||
| { provider: 'openai', model: 'gpt-4o', label: 'OpenAI - GPT-4o' }, | ||
| { provider: 'openai', model: 'gpt-4o-mini', label: 'OpenAI - GPT-4o Mini' }, | ||
| { provider: 'openai', model: 'gpt-5', label: 'OpenAI - GPT-5' }, | ||
| { | ||
| provider: 'openai', | ||
| model: 'gpt-5.2-2025-12-11', | ||
| label: 'OpenAI - GPT-5.2', | ||
| }, | ||
|
|
||
| // Anthropic | ||
| { | ||
| provider: 'anthropic', | ||
| model: 'claude-sonnet-4-5-20250929', | ||
| label: 'Anthropic - Claude Sonnet 4.5', | ||
| }, | ||
| { | ||
| provider: 'anthropic', | ||
| model: 'claude-opus-4-5-20251101', | ||
| label: 'Anthropic - Claude Opus 4.5', | ||
| }, | ||
| { | ||
| provider: 'anthropic', | ||
| model: 'claude-haiku-4-0-20250514', | ||
| label: 'Anthropic - Claude Haiku 4.0', | ||
| }, | ||
|
|
||
| // Gemini | ||
| { | ||
| provider: 'gemini', | ||
| model: 'gemini-2.0-flash-exp', | ||
| label: 'Gemini - 2.0 Flash', | ||
| }, | ||
| { | ||
| provider: 'gemini', | ||
| model: 'gemini-exp-1206', | ||
| label: 'Gemini - Exp 1206 (Pro)', | ||
| }, | ||
|
|
||
| // Ollama | ||
| { | ||
| provider: 'ollama', | ||
| model: 'mistral:7b', | ||
| label: 'Ollama - Mistral 7B', | ||
| }, | ||
| { | ||
| provider: 'ollama', | ||
| model: 'mistral', | ||
| label: 'Ollama - Mistral', | ||
| }, | ||
| { | ||
| provider: 'ollama', | ||
| model: 'gpt-oss:20b', | ||
| label: 'Ollama - GPT-OSS 20B', | ||
| }, | ||
| { | ||
| provider: 'ollama', | ||
| model: 'granite4:3b', | ||
| label: 'Ollama - Granite4 3B', | ||
| }, | ||
| { | ||
| provider: 'ollama', | ||
| model: 'smollm', | ||
| label: 'Ollama - SmolLM', | ||
| }, | ||
| ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
@tanstack/ai openai adapter supported models gpt-4o gpt-5
💡 Result:
Yes — the @tanstack/ai OpenAI adapter supports gpt-4o and gpt-5 (you can pass model: "gpt-4o" or model: "gpt-5" to chat/summarize/etc.). [1][2]
Sources:
- TanStack AI Core API (examples using model: "gpt-4o"). [1]
- Per-model type-safety docs (examples and capability notes for "gpt-5"). [2]
🌐 Web query:
@tanstack/ai anthropic adapter supported models claude sonnet opus haiku
💡 Result:
Yes — the @tanstack/ai Anthropic adapter supports Claude-family models including Sonnet, Opus, and Haiku. Documented available models include (examples):
- claude-3-5-sonnet-20241022 (Claude 3.5 Sonnet)
- claude-3-opus-20240229 (Claude 3 Opus)
- claude-3-haiku-20240307 (Claude 3 Haiku)
- claude-3-5-sonnet-20240620, claude-2.1, claude-2.0, etc.
The docs also note support for newer Claude Sonnet/Opus snapshots (e.g., claude-sonnet-4-5-20250929, claude-opus-4-5-20251101) and provider-specific options like extended thinking. [1]
Sources:
[1] TanStack AI — Anthropic adapter (Available models & examples).
🌐 Web query:
@tanstack/ai gemini adapter supported models gemini-2.0-flash
💡 Result:
Short answer: Not officially — the TanStack Gemini adapter docs list only "gemini-pro", "gemini-pro-vision" and "gemini-ultra" as supported model names, and do not mention "gemini-2.0-flash". [1][2]
Notes/sources:
- TanStack Gemini adapter docs (supported model names). [1]
- Google Gemini model listing showing the "gemini-2.0-flash" model exists. [2]
If you want, I can try an example showing whether the adapter accepts "gemini-2.0-flash" (it may work if the adapter simply forwards the model string to the Gemini API).
🌐 Web query:
@tanstack/ai ollama adapter supported models
💡 Result:
The Ollama adapter can use whatever models you have installed in your local Ollama server — there’s no fixed list in the adapter itself. Common models shown in the TanStack docs include llama2, llama3, mistral, codellama, phi, gemma, etc. To see exactly which models your adapter can use, run:
ollama list
Sources: TanStack AI Ollama adapter docs and package notes. [1][2]
References
[1] TanStack AI — Ollama Adapter docs.
[2] npm / ai-sdk-ollama (examples of commonly used Ollama models).
🏁 Script executed:
# Search for server-side model validation
fd -e ts -e js | xargs rg -l "api.*chat\|validateModel\|MODEL_OPTIONS" | head -20Repository: TanStack/ai
Length of output: 37
🏁 Script executed:
# Look for api.chat implementation to check server validation
fd -type f -name "*chat*" | grep -E "\.(ts|js)$"Repository: TanStack/ai
Length of output: 225
🏁 Script executed:
# Check if there's any allowlist or validation logic already in place
rg "model.*valid|allowlist|ALLOWED.*MODEL|validate.*model" -iRepository: TanStack/ai
Length of output: 1719
🌐 Web query:
OpenAI gpt-4o-mini model ID API
💡 Result:
Model ID: gpt-4o-mini
Aliases / snapshots you can also use: gpt-4o-mini-2024-07-18 (and the base alias gpt-4o-mini). [1]
Typical API endpoints that accept this model:
- v1/chat/completions (chat)
- v1/responses (Responses API)
- v1/realtime (Realtime)
- v1/fine-tuning (fine-tune support)
(See model page and GPT-4o docs for full endpoint support and rate/pricing details.) [1][2]
If you want, I can show a minimal curl or Python example using the model. [1][2]
Sources: OpenAI model docs (gpt-4o-mini, GPT-4o docs). [1][2]
🌐 Web query:
OpenAI gpt-5.2-2025-12-11 model identifier
💡 Result:
The model identifier is gpt-5.2-2025-12-11. OpenAI also exposes API aliases: gpt-5.2 (Thinking), gpt-5.2-chat-latest (Instant), and gpt-5.2-pro (Pro). (platform.openai.com)
Citations:
🌐 Web query:
Anthropic claude-haiku-4-0-20250514 model ID
💡 Result:
There is no Anthropic model named "claude-haiku-4-0-20250514". Anthropic’s documented model IDs include:
- claude-opus-4-20250514 (Opus 4). [1]
- claude-sonnet-4-20250514 (Sonnet 4). [1]
- claude-3-5-haiku-20241022 (Haiku 3.5). [1]
If you meant a Haiku-family model, use claude-3-5-haiku-20241022; if you meant a Claude 4 snapshot, use claude-opus-4-20250514 or claude-sonnet-4-20250514. [1][2]
Sources: Anthropic models overview. [1] [2]
🌐 Web query:
Google Gemini gemini-exp-1206 model supported
💡 Result:
Short answer: gemini-exp-1206 was an experimental/preview Gemini model (announced Dec 6, 2024) and was made available to Gemini Advanced users, but it is experimental (not a stable, supported production model) and has been intermittently removed/retired in favor of newer Gemini releases. See Google’s announcement and the Vertex AI docs about experimental/retired Gemini models. [1][2][3]
Sources
- Google blog announcement (gemini-exp-1206 preview). [1]
- Reports of the model’s removal/return and replacement by newer experimental/pro models. [2]
- Vertex AI / Gemini model guidance (experimental models, retirement behavior, and API/location requirements). [3]
🏁 Script executed:
# Find the server-side chat API handler
fd -type f -name "*" | xargs rg "chat|model" | grep -i "server\|api\|handler" | head -30Repository: TanStack/ai
Length of output: 2516
🏁 Script executed:
# Look in the example project structure for api or server files
find . -type f \( -name "*.ts" -o -name "*.js" \) | grep -E "(api|server)" | head -20Repository: TanStack/ai
Length of output: 854
🏁 Script executed:
# Search for where MODEL_OPTIONS is actually used
rg "MODEL_OPTIONS" -B 3 -A 3Repository: TanStack/ai
Length of output: 24293
🏁 Script executed:
# Read the api.chat.ts file for the ts-vue-start-chat example
cat -n examples/ts-vue-start-chat/src/routes/api.chat.ts | head -100Repository: TanStack/ai
Length of output: 4169
🏁 Script executed:
# Check if there's any model validation or allowlist in the server
rg "model.*valid|validate.*model|ALLOWED.*MODEL|MODEL.*LIST" -i --max-count=20Repository: TanStack/ai
Length of output: 2019
Remove unsupported model identifiers and add server-side allowlist validation
Three model identifiers are invalid and will fail or behave unexpectedly:
claude-haiku-4-0-20250514(Anthropic): Useclaude-3-5-haiku-20241022insteadgemini-2.0-flash-exp(Gemini): Not in official TanStack adapter docs; use documented variants or validate against Vertex AIgemini-exp-1206(Gemini): Experimental/retired model, avoid using
The server currently accepts any model string without validation (line 94 in api.chat.ts). Add server-side checks to validate data.model against an allowlist exported from a shared module, ensuring only supported models can be sent to adapters.
| "exports": { | ||
| ".": { | ||
| "browser": { | ||
| "import": { | ||
| "types": "./dist/esm/index.d.ts", | ||
| "default": "./dist/esm/index.js" | ||
| } | ||
| }, | ||
| "node": { | ||
| "import": { | ||
| "types": "./dist/esm/server.d.ts", | ||
| "default": "./dist/esm/server.js" | ||
| } | ||
| }, | ||
| "import": { | ||
| "types": "./dist/esm/index.d.ts", | ||
| "default": "./dist/esm/index.js" | ||
| } | ||
| }, | ||
| "./production": { | ||
| "browser": { | ||
| "import": { | ||
| "types": "./dist/esm/production.d.ts", | ||
| "default": "./dist/esm/production.js" | ||
| } | ||
| }, | ||
| "node": { | ||
| "import": { | ||
| "types": "./dist/esm/server.d.ts", | ||
| "default": "./dist/esm/server.js" | ||
| } | ||
| }, | ||
| "import": { | ||
| "types": "./dist/esm/production.d.ts", | ||
| "default": "./dist/esm/production.js" | ||
| } | ||
| }, | ||
| "./package.json": "./package.json" | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
Node.js package.json exports resolution order "browser" "node" "import" conditions bundler
💡 Result:
Short answer: condition keys in package.json "exports" are matched in object order (first match wins). But runtimes/resolvers use their own default condition priority lists (so which keys are checked first depends on the resolver). Node checks its built-in conditions in a specific priority (e.g. import/require before node, and default last), while bundlers (esbuild, Vite, Parcel, etc.) publish different default orderings — so "browser" vs "node" vs "import" will be chosen according to the resolver’s condition priority, not an absolute universal order. Always put the most-specific conditions first and include a "default" fallback. [1][2][3][4]
Notes / references:
- Node.js docs: condition matching uses object order; Node’s built-in guidance: use import/require before node and default last. "default" should be last. [2][5]. (nodejs.org)
- Parcel: conditions are matched in the object order defined in package.json; Parcel lists supported conditions (import, require, node, browser, default, etc.). (parceljs.org)
- Community guide (examples): different tools have different default condition lists (e.g. Vite, esbuild differ), so bundlers may match "import" or "browser" before "node". Always design exports with explicit ordering and a "default" fallback. (hirok.io)
If you want, tell me which resolver/bundler you care about (Node, webpack, Rollup, Vite, esbuild, Parcel) and I’ll give the exact priority that resolver uses.
Citations:
- 1: https://nodejs.org/api/packages.html?utm_source=openai
- 2: https://parceljs.org/features/dependency-resolution/?utm_source=openai
- 3: https://hirok.io/posts/package-json-exports?utm_source=openai
🌐 Web query:
Vite Rollup Webpack conditional exports browser node SSR prerender pitfalls
💡 Result:
Here are the common pitfalls and practical mitigations when using conditional exports (package.json exports/browser), Vite/Rollup/Webpack, and when targeting browser vs Node (SSR / prerender):
-
Confusing resolution order between "exports" and legacy "browser" (Webpack/Rollup/Vite).
- Problem: tools differ in whether they use package.json "exports", the "browser" field, or browser-specific conditions inside "exports" — causing different builds to pick different entry files. [1][2]
- Mitigation: prefer using conditional exports entirely (include "browser" condition inside "exports") and avoid relying on the legacy top-level "browser" field; test resolution with each bundler/resolver you support. [1][3]
-
Dual-published packages (CJS + ESM) and interop mismatches.
- Problem: mixing CJS and ESM across the graph can produce duplicated singletons, broken instanceof checks, or wrong default/named exports due to different interop implementations. Bundlers may add wrappers like __esModule which changes runtime shape. [4][5]
- Mitigation: publish a clear single-entry ESM build when possible; if dual-publishing, ensure "exports" maps subpaths for both "import" and "require" and test runtime interop across your target environments. Use consistent package boundaries in monorepos. [4][5]
-
Bundler-specific exports/condition support gaps.
- Problem: some versions of Webpack, Rollup plugins, or resolve plugins may not implement all package exports condition logic (e.g., custom condition names like "worker", "browser", "node", "import", "require"), so a subpath may resolve differently in dev vs build or in SSR vs client. [1][4]
- Mitigation: pin tool versions; read resolver docs (resolve.exportsFields in Webpack); add fallbacks in "exports" for broadest conditions (include "default" plus "import"/"require"/"node"/"browser"). Test both dev, build, SSR and prerender. [1][4]
-
Prerender / SSR surprises with browser-only code.
- Problem: prerender/SSR runs in Node — importing a module that resolves to browser-only code (e.g., DOM APIs) via conditional exports causes runtime errors during build/prerender. Tools may still resolve to browser entry unexpectedly. [2][3]
- Mitigation: ensure server-side conditions in "exports" (e.g., "node" or "default") point to Node-safe implementations. Use separate server-side entry points or guard imports behind dynamic imports / typeof window checks. Add tests that run the prerender build. [2][3]
-
Monorepo / workspace symlinked-package behavior.
- Problem: bundlers may treat workspace packages as source (not published artifact), causing resolution to ignore "exports" or produce inconsistent graphs. This can cause duplicated code or unresolved imports. [4]
- Mitigation: build/publish packages locally when testing, or configure resolver to respect package boundaries; align package.json fields (type/module/main/exports) across workspaces. [4]
-
Different dev vs production resolver behavior.
- Problem: dev servers (Vite) use native ESM resolution and long-form paths, while production bundles (Rollup) and Webpack may perform different tree-shaking/resolution — leading to runtime differences between dev, prerender, and production. [3][4]
- Mitigation: test full build + preview + prerender flows; use integration tests that exercise SSR and static generation; avoid relying on dev-only resolution quirks.
-
Lack of clear diagnostics when an "exports" subpath is missing.
- Problem: when "exports" exists, invalid specifiers throw ModuleNotFound errors; some bundlers give unhelpful messages making debugging harder. [1]
- Mitigation: keep simple subpath mappings for common imports, include fallback "default", and add a debug build that prints resolved paths or use resolver logging.
Quick recommended checklist
- Use conditional "exports" with explicit keys: "import", "require", "node", "browser", and "default". Test each combination. [1][4]
- Provide server-safe fallback entries and avoid DOM-only code in server resolution paths. [2][3]
- Test in all targeted flows: dev server, production bundle, SSR runtime, and prerender pipeline. [2][3][4]
- Pin bundler/resolver versions and read their resolve/export docs (Webpack resolve.exportsFields, Rollup resolver plugins, Vite resolver). [1][4]
- For monorepos, ensure published package shape is identical to local dev shape (or test with published tarball). [4]
Sources:
- Webpack discussion on exports vs browser field behavior. [1]
- vite-plugin-ssr (prerender/SSG and doNotPrerender) docs. [2][3]
- Rollup/plugins interop and dual-package issues. [4]
- Article on Rollup bundling pitfalls (duals, workspaces) and recommendations. [5]
🌐 Web query:
SSR frameworks conditional exports wrong condition selected "browser" "node"
💡 Result:
Cause (short): package.json conditional exports are matched by the resolver against an ordered list of "conditions". Tools (Vite, bundlers) and Node each have their own allowed-condition order — if "browser" appears earlier in the matcher list (or the exports object is ordered with browser first) the browser branch will be selected even when running SSR under Node. Node itself ignores unknown conditions (like browser) unless the tool honors them; object order in exports is significant. [1][2][3]
How to fix
- If you use Vite:
- Adjust resolve.conditions or ssr.resolve.conditions so "node" (or "import"/"default") precedes "browser". Example: set ssr.resolve.externalConditions / ssr.resolve.conditions to include 'node' first. Also ensure NODE_OPTIONS='--conditions ...' matches at runtime if you add custom conditions. [1][2]
- If you control the package: prefer providing a "node" branch and a "default" fallback (or move node/default earlier than browser) so Node/tools will pick the correct implementation. Node recommends including a "default" branch. [3]
- Workarounds: externalize the dependency for SSR, force CommonJS/ES entry selection with mainFields or by importing the node-specific path (e.g., import 'pkg/dist/node/...').
References
- Vite resolve/SSR docs (resolve.conditions, ssr.resolve.externalConditions). [Vite docs]. (v4.vite.dev)
- Vite issue / community discussion showing browser branch selected during bundling; explains ordering matters. (lightrun.com)
- Node.js ESM conditional exports docs — condition matching order, significance of object order, and recommendation to include "default" / "node" branches. (nodejs.org)
If you want, paste your package.json exports and your Vite (or other bundler) config and I’ll show the exact change.
Citations:
- 1: https://v4.vite.dev/config/shared-options?utm_source=openai
- 2: https://lightrun.com/answers/vitejs-vite-when-bundling-conditional-exports-in-packagejson-are-selected-incorrectly?utm_source=openai
- 3: https://nodejs.org/download/release/v14.11.0/docs/api/esm.html?utm_source=openai
🏁 Script executed:
# Check the exact order and structure of exports in the file
cd packages/typescript/vue-ai-devtools
cat -n package.json | sed -n '30,68p'Repository: TanStack/ai
Length of output: 1249
Conditional export order creates SSR risk: "browser" should not precede "node"/"import".
The current export structure resolves "browser" before "node" and the fallback "import" condition. During SSR/prerender in Node environments, some bundlers (Vite, Webpack) may select the browser entry despite running under Node, causing DOM-dependent code to execute during build and fail. Reorder the conditions to place "import" or "node" before "browser" to ensure Node-safe implementations are selected during server-side rendering. For example, move the generic "import" fallback (lines 44–47) before the "browser" object (lines 32–37) at each export level.
🤖 Prompt for AI Agents
In packages/typescript/vue-ai-devtools/package.json around lines 30 to 68, the
exports conditional order places "browser" before "node" and the generic
"import" fallback which can cause bundlers to pick browser builds during SSR;
reorder each export block so "import" (or "node") appears before "browser" —
i.e., move the generic "import" entry (types/default) above the "browser" object
in both the main "." export and the "./production" export so Node/server-safe
implementations are selected during SSR/prerender.
| /// <reference types="vite/client" /> | ||
| import { AiDevtoolsCore } from '@tanstack/ai-devtools-core' | ||
| import { createVuePanel, type DevtoolsPanelProps } from './createVuePanel' | ||
| import { defineComponent, h } from 'vue' | ||
| import type { PropType } from 'vue' | ||
|
|
||
| export interface AiDevtoolsVueInit { | ||
| /** | ||
| * Theme for the devtools UI. | ||
| * Defaults to 'dark'. | ||
| */ | ||
| theme?: DevtoolsPanelProps['theme'] | ||
| } | ||
|
|
||
| const [InternalPanel] = createVuePanel( | ||
| AiDevtoolsCore as unknown as new ( | ||
| _props: AiDevtoolsVueInit, | ||
| ) => InstanceType<typeof AiDevtoolsCore>, | ||
| ) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "package.json" | head -20Repository: TanStack/ai
Length of output: 890
🏁 Script executed:
find . -type f -name "tsconfig.json" | head -20Repository: TanStack/ai
Length of output: 932
🏁 Script executed:
find . -type f -name "env.d.ts"Repository: TanStack/ai
Length of output: 37
🏁 Script executed:
find . -type d -name "vue-ai-devtools"Repository: TanStack/ai
Length of output: 92
🏁 Script executed:
find . -type d -name "*devtools*" | grep -v node_modulesRepository: TanStack/ai
Length of output: 206
🏁 Script executed:
ls -la packages/typescript/ | grep -i devtoolsRepository: TanStack/ai
Length of output: 314
🏁 Script executed:
ls -la packages/typescript/vue-ai-devtools/Repository: TanStack/ai
Length of output: 537
🏁 Script executed:
cat packages/typescript/vue-ai-devtools/package.jsonRepository: TanStack/ai
Length of output: 2124
🏁 Script executed:
ls -la packages/typescript/vue-ai-devtools/src/Repository: TanStack/ai
Length of output: 540
🏁 Script executed:
cat packages/typescript/vue-ai-devtools/vite.config.tsRepository: TanStack/ai
Length of output: 926
🏁 Script executed:
find packages/typescript/ai-devtools -name "*.ts" -type fRepository: TanStack/ai
Length of output: 664
🏁 Script executed:
find packages -name "*devtools-core*" -type dRepository: TanStack/ai
Length of output: 37
🏁 Script executed:
cat packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.tsRepository: TanStack/ai
Length of output: 1123
🏁 Script executed:
cat packages/typescript/vue-ai-devtools/src/index.tsRepository: TanStack/ai
Length of output: 803
🏁 Script executed:
find packages -path "*/ai-devtools-core*" -type dRepository: TanStack/ai
Length of output: 37
🏁 Script executed:
rg "class AiDevtoolsCore" --type ts -A 20Repository: TanStack/ai
Length of output: 37
🏁 Script executed:
cat packages/typescript/vue-ai-devtools/tsconfig.jsonRepository: TanStack/ai
Length of output: 366
🏁 Script executed:
rg "vite" packages/typescript/vue-ai-devtools/src/ --type tsRepository: TanStack/ai
Length of output: 151
🏁 Script executed:
rg "HMR\|import.meta" packages/typescript/vue-ai-devtools/src/ --type tsRepository: TanStack/ai
Length of output: 37
🏁 Script executed:
grep -r "/// <reference" packages/typescript/vue-ai-devtools/Repository: TanStack/ai
Length of output: 151
🏁 Script executed:
ls -la packages/typescript/vue-ai-devtools/dist/ 2>/dev/null || echo "No dist directory"Repository: TanStack/ai
Length of output: 72
🏁 Script executed:
rg "import.*vite" packages/typescript/vue-ai-devtools/src/ --type tsRepository: TanStack/ai
Length of output: 37
🏁 Script executed:
cat packages/typescript/ai-devtools/src/index.ts | head -30Repository: TanStack/ai
Length of output: 271
Remove the unused vite/client triple-slash reference that will leak into published type definitions.
The /// <reference types="vite/client" /> directive (line 1) serves no purpose in this file—there are no Vite-specific usages (no HMR, import.meta, etc.). With declaration: true in tsconfig.json, TypeScript preserves this directive in generated .d.ts files, forcing library consumers to have vite/client types available even if they don't use Vite, which breaks type checking for consumers without Vite as a dependency.
-/// <reference types="vite/client" />Also verify that AiDevtoolsCore's constructor signature matches the expected new (props: AiDevtoolsVueInit) signature. The current cast (line 16–18) masks potential type mismatches and should be replaced with direct type compatibility.
🤖 Prompt for AI Agents
packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.ts lines 1–20: remove
the top-line triple-slash directive `/// <reference types="vite/client" />` (it
is unused and will be emitted into .d.ts files); then replace the unsafe cast of
AiDevtoolsCore to a constructor type with a proper typing — either update
AiDevtoolsCore's exported type to include a constructor signature matching new
(props: AiDevtoolsVueInit) => InstanceType<typeof AiDevtoolsCore> and pass that
type into createVuePanel, or change the createVuePanel call to accept
AiDevtoolsCore directly without the cast so TypeScript will surface any
mismatch; ensure no lingering Vite references remain in the file.
| const props = { | ||
| theme: { | ||
| type: String as () => DevtoolsPanelProps['theme'], | ||
| }, | ||
| devtoolsProps: { | ||
| type: Object as () => TComponentProps, | ||
| }, | ||
| } | ||
|
|
||
| const Panel = defineComponent({ | ||
| props, | ||
| setup(config) { | ||
| const devToolRef = ref<HTMLElement | null>(null) | ||
| // Keep devtools instance non-reactive to avoid proxying, | ||
| // since private fields (e.g. #isMounted) break on proxies. | ||
| let devtools: TCoreDevtoolsClass | null = null | ||
| let didMount = false | ||
| let isUnmounted = false | ||
|
|
||
| onMounted(async () => { | ||
| const instance = new CoreClass(config.devtoolsProps as TComponentProps) | ||
| devtools = instance | ||
|
|
||
| if (devToolRef.value) { | ||
| await instance.mount(devToolRef.value, config.theme ?? 'dark') | ||
| if (isUnmounted) { | ||
| // If we unmounted before mount finished, clean up. | ||
| try { | ||
| instance.unmount() | ||
| } catch { | ||
| // ignore | ||
| } | ||
| } else { | ||
| didMount = true | ||
| } | ||
| } | ||
| }) | ||
|
|
||
| onUnmounted(() => { | ||
| isUnmounted = true | ||
| if (didMount) { | ||
| try { | ||
| devtools?.unmount() | ||
| } catch { | ||
| // ignore | ||
| } | ||
| } | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make devtoolsProps required (or defaulted) + honor devtoolsProps.theme + harden async mount cleanup.
Right now, devtoolsProps can be omitted at runtime (Line 23-25) but is assumed present (Line 39). Also, TComponentProps extends DevtoolsPanelProps, but config.devtoolsProps.theme is ignored (Line 43).
Suggested tightening:
const props = {
theme: {
type: String as () => DevtoolsPanelProps['theme'],
},
devtoolsProps: {
type: Object as () => TComponentProps,
+ required: true,
},
}
const Panel = defineComponent({
props,
setup(config) {
const devToolRef = ref<HTMLElement | null>(null)
// Keep devtools instance non-reactive to avoid proxying,
// since private fields (e.g. #isMounted) break on proxies.
let devtools: TCoreDevtoolsClass | null = null
let didMount = false
let isUnmounted = false
onMounted(async () => {
- const instance = new CoreClass(config.devtoolsProps as TComponentProps)
- devtools = instance
-
- if (devToolRef.value) {
- await instance.mount(devToolRef.value, config.theme ?? 'dark')
+ const el = devToolRef.value
+ if (!el) return
+
+ const instance = new CoreClass(config.devtoolsProps as TComponentProps)
+ devtools = instance
+
+ const theme = (config.theme ?? config.devtoolsProps?.theme ?? 'dark') as Theme
+ try {
+ await instance.mount(el, theme)
if (isUnmounted) {
// If we unmounted before mount finished, clean up.
try {
instance.unmount()
} catch {
// ignore
}
} else {
didMount = true
}
- }
+ } catch {
+ try {
+ instance.unmount()
+ } catch {
+ // ignore
+ }
+ }
})
onUnmounted(() => {
isUnmounted = true
- if (didMount) {
- try {
- devtools?.unmount()
- } catch {
- // ignore
- }
- }
+ try {
+ // ok even if mount never completed
+ devtools?.unmount()
+ } catch {
+ // ignore
+ }
})This keeps the “wrapper can make devtoolsProps optional” pattern, while making the exported Panel safer/less surprising for direct consumers.
🤖 Prompt for AI Agents
packages/typescript/vue-ai-devtools/src/createVuePanel.ts around lines 19 to 66:
devtoolsProps is declared optional but used as required and its theme is
ignored; the async mount logic can leave an instance untracked if unmounted
during await. Make devtoolsProps required or provide a safe default (e.g.,
default: () => ({})) so config.devtoolsProps is always defined, use the theme
from config.devtoolsProps.theme with fallback to config.theme and then 'dark'
when calling mount, assign devtools = instance before awaiting mount and capture
a local mountedInstance flag or check devtools !== null when deciding to
unmount, and in the onUnmounted cleanup always check for the concrete instance
and call unmount inside try/catch to harden async mount/unmount races.
| const NullComponent = /* #__PURE__ */ defineComponent({ | ||
| name: 'NullAiDevtoolsPanel', | ||
| setup() { | ||
| return () => null | ||
| }, | ||
| }) | ||
|
|
||
| export const AiDevtoolsPanel = | ||
| process.env.NODE_ENV !== 'development' | ||
| ? NullComponent | ||
| : DevToolsPanelComponent | ||
|
|
||
| export const AiDevtoolsPanelInProd = DevToolsPanelComponent | ||
|
|
||
| export const aiDevtoolsPlugin = | ||
| process.env.NODE_ENV !== 'development' | ||
| ? plugin.aiDevtoolsNoOpPlugin | ||
| : plugin.aiDevtoolsPlugin | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find vite/build configuration files
find . -maxdepth 3 -type f \( -name "vite.config.*" -o -name "tsconfig.json" -o -name "package.json" \) | head -20Repository: TanStack/ai
Length of output: 827
🏁 Script executed:
# Check how environment variables are used elsewhere in the codebase
rg "process\.env\.(NODE_ENV|DEV|PROD)" --type ts --type js -A 2 | head -50Repository: TanStack/ai
Length of output: 1837
🏁 Script executed:
# Also check for import.meta.env usage
rg "import\.meta\.env" --type ts --type js -A 2 | head -50Repository: TanStack/ai
Length of output: 37
🏁 Script executed:
# Look at the package.json to understand the library setup
find . -name "package.json" -path "*/vue-ai-devtools/*" -o -name "package.json" | xargs grep -l "vue-ai-devtools" | head -5Repository: TanStack/ai
Length of output: 183
🏁 Script executed:
# Check the index.ts file and surrounding context
cat -n packages/typescript/vue-ai-devtools/src/index.ts | head -40Repository: TanStack/ai
Length of output: 985
🏁 Script executed:
cat packages/typescript/vue-ai-devtools/package.jsonRepository: TanStack/ai
Length of output: 2124
🏁 Script executed:
# Check if there's a vite config in the root or packages directory
find . -maxdepth 2 -name "vite.config.*" -o -name "tsup.config.*" | head -10Repository: TanStack/ai
Length of output: 37
🏁 Script executed:
# Check the root package.json for build setup
head -50 package.jsonRepository: TanStack/ai
Length of output: 2651
🏁 Script executed:
# Check for build scripts in root
rg "build.*vue-ai-devtools|NODE_ENV" package.json | head -20Repository: TanStack/ai
Length of output: 37
🏁 Script executed:
find packages/typescript -maxdepth 2 -name "vite.config.*" -o -name "vitest.config.*"Repository: TanStack/ai
Length of output: 1141
🏁 Script executed:
# Check if there's a shared vite config at package root level
ls -la packages/typescript/ | grep -E "vite|tsup|build"Repository: TanStack/ai
Length of output: 37
🌐 Web query:
Vite library build process.env.NODE_ENV replacement ESM best practices
💡 Result:
Short best-practices for handling process.env.NODE_ENV in Vite-built ESM libraries
- Prefer import.meta.env (Vite’s model)
- Use import.meta.env (and VITE_* names for user vars) for build-time/static replacements in apps. It’s the Vite-recommended API. [1][2]
- For libraries, avoid forcing process.env replacements
- Vite’s library mode does NOT automatically replace process.env.* so consumers can control runtime behavior. Prefer leaving process.env references untouched in your distributed ESM (so downstream bundlers / runtimes decide). If you want runtime configurability, explicitly expose a config API instead of baking NODE_ENV into your bundle. [0][1]
- If you need static replacement at build time
- Add a define or replacement at build config time (vite define or rollup/@rollup/plugin-replace) to statically inline a value:
- vite.config.ts: define: { 'process.env.NODE_ENV': JSON.stringify(modeOrValue) }
This will enable dead‑code elimination for conditionals that rely on NODE_ENV. [0][4]
- vite.config.ts: define: { 'process.env.NODE_ENV': JSON.stringify(modeOrValue) }
- Consider esm-friendly solutions for cross-bundler runtime detection
- Use libraries like esm-env or import-meta-env when you need consistent environment-condition detection across ESM outputs and runtimes (they use package export conditions / runtime shims rather than hard string replaces). These are helpful for library authors who want broad bundler/runtime compatibility. [5][6]
- Packaging / exports notes for ESM libraries
- If publishing ESM (package.json "type":"module" or .mjs), ensure your exports and file extensions match consumer expectations. Avoid assuming Node’s process globals in browser-targeted ESM. Prefer explicit runtime config or documented expectations (e.g., “consumer must set NODE_ENV or pass config”). [0][7]
Quick recommended pattern for library authors
- Internally use import.meta.env for build-time-only behavior in app code; for library features that must be configurable at runtime, accept an options object or read from global/runtime APIs (not implicitly from process.env). If you must optimize for production builds, allow library consumers to opt into static replacement (document how to use define/replace). [1][0]
Sources
[0] Vite — Build / Library mode (env note).
[1] Vite — Env variables & modes.
[2] Vite — Building for Production / library mode (env paragraph).
[4] StackOverflow — define process.env.NODE_ENV in Vite builds.
[5] npm: esm-env package.
[6] import-meta-env project (runtime env solution).
🏁 Script executed:
cat packages/typescript/vue-ai-devtools/vite.config.tsRepository: TanStack/ai
Length of output: 926
🏁 Script executed:
cat packages/typescript/react-ai-devtools/vite.config.tsRepository: TanStack/ai
Length of output: 850
🏁 Script executed:
cat packages/typescript/ai-devtools/vite.config.tsRepository: TanStack/ai
Length of output: 664
🏁 Script executed:
find . -name "@tanstack/vite-config" -o -name "vite-config" -type d | head -10Repository: TanStack/ai
Length of output: 37
🏁 Script executed:
# Search for where tanstackViteConfig is defined
rg "export.*tanstackViteConfig|function tanstackViteConfig" --type ts --type js | head -20Repository: TanStack/ai
Length of output: 37
🏁 Script executed:
# Check if it's in node_modules or an internal package
find . -path "*/node_modules" -prune -o -name "*.ts" -type f -exec grep -l "tanstackViteConfig" {} \; | head -10Repository: TanStack/ai
Length of output: 734
🏁 Script executed:
# Look for vite-config package in packages directory
ls -la packages/ | grep -i configRepository: TanStack/ai
Length of output: 37
🏁 Script executed:
# Check root package.json for tanstack/vite-config dependency
rg "@tanstack/vite-config" package.jsonRepository: TanStack/ai
Length of output: 92
🌐 Web query:
@tanstack/vite-config process.env.NODE_ENV define library mode
💡 Result:
Short answer — define it in your Vite config (your custom config merged with tanstackViteConfig). Example:
export default mergeConfig(
defineConfig(({ mode }) => ({
define: { 'process.env.NODE_ENV': JSON.stringify(mode) },
})),
tanstackViteConfig({ entry: './src/index.ts', srcDir: './src' }),
)
This forces static replacement of process.env.NODE_ENV for library builds (or set it to '"production"' directly). (main.vitejs.dev)
Citations:
Add explicit define for process.env.NODE_ENV in vite.config.ts
The code relies on process.env.NODE_ENV for conditional exports, but the vite.config.ts does not include a define to ensure static replacement during the build. Add to the config:
define: { 'process.env.NODE_ENV': JSON.stringify(mode) }Without this, process.env.NODE_ENV may be undefined at runtime in consumers' bundlers, breaking the conditional logic. This applies to all devtools packages (vue, react, solid).
🤖 Prompt for AI Agents
In packages/typescript/vue-ai-devtools/src/index.ts around lines 7 to 25 the
code conditionally exports components/plugins based on process.env.NODE_ENV, but
the project's vite.config.ts does not statically define that variable for
builds; update the vite.config.ts for this and the other devtools packages (vue,
react, solid) to include a define entry that maps 'process.env.NODE_ENV' to
JSON.stringify(mode) (or the build mode variable you use) so the conditional
exports are replaced at build time and won't be undefined at runtime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (3)
examples/ts-vue-chat/src/App.vue (1)
3-10: Gate devtools to development-only and mark plugin as non-reactive (avoid shipping server-bus in prod).
This is currently rendered/configured unconditionally, andconnectToServerBus: truemay create an unintended prod connection surface.<script setup lang="ts"> import Header from './components/Header.vue' import { aiDevtoolsPlugin } from '@tanstack/vue-ai-devtools' import { TanStackDevtools } from '@tanstack/vue-devtools' +import { markRaw } from 'vue' -const devtoolsConfig = { position: 'bottom-right' as const } - -const devtoolsPlugins = [aiDevtoolsPlugin()] - -const devtoolsEventBusConfig = { connectToServerBus: true } +const isDev = import.meta.env.DEV +const devtoolsConfig = isDev ? ({ position: 'bottom-right' as const }) : undefined +const devtoolsPlugins = isDev ? [markRaw(aiDevtoolsPlugin())] : [] +const devtoolsEventBusConfig = isDev ? ({ connectToServerBus: true }) : undefined </script> <template> <div class="min-h-screen bg-gray-900"> <Header /> <router-view /> - <TanStackDevtools + <TanStackDevtools + v-if="isDev" :config="devtoolsConfig" :plugins="devtoolsPlugins" :eventBusConfig="devtoolsEventBusConfig" /> </div> </template>@tanstack/vue-devtools TanStackDevtools: does it require plugin descriptors to be wrapped with Vue markRaw to avoid reactivity warnings, and is there a recommended pattern for dev-only inclusion?Also applies to: 18-22
packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.ts (2)
1-6: Removevite/clienttriple-slash reference to avoid leaking Vite types to consumers.With
declarationbuilds, this directive can propagate into published.d.tsand make non‑Vite consumers fail type-checking.-/// <reference types="vite/client" /> - import { AiDevtoolsCore } from '@tanstack/ai-devtools-core' import { defineComponent, h } from 'vue' import { createVuePanel } from './createVuePanel' import type { DevtoolsPanelProps } from './createVuePanel' import type { PropType } from 'vue'
16-20: Avoidas unknown asonAiDevtoolsCore; let TypeScript validate the constructor contract.The cast can mask a real mismatch between
AiDevtoolsVueInitand@tanstack/ai-devtools-core’s constructor. Prefer typing viacreateVuePanelgenerics (or update/export a proper init type from@tanstack/ai-devtools-core).-const [InternalPanel] = createVuePanel( - AiDevtoolsCore as unknown as new ( - _props: AiDevtoolsVueInit, - ) => InstanceType<typeof AiDevtoolsCore>, -) +const [InternalPanel] = createVuePanel<AiDevtoolsVueInit, AiDevtoolsCore>( + AiDevtoolsCore, +)#!/bin/bash set -euo pipefail # 1) Inspect the actual ctor signature we are adapting to. rg -n --type=ts -C3 "export class AiDevtoolsCore|class AiDevtoolsCore|constructor\s*\(" packages/typescript/ai-devtools-core packages/typescript/vue-ai-devtools || true # 2) Confirm `createVuePanel` signature to ensure the generic call is correct. rg -n --type=ts -C3 "function createVuePanel|export function createVuePanel" packages/typescript/vue-ai-devtools/src/createVuePanel.ts # 3) Find duplicate definitions of AiDevtoolsVueInit to assess drift risk. rg -n --type=ts -C2 "export interface AiDevtoolsVueInit" packages/typescript/vue-ai-devtools/src
🧹 Nitpick comments (1)
packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.ts (1)
8-14: Avoid duplicatingAiDevtoolsVueInitacross entrypoints; extract to a sharedtypes.tsto prevent drift.Given
packages/typescript/vue-ai-devtools/src/server.tsalso definesAiDevtoolsVueInit(per provided snippet), a shared source-of-truth reduces the chance prod and SSR diverge later (and avoids subtle “same-name, different-type” exports).Also applies to: 22-41
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
examples/ts-vue-chat/src/App.vue(1 hunks)examples/ts-vue-start-chat/package.json(1 hunks)packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- examples/ts-vue-start-chat/package.json
🧰 Additional context used
🧬 Code graph analysis (1)
packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.ts (2)
packages/typescript/vue-ai-devtools/src/server.ts (1)
AiDevtoolsVueInit(3-5)packages/typescript/vue-ai-devtools/src/createVuePanel.ts (2)
DevtoolsPanelProps(4-6)createVuePanel(12-93)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/typescript/ai-devtools/src/store/ai-context.tsx (1)
1327-1345: Don’t spread rawchat:startedpayload intoConversation(and verify requestId mapping)
updateConversation(clientId, { status: 'active', ...e.payload })can store non-Conversationkeys into the conversation object (due to thekey as keyof Conversationcast). Prefer explicitly picking known fields (e.g.,model,provider,options,providerOptions,status).- Please confirm the chat flow sets
requestToConversationsomewhere; otherwisechat:completed/usage:tokensmay never resolve a conversation for a request.- updateConversation(clientId, { status: 'active', ...e.payload }) + updateConversation(clientId, { + status: 'active', + model: e.payload.model, + provider: e.payload.provider, + options: e.payload.options, + providerOptions: e.payload.providerOptions, + })#!/bin/bash # Verify chat requests are mapped for chat:completed/usage:tokens lookups rg -n --type=ts -C3 'requestToConversation\.set\(' rg -n --type=ts -C3 "on\\('chat:started'" packages/typescript/ai-devtools/src/store/ai-context.tsx rg -n --type=ts -C3 "on\\('chat:completed'" packages/typescript/ai-devtools/src/store/ai-context.tsx
♻️ Duplicate comments (1)
packages/typescript/vue-ai-devtools/src/index.ts (1)
15-25: Ensure process.env.NODE_ENV is properly defined at build time.The conditional exports rely on
process.env.NODE_ENVbeing statically replaced during the build. As noted in the previous review, without explicitdefineconfiguration in vite.config.ts, this variable may be undefined at runtime in consumer bundles, causing the wrong code path to be taken.
🧹 Nitpick comments (2)
packages/typescript/ai-devtools/src/store/ai-context.tsx (1)
360-376: Guard the message merge to avoid clobbering existing fields during dedupe
Object.assign(m, message)can overwrite existing optional fields (e.g.,chunks,toolCalls,usage) if the incoming “duplicate” message is partial. Consider a merge that only applies defined keys (and/or special-cases arrays to merge instead of replace).- produce((m: Message) => Object.assign(m, message)), + produce((m: Message) => { + for (const [k, v] of Object.entries(message) as Array< + [keyof Message, Message[keyof Message]] + >) { + if (v !== undefined) (m as any)[k] = v + } + }),packages/typescript/vue-ai-devtools/src/AiDevtoolsPanelInProd.ts (1)
9-13: Review the double type assertion for potential type mismatches.The
as unknown aspattern suggests a type incompatibility betweenAiDevtoolsCoreand the expected constructor signature. Consider verifying whether:
- The types can be aligned to avoid the double assertion
- The AiDevtoolsCore type definition correctly matches the createVuePanel expectations
This pattern works but may hide underlying type issues.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
examples/ts-vue-start-chat/src/routes/__root.tsx(1 hunks)packages/typescript/ai-devtools/src/store/ai-context.tsx(2 hunks)packages/typescript/vue-ai-devtools/src/AiDevtoolsPanelInProd.ts(1 hunks)packages/typescript/vue-ai-devtools/src/index.ts(1 hunks)packages/typescript/vue-ai-devtools/src/production.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/typescript/ai-devtools/src/store/ai-context.tsx (2)
examples/vanilla-chat/src/main.js (1)
message(34-34)packages/typescript/ai-devtools/src/store/ai-store.ts (1)
Message(2-2)
packages/typescript/vue-ai-devtools/src/index.ts (5)
packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.ts (1)
AiDevtoolsPanel(23-41)packages/typescript/vue-ai-devtools/src/server.ts (3)
AiDevtoolsPanel(15-15)AiDevtoolsPanelInProd(16-16)aiDevtoolsPlugin(18-25)packages/typescript/vue-ai-devtools/src/AiDevtoolsPanelInProd.ts (1)
AiDevtoolsPanelInProd(16-34)packages/typescript/vue-ai-devtools/src/production.ts (2)
AiDevtoolsPanelInProd(1-1)aiDevtoolsPlugin(10-17)packages/typescript/vue-ai-devtools/src/plugin.ts (1)
aiDevtoolsPlugin(16-23)
🔇 Additional comments (9)
examples/ts-vue-start-chat/src/routes/__root.tsx (3)
1-16: LGTM! Clean imports.The imports are well-organized and correctly use Vite's
?urlsuffix for the CSS file.
42-76: Well-structured root component.The JSX layout correctly integrates the Header, Outlet, DevTools, and Scripts. The dual-plugin configuration for Router and AI devtools is appropriate for this application.
67-69: > Likely an incorrect or invalid review comment.packages/typescript/vue-ai-devtools/src/AiDevtoolsPanelInProd.ts (1)
15-34: LGTM! Clean component structure.The component properly handles optional props with sensible defaults and correctly forwards them to the InternalPanel. The theme resolution logic with fallback is well-implemented.
packages/typescript/vue-ai-devtools/src/production.ts (2)
1-2: LGTM! Clean production entry re-exports.The aliasing of
AiDevtoolsPanelInProdasAiDevtoolsPanelprovides a consistent interface for production consumers while maintaining clear internal naming.
7-17: LGTM! Consistent plugin implementation.The plugin function correctly mirrors the development plugin structure while using the production-ready component. The API surface is consistent and well-documented.
packages/typescript/vue-ai-devtools/src/index.ts (3)
1-6: LGTM! Clean imports and type exports.The imports are well-organized, bringing in both development and production variants for conditional exports. The type re-export provides necessary public API surface.
8-13: LGTM! Appropriate no-op component for production.The
NullComponentwith/* #__PURE__ */annotation enables proper tree-shaking in production builds. The implementation is minimal and correct.
27-27: LGTM! Helpful type alias for plugin context.The type alias
AiDevtoolsVuePluginInitprovides a clearer name when used in plugin registration contexts.
| export const Route = createRootRoute({ | ||
| head: () => ({ | ||
| meta: [ | ||
| { | ||
| charSet: 'utf-8', | ||
| }, | ||
| { | ||
| name: 'viewport', | ||
| content: 'width=device-width, initial-scale=1', | ||
| }, | ||
| { | ||
| title: 'TanStack AI Vue Start Chat', | ||
| }, | ||
| ], | ||
| links: [ | ||
| { | ||
| rel: 'stylesheet', | ||
| href: appCss, | ||
| }, | ||
| ], | ||
| }), | ||
| component: RootComponent, | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Verify the head API structure - title placement looks incorrect.
The title property is placed inside the meta array at line 29, but HTML titles are typically not meta tags. Additionally, the charSet property uses camelCase, which should be verified against the TanStack Router API.
Please verify the correct structure for TanStack Vue Router's head API:
#!/bin/bash
# Search for head API usage patterns in TanStack Router examples or tests
rg -n -C3 'head.*:.*\(' --type=ts --type=tsx -g '!node_modules/**'
# Search for title property usage in head config
rg -n -C3 'title:' --type=ts --type=tsx -g '!node_modules/**' | head -50TanStack Vue Router head API documentation for meta tags and title
🤖 Prompt for AI Agents
In examples/ts-vue-start-chat/src/routes/__root.tsx around lines 18 to 40, the
head config incorrectly places title inside the meta array and uses camelCase
charSet; update the head shape to match the TanStack Router head API by moving
title out of the meta array to a top-level title property, replace the meta item
{ charSet: 'utf-8' } with { charset: 'utf-8' } (lowercase key) and keep other
meta entries as { name, content } objects, preserving the links array; verify
the final object conforms to the router's head schema.
| @@ -0,0 +1,34 @@ | |||
| /// <reference types="vite/client" /> | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat packages/typescript/vue-ai-devtools/src/AiDevtoolsPanelInProd.tsRepository: TanStack/ai
Length of output: 1091
🏁 Script executed:
# Check if any Vite-specific APIs are used in this file
rg -n "import\.meta|__VITE_|import_meta_" packages/typescript/vue-ai-devtools/src/AiDevtoolsPanelInProd.tsRepository: TanStack/ai
Length of output: 37
🏁 Script executed:
# Also check if other files in the same directory use vite/client reference
fd -e ts -e tsx . packages/typescript/vue-ai-devtools/src/ | head -20 | xargs grep -l "vite/client" 2>/dev/nullRepository: TanStack/ai
Length of output: 178
🏁 Script executed:
# Check if AiDevtoolsPanel.ts uses Vite-specific APIs
rg -n "import\.meta|__VITE_|import_meta_" packages/typescript/vue-ai-devtools/src/AiDevtoolsPanel.tsRepository: TanStack/ai
Length of output: 37
Remove the unnecessary Vite client types reference.
This file contains no Vite-specific APIs (no import.meta, __VITE_ globals, etc.). The /// <reference types="vite/client" /> directive is unused boilerplate that should be removed.
🤖 Prompt for AI Agents
In packages/typescript/vue-ai-devtools/src/AiDevtoolsPanelInProd.ts around line
1, remove the unnecessary triple-slash reference to "vite/client" because this
file doesn't use any Vite-specific globals or APIs; delete the line '///
<reference types="vite/client" />' and ensure no other references require that
directive so the file compiles without that unused directive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
examples/ts-vue-start-chat/src/components/ClientOnly.vue (1)
11-13: Consider adding an optional fallback slot.The template correctly defers rendering until mount. For improved UX, you could optionally support a fallback slot to display placeholder content during SSR or before the component mounts:
<template> - <slot v-if="mounted" /> + <slot v-if="mounted" /> + <slot v-else name="fallback" /> </template>This would allow consumers to provide placeholder content when needed, reducing layout shifts.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
examples/ts-vue-start-chat/src/components/ClientOnly.vue(1 hunks)examples/ts-vue-start-chat/src/routes/__root.tsx(1 hunks)examples/ts-vue-start-chat/vite.config.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- examples/ts-vue-start-chat/src/routes/__root.tsx
- examples/ts-vue-start-chat/vite.config.ts
🔇 Additional comments (1)
examples/ts-vue-start-chat/src/components/ClientOnly.vue (1)
1-9: LGTM! Standard Vue 3 composition API implementation.The script logic correctly implements the ClientOnly pattern using Vue 3's composition API. The component will defer rendering its slot content until after the client-side mount, which is the expected behavior for SSR scenarios.
Introducing the
And an example with SSR, ported from react-start to vue-start:
Using the new devtools in both
🎯 Changes
✅ Checklist
pnpm run test:pr.🚀 Release Impact
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.