Base UI, tests, AI and observability
A new primitive layer, 183 tests, a streaming AI assistant and self-hosted error tracking. Plus a backlog of majors and four bugs that every derived project inherited.
The first release since April, and the largest so far. It replaces the primitive layer, adds the three things a paid SaaS kit is now expected to ship, and fixes four bugs that every project started from this boilerplate has been inheriting.
Base UI replaces Radix
shadcn/ui made Base UI its default in July. Base UI is built by the same team behind Radix, and it
collapses 24 @radix-ui/* packages into one. Thirty-one components migrated.
The one change you will feel is asChild, which becomes render:
<Button asChild>
<Link href="/x">Text</Link>
</Button>
// becomes
<Button render={<Link href="/x" />}>Text</Button>
Two components stay on Radix on purpose: aspect-ratio, which Base UI has no equivalent for, and
label, whose Base UI counterpart only works inside a Field.Root provider.
Tests, at last
The previous release shipped a test command backed by a single test file. This one ships 166
unit tests on Vitest and 17 end-to-end specs on Playwright, covering signup, onboarding, login and
dashboard navigation, with GitHub Actions running lint, typecheck, tests and e2e against a real
Postgres.
An AI assistant that bills itself
A streaming chat at /dashboard/ai built on AI SDK 7, with OpenAI or Anthropic selected by an
environment variable. It is wired into the credit system that payments already shipped, rather than
inventing a second billing concept.
Two deliberate decisions: metering only applies when a payment provider is configured, so a project that does not sell anything yet still has a working assistant; and credits are charged after the stream, because the token count does not exist until it ends.
Observability you can host yourself
Every competing kit assumes Sentry cloud. This one assumes Docker on your own VPS, so the default is
GlitchTip — drop-in compatible with the Sentry SDKs, same code, different DSN — with a compose
file to run it. OpenTelemetry instrumentation, structured logging on pino behind the existing logger
API, and every logger.error becoming an issue with no call-site changes.
Four bugs you were inheriting
prisma/migrations/was in.gitignore. Four migrations were never versioned, so they existed on one machine only.createApiHandlerreturned a 500 on any POST without a body.verifyCsrfTokenthrewRangeErroron a length mismatch instead of returningfalse.garage-setupnever started. The image is distroless, so the compose service with a shell entrypoint failed on everybun docker:up. Local storage setup is nowbun storage:setup.
Upgrades and removals
React 19.3, Stripe 22, Polar SDK 0.49, react-email 6, lucide-react 1, isomorphic-dompurify 4 — which alone closes 34 advisories, two of them XSS in the blog sanitizer. Node 22.22 is the new minimum.
Out: Phosphor Icons (two icon libraries in one product), and jscpd and madge, whose job ESLint
was already doing.
What did not make it, and why
ESLint 10 and TypeScript 7 are both stable and neither is usable here. eslint-config-next
depends on eslint-plugin-react, whose stable release caps at ESLint 9.7. TypeScript 7 ships no
programmatic API, so typescript-eslint cannot run on it at all. Both blockers are upstream.
next-intl is pinned to 4.14.1. From 4.14.6, getTranslations() inside generateMetadata()
counts as runtime data under Cache Components and the production build fails while prerendering.
Worth knowing because it compiles fine and only fails at static generation: tsc, bun dev and the
linter all pass.
Prisma 8 is still a release candidate, and it is a rewrite rather than an upgrade. Staying on 7.