6.9 KiB
6.9 KiB
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):
- Type imports (
types/) - Dependency imports (npm packages)
- Triplit imports (
#triplit/,@triplit/) - Vue/Nuxt imports (
vue,#app,~~/,~/,@/) - Local imports (
lib/,server/)
// 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<T, E> type with Ok() and Err() factory functions:
import { type Result, Ok, Err } from '~~/types/result';
const myFunction(): Result<ReturnType, ErrorType> {
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:
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
// 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)
// 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
- Login: Client uses
authClient.signIn.email()→ derives encryption key from password - Session: Better-auth creates session token → stored in cookie
- Triplit: Session token passed to
triplit.startSession(token)for DB access - 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.lightwithcolor-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)
- Triplit occasionally makes duplicate connections (race condition)
- Sidebar hover animation occasionally glitches on agent routes
- Theme switcher + sidebar interaction bug
Development Notes
- Never run dev server as agent - just complete tasks and signal done
- Always run
bunx triplit schema pushafter 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