v2.0.0 September 15, 2026
Major Dependency Upgrades & Security Hardening
Breaking Changes
- • Coordinated major dependency upgrades: Nuxt 4.3 → 4.5, Better Auth 1.5 → 1.7, AI SDK 6 → 7 (ai v7 / @ai-sdk/vue v4 / @ai-sdk/openai v4 / @openrouter/ai-sdk-provider v3), Zod 3 → 4, vue-router 4 → 5, Stripe SDK 20 → 22, ioredis 5 → 6 with rate-limiter-flexible 10 → 11, nodemailer 8 → 10, @nuxtjs/seo 3 → 5, @nuxt/scripts 0.13 → 1.3, Vitest 4 → 5, lint-staged 16 → 17. Review pnpm-lock.yaml and your own overrides before upgrading.
- • Markdown rendering moved from the deprecated vue-renderer-markdown package to @nuxtjs/mdc (the AiMarkdown component). Update any custom components that imported the old renderer.
- • createPaginationSchema no longer accepts an extra option. Extend the returned schema with .extend({ ... }) instead (server/utils/pagination.ts).
- • MCP tools get-page and list-pages were removed with the Nuxt Content v3 migration. Use queryCollection from @nuxt/content/server if you need equivalent tooling in your own repo.
- • POST /api/ai/chat now returns an AI SDK v7 UI message stream (toUIMessageStream / createUIMessageStreamResponse). Clients must use @ai-sdk/vue v4 / ai v7 useChat. The x-conversation-id response header and messageMetadata.usage contract are unchanged.
- • Database migration adds team.member_count and team_member.membership_key (unique). Run pnpm db:migrate as part of the upgrade; Better Auth 1.7.4 account keying no longer uses an issuer column.
Added
- • NUXT_AUTH_RATE_LIMIT_DISABLED environment variable to disable IP rate limiting on auth endpoints for e2e/load tests. Only the exact string true enables it, and only with NODE_ENV=development or NODE_ENV=test (default-deny otherwise).
- • Client-disconnect handling for the AI chat stream: the request abort signal is forwarded into streamText, so provider generation stops when the client disconnects mid-response.
- • AI chat input hardening: message history sent to the provider is capped to the most recent 40 messages, and parts payloads are validated against known UIMessage part shapes instead of being forwarded untyped.
- • AI chat input size caps: text parts and content strings are limited to 100k characters, parts arrays to 100 entries per message, and the total conversation text to 400k characters per request (HTTP 413 beyond that).
Security
- • AI credit settlement now claims the pending ledger entry atomically before adjusting the wallet (conditional UPDATE ... WHERE status = 'pending_settlement' RETURNING), so concurrent finish callbacks can no longer double-apply a wallet adjustment or refund (server/utils/ai-credits-settlement.ts).
- • AiMarkdown no longer renders raw HTML embedded in model output. Raw nodes (e.g. <iframe>, javascript: links) are dropped by disabling allowDangerousHtml and the rehype-raw plugin in parseMarkdown options.
- • NUXT_AUTH_RATE_LIMIT_DISABLED is default-deny: enabling it outside NODE_ENV=development|test fails env validation instead of only being rejected when NODE_ENV is exactly production.
Changed
- • Zod v4 idioms across shared validation and server utilities: z.ZodType instead of z.ZodTypeAny, z.flattenError() / z.treeifyError() instead of deprecated instance methods, and literal code: 'custom' issues.
- • AI chat billing semantics made explicit: when a client disconnects mid-stream without tool steps, the reserved credits are settled in full (46 credits at default limits); when the stream errors before completion, the reservation is refunded in full.
- • Boolean-style environment flags (NUXT_SMTP_SECURE, NUXT_AUTH_RATE_LIMIT_DISABLED) share one booleanFromEnv parser that only accepts the exact string true.
- • nuxt typecheck runs through vue-tsc for Vue SFC-aware type checking.
- • E2E scripts (e2e:prepare, e2e:seed, e2e:server) default NODE_ENV=test when unset so the rate-limit switch passes its default-deny guard.
- • Admin list endpoints run their data and total-count queries in parallel.
- • Test suite grew from 465 to 503 cases across 86 test files.
Fixed
- • Billing change-plan no longer crashes when the Stripe subscription update response lacks an items field before local plan sync.
- • The collapsed team membership migration applies member_count/membership_key in one step instead of an add-and-drop migration pair.