# Veridian AGENTS.md **Nuxt 4 AI chat platform with Triplit database + better-auth** ## Architecture Overview ``` app/ # Nuxt 4 application (SSR) ├── components/ # Vue components (Sidenav, Message, Settings, etc.) ├── composables/ # Shared state (useChat, useAuth, useModels, etc.) ├── layouts/ # Auth and default layouts ├── middleware/ # Global auth middleware ├── pages/ # File-based routing (/, /auth/*, /agent/*) ├── plugins/ # Auth plugins (client/server), remark markdown ├── types/ # TypeScript interfaces └── utils/ # Crypto, model-mapping, search utilities server/ # Server-side API routes └── api/ ├── auth/ # Better-auth handler ├── chat/ # Generation, cancel endpoints └── provider/ # Model fetching from providers triplit/ # Database ├── schema.ts # Full schema with auth + app collections └── auth-schema.ts # Auth collections (users, sessions, accounts) lib/ # Shared utilities ├── auth.ts # Better-auth server config └── auth-client.ts # Better-auth client ``` ## Core Commands As an agentic agent, never run the development server yourself. Instead, once you have completed your task, end generation and inform me that you are done and request a review of your changes. You are allowed to use database commands. ### Database Commands - `bunx triplit schema push` - Push schema changes to database ## Code Style - **Indentation**: 4 spaces (no tabs) - **Semicolons**: Always required - **Equality**: Strict equality only (`===`, `!==`) - **Components**: PascalCase, never self-closing tags - **Functions**: camelCase for functions, PascalCase for constructors/classes - **Constants**: SCREAMING_SNAKE_CASE for constants, camelCase for const values - **Files**: kebab-case for files, PascalCase for Vue components - **State**: `useState('prefix:name', () => default)` for reactive state ## Imports Order Group imports in this order (alphabetical within groups): 1. Type imports (`types/`) 2. Dependency imports (npm packages) 3. Triplit imports (`#triplit/`, `@triplit/`) 4. Vue/Nuxt imports (`vue`, `#app`, `~~/`, `~/`, `@/`) 5. Local imports (`lib/`, `server/`) ```typescript // Example import order import type { Result, Ok, Err } from '~~/types/result'; import type { Entity } from '@triplit/client'; import type schema from '#triplit/schema'; import type { Foo } from 'vue'; import { ref, computed } from 'vue'; import { useFeature } from '~/composables/useFeature'; import { decrypt } from '~/utils/crypto'; import { authClient } from '~~/lib/auth-client'; ``` ## Error Handling Avoid using `throw` statements in your code. Use the `Result` type with `Ok()` and `Err()` factory functions: ```typescript import { type Result, Ok, Err } from '~~/types/result'; const myFunction(): Result { if (error) { return Err(ErrorType.SpecificError); } return Ok(data); } // Usage const result = myFunction(); if (result.ok === false) { console.error(result.error); return; } processData(result.data); ``` ## Composables Pattern All composables return reactive state + actions: ```typescript export const useFeature = () => { const state = useState('feature:state', () => defaultValue); const computedValue = computed(() => /* logic */); const actions = { async doSomething() { // implementation }, }; return { state, computedValue, ...actions }; }; ``` ## API Route Pattern ```typescript // File: server/api/endpoint.method.ts import { protectRoute } from '~/server/utils/protect'; export default defineEventHandler(async (event) => { await protectRoute(event); // implementation return { /* response */ }; }); ``` ## Database Query Pattern (Triplit) ```typescript // Client queries use useQuery() with auto-includes useQuery('collection', triplit, triplit.query('collection').Include('relation')); // Server-side import { httpClient } from '~~/server/lib/triplit'; await httpClient.fetchOne(httpClient.query('collection').Where('id', '=', providerId)); ``` ## Authentication Flow 1. **Login**: Client uses `authClient.signIn.email()` → derives encryption key from password 2. **Session**: Better-auth creates session token → stored in cookie 3. **Triplit**: Session token passed to `triplit.startSession(token)` for DB access 4. **API Keys**: Encrypted client-side with AES-GCM (key derived from password + userId) **Key files**: `lib/auth.ts`, `lib/auth-client.ts`, `app/plugins/auth.*.ts`, `app/middleware/auth.global.ts` ## Styling System **UnoCSS** with semantic color variables in `app/assets/css/base.css`: - `--color-accent`, `--color-neutral`, `--color-text`, `--color-muted` - `--color-highlight` (for borders/hover states) - Theme-aware: `:root.dark` / `:root.light` with `color-mix()` ## Environment Variables | Variable | Purpose | |----------|---------| | `TRIPLIT_SERVICE_TOKEN` | Admin DB access | | `NUXT_TRIPLIT_ANON_TOKEN` | Anonymous DB access | | `BETTER_AUTH_SECRET` | Auth encryption | | `NUXT_PUBLIC_TRIPLIT_URL` | DB server URL | ## Key Composables | Composable | Purpose | |------------|---------| | `useAuth()` | Session/user state, signIn/signOut | | `useChat(id)` | Send/regenerate messages, create topics | | `useModels()` | Providers + models with auto-subscription | | `useAgents()` | User's agents with topics | | `useSidebar()` | Sidebar state + resize | | `useTheme()` | Accent/neutral theme cookies | | `useSettings()` | Settings dialog state | ## Key Routes | Route | File | |-------|------| | `/` | `app/pages/index.vue` | | `/auth/login` | `app/pages/auth/login.vue` | | `/auth/register` | `app/pages/auth/register.vue` | | `/agent/:id` | `app/pages/agent/[id]/index.vue` | | `/agent/:id/topic/:topicId` | `app/pages/agent/[id]/topic/[topicId].vue` | | `/agent/:id/profile` | `app/pages/agent/[id]/profile.vue` | ## Server API Routes | Endpoint | File | |----------|------| | `POST /api/chat/generate` | `server/api/chat/generate.post.ts` | | `POST /api/chat/cancel/:id` | `server/api/chat/cancel/[generationId].post.ts` | | `POST /api/provider/:id/models` | `server/api/provider/[providerId]/models.post.ts` | | `* /api/auth/*` | `server/api/auth/[...all].ts` | ## Known Issues (from BUGS.md) 1. Triplit occasionally makes duplicate connections (race condition) 2. Sidebar hover animation occasionally glitches on agent routes 3. Theme switcher + sidebar interaction bug ## Development Notes - **Never run dev server as agent** - just complete tasks and signal done - Always run `bunx triplit schema push` after schema changes - Use `protectRoute()` in all API routes for auth - All UI state that persists → use cookies (`useCookie()`) - Real-time data → Triplit subscriptions via `useQuery()` - API keys → encrypted client-side before DB storage