feat: rework starter kit for self-hosted deploy (Vite/React + Fastify/Drizzle/Postgres)

Replace Next.js/Supabase/Vercel stack with a self-hostable setup: Vite+React
frontend, Fastify+Drizzle+PostgreSQL backend with own JWT auth, deploy via
git pull + rsync + systemd (checkout/service separated, updates pulled from
Gitea). Adds a reference auth route (register/login/me) as the canonical
pattern for new routes. Updates all skills/rules/agents/docs to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BMdBJfD9TT6XUU2NqGG1PN
This commit is contained in:
patrickandClaude Sonnet 5 committed 2026-09-08 11:42:47 +02:00
1 parent a893084a88
commit 4a19335a1e
99 files changed
+8898 -9835

No files matched your search

+5 -6
View File
@@ -1,6 +1,6 @@
---
name: Backend Developer
description: Builds APIs, database schemas, and server-side logic with Supabase
description: Builds APIs, database schemas, and server-side logic with Fastify + Drizzle + PostgreSQL
model: opus
maxTurns: 50
tools:
@@ -13,16 +13,15 @@ tools:
- AskUserQuestion
---
You are a Backend Developer building APIs, database schemas, and server-side logic with Supabase.
You are a Backend Developer building APIs, database schemas, and server-side logic with Fastify, Drizzle ORM, and PostgreSQL.
Key rules:
- ALWAYS enable Row Level Security on every new table
- Create RLS policies for SELECT, INSERT, UPDATE, DELETE
- ALWAYS check authentication (`request.jwtVerify()`) and authorization (ownership) explicitly in route handlers — there is no RLS backstop
- Validate all inputs with Zod schemas on POST/PUT endpoints
- Add database indexes on frequently queried columns
- Use Supabase joins instead of N+1 query loops
- Use Drizzle joins/relational queries instead of N+1 query loops
- Never hardcode secrets in source code
- Always check authentication before processing requests
- Generate migrations with `npm run db:generate`, review the SQL before applying with `npm run db:migrate`
Read `.claude/rules/backend.md` for detailed backend rules.
Read `.claude/rules/security.md` for security requirements.
+4 -4
View File
@@ -1,6 +1,6 @@
---
name: Frontend Developer
description: Builds UI components with React, Next.js, Tailwind CSS, and shadcn/ui
description: Builds UI components with React, Vite, Tailwind CSS, and shadcn/ui
model: opus
maxTurns: 50
tools:
@@ -13,11 +13,11 @@ tools:
- AskUserQuestion
---
You are a Frontend Developer building UI with React, Next.js, Tailwind CSS, and shadcn/ui.
You are a Frontend Developer building UI with React, Vite, Tailwind CSS, and shadcn/ui.
Key rules:
- ALWAYS check shadcn/ui components before creating custom ones: `ls src/components/ui/`
- If a shadcn component is missing, install it: `npx shadcn@latest add <name> --yes`
- ALWAYS check shadcn/ui components before creating custom ones: `ls frontend/src/components/ui/`
- If a shadcn component is missing, install it: `npx shadcn@latest add <name> --yes` (run inside `frontend/`)
- Use Tailwind CSS exclusively for styling (no inline styles, no CSS modules)
- Follow the component architecture from the feature spec's Tech Design section
- Implement loading, error, and empty states for all components
+24 -16
View File
@@ -1,32 +1,40 @@
---
paths:
- "src/app/api/**"
- "src/lib/supabase*"
- "supabase/**"
- "backend/src/routes/**"
- "backend/src/db/**"
---
# Backend Development Rules
## Database (Supabase)
- ALWAYS enable Row Level Security on every table
- Create RLS policies for SELECT, INSERT, UPDATE, DELETE
## Database (PostgreSQL + Drizzle)
- Every table gets explicit foreign keys with `onDelete` behavior defined
- Add indexes on columns used in WHERE, ORDER BY, and JOIN clauses
- Use foreign keys with ON DELETE CASCADE where appropriate
- Never skip RLS - security first
- Auth/ownership checks happen in route handlers, not in the database — there is no RLS equivalent
- Migrations generated via `npm run db:generate`, applied via `npm run db:migrate` — never hand-edit generated SQL after it has been applied
## API Routes
## API Routes (Fastify)
- Validate all inputs using Zod schemas before processing
- Always check authentication: verify user session exists
- Always check authentication: verify JWT via `request.jwtVerify()` before touching protected data
- Return meaningful error messages with appropriate HTTP status codes
- Use `.limit()` on all list queries
- Paginate all list queries (`limit`/`offset`, sane default limit)
## Query Patterns
- Use Supabase joins instead of N+1 query loops
- Use `unstable_cache` from Next.js for rarely-changing data
- Always handle errors from Supabase responses
- Use Drizzle relational queries / joins instead of N+1 query loops
- Wrap multi-statement writes in a transaction (`db.transaction(...)`)
- Always handle and log errors from `pg` / Drizzle calls
## Security
- Never hardcode secrets in source code
- Use environment variables for all credentials
- Use environment variables for all credentials (see `.env.example`)
- Validate and sanitize all user input
- Use parameterized queries (Supabase handles this)
- Drizzle uses parameterized queries by default — never build SQL via string concatenation
## Reference Pattern: Auth Routes
`backend/src/routes/auth.ts` is the canonical example new routes should follow:
- Zod schema (`credentialsSchema`) parsed with `safeParse`, 400 on failure with `error.issues[0].message`
- Passwords hashed with `bcryptjs` (`bcrypt.hash(password, 12)`), never stored or logged in plaintext
- Duplicate/invalid-credential checks return generic messages ("Invalid email or password") — never reveal whether the email exists on login
- Protected routes use `{ preHandler: app.authenticate }` (decorator defined in `server.ts`, verifies the JWT via `request.jwtVerify()`)
- Auth endpoints get a tighter route-level rate limit (`config: { rateLimit: { max: 5, timeWindow: "1 minute" } }`) on top of the global limit
- `app.jwt.sign({ sub: user.id, email: user.email })` — the JWT payload shape is declared once in `backend/src/types.d.ts` (`FastifyJWT` module augmentation), so `request.user` is typed everywhere without casts
- New protected/authenticated routes should follow this same shape: typed payload via `types.d.ts`, `app.authenticate` as `preHandler`, never `request.user as any`
+9 -9
View File
@@ -1,17 +1,16 @@
---
paths:
- "src/components/**"
- "src/app/**/page.tsx"
- "src/app/**/layout.tsx"
- "src/hooks/**"
- "frontend/src/components/**"
- "frontend/src/**/*.tsx"
- "frontend/src/hooks/**"
---
# Frontend Development Rules
## shadcn/ui First (MANDATORY)
- Before creating ANY UI component, check if shadcn/ui has it: `ls src/components/ui/`
- Before creating ANY UI component, check if shadcn/ui has it: `ls frontend/src/components/ui/`
- NEVER create custom implementations of: Button, Input, Select, Checkbox, Switch, Dialog, Modal, Alert, Toast, Table, Tabs, Card, Badge, Dropdown, Popover, Tooltip, Navigation, Sidebar, Breadcrumb
- If a shadcn component is missing, install it: `npx shadcn@latest add <name> --yes`
- If a shadcn component is missing, install it: `npx shadcn@latest add <name> --yes` (run inside `frontend/`)
- Custom components are ONLY for business-specific compositions that internally use shadcn primitives
## Import Pattern
@@ -28,7 +27,8 @@ import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"
- Keep components small and focused
- Use TypeScript interfaces for all props
## Auth Best Practices (Supabase)
- Use `window.location.href` for post-login redirect (not `router.push`)
- Always verify `data.session` exists before redirecting
## Auth Best Practices (own JWT backend)
- Store the JWT in memory/state, not `localStorage`, if XSS exposure is a concern for the project; document the choice in the feature spec
- After login, redirect via `react-router`'s `navigate()` once the auth state is confirmed set
- Always reset loading state in all code paths (success, error, finally)
- Attach the JWT as `Authorization: Bearer <token>` on every API call to `/api/**`
+2 -2
View File
@@ -25,8 +25,8 @@ Before starting ANY work, check if the project has been initialized:
- Commit format: `type(PROJ-X): description`
- Types: feat, fix, refactor, test, docs, deploy, chore
- Check existing features before creating new ones: `ls features/ | grep PROJ-`
- Check existing components before building: `git ls-files src/components/`
- Check existing APIs before building: `git ls-files src/app/api/`
- Check existing components before building: `git ls-files frontend/src/components/`
- Check existing APIs before building: `git ls-files backend/src/routes/`
## Human-in-the-Loop
- Always ask for user approval before finalizing deliverables
+10 -11
View File
@@ -1,18 +1,16 @@
---
paths:
- "src/app/api/**"
- "backend/src/**"
- ".env*"
- "supabase/**"
- "next.config.*"
- "backend/drizzle.config.ts"
---
# Security Rules
## Secrets Management
- NEVER commit secrets, API keys, or credentials to git
- Use `.env.local` for local development (already in .gitignore)
- Use `NEXT_PUBLIC_` prefix ONLY for values safe to expose in browser
- Document all required env vars in `.env.local.example` with dummy values
- Use `.env` for local/server config (already in `.gitignore`), document required vars in `.env.example` with dummy values
- Frontend env vars must be explicitly prefixed `VITE_` and contain nothing secret — anything with that prefix ships to the browser
## Input Validation
- Validate ALL user input on the server side with Zod
@@ -20,17 +18,18 @@ paths:
- Sanitize data before database insertion
## Authentication
- Always verify authentication before processing API requests
- Use Supabase RLS as a second line of defense
- Implement rate limiting on authentication endpoints
- Always verify the JWT (`request.jwtVerify()`) before processing protected API requests
- Authorization/ownership checks live in route handlers — there is no database-level RLS backstop, so don't skip them
- Implement rate limiting on authentication endpoints (`@fastify/rate-limit`, tighter limits on `/api/auth/**`)
## Security Headers
- X-Frame-Options: DENY
- X-Content-Type-Options: nosniff
- Referrer-Policy: origin-when-cross-origin
- Strict-Transport-Security with includeSubDomains
- Set via `@fastify/helmet` on the backend, or nginx `add_header` directives at the reverse proxy
## Code Review Triggers
- Any changes to RLS policies require explicit user approval
- Any changes to authentication flow require explicit user approval
- Any new environment variables must be documented in .env.local.example
- Any changes to database migrations require explicit user approval before running `db:migrate` against a server
- Any new environment variables must be documented in `.env.example`
+2 -2
View File
@@ -21,8 +21,8 @@ NEVER write code or show implementation details:
## Before Starting
1. Read `features/INDEX.md` to understand project context
2. Check existing components: `git ls-files src/components/`
3. Check existing APIs: `git ls-files src/app/api/`
2. Check existing components: `git ls-files frontend/src/components/`
3. Check existing APIs: `git ls-files backend/src/routes/`
4. Read the feature spec the user references
## Workflow
+37 -31
View File
@@ -1,6 +1,6 @@
---
name: backend
description: Build APIs, database schemas, and server-side logic with Supabase. Use after frontend is built.
description: Build APIs, database schemas, and server-side logic with Fastify + Drizzle + PostgreSQL. Use after frontend is built.
argument-hint: [feature-spec-path]
user-invocable: true
context: fork
@@ -11,20 +11,20 @@ model: opus
# Backend Developer
## Role
You are an experienced Backend Developer. You read feature specs + tech design and implement APIs, database schemas, and server-side logic using Supabase and Next.js.
You are an experienced Backend Developer. You read feature specs + tech design and implement APIs, database schemas, and server-side logic using Fastify, Drizzle ORM, and PostgreSQL.
## Before Starting
1. Read `features/INDEX.md` for project context
2. Read the feature spec referenced by the user (including Tech Design section)
3. Check existing APIs: `git ls-files src/app/api/`
4. Check existing database patterns: `git log --oneline -S "CREATE TABLE" -10`
5. Check existing lib files: `ls src/lib/`
3. Check existing routes: `git ls-files backend/src/routes/`
4. Check existing schema: `cat backend/src/db/schema.ts`
5. Check existing migrations: `ls backend/drizzle/`
## Workflow
### 1. Read Feature Spec + Design
- Understand the data model from Solution Architect
- Identify tables, relationships, and RLS requirements
- Identify tables, relationships, and authorization requirements
- Identify API endpoints needed
### 2. Ask Technical Questions
@@ -35,21 +35,21 @@ Use `AskUserQuestion` for:
- What specific input validations are required?
### 3. Create Database Schema
- Write SQL for new tables in Supabase SQL Editor
- Enable Row Level Security on EVERY table
- Create RLS policies for all CRUD operations
- Add/extend tables in `backend/src/db/schema.ts` (Drizzle)
- Run `npm run db:generate` to produce the migration, review the generated SQL
- Add indexes on performance-critical columns (WHERE, ORDER BY, JOIN)
- Use foreign keys with ON DELETE CASCADE where appropriate
- Use foreign keys with `onDelete: "cascade"` where appropriate
- Run `npm run db:migrate` against the dev database
### 4. Create API Routes
- Create route handlers in `/src/app/api/`
- Create route handlers in `backend/src/routes/`, register them in `server.ts`
- Implement CRUD operations
- Add Zod input validation on all POST/PUT endpoints
- Add proper error handling with meaningful messages
- Always check authentication (verify user session)
- Always check authentication (`request.jwtVerify()`) and authorization (ownership) explicitly — there is no RLS backstop
### 5. Connect Frontend
- Update frontend components to use real API endpoints
- Update frontend components to use real API endpoints (`fetch`/`axios` against `/api/**`, proxied by Vite in dev)
- Replace any mock data or localStorage with API calls
- Handle loading and error states
@@ -62,30 +62,36 @@ If your context was compacted mid-task:
1. Re-read the feature spec you're implementing
2. Re-read `features/INDEX.md` for current status
3. Run `git diff` to see what you've already changed
4. Run `git ls-files src/app/api/` to see current API state
4. Run `git ls-files backend/src/routes/` to see current API state
5. Continue from where you left off - don't restart or duplicate work
## Output Format Examples
### Database Migration
```sql
CREATE TABLE tasks (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
title TEXT NOT NULL,
status TEXT CHECK (status IN ('todo', 'in_progress', 'done')) DEFAULT 'todo',
created_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users see own tasks" ON tasks
FOR SELECT USING (auth.uid() = user_id);
CREATE INDEX idx_tasks_user_id ON tasks(user_id);
CREATE INDEX idx_tasks_status ON tasks(status);
### Drizzle Schema
```ts
export const tasks = pgTable("tasks", {
id: uuid("id").primaryKey().defaultRandom(),
userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
title: text("title").notNull(),
status: text("status", { enum: ["todo", "in_progress", "done"] }).notNull().default("todo"),
createdAt: timestamp("created_at").notNull().defaultNow(),
}, (table) => ({
userIdx: index("idx_tasks_user_id").on(table.userId),
statusIdx: index("idx_tasks_status").on(table.status),
}));
```
### Route Handler
```ts
app.get("/api/tasks", { preHandler: app.authenticate }, async (request) => {
const userId = request.user.sub;
return db.select().from(tasks).where(eq(tasks.userId, userId)).limit(50);
});
```
## Reference Implementation
`backend/src/routes/auth.ts` + `backend/src/types.d.ts` show the canonical route pattern (Zod validation, `app.authenticate` preHandler, typed JWT payload). Follow this shape for new routes rather than inventing a new one. See [.claude/rules/backend.md](../../rules/backend.md#reference-pattern-auth-routes) for the annotated breakdown.
## Production References
- See [database-optimization.md](../../docs/production/database-optimization.md) for query optimization
- See [rate-limiting.md](../../docs/production/rate-limiting.md) for rate limiting setup
+14 -14
View File
@@ -1,33 +1,33 @@
# Backend Implementation Checklist
## Core Checklist
- [ ] Checked existing tables/APIs via git before creating new ones
- [ ] Database tables created in Supabase
- [ ] Row Level Security enabled on ALL new tables
- [ ] RLS policies created for SELECT, INSERT, UPDATE, DELETE
- [ ] Checked existing tables/routes via git before creating new ones
- [ ] Database tables added to Drizzle schema (`backend/src/db/schema.ts`)
- [ ] Migration generated (`npm run db:generate`) and applied (`npm run db:migrate`)
- [ ] Indexes created on performance-critical columns
- [ ] Foreign keys set with appropriate ON DELETE behavior
- [ ] All planned API endpoints implemented in `/src/app/api/`
- [ ] Authentication verified (no access without valid session)
- [ ] Foreign keys set with appropriate `onDelete` behavior
- [ ] All planned endpoints implemented in `backend/src/routes/` — follow the pattern in `backend/src/routes/auth.ts` (Zod `safeParse`, typed JWT payload via `types.d.ts`, `app.authenticate` preHandler)
- [ ] Authentication verified (no access without a valid JWT)
- [ ] Ownership/authorization checked explicitly in the route handler
- [ ] Input validation with Zod on all POST/PUT requests
- [ ] Meaningful error messages with correct HTTP status codes
- [ ] No TypeScript errors in API routes
- [ ] No TypeScript errors in routes
- [ ] All endpoints tested manually
- [ ] No hardcoded secrets in source code
- [ ] Frontend connected to real API endpoints
- [ ] User has reviewed and approved
## Verification (run before marking complete)
- [ ] `npm run build` passes without errors
- [ ] All acceptance criteria from feature spec addressed in API
- [ ] All API endpoints return correct status codes (test with curl or browser)
- [ ] `npm run build` passes without errors (backend)
- [ ] All acceptance criteria from feature spec addressed in the API
- [ ] All endpoints return correct status codes (test with curl or Thunder Client)
- [ ] `features/INDEX.md` status updated to "In Progress"
- [ ] Code committed to git
## Performance Checklist
- [ ] All frequently filtered columns have indexes
- [ ] No N+1 queries (use Supabase joins instead of loops)
- [ ] All list queries use `.limit()`
- [ ] No N+1 queries (use Drizzle joins/relational queries instead of loops)
- [ ] All list endpoints paginate (limit/offset)
- [ ] Zod validation on all write endpoints
- [ ] Slow queries cached where appropriate (optional for MVP)
- [ ] Rate limiting on public-facing APIs (optional for MVP)
- [ ] Rate limiting on public-facing routes (optional for MVP)
+57 -42
View File
@@ -1,7 +1,7 @@
---
name: deploy
description: Deploy to Vercel with production-ready checks, error tracking, and security headers setup.
argument-hint: [feature-spec-path or "to Vercel"]
description: Deploy to a self-managed Linux server via git pull + rsync + systemd, with production-ready checks.
argument-hint: [feature-spec-path or "to production"]
user-invocable: true
allowed-tools: Read, Write, Edit, Glob, Grep, Bash, AskUserQuestion
model: sonnet
@@ -10,7 +10,16 @@ model: sonnet
# DevOps Engineer
## Role
You are an experienced DevOps Engineer handling deployment, environment setup, and production readiness.
You are an experienced DevOps Engineer handling deployment, environment setup, and production readiness on a self-hosted server (no PaaS/Vercel).
## Server Layout Convention
Checkout and running service are kept separate, so a `git pull` never silently swaps out a running process's code:
```
/opt/<project>/repo git checkout only, pulled from Gitea
/opt/<project>/backend running backend service, own .env, own node_modules
/var/www/<project> built frontend static files, served by nginx
```
## Before Starting
1. Read `features/INDEX.md` to know what is being deployed
@@ -21,83 +30,90 @@ You are an experienced DevOps Engineer handling deployment, environment setup, a
## Workflow
### 1. Pre-Deployment Checks
- [ ] `npm run build` succeeds locally
- [ ] `npm run build` succeeds locally (frontend and backend)
- [ ] `npm run lint` passes
- [ ] QA Engineer has approved the feature (check feature spec)
- [ ] No Critical/High bugs in test report
- [ ] All environment variables documented in `.env.local.example`
- [ ] All environment variables documented in `.env.example`
- [ ] No secrets committed to git
- [ ] All database migrations applied in Supabase (if applicable)
- [ ] All code committed and pushed to remote
- [ ] All Drizzle migrations generated and committed
- [ ] All code committed and pushed to Gitea
### 2. Vercel Setup (first deployment only)
Guide the user through:
- [ ] Create Vercel project: `npx vercel` or via vercel.com
- [ ] Connect GitHub repository for auto-deploy on push
- [ ] Add all environment variables from `.env.local.example` in Vercel Dashboard
- [ ] Build settings: Framework Preset = Next.js (auto-detected)
- [ ] Configure domain (or use default `*.vercel.app`)
### 2. Server Setup (first deployment only)
Guide the user through, on the target server:
- [ ] `git clone <gitea-url> /opt/<project>/repo`
- [ ] Create `/opt/<project>/backend`, copy backend files in, create `.env` from `.env.example`
- [ ] Create PostgreSQL database + role for the app
- [ ] Create a systemd unit for the backend (`/etc/systemd/system/<project>-backend.service`), `ExecStart=node dist/server.js`, `WorkingDirectory=/opt/<project>/backend`, `EnvironmentFile=/opt/<project>/backend/.env`
- [ ] Configure nginx: reverse proxy `/api/` to the backend port, serve `/var/www/<project>` as static root for everything else
- [ ] `systemctl enable --now <project>-backend`
### 3. Deploy
- Push to main branch → Vercel auto-deploys
- Or manual: `npx vercel --prod`
- Monitor build in Vercel Dashboard
### 3. Deploy (update)
Run from a machine with SSH access to the server:
```bash
ssh <user>@<server> "cd /opt/<project>/repo && git pull --ff-only \
&& rsync -a --exclude='.env' --exclude='node_modules' /opt/<project>/repo/backend/ /opt/<project>/backend/ \
&& cd /opt/<project>/backend && npm ci --omit=dev && npm run db:migrate \
&& systemctl restart <project>-backend \
&& cd /opt/<project>/repo/frontend && npm ci && npm run build \
&& rsync -a --delete dist/ /var/www/<project>/"
```
- Frontend-only change → skip the backend rsync/migrate/restart block, just rebuild and rsync `dist/`
- Updates are pulled from Gitea (`git pull --ff-only`) — never edit code directly on the server
### 4. Post-Deployment Verification
- [ ] Production URL loads correctly
- [ ] Deployed feature works as expected
- [ ] Database connections work (if applicable)
- [ ] Database connections work (check backend logs: `journalctl -u <project>-backend -f`)
- [ ] Authentication flows work (if applicable)
- [ ] No errors in browser console
- [ ] No errors in Vercel function logs
- [ ] No errors in `journalctl -u <project>-backend`
### 5. Production-Ready Essentials
For first deployment, guide the user through these setup guides:
**Error Tracking (5 min):** See [error-tracking.md](../../docs/production/error-tracking.md)
**Security Headers (copy-paste):** See [security-headers.md](../../docs/production/security-headers.md)
**Error Tracking:** See [error-tracking.md](../../docs/production/error-tracking.md)
**Security Headers:** See [security-headers.md](../../docs/production/security-headers.md)
**Performance Check:** See [performance.md](../../docs/production/performance.md)
**Database Optimization:** See [database-optimization.md](../../docs/production/database-optimization.md)
**Rate Limiting (optional):** See [rate-limiting.md](../../docs/production/rate-limiting.md)
**Rate Limiting:** See [rate-limiting.md](../../docs/production/rate-limiting.md)
### 6. Post-Deployment Bookkeeping
- Update feature spec: Add deployment section with production URL and date
- Update feature spec: Add deployment section with date
- Update `features/INDEX.md`: Set status to **Deployed**
- Create git tag: `git tag -a v1.X.0-PROJ-X -m "Deploy PROJ-X: [Feature Name]"`
- Push tag: `git push origin v1.X.0-PROJ-X`
## Common Issues
### Build fails on Vercel but works locally
- Check Node.js version (Vercel may use different version)
- Ensure all dependencies are in package.json (not just devDependencies)
- Review Vercel build logs for specific error
### Build works locally but fails on server
- Check Node.js version matches between local and server (`node -v`)
- Ensure all dependencies are in `dependencies`, not just `devDependencies`, if `npm ci --omit=dev` is used for the backend
### Environment variables not available
- Verify vars are set in Vercel Dashboard (Settings → Environment Variables)
- Client-side vars need `NEXT_PUBLIC_` prefix
- Redeploy after adding new env vars (they don't apply retroactively)
- Verify `.env` exists in `/opt/<project>/backend` (it is excluded from rsync on purpose, so it survives deploys)
- Frontend vars need the `VITE_` prefix and are baked in at build time — rebuild after changing them
### Database connection errors
- Verify Supabase URL and anon key in Vercel env vars
- Check RLS policies allow the operations being attempted
- Verify Supabase project is not paused (free tier pauses after inactivity)
- Verify `DATABASE_URL` in the server's `.env`
- Check the app's database role has the needed grants
- `journalctl -u <project>-backend` for the actual driver error
## Rollback Instructions
If production is broken:
1. **Immediate:** Vercel Dashboard → Deployments → Click "..." on previous working deployment → "Promote to Production"
2. **Fix locally:** Debug the issue, `npm run build`, commit, push
3. Vercel auto-deploys the fix
1. **Immediate:** on the server, `cd /opt/<project>/repo && git checkout <last-good-tag>`, redo step 3's rsync/build for that commit
2. **Fix locally:** debug the issue, `npm run build`, commit, push
3. Redeploy via step 3
## Full Deployment Checklist
- [ ] Pre-deployment checks all pass
- [ ] Vercel build successful
- [ ] Build successful (frontend + backend)
- [ ] Production URL loads and works
- [ ] Feature tested in production environment
- [ ] No console errors, no Vercel log errors
- [ ] Error tracking setup (Sentry or alternative)
- [ ] Security headers configured in next.config
- [ ] No console errors, no backend log errors
- [ ] Error tracking setup
- [ ] Security headers configured (nginx or `@fastify/helmet`)
- [ ] Lighthouse score checked (target > 90)
- [ ] Feature spec updated with deployment info
- [ ] `features/INDEX.md` updated to Deployed
@@ -108,6 +124,5 @@ If production is broken:
```
deploy(PROJ-X): Deploy [feature name] to production
- Production URL: https://your-app.vercel.app
- Deployed: YYYY-MM-DD
```
+14 -14
View File
@@ -1,6 +1,6 @@
---
name: frontend
description: Build UI components with React, Next.js, Tailwind CSS, and shadcn/ui. Use after architecture is designed.
description: Build UI components with React, Vite, Tailwind CSS, and shadcn/ui. Use after architecture is designed.
argument-hint: [feature-spec-path]
user-invocable: true
context: fork
@@ -11,15 +11,15 @@ model: opus
# Frontend Developer
## Role
You are an experienced Frontend Developer. You read feature specs + tech design and implement the UI using React, Next.js, Tailwind CSS, and shadcn/ui.
You are an experienced Frontend Developer. You read feature specs + tech design and implement the UI using React, Vite, Tailwind CSS, and shadcn/ui.
## Before Starting
1. Read `features/INDEX.md` for project context
2. Read the feature spec referenced by the user (including Tech Design section)
3. Check installed shadcn/ui components: `ls src/components/ui/`
4. Check existing custom components: `ls src/components/*.tsx 2>/dev/null`
5. Check existing hooks: `ls src/hooks/ 2>/dev/null`
6. Check existing pages: `ls src/app/`
3. Check installed shadcn/ui components: `ls frontend/src/components/ui/`
4. Check existing custom components: `ls frontend/src/components/*.tsx 2>/dev/null`
5. Check existing hooks: `ls frontend/src/hooks/ 2>/dev/null`
6. Check existing pages/routes: `ls frontend/src/`
## Workflow
@@ -43,19 +43,19 @@ If no design specs exist, ask the user:
- Accessibility requirements beyond defaults (WCAG 2.1 AA)?
### 4. Implement Components
- Create components in `/src/components/`
- ALWAYS use shadcn/ui for standard UI elements (check `src/components/ui/` first!)
- If a shadcn component is missing, install it: `npx shadcn@latest add <name> --yes`
- Create components in `frontend/src/components/`
- ALWAYS use shadcn/ui for standard UI elements (check `frontend/src/components/ui/` first!)
- If a shadcn component is missing, install it: `npx shadcn@latest add <name> --yes` (run inside `frontend/`)
- Only create custom components as compositions of shadcn primitives
- Use Tailwind CSS for all styling
### 5. Integrate into Pages
- Add components to pages in `/src/app/`
- Set up routing if needed
- Connect to backend APIs or localStorage as specified in tech design
- Add components to routes/pages in `frontend/src/`
- Set up routing (`react-router-dom`) if needed
- Connect to backend APIs (`/api/**`, proxied by Vite in dev) or localStorage as specified in tech design
### 6. User Review
- Tell the user to test in browser (localhost:3000)
- Tell the user to test in browser (localhost:5173)
- Ask: "Does the UI look right? Any changes needed?"
- Iterate based on feedback
@@ -64,7 +64,7 @@ If your context was compacted mid-task:
1. Re-read the feature spec you're implementing
2. Re-read `features/INDEX.md` for current status
3. Run `git diff` to see what you've already changed
4. Run `git ls-files src/components/ | head -20` to see current component state
4. Run `git ls-files frontend/src/components/ | head -20` to see current component state
5. Continue from where you left off - don't restart or duplicate work
## After Completion: Backend & QA Handoff
+3 -3
View File
@@ -31,9 +31,9 @@ Read these files to understand where the project stands:
- Deployment section exists (added by /deploy)
4. **Check Codebase:** Quick scan of what's been built
- `ls src/components/*.tsx 2>/dev/null` → Custom components
- `ls src/app/api/ 2>/dev/null` → API routes
- `ls src/components/ui/` → Installed shadcn components
- `ls frontend/src/components/*.tsx 2>/dev/null` → Custom components
- `ls backend/src/routes/ 2>/dev/null` → API routes
- `ls frontend/src/components/ui/` → Installed shadcn components
### Step 2: Determine Next Action
+1 -1
View File
@@ -8,7 +8,7 @@ Add this section to the END of the feature spec `/features/PROJ-X.md`:
## QA Test Results
**Tested:** YYYY-MM-DD
**App URL:** http://localhost:3000
**App URL:** http://localhost:5173
**Tester:** QA Engineer (AI)
### Acceptance Criteria Status
+2 -2
View File
@@ -100,8 +100,8 @@ feat: Initialize project - PRD and X feature specifications
Use this mode when the project already has a PRD and the user wants to add a new feature.
### Phase 1: Understand the Feature
1. Check existing components: `git ls-files src/components/`
2. Check existing APIs: `git ls-files src/app/api/`
1. Check existing components: `git ls-files frontend/src/components/`
2. Check existing APIs: `git ls-files backend/src/routes/`
3. Ensure you are not duplicating an existing feature
Ask the user interactive questions to clarify:
+5 -2
View File
@@ -11,12 +11,15 @@
- As a [user type], I want to [action] so that [goal]
## Acceptance Criteria
_Each one must be testable by a QA engineer without asking a follow-up question._
- [ ] Criterion 1
- [ ] Criterion 2
## Edge Cases
- What happens when...?
- How do we handle...?
_Concrete failure/boundary scenarios, not rhetorical questions — state the expected behavior._
- Duplicate input: [expected behavior]
- Network/API failure mid-action: [expected behavior]
- Concurrent edits by two users: [expected behavior]
## Technical Requirements (optional)
- Performance: < 200ms response time
-9
View File
@@ -1,9 +0,0 @@
# Supabase (Optional - remove if not using backend)
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url_here
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key_here
# Add more environment variables as needed
# STRIPE_SECRET_KEY=sk_...
# SMTP_HOST=smtp.sendgrid.net
# SMTP_USER=your_smtp_user
# SMTP_PASS=your_smtp_password
+8 -21
View File
@@ -1,20 +1,14 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
node_modules/
# testing
/coverage
# build output
frontend/dist/
backend/dist/
# next.js
/.next/
/out/
# production
/build
# env files
.env
.env.local
!*.env.example
# misc
.DS_Store
@@ -25,15 +19,8 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# claude code personal settings
.claude/settings.local.json
# typescript
*.tsbuildinfo
next-env.d.ts
+33 -18
View File
@@ -1,30 +1,37 @@
# AI Coding Starter Kit
> A Next.js template with an AI-powered development workflow using specialized skills for Requirements, Architecture, Frontend, Backend, QA, and Deployment.
> A self-hosted Vite/React + Fastify template with an AI-powered development workflow using specialized skills for Requirements, Architecture, Frontend, Backend, QA, and Deployment.
## Tech Stack
- **Framework:** Next.js 16 (App Router), TypeScript
- **Frontend:** Vite + React 19 + TypeScript
- **Styling:** Tailwind CSS + shadcn/ui (copy-paste components)
- **Backend:** Supabase (PostgreSQL + Auth + Storage) - optional
- **Deployment:** Vercel
- **Backend:** Fastify + Drizzle ORM + PostgreSQL, own JWT auth
- **Deployment:** self-hosted Linux server — git pull + rsync + systemd, updates pulled from Gitea
- **Validation:** Zod + react-hook-form
- **State:** React useState / Context API
## Project Structure
```
src/
app/ Pages (Next.js App Router)
components/
ui/ shadcn/ui components (NEVER recreate these)
hooks/ Custom React hooks
lib/ Utilities (supabase.ts, utils.ts)
features/ Feature specifications (PROJ-X-name.md)
INDEX.md Feature status overview
frontend/
src/
main.tsx, App.tsx Entry points
components/
ui/ shadcn/ui components (NEVER recreate these)
hooks/ Custom React hooks
lib/ Utilities (utils.ts)
backend/
src/
server.ts Fastify app entry
routes/ Route handlers
db/ Drizzle schema + connection
drizzle/ Generated migrations
features/ Feature specifications (PROJ-X-name.md)
INDEX.md Feature status overview
docs/
PRD.md Product Requirements Document
production/ Production guides (Sentry, security, performance)
PRD.md Product Requirements Document
production/ Production guides (error tracking, security, performance)
```
## Development Workflow
@@ -32,9 +39,9 @@ docs/
1. `/requirements` - Create feature spec from idea
2. `/architecture` - Design tech architecture (PM-friendly, no code)
3. `/frontend` - Build UI components (shadcn/ui first!)
4. `/backend` - Build APIs, database, RLS policies
4. `/backend` - Build APIs, database schema, auth checks
5. `/qa` - Test against acceptance criteria + security audit
6. `/deploy` - Deploy to Vercel + production-ready checks
6. `/deploy` - Deploy to the server + production-ready checks
## Feature Tracking
@@ -47,14 +54,22 @@ All features tracked in `features/INDEX.md`. Every skill reads it at start and u
- **Single Responsibility:** One feature per spec file
- **shadcn/ui first:** NEVER create custom versions of installed shadcn components
- **Human-in-the-loop:** All workflows have user approval checkpoints
- **No RLS backstop:** authorization/ownership checks are explicit in Fastify route handlers, not enforced by the database
## Build & Test Commands
```bash
npm run dev # Development server (localhost:3000)
# frontend/
npm run dev # Vite dev server (localhost:5173)
npm run build # Production build
npm run lint # ESLint
npm run start # Production server
# backend/
npm run dev # tsx watch (localhost:4000)
npm run build # tsc build
npm run start # node dist/server.js
npm run db:generate # generate Drizzle migration
npm run db:migrate # apply migrations
```
## Product Context
+53 -53
View File
@@ -1,6 +1,6 @@
# AI Coding Starter Kit
> Build production-ready web apps faster with AI-powered Skills handling Requirements, Architecture, Development, QA, and Deployment.
> Build production-ready web apps faster with AI-powered Skills handling Requirements, Architecture, Development, QA, and Deployment. Self-hosted, no vendor lock-in — Vite/React + Fastify/PostgreSQL, deployed via git pull + rsync + systemd.
This template uses [Claude Code](https://docs.anthropic.com/en/docs/claude-code) with modern Skills, Rules, and Sub-Agents to provide a complete AI-powered development workflow.
@@ -9,30 +9,28 @@ This template uses [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
### 1. Clone & Install
```bash
git clone https://github.com/YOUR_USERNAME/ai-coding-starter-kit.git my-project
git clone <your-gitea-url>/ai-coding-starter-kit.git my-project
cd my-project
npm install
cd frontend && npm install && cd ..
cd backend && npm install && cd ..
```
### 2. (Optional) Supabase Setup
### 2. Set Up PostgreSQL
If you need a backend:
1. Create a local PostgreSQL database and role for the app
2. Copy `backend/.env.example` to `backend/.env`
3. Set `DATABASE_URL` and `JWT_SECRET`
4. Run `cd backend && npm run db:generate && npm run db:migrate`
1. Create Supabase Project: [supabase.com](https://supabase.com)
2. Copy `.env.local.example` to `.env.local`
3. Add your Supabase credentials
4. Uncomment the Supabase client in `src/lib/supabase.ts`
Skip this step if you're building frontend-only (landing pages, portfolios, etc.)
Skip the backend entirely if you're building frontend-only (landing pages, portfolios, etc.) — just run the frontend.
### 3. Start Development
```bash
npm run dev
cd backend && npm run dev # localhost:4000
cd frontend && npm run dev # localhost:5173, proxies /api to the backend
```
Open [http://localhost:3000](http://localhost:3000) in your browser.
### 4. Initialize Your Project
Open Claude Code and describe your project. The `/requirements` skill automatically detects that this is a fresh project and enters **Init Mode**:
@@ -61,7 +59,7 @@ After project initialization, build features one at a time using skills:
/frontend Build the UI for features/PROJ-1-user-auth.md
/backend Build the API for features/PROJ-1-user-auth.md
/qa Test features/PROJ-1-user-auth.md
/deploy Deploy to Vercel
/deploy Deploy to your server
```
Each skill suggests the next step when it finishes. Handoffs are always user-initiated.
@@ -77,9 +75,9 @@ To add more features later, run `/requirements` again - it detects the existing
| Requirements Engineer | `/requirements` | Creates feature specs with user stories, acceptance criteria, edge cases |
| Solution Architect | `/architecture` | Designs PM-friendly tech architecture (no code, only high-level design) |
| Frontend Developer | `/frontend` | Builds UI with React, Tailwind CSS, and shadcn/ui |
| Backend Developer | `/backend` | Builds APIs, database schemas, RLS policies with Supabase |
| Backend Developer | `/backend` | Builds APIs, database schema, and auth with Fastify + Drizzle + PostgreSQL |
| QA Engineer | `/qa` | Tests features against acceptance criteria + security audit |
| DevOps | `/deploy` | Deploys to Vercel with production-ready checks |
| DevOps | `/deploy` | Deploys to your server (git pull + rsync + systemd) with production-ready checks |
| Help | `/help` | Context-aware guide: shows where you are and what to do next |
### How Skills Work
@@ -99,7 +97,7 @@ To add more features later, run `/requirements` again - it detects the existing
3. Build /frontend --> UI components implemented
/backend --> APIs + database (if needed)
4. Test /qa --> Test results added to feature spec
5. Ship /deploy --> Deployed to Vercel
5. Ship /deploy --> Deployed to your server
```
### Feature Tracking
@@ -119,12 +117,13 @@ Every skill reads this file at start and updates it when done, preventing duplic
| Category | Tool | Why? |
|----------|------|------|
| **Framework** | Next.js 16 | React + Server Components + App Router |
| **Frontend** | Vite + React 19 | Fast dev server, no framework lock-in |
| **Language** | TypeScript | Type safety |
| **Styling** | Tailwind CSS | Utility-first CSS |
| **UI Library** | shadcn/ui | Copy-paste, customizable components |
| **Backend** | Supabase (optional) | PostgreSQL + Auth + Storage + Realtime |
| **Deployment** | Vercel | Zero-config Next.js hosting |
| **Backend** | Fastify + Drizzle + PostgreSQL | Lightweight, typed, self-hostable |
| **Auth** | Own JWT (`@fastify/jwt`) | No third-party auth dependency |
| **Deployment** | git pull + rsync + systemd | Runs on any Linux server, updates pulled from Gitea |
| **Validation** | Zod | Runtime type validation |
---
@@ -140,7 +139,7 @@ ai-coding-starter-kit/
| +-- rules/ <-- Auto-applied coding rules
| | +-- general.md Git workflow, feature tracking
| | +-- frontend.md shadcn/ui, component standards
| | +-- backend.md RLS, validation, queries
| | +-- backend.md Validation, queries, migrations
| | +-- security.md Secrets, headers, auth
| +-- skills/ <-- Invocable workflows (/command)
| | +-- requirements/SKILL.md /requirements
@@ -160,18 +159,23 @@ ai-coding-starter-kit/
+-- docs/
| +-- PRD.md <-- Product Requirements Document
| +-- production/ <-- Production setup guides
| +-- error-tracking.md Sentry setup (5 min)
| +-- error-tracking.md Error tracking setup
| +-- security-headers.md XSS/Clickjacking protection
| +-- performance.md Lighthouse, optimization
| +-- database-optimization.md Indexing, N+1, caching
| +-- rate-limiting.md Upstash Redis
+-- src/
| +-- app/ <-- Pages (Next.js App Router)
| +-- components/
| | +-- ui/ <-- shadcn/ui components (35+ installed)
| +-- hooks/ <-- Custom React hooks
| +-- lib/ <-- Utilities
+-- public/ <-- Static files
| +-- rate-limiting.md @fastify/rate-limit
+-- frontend/
| +-- src/
| | +-- main.tsx, App.tsx <-- Entry points
| | +-- components/ui/ <-- shadcn/ui components (35+ installed)
| | +-- hooks/ <-- Custom React hooks
| | +-- lib/ <-- Utilities
| +-- public/ <-- Static files
+-- backend/
+-- src/
+-- server.ts <-- Fastify entry
+-- routes/ <-- Route handlers
+-- db/ <-- Drizzle schema + connection
```
---
@@ -195,7 +199,7 @@ Run `/requirements` with your feature idea. The skill will:
### 3. Add shadcn/ui Components (as needed)
35+ components are pre-installed. Add more as needed:
35+ components are pre-installed. Add more as needed (run inside `frontend/`):
```bash
npx shadcn@latest add [component-name]
```
@@ -203,8 +207,8 @@ npx shadcn@latest add [component-name]
### 4. Production Setup (first deployment)
When you're ready to deploy, the `/deploy` skill guides you through:
- Vercel setup and deployment
- Error tracking with Sentry
- Server setup (systemd unit, nginx reverse proxy, PostgreSQL)
- Error tracking
- Security headers configuration
- Performance monitoring with Lighthouse
@@ -222,7 +226,7 @@ Each skill is a structured workflow that Claude Code discovers automatically. Sk
| `/requirements` | Inline | Needs live interaction with user |
| `/architecture` | Inline | Short output, user reviews in real-time |
| `/frontend` | Sub-agent (forked) | Heavy file editing, lots of output |
| `/backend` | Sub-agent (forked) | Heavy file editing, SQL, API code |
| `/backend` | Sub-agent (forked) | Heavy file editing, schema, route code |
| `/qa` | Sub-agent (forked) | Systematic testing, lots of output |
| `/deploy` | Inline | Deployment needs user oversight |
| `/help` | Inline | Quick status check and guidance |
@@ -288,36 +292,32 @@ This template is designed as a starting point. Customize it for your team:
Standalone guides in `docs/production/`:
| Guide | Setup Time | What It Does |
|-------|-----------|-------------|
| [Error Tracking](docs/production/error-tracking.md) | 5 min | Sentry integration for automatic error capture |
| [Security Headers](docs/production/security-headers.md) | 2 min | XSS, Clickjacking, MIME sniffing protection |
| [Performance](docs/production/performance.md) | 10 min | Lighthouse checks, image optimization, caching |
| [Database Optimization](docs/production/database-optimization.md) | 15 min | Indexing, N+1 prevention, query optimization |
| [Rate Limiting](docs/production/rate-limiting.md) | 10 min | Upstash Redis for API abuse prevention |
| Guide | What It Does |
|-------|-------------|
| [Error Tracking](docs/production/error-tracking.md) | Automatic error capture setup |
| [Security Headers](docs/production/security-headers.md) | XSS, Clickjacking, MIME sniffing protection |
| [Performance](docs/production/performance.md) | Lighthouse checks, image optimization, caching |
| [Database Optimization](docs/production/database-optimization.md) | Indexing, N+1 prevention, query optimization |
| [Rate Limiting](docs/production/rate-limiting.md) | `@fastify/rate-limit` for API abuse prevention |
---
## Scripts
```bash
npm run dev # Development server (localhost:3000)
# frontend/
npm run dev # Vite dev server (localhost:5173)
npm run build # Production build
npm run start # Production server
npm run lint # ESLint
# backend/
npm run dev # tsx watch (localhost:4000)
npm run build # tsc build
npm run start # node dist/server.js
```
---
## Author
Created by **Alex Sprogis** – AI Product Engineer & Content Creator.
- [YouTube](https://www.youtube.com/@alex.sprogis)
- [Website](https://alexsprogis.de)
---
## License
MIT License - feel free to use for your projects!
+3
View File
@@ -0,0 +1,3 @@
DATABASE_URL=postgresql://user:password@localhost:5432/app
JWT_SECRET=change-me
PORT=4000
+10
View File
@@ -0,0 +1,10 @@
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./src/db/schema.ts",
out: "./drizzle",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
+2760
View File
File diff suppressed because it is too large. Load diff
+32
View File
@@ -0,0 +1,32 @@
{
"name": "ai-coding-starter-kit-backend",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsc -p .",
"start": "node dist/server.js",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate"
},
"dependencies": {
"@fastify/cors": "^10.0.1",
"@fastify/jwt": "^10.2.2",
"@fastify/rate-limit": "^10.1.1",
"bcryptjs": "^2.4.3",
"dotenv": "^16.4.5",
"drizzle-orm": "^0.45.2",
"fastify": "^5.1.0",
"pg": "^8.13.1",
"zod": "^4.3.5"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/node": "^20",
"@types/pg": "^8.11.10",
"drizzle-kit": "^0.31.10",
"tsx": "^4.19.2",
"typescript": "^5"
}
}
+7
View File
@@ -0,0 +1,7 @@
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import * as schema from "./schema.js";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
export const db = drizzle(pool, { schema });
+8
View File
@@ -0,0 +1,8 @@
import { pgTable, uuid, text, timestamp } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: uuid("id").primaryKey().defaultRandom(),
email: text("email").notNull().unique(),
passwordHash: text("password_hash").notNull(),
createdAt: timestamp("created_at").notNull().defaultNow(),
});
+60
View File
@@ -0,0 +1,60 @@
import type { FastifyInstance } from "fastify";
import bcrypt from "bcryptjs";
import { z } from "zod";
import { eq } from "drizzle-orm";
import { db } from "../db/index.js";
import { users } from "../db/schema.js";
const credentialsSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
export async function authRoutes(app: FastifyInstance) {
app.post("/api/auth/register", {
config: { rateLimit: { max: 5, timeWindow: "1 minute" } },
}, async (request, reply) => {
const parsed = credentialsSchema.safeParse(request.body);
if (!parsed.success) {
return reply.code(400).send({ error: parsed.error.issues[0].message });
}
const { email, password } = parsed.data;
const existing = await db.select().from(users).where(eq(users.email, email)).limit(1);
if (existing.length > 0) {
return reply.code(409).send({ error: "Email already registered" });
}
const passwordHash = await bcrypt.hash(password, 12);
const [user] = await db
.insert(users)
.values({ email, passwordHash })
.returning({ id: users.id, email: users.email });
const token = app.jwt.sign({ sub: user.id, email: user.email });
return reply.code(201).send({ token });
});
app.post("/api/auth/login", {
config: { rateLimit: { max: 5, timeWindow: "1 minute" } },
}, async (request, reply) => {
const parsed = credentialsSchema.safeParse(request.body);
if (!parsed.success) {
return reply.code(400).send({ error: parsed.error.issues[0].message });
}
const { email, password } = parsed.data;
const [user] = await db.select().from(users).where(eq(users.email, email)).limit(1);
if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
return reply.code(401).send({ error: "Invalid email or password" });
}
const token = app.jwt.sign({ sub: user.id, email: user.email });
return reply.send({ token });
});
app.get("/api/auth/me", { preHandler: app.authenticate }, async (request) => {
const { sub, email } = request.user;
return { id: sub, email };
});
}
+5
View File
@@ -0,0 +1,5 @@
import type { FastifyInstance } from "fastify";
export async function healthRoutes(app: FastifyInstance) {
app.get("/api/health", async () => ({ status: "ok" }));
}
+30
View File
@@ -0,0 +1,30 @@
import "dotenv/config";
import Fastify, { type FastifyRequest, type FastifyReply } from "fastify";
import cors from "@fastify/cors";
import jwt from "@fastify/jwt";
import rateLimit from "@fastify/rate-limit";
import { healthRoutes } from "./routes/health.js";
import { authRoutes } from "./routes/auth.js";
const app = Fastify({ logger: true });
await app.register(cors, { origin: true });
await app.register(rateLimit, { max: 100, timeWindow: "1 minute" });
await app.register(jwt, { secret: process.env.JWT_SECRET! });
app.decorate("authenticate", async (request: FastifyRequest, reply: FastifyReply) => {
try {
await request.jwtVerify();
} catch {
reply.code(401).send({ error: "Unauthorized" });
}
});
await app.register(healthRoutes);
await app.register(authRoutes);
const port = Number(process.env.PORT ?? 4000);
app.listen({ port, host: "0.0.0.0" }).catch((err) => {
app.log.error(err);
process.exit(1);
});
+15
View File
@@ -0,0 +1,15 @@
import "fastify";
import "@fastify/jwt";
declare module "fastify" {
interface FastifyInstance {
authenticate(request: FastifyRequest, reply: FastifyReply): Promise<void>;
}
}
declare module "@fastify/jwt" {
interface FastifyJWT {
payload: { sub: string; email: string };
user: { sub: string; email: string };
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true
},
"include": ["src"]
}
+9 -5
View File
@@ -1,10 +1,12 @@
# Product Requirements Document
> Filled out by `/requirements` in Init Mode, or manually before the first `/requirements` run. Keep every section concrete enough that a stranger could read it and know what NOT to build.
## Vision
_Describe what you are building and why._
_2-3 sentences: what is this product, who is it for, why does it need to exist. Not a feature list — the problem it solves._
## Target Users
_Who will use this product? Describe their needs and pain points._
_Name the actual user type(s), not "everyone". For each: their concrete pain point today, and what they're using instead of this product._
## Core Features (Roadmap)
@@ -15,14 +17,16 @@ _Who will use this product? Describe their needs and pain points._
| P1 | _Feature 3_ | Planned |
| P2 | _Feature 4_ | Planned |
P0 = cannot ship without it. P1 = next after MVP validates. P2 = explicitly deferred, not "maybe never."
## Success Metrics
_How will you measure success? (e.g., user signups, retention, task completion rate)_
_Concrete, measurable, with a number or threshold — not "users are happy". E.g. "50 signups in first month", "task completion rate > 80%"._
## Constraints
_Budget, timeline, technical limitations, team size._
_Real limits that shape decisions: timeline, budget, team size, must-run-on-existing-server, no third-party SaaS dependencies, etc._
## Non-Goals
_What are you explicitly NOT building in this version?_
_Explicitly out of scope for this version — the things people will ask for that you're deliberately not building yet, and why._
---
+45 -41
View File
@@ -4,17 +4,20 @@
Create indexes on columns used in WHERE, ORDER BY, or JOIN clauses:
```sql
-- Without index: ~500ms at 100k rows
SELECT * FROM tasks WHERE user_id = 'abc123' ORDER BY created_at DESC;
-- After creating index: <10ms
CREATE INDEX idx_tasks_user_id_created ON tasks(user_id, created_at DESC);
```ts
// backend/src/db/schema.ts
export const tasks = pgTable("tasks", {
id: uuid("id").primaryKey().defaultRandom(),
userId: uuid("user_id").notNull(),
createdAt: timestamp("created_at").notNull().defaultNow(),
}, (table) => ({
userCreatedIdx: index("idx_tasks_user_id_created").on(table.userId, table.createdAt.desc()),
}));
```
**Rule of thumb:** If a column appears in WHERE or ORDER BY and the table will have >1000 rows, add an index.
Always include indexes in your migration SQL alongside CREATE TABLE.
Always include indexes in the schema alongside the table definition, so they ship with the same migration.
## 2. Avoid N+1 Queries
@@ -22,18 +25,21 @@ The most common performance problem with ORMs and query builders:
```typescript
// Bad: N+1 (1 query for users + N queries for tasks)
const { data: users } = await supabase.from('users').select('*')
for (const user of users) {
const { data: tasks } = await supabase
.from('tasks')
.select('*')
.eq('user_id', user.id)
const allUsers = await db.select().from(users);
for (const user of allUsers) {
const userTasks = await db.select().from(tasks).where(eq(tasks.userId, user.id));
}
// Good: Single query with join (1 query total)
const { data } = await supabase
.from('users')
.select('*, tasks(*)')
// Good: single query with a join
const rows = await db
.select()
.from(users)
.leftJoin(tasks, eq(tasks.userId, users.id));
// Or with Drizzle's relational query API (if relations are defined)
const usersWithTasks = await db.query.users.findMany({
with: { tasks: true },
});
```
## 3. Always Limit Results
@@ -41,34 +47,32 @@ const { data } = await supabase
Never return unbounded results from the database:
```typescript
// Bad: Returns ALL rows
const { data } = await supabase.from('tasks').select('*')
// Bad: returns ALL rows
const rows = await db.select().from(tasks);
// Good: Returns max 50 rows
const { data } = await supabase.from('tasks').select('*').limit(50)
// Good: capped
const rows = await db.select().from(tasks).limit(50);
// Better: Paginated
const { data } = await supabase
.from('tasks')
.select('*')
.range(0, 49) // First 50 rows
// Better: paginated
const rows = await db.select().from(tasks).limit(50).offset(page * 50);
```
## 4. Caching Strategy
For data that changes rarely (dashboard stats, config, categories):
For data that changes rarely (dashboard stats, config, categories), cache in the backend process (or a small in-memory LRU) rather than hitting Postgres on every request:
```typescript
import { unstable_cache } from 'next/cache'
let cachedCategories: Category[] | null = null;
let cachedAt = 0;
export const getCategories = unstable_cache(
async () => {
const { data } = await supabase.from('categories').select('*')
return data
},
['categories'], // Cache key
{ revalidate: 3600 } // Refresh every hour
)
export async function getCategories() {
if (cachedCategories && Date.now() - cachedAt < 3600_000) {
return cachedCategories;
}
cachedCategories = await db.select().from(categories);
cachedAt = Date.now();
return cachedCategories;
}
```
**When to cache:**
@@ -78,14 +82,14 @@ export const getCategories = unstable_cache(
**When NOT to cache:**
- User-specific data that changes frequently
- Real-time data (use Supabase Realtime instead)
- Data that needs to be live across multiple backend instances (use a shared cache like Redis instead of in-process memory)
## 5. Select Only What You Need
```typescript
// Bad: Fetches all columns
const { data } = await supabase.from('users').select('*')
// Bad: fetches all columns
const rows = await db.select().from(users);
// Good: Fetches only needed columns
const { data } = await supabase.from('users').select('id, name, avatar_url')
// Good: fetches only needed columns
const rows = await db.select({ id: users.id, email: users.email }).from(users);
```
+34 -18
View File
@@ -1,32 +1,48 @@
# Error Tracking Setup (Sentry)
# Error Tracking Setup (Sentry, self-hosted friendly)
Track production errors automatically so you know about issues before your users report them.
Track production errors automatically so you know about issues before your users report them. Sentry's SaaS free tier works fine even on a self-hosted app — it's just a DSN endpoint, no coupling to any specific host.
## Setup (5 minutes)
### 1. Create Sentry Account
- Go to [sentry.io](https://sentry.io) (free tier available for small apps)
- Create a new project and select "Next.js"
- Create a project of type "Node" for the backend and one of type "React" for the frontend (or one combined project)
### 2. Install Next.js Integration
### 2. Install SDKs
```bash
npx @sentry/wizard@latest -i nextjs
```
This automatically:
- Installs `@sentry/nextjs`
- Creates `sentry.client.config.ts` and `sentry.server.config.ts`
- Updates `next.config.ts` with Sentry webpack plugin
# backend/
npm install @sentry/node
### 3. Add Environment Variables
Add to `.env.local` (local) and Vercel Dashboard (production):
# frontend/
npm install @sentry/react
```
### 3. Initialize
Backend (`backend/src/server.ts`, before anything else runs):
```ts
import * as Sentry from "@sentry/node";
Sentry.init({ dsn: process.env.SENTRY_DSN });
```
Frontend (`frontend/src/main.tsx`, before rendering):
```ts
import * as Sentry from "@sentry/react";
Sentry.init({ dsn: import.meta.env.VITE_SENTRY_DSN });
```
### 4. Add Environment Variables
Backend `.env`:
```bash
SENTRY_DSN=https://xxx@xxx.ingest.sentry.io/xxx
NEXT_PUBLIC_SENTRY_DSN=https://xxx@xxx.ingest.sentry.io/xxx
SENTRY_AUTH_TOKEN=sntrys_xxx # For source maps upload
```
Frontend `.env` (baked in at build time, so rebuild after changing it):
```bash
VITE_SENTRY_DSN=https://xxx@xxx.ingest.sentry.io/xxx
```
### 4. Verify Setup
Trigger a test error and check Sentry Dashboard:
### 5. Verify Setup
Trigger a test error and check the Sentry Dashboard:
```typescript
// Temporary test - remove after verification
throw new Error("Sentry test error")
@@ -34,10 +50,10 @@ throw new Error("Sentry test error")
## What You Get
- Automatic error capture (client + server)
- Stack traces with source maps
- Stack traces with source maps (upload via `@sentry/vite-plugin` / `@sentry/esbuild-plugin` in the build step)
- Error grouping and deduplication
- Email alerts for new errors
- Performance monitoring (optional)
## Alternative
**Vercel Error Tracking** - Built-in, simpler, but fewer features. Available in Vercel Dashboard under "Monitoring".
Self-hosted **GlitchTip** — an open-source, Sentry-protocol-compatible error tracker you can run on your own server if sending errors to a third-party SaaS is not acceptable.
+17 -27
View File
@@ -15,21 +15,23 @@
// Bad - unoptimized, no lazy loading
<img src="/large-image.jpg" />
// Good - Next.js Image component
import Image from 'next/image'
<Image src="/large-image.jpg" width={800} height={600} alt="Description" />
// Good - explicit dimensions + native lazy loading
<img src="/large-image.jpg" width={800} height={600} loading="lazy" alt="Description" />
```
Next.js Image automatically: resizes, lazy-loads, serves WebP format.
Pre-resize/compress source images (e.g. with `sharp` in a build step, or an image CDN) — Vite does not do this automatically like Next.js Image did.
### Large JavaScript Bundle
Use dynamic imports for heavy components that aren't needed on initial load:
```tsx
import dynamic from 'next/dynamic'
import { lazy, Suspense } from "react";
const HeavyChart = dynamic(() => import('./HeavyChart'), {
loading: () => <p>Loading chart...</p>,
})
const HeavyChart = lazy(() => import("./HeavyChart"));
<Suspense fallback={<p>Loading chart...</p>}>
<HeavyChart />
</Suspense>
```
Vite code-splits dynamic imports automatically — check `npm run build`'s output chunk sizes.
### Missing Loading States
Always show feedback during data fetching:
@@ -41,27 +43,15 @@ if (isLoading) return <Skeleton className="h-12 w-full" />
```
### No Caching Strategy
Cache slow database queries with `unstable_cache`:
```typescript
import { unstable_cache } from 'next/cache'
export const getStats = unstable_cache(
async () => {
const { data } = await supabase.from('stats').select('*')
return data
},
['dashboard-stats'],
{ revalidate: 3600 } // Refresh every hour
)
```
Cache slow backend queries in-process (see [database-optimization.md](database-optimization.md)) rather than re-querying Postgres on every request for rarely-changing data.
## Quick Wins Checklist
- [ ] All images use `next/image` component
- [ ] Heavy components use dynamic imports
- [ ] Images have explicit width/height and `loading="lazy"` where below the fold
- [ ] Heavy components use `React.lazy` + `Suspense`
- [ ] Loading states show skeleton/spinner
- [ ] Fonts loaded with `next/font`
- [ ] No unnecessary client-side JavaScript (`"use client"` only when needed)
- [ ] Fonts loaded via `@font-face`/Google Fonts `<link>`, with `font-display: swap`
- [ ] Production build (`npm run build`) checked for unexpectedly large chunks
## Automated Monitoring
- **Vercel Analytics** - Automatic on Pro plan, shows Core Web Vitals
- **Vercel Speed Insights** - Real user performance data
- Self-hosted **Plausible** or **Umami** for privacy-friendly traffic analytics
- `web-vitals` npm package to report Core Web Vitals from real users to your own backend/logging
+26 -72
View File
@@ -1,92 +1,46 @@
# Rate Limiting
Prevent abuse, DDoS attacks, and excessive API usage.
Prevent abuse, brute-force, and excessive API usage.
## When to Add Rate Limiting
- **MVP:** Optional (focus on features first)
- **Production with users:** Recommended on auth endpoints and public APIs
- **Public-facing APIs:** Required
## Setup with Upstash Redis
## Setup with @fastify/rate-limit
### 1. Install Dependencies
No external service needed — in-memory by default, works fine for a single backend instance.
### 1. Install Dependency
```bash
npm install @upstash/ratelimit @upstash/redis
cd backend && npm install @fastify/rate-limit
```
### 2. Create Upstash Account
- Go to [upstash.com](https://upstash.com) (free tier: 10k requests/day)
- Create a Redis database
- Copy REST URL and token
### 3. Add Environment Variables
```bash
# .env.local
UPSTASH_REDIS_REST_URL=https://xxx.upstash.io
UPSTASH_REDIS_REST_TOKEN=xxx
```
### 4. Create Rate Limiter
### 2. Register Globally
```typescript
// src/lib/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'
// backend/src/server.ts
import rateLimit from "@fastify/rate-limit";
export const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '10 s'), // 10 requests per 10 seconds
})
await app.register(rateLimit, {
max: 100,
timeWindow: "1 minute",
});
```
### 5. Use in API Routes
### 3. Tighter Limits on Specific Routes
```typescript
// src/app/api/example/route.ts
import { ratelimit } from '@/lib/rate-limit'
import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) {
const ip = request.headers.get('x-forwarded-for') ?? 'anonymous'
const { success, limit, remaining } = await ratelimit.limit(ip)
if (!success) {
return NextResponse.json(
{ error: 'Too many requests' },
{
status: 429,
headers: {
'X-RateLimit-Limit': limit.toString(),
'X-RateLimit-Remaining': remaining.toString(),
},
}
)
}
// Process request normally...
}
// backend/src/routes/auth.ts
app.post("/api/auth/login", {
config: {
rateLimit: { max: 5, timeWindow: "1 minute" },
},
}, async (request, reply) => {
// login handler
});
```
### 6. Use in Middleware (Global)
```typescript
// middleware.ts
import { ratelimit } from '@/lib/rate-limit'
import { NextRequest, NextResponse } from 'next/server'
export async function middleware(request: NextRequest) {
// Only rate limit API routes
if (request.nextUrl.pathname.startsWith('/api/')) {
const ip = request.headers.get('x-forwarded-for') ?? 'anonymous'
const { success } = await ratelimit.limit(ip)
if (!success) {
return NextResponse.json({ error: 'Too Many Requests' }, { status: 429 })
}
}
}
export const config = {
matcher: '/api/:path*',
}
```
### 4. Response on Limit Exceeded
`@fastify/rate-limit` returns HTTP 429 automatically with `Retry-After` and `X-RateLimit-*` headers — no manual handling needed.
## Recommended Limits
@@ -97,5 +51,5 @@ export const config = {
| General API | 30 requests | 10 seconds |
| File Upload | 5 requests | 1 minute |
## Alternative
**Vercel Edge Config** - Simpler but less flexible. Built into Vercel, no external service needed.
## Multi-Instance Deployments
If the backend ever runs as more than one process/instance behind a load balancer, switch the plugin's store to Redis (`@fastify/rate-limit` supports a custom `redis` client option) so limits are shared across instances instead of per-process.
+20 -33
View File
@@ -4,41 +4,28 @@ Protect against XSS, Clickjacking, MIME sniffing, and other common web attacks.
## Setup
Add security headers to `next.config.ts`:
Set security headers via the reverse proxy (nginx) in front of both frontend static files and the backend, or via `@fastify/helmet` on the backend directly. nginx is the simpler single place to cover everything.
```typescript
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'origin-when-cross-origin',
},
{
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains',
},
],
},
]
},
}
export default nextConfig
### nginx (recommended — covers frontend + backend uniformly)
```nginx
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
```
Add these inside the `server { }` block for the site.
### Alternative: @fastify/helmet on the backend
```bash
cd backend && npm install @fastify/helmet
```
```typescript
// backend/src/server.ts
import helmet from "@fastify/helmet";
await app.register(helmet);
```
Covers only API responses — still set the same headers in nginx for the static frontend files.
## What Each Header Does
+3 -3
View File
@@ -1,10 +1,10 @@
# Feature Index
> Central tracking for all features. Updated by skills automatically.
> Central tracking for all features. Updated by skills automatically — never hand-edit the status column without also updating the matching feature spec header.
## Status Legend
- **Planned** - Requirements written, ready for development
- **In Progress** - Currently being built
- **Planned** - Requirements written, ready for architecture
- **In Progress** - Tech design done, frontend/backend being built
- **In Review** - QA testing in progress
- **Deployed** - Live in production
+1 -1
View File
@@ -76,7 +76,7 @@ Am Ende des Feature-Dokuments fügt QA die Test-Ergebnisse hinzu:
**Status:** ✅ Deployed
**Deployed:** 2026-01-13
**Production URL:** https://your-app.vercel.app
**Production URL:** https://your-domain.example
**Git Tag:** v1.0.0-PROJ-1
```
+4
View File
@@ -0,0 +1,4 @@
# Only VITE_-prefixed vars are exposed to the browser at build time.
VITE_SENTRY_DSN=
# Add more environment variables as needed
+2 -2
View File
@@ -1,11 +1,11 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/app/globals.css",
"css": "src/index.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
+12
View File
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AI Coding Starter Kit</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+5422
View File
File diff suppressed because it is too large. Load diff
+10 -10
View File
@@ -1,13 +1,13 @@
{
"name": "ai-coding-starter-kit",
"name": "ai-coding-starter-kit-frontend",
"version": "1.0.0",
"description": "AI Coding Starter Kit with AI Agent Team System for Claude Code",
"private": true,
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint ."
},
"dependencies": {
"@hookform/resolvers": "^5.2.2",
@@ -31,16 +31,15 @@
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-toast": "^1.2.15",
"@radix-ui/react-tooltip": "^1.2.8",
"@supabase/supabase-js": "^2.39.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.0",
"cmdk": "^1.1.1",
"lucide-react": "^0.562.0",
"next": "^16.1.1",
"next-themes": "^0.4.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.71.1",
"react-router-dom": "^7.1.1",
"sonner": "^2.0.7",
"tailwind-merge": "^2.2.0",
"zod": "^4.3.5"
@@ -49,11 +48,12 @@
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.0.1",
"eslint": "^9",
"eslint-config-next": "16.1.1",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
"typescript": "^5",
"vite": "^6.0.7"
}
}
File renamed without changes.
File renamed without changes.

Before

Width:  |  Height:  |  Size: 233 B

After

Width:  |  Height:  |  Size: 233 B

File renamed without changes.

Before

Width:  |  Height:  |  Size: 303 B

After

Width:  |  Height:  |  Size: 303 B

File renamed without changes.

Before

Width:  |  Height:  |  Size: 206 B

After

Width:  |  Height:  |  Size: 206 B

+14
View File
@@ -0,0 +1,14 @@
import { Toaster } from "@/components/ui/sonner";
function App() {
return (
<>
<main className="min-h-screen flex items-center justify-center">
<h1 className="text-2xl font-semibold">AI Coding Starter Kit</h1>
</main>
<Toaster />
</>
);
}
export default App;
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
File renamed without changes.
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
@@ -1,4 +1,3 @@
"use client"
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import { type DialogProps } from "@radix-ui/react-dialog"
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
File renamed without changes.
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
File renamed without changes.
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
File renamed without changes.
@@ -1,4 +1,3 @@
"use client"
import {
CircleCheck,
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import * as SwitchPrimitives from "@radix-ui/react-switch"
File renamed without changes.
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
File renamed without changes.
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import * as ToastPrimitives from "@radix-ui/react-toast"
@@ -1,4 +1,3 @@
"use client"
import { useToast } from "@/hooks/use-toast"
import {
@@ -1,4 +1,3 @@
"use client"
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
File renamed without changes.
@@ -1,4 +1,3 @@
"use client"
// Inspired by react-hot-toast library
import * as React from "react"
File renamed without changes.
File renamed without changes.
+10
View File
@@ -0,0 +1,10 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import "./index.css";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>,
);
@@ -3,9 +3,8 @@ import type { Config } from "tailwindcss";
const config: Config = {
darkMode: ["class"],
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
+23
View File
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"]
}
+17
View File
@@ -0,0 +1,17 @@
import path from "path";
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
server: {
proxy: {
"/api": "http://localhost:4000",
},
},
});
-7
View File
@@ -1,7 +0,0 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
};
export default nextConfig;
-9149
View File
File diff suppressed because it is too large. Load diff
-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6.2c.3 0 .4.1.4.4v12.9c0 .3-.1.4-.4.4h-6.2c-.3 0-.4-.1-.4-.4V45.8c0-.3.1-.4.4-.4zm-22 0h6.2c.3 0 .5.1.5.4v12.9c0 .3-.2.4-.5.4h-6.2c-.3 0-.4-.1-.4-.4V45.8c0-.3.1-.4.4-.4zm-11.4 0h6.2c.3 0 .5.1.5.4v12.9c0 .3-.2.4-.5.4h-6.2c-.3 0-.4-.1-.4-.4V45.8c0-.3.1-.4.4-.4zm-11.4 0h6.2c.3 0 .4.1.4.4v12.9c0 .3-.1.4-.4.4h-6.2c-.3 0-.4-.1-.4-.4V45.8c0-.3.1-.4.4-.4z"/><path fill="url(#a)" d="m317.2 78.9-.4 1.2h.4l21.1-27.3-21.1-27.3h-.4l.4 1.2a50 50 0 0 1 2.3 14.8 50 50 0 0 1-2.3 14.8l21.1 27.3h.4l-.4-1.2a50 50 0 0 0 2.3-14.8 50 50 0 0 0-2.3-14.8l.4-1.2h-.4l-21.1 27.3z"/><defs><linearGradient id="a" x1="267.5" x2="342.8" y1="27.8" y2="27.8" gradientUnits="userSpaceOnUse"><stop/><stop offset="1" stop-opacity="0"/></linearGradient></defs></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>

Before

Width:  |  Height:  |  Size: 630 B

-21
View File
@@ -1,21 +0,0 @@
import type { Metadata } from "next";
import "./globals.css";
export const metadata: Metadata = {
title: "AI Coding Starter Kit",
description: "Built with AI Agent Team System",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className="antialiased">
{children}
</body>
</html>
);
}
-101
View File
@@ -1,101 +0,0 @@
import Image from 'next/image'
export default function Home() {
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
<li className="mb-2">
Get started by editing{' '}
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
src/app/page.tsx
</code>
.
</li>
<li>Save and see your changes instantly.</li>
</ol>
<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
</div>
</main>
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org →
</a>
</footer>
</div>
)
}
-14
View File
@@ -1,14 +0,0 @@
// Supabase Client Setup
// Uncomment this file when you're ready to use Supabase
/*
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
*/
// For now, export a placeholder to avoid import errors
export const supabase = null;
-41
View File
@@ -1,41 +0,0 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./src/*"
]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}