# Tacy Stack - Agent Guidelines
This is a minimal full-stack web application starter built on the Bun fullstack pattern. It demonstrates passkey authentication, user-specific data storage, and reactive web components.
## Project Overview
**What is Tacy Stack?**
A TypeScript-based web stack using:
- **Bun** - Fast JavaScript runtime and bundler
- **TypeScript** - Strict typing with decorators enabled
- **Lit** - Lightweight (~8-10KB) web components library
- **Drizzle ORM** - Type-safe SQLite database access
- **Passkeys (WebAuthn)** - Passwordless authentication
**Demo Application Features:**
- User registration and login via passkeys (no passwords!)
- User-specific click counter stored in database
- Reactive UI with Lit web components
- Session management with cookies
## Commands
```bash
# Install dependencies
bun install
# Development server with hot reload (default: localhost:3000)
bun dev
# Database management
bun run db:generate # Generate migration files from schema changes
bun run db:push # Push schema directly to database (for development)
bun run db:studio # Open Drizzle Studio (visual database browser)
# Testing
bun test # Run all tests
```
**IMPORTANT:** Never run `bun dev` yourself - the user always has it running already with HMR enabled.
## Tech Stack Philosophy
### NO FRAMEWORKS
**Explicitly forbidden:**
- React, React DOM
- Vue
- Svelte
- Angular
- Any framework with virtual DOM or large runtime
**Why?** This project prioritizes:
- **Speed:** Minimal JavaScript, fast load times
- **Small bundles:** Keep total JS under 50KB
- **Native web platform:** Web standards over framework abstractions
- **Simplicity:** Vanilla HTML, CSS, and TypeScript
**Allowed:**
- Lit (~8-10KB) for reactive web components
- Native Web Components API
- Plain JavaScript/TypeScript
- Native DOM APIs
### When to use Lit
**Use Lit for:**
- Components with reactive properties (auto-updates on data changes)
- Complex components needing scoped styles
- Form controls with internal state
- Components with lifecycle needs
**Skip Lit for:**
- Static content (use plain HTML)
- Simple one-off interactions (use vanilla JS)
- Anything without reactive state
## Project Structure
Based on Bun's fullstack pattern where HTML files are imported as modules:
```
src/
index.ts # Server entry point, imports HTML routes
db/
db.ts # Drizzle database instance
schema.ts # Database schema (Drizzle tables)
lib/
auth.ts # User/session management
passkey.ts # WebAuthn passkey logic
counter.ts # Counter business logic
middleware.ts # Auth middleware
client-passkey.ts # Client-side passkey helpers
pages/
index.html # Route entry point (imports components)
components/
auth.ts # Login/register Lit component
counter.ts # Counter Lit component
styles/
main.css # Global styles
public/ # Static assets (if needed)
```
**File flow:**
1. `src/index.ts` imports HTML: `import indexHTML from "./pages/index.html"`
2. HTML imports components: ``
3. HTML links styles: ``
4. Components self-register via `@customElement` decorator
5. Bun bundles everything automatically during development
## Database (Drizzle ORM)
**Database file:** `tacy-stack.db` (SQLite, auto-created)
**Schema location:** `src/db/schema.ts`
**Tables:**
- `users` - User accounts (id, email, name, avatar, created_at)
- `sessions` - Active sessions (id, user_id, ip, user_agent, expires_at)
- `passkeys` - WebAuthn credentials (id, user_id, credential_id, public_key, counter, etc.)
- `counters` - User click counters (user_id, count, updated_at)
**Making schema changes:**
1. Edit `src/db/schema.ts` (modify tables using Drizzle syntax)
2. Run `bun run db:push` to apply changes to database
3. For production, use `bun run db:generate` to create migrations
4. Schema changes are type-safe and auto-complete in your IDE
**Querying patterns:**
```typescript
import { eq } from "drizzle-orm";
import db from "../db/db";
import { users } from "../db/schema";
// Select with where clause
const user = db.select().from(users).where(eq(users.id, userId)).get();
// Insert
db.insert(users).values({ email, name, avatar }).run();
// Update
db.update(users).set({ name: "New Name" }).where(eq(users.id, userId)).run();
// Delete
db.delete(users).where(eq(users.id, userId)).run();
```
**Important:** Use `.get()` for single results, `.all()` for arrays, `.run()` for mutations.
## TypeScript Configuration
**Strict mode enabled:**
- `strict: true`
- `noFallthroughCasesInSwitch: true`
- `noUncheckedIndexedAccess: true`
- `noImplicitOverride: true`
**Decorators:**
- `experimentalDecorators: true` (required for Lit's `@customElement`, `@property`, etc.)
- `useDefineForClassFields: false` (required for Lit decorators)
**Module system:**
- `moduleResolution: "bundler"`
- `module: "Preserve"`
- Can import `.ts` extensions directly
- JSX: `preserve` (NOT react-jsx - we don't use React!)
**Deliberately disabled:**
- `noUnusedLocals: false`
- `noUnusedParameters: false`
- `noPropertyAccessFromIndexSignature: false`
## Bun Usage
**Always use Bun instead of Node.js:**
- ✅ `bun ` NOT `node ` or `ts-node `
- ✅ `bun test` NOT `jest` or `vitest`
- ✅ `bun build ` NOT `webpack` or `esbuild`
- ✅ `bun install` NOT `npm install`
- ✅ `bun run