This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in node_modules/next/dist/docs/ before writing any code. Heed deprecation notices.
- This file — project rules, file layout, critical conventions, recipes, quality bar. Read first.
docs/PLAN.md— full implementation plan: stack, structure, features, data model, state, UX flows, route skeleton, phases, deploy. Read when you need context on why something is built a certain way, or for feature scope decisions.node_modules/next/dist/docs/01-app/02-guides/upgrading/version-16.md— authoritative Next.js 16 upgrade notes. **Read before touchingapp/**ornext.config.ts.**node_modules/ai/dist/docs/— AI SDK 7 reference (after install). Read before writing or reviewing any AI SDK code..opencode/skills/<name>/SKILL.md— project-local skills:ai-sdk(official Vercel AI SDK reference),shadcn-base-vega(matches the installedcomponents.json). Load via theskilltool when relevant.
Streaming chat UI for multiple LLM providers (Groq primary). Built on Next.js 16 App Router, React 19.2, AI SDK 7, shadcn/ui, and AI Elements.
| Layer | Choice |
|---|---|
| Framework | Next.js 16.2.10, App Router, React 19.2.4, TypeScript 5.9 |
| Bundler | Turbopack (default — no flag) |
| Styling | Tailwind v4, cn from @/lib/utils, CSS variables for theme |
| UI | shadcn/ui (new-york, neutral) + AI Elements registry |
| AI runtime | ai, @ai-sdk/react, @ai-sdk/groq |
| LLM | Groq |
| State | Zustand for threads/settings; useChat for messages |
| Persistence | localStorage (Zustand persist middleware) |
| Markdown | streamdown (AI-SDK-friendly) |
| Icons / Toasts | @phosphor-icons/react, sonner |
| Package manager | pnpm 11+ (via corepack) |
| Deploy | Vercel |
app/
layout.tsx # Inter + JetBrains Mono fonts, ThemeProvider, <Toaster />
page.tsx # redirect("/chat")
chat/
page.tsx # main chat UI
layout.tsx # sidebar + topbar shell
api/
chat/route.ts # POST streamText() with Groq + tools
tools/ # optional tool routers (Phase 3+)
components/
ui/ # shadcn primitives (do not edit)
ai-elements/ # AI Elements registry output (do not edit)
app/
sidebar.tsx
topbar.tsx
model-picker.tsx
thread-list.tsx
empty-state.tsx
tools-panel.tsx
lib/
utils.ts # cn() helper
prompts/system.ts # versioned system prompt
tools/ # tool definitions (zod + tool() from 'ai')
get-time.ts
calculator.ts
web-search.ts
store/
chat-store.ts # zustand: threads, activeId, settings
hooks/
use-persisted-chat.ts # useChat + localStorage bridge
use-auto-title.ts
types/
chat.ts # UIMessage extensions, ToolPart discriminated unions
public/ # static assets
- Async Request APIs:
params,searchParams,cookies(),headers(),draftMode()must beawaited. No sync access. - Route Handlers: use
RouteContext<'/path/[id]'>global type for typed params. Example:export async function POST(_req: Request, ctx: RouteContext<'/chat/...'>) { const { id } = await ctx.params }. - No
next lint— usepnpm lint(already wired toeslint). - No
runtimeConfig— useprocess.env+NEXT_PUBLIC_*prefix for client vars. - No
next/legacy/image—next/imageonly. Useimages.remotePatterns(notimages.domains). - Turbopack default — do NOT add
--turbopackflags. middleware.tsis deprecated — useproxy.tsif needed. Not used in MVP.- Parallel routes require
default.js— avoid unless needed. - React 19.2:
useEffectEvent,use(),<Activity>,<ViewTransition>available. - Type-safe props: run
pnpm dlx next typegento generatePageProps,LayoutProps,RouteContexthelpers.
useChat()from@ai-sdk/reactis the source of truth for messages + streaming.- Server route returns
streamText({...}).toUIMessageStreamResponse(). - Convert UI messages with
convertToModelMessages(messages)server-side. - Use
stopWhen: stepCountIs(10)for multi-step agentic loops. - Tool definitions:
tool({ description, inputSchema: z.object({...}), execute })from'ai'. - For approval-required tools: set
needsApproval: true→ AI SDK emitstool-input-availablepart; render<Confirmation>and reply with synthetic result. - Reasoning content lives on the message; for
qwen/qwen3-32banddeepseek-r1-distill-llama-70b, surface it via the<Reasoning>AI Element.
- Never commit
.env.local(already gitignored by Next). - Server-only vars (e.g.
GROQ_API_KEY) accessed only inapp/api/**or server components. - Public vars must start with
NEXT_PUBLIC_. - Create
.env.examplewith all keys documented; check it in.
- Use
@/alias for everything aboveapp/(e.g.@/lib/utils,@/components/ui/button). - shadcn components in
components/ui/and AI Elements incomponents/ai-elements/— do not hand-edit. Re-run the add command to update. - One component per file. Co-locate small helpers; promote to
lib/when reused. - Tailwind v4: classes only. CSS variables via
cn+tailwind-merge. No inlinestylefor theme tokens. - TypeScript: no
any. Discriminated unions for tool parts (typefield). Zod for all tool inputs and env parsing.
useChatowns messages for the active thread.- Zustand
useChatStoreownsthreads[],activeId,settings; persisted under keylumin:v1. - On thread switch: seed
useChatwithsetMessages(thread.messages). - On first user message: generate a title (first 40 chars or via
useAutoTitle). - Cap messages per thread at ~200 to bound context.
pnpm dev # next dev (Turbopack)
pnpm build # next build (Turbopack)
pnpm start # next start
pnpm lint # eslint
pnpm typecheck # tsc --noEmit (add this script)pnpm dlx shadcn@latest add <name>pnpm dlx ai-elements@latest add <name>- Create
lib/tools/<name>.tsexportingtool({ description, inputSchema, execute }). - Register in
app/api/chat/route.tsunder thetoolsobject. - Add a UI handler in
components/app/tools-panel.tsx(status: running/done/error). - Update
types/chat.tsif the tool needs a newToolPartvariant.
- Add the id to the grouped list in
components/app/model-picker.tsx. - (If reasoning) wrap usage in
<Reasoning>and ensure the model id matches one Groq supports.
const { stop, regenerate } = useChat()— already exposed by@ai-sdk/react.
- No
any; no// @ts-ignore. - Every tool input → typed
ToolPartconsumed by<Tool>via discriminated union. - All async route handlers
awaitparams andawaitreq.json(). - Stream errors surface as
sonnertoasts with a retry button. - Mobile-first: sidebar becomes a
Sheetbelowmd:; prompt input respects safe-area inset. - Focus rings visible (AI Elements inherit Radix focus states — verify after theming).
- Database (everything is
localStoragefor MVP). - Auth.
- Server-side file/session storage.
next.config.tswebpack customizations (Turbopack-first).
- Re-read
node_modules/next/dist/docs/01-app/02-guides/upgrading/version-16.md. - Re-read the relevant AI SDK 7 doc for the API you're touching.
- If a shadcn or AI Element component doesn't render, check the registry install ran cleanly and
components.jsonaliases match.