21 KiB
AI Agent Instructions for TaylorDB Full-Stack Template
This template is designed for AI agents to build modern, type-safe, full-stack applications using TaylorDB as the data source. This template provides a complete monorepo setup with a React frontend (Vite + shadcn/ui) and a Node.js backend (tRPC + TaylorDB query builder)
🎯 Your Mission
Build production-ready, modern web applications (primarily dashboards and CRUD interfaces) that:
- Are fully type-safe from database to UI
- Use TaylorDB as the single source of truth for data
- Have stunning, modern UI/UX that wows users
- Follow best practices in architecture and code organization
📚 Docs for Agents
Before you start implementing, skim these docs in the docs/ folder:
-
TaylorDB Query Builder
docs/TAYLORDB_QUERY_REFERENCE.md– index for all query examplesdocs/TAYLORDB_BASIC_QUERIES.md– basic reads, filtering, datesdocs/TAYLORDB_WRITE_OPERATIONS.md– inserts, updates, deletesdocs/TAYLORDB_ADVANCED_PATTERNS.md– aggregations, pagination, conditional queriesdocs/TAYLORDB_FIELD_TYPES.md– field type mapping & enumsdocs/TAYLORDB_ATTACHMENTS.md– working with attachment fieldsdocs/TAYLORDB_PITFALLS_BEST_PRACTICES.md– common mistakes & best practices
-
shadcn/ui Components & Dashboard Patterns
docs/SHADCN_COMPONENTS_GUIDE.md– index for all shadcn/ui docsdocs/SHADCN_INSTALLATION.md– how to install shadcn/ui componentsdocs/SHADCN_DASHBOARD_PATTERNS.md– ready-made dashboard/layout patternsdocs/SHADCN_DESIGN_AND_LAYOUT.md– design tokens, layout, responsive & performance tips
Use AGENTS.md for workflow and rules and the docs/ files for detailed code examples.
📋 Development Workflow
Phase 1: Understand Requirements & Design
Step 1: Gather Requirements
Ask the user clarifying questions about:
- What data they want to work with (understand the domain)
- Key features and user workflows
- Target users and use cases
- Any specific UI/UX preferences
Step 2: Understand the Database Schema
CRITICAL: Always start by reading these files to understand the data model:
apps/server/taylordb/types.ts- TypeScript types generated from TaylorDB schemaapps/server/taylordb/query-builder.ts- Query builder patterns (review for examples)
⚠️ IMPORTANT: If these files don't exist, STOP and ask the user to generate them first. Never proceed with mock data.
Step 3: Design the Color Scheme & Visual Identity
Based on the requirements, decide on:
- Primary color scheme (use HSL values for flexibility)
- Design aesthetic (modern glassmorphism, gradients, minimalism, etc.)
- Typography (Google Fonts like Inter, Outfit, or Manrope)
- Animation style (subtle micro-interactions vs. bold animations)
Document your design decisions briefly before implementing.
Phase 2: Build the Foundation
Step 1: Set Up Server-Side Data Layer
Use querybuilder which is in File: apps/server/taylordb/query-builder.ts
You can access the query builder from
publicProcedure.input({}).query(({ input, ctx }) => {
const queryBuilder = ctx.queryBuilder;
});
// ============================================================================ // READ Operations // ============================================================================
/**
- Get all records from a table */ export async function getAllItems() { return await queryBuilder .selectFrom("items") .select(["id", "name", "status", "createdAt"]) .orderBy("createdAt", "desc") .execute(); }
/**
- Get a single record by ID */ export async function getItemById(id: number) { return await queryBuilder .selectFrom("items") .where("id", "=", id) .executeTakeFirst(); }
// ============================================================================ // CREATE Operations // ============================================================================
export async function createItem(data: { name: string; status: string }) { return await queryBuilder.insertInto("items").values(data).executeTakeFirst(); }
// ============================================================================ // UPDATE Operations // ============================================================================
export async function updateItem( id: number, data: { name?: string; status?: string }, ) { return await queryBuilder .update("items") .set(data) .where("id", "=", id) .execute(); }
// ============================================================================ // DELETE Operations // ============================================================================
export async function deleteItem(id: number) { return await queryBuilder.deleteFrom("items").where("id", "=", id).execute(); }
**Query Builder Patterns:**
- **Filtering**: `.where("field", "=", value)`, `.where("date", ">=", ["exactDay", "2024-01-01"])`
- **Select specific fields**: `.select(["id", "name", "status"])`
- **Ordering**: `.orderBy("createdAt", "desc")`
- **Single record**: `.executeTakeFirst()`
- **Multiple records**: `.execute()`
- **Array fields**: Use `[value]` for single-select enums
- **Date filters**: Use `["exactDay", date]` format
Organize functions by domain (e.g., all user-related functions together).
#### Step 2: Create tRPC API Router
**File: `apps/server/router.ts`**
Expose your database functions as type-safe tRPC procedures:
```typescript
import { z } from "zod";
import { router, publicProcedure } from "./trpc";
import * as db from "./taylordb/query-builder";
export const appRouter = router({
// Group by domain/feature
items: {
getAll: publicProcedure.query(async () => {
return await db.getAllItems();
}),
getById: publicProcedure
.input(z.object({ id: z.number() }))
.query(async ({ input }) => {
return await db.getItemById(input.id);
}),
create: publicProcedure
.input(
z.object({
name: z.string().min(1),
status: z.string(),
}),
)
.mutation(async ({ input }) => {
return await db.createItem(input);
}),
update: publicProcedure
.input(
z.object({
id: z.number(),
name: z.string().optional(),
status: z.string().optional(),
}),
)
.mutation(async ({ input }) => {
const { id, ...data } = input;
return await db.updateItem(id, data);
}),
delete: publicProcedure
.input(z.object({ id: z.number() }))
.mutation(async ({ input }) => {
return await db.deleteItem(input.id);
}),
},
});
export type AppRouter = typeof appRouter;
Organization:
- Group related procedures (e.g.,
items,users,projects) - Use Zod for input validation
- Queries for reads, mutations for writes
- Export
AppRoutertype for frontend
Phase 3: Build the Frontend
Step 1: Update Design System
File: apps/client/src/index.css
Update the design tokens based on your chosen color scheme:
@layer base {
:root {
/* Update these HSL values for your color scheme */
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 262 83% 58%; /* Example: Purple */
--primary-foreground: 210 40% 98%;
--accent: 262 90% 95%;
--accent-foreground: 262 83% 58%;
/* ... customize all tokens ... */
--radius: 0.75rem; /* Border radius */
}
.dark {
/* Dark mode colors */
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--primary: 263 70% 65%;
/* ... */
}
}
Step 2: Create shadcn/ui Components
Always use shadcn/ui components. Install components as needed with:
pnpm dlx shadcn@latest add <component-name>
For concrete install commands and patterns:
- See
docs/SHADCN_INSTALLATION.mdfor component install snippets - See
docs/SHADCN_DASHBOARD_PATTERNS.mdfor tables, dialogs, forms, toasts, sheets, command palette, etc.
Step 3: Build Page Components
File: apps/client/src/pages/DashboardPage.tsx
Create feature-rich, modern pages:
import { useState } from "react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { trpc } from "@/lib/trpc";
import { PlusIcon, Loader2 } from "lucide-react";
export default function DashboardPage() {
const [name, setName] = useState("");
const { data: items, isLoading, refetch } = trpc.items.getAll.useQuery();
const createMutation = trpc.items.create.useMutation({
onSuccess: () => {
refetch();
setName("");
},
});
const deleteMutation = trpc.items.delete.useMutation({
onSuccess: () => refetch(),
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (name) {
createMutation.mutate({ name, status: "active" });
}
};
return (
<div className="container mx-auto p-8 max-w-6xl">
<div className="mb-8">
<h1 className="text-4xl font-bold mb-2">Dashboard</h1>
<p className="text-muted-foreground">Manage your items</p>
</div>
<div className="grid gap-6">
{/* Create Form */}
<Card>
<CardHeader>
<CardTitle>Add New Item</CardTitle>
<CardDescription>
Create a new item in your database
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="flex gap-4">
<div className="flex-1">
<Label htmlFor="name">Item Name</Label>
<Input
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter item name..."
required
/>
</div>
<Button
type="submit"
disabled={createMutation.isPending}
className="mt-auto"
>
{createMutation.isPending ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Adding...
</>
) : (
<>
<PlusIcon className="mr-2 h-4 w-4" />
Add Item
</>
)}
</Button>
</form>
</CardContent>
</Card>
{/* Items List */}
<Card>
<CardHeader>
<CardTitle>Your Items</CardTitle>
</CardHeader>
<CardContent>
{isLoading && (
<div className="flex items-center justify-center py-8">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
)}
{items && items.length === 0 && (
<p className="text-center text-muted-foreground py-8">
No items yet. Create your first item above.
</p>
)}
<div className="space-y-3">
{items?.map((item) => (
<div
key={item.id}
className="flex items-center justify-between p-4 border rounded-lg hover:bg-accent transition-colors"
>
<div>
<p className="font-medium">{item.name}</p>
<p className="text-sm text-muted-foreground">
{item.status}
</p>
</div>
<Button
variant="destructive"
size="sm"
onClick={() =>
item.id && deleteMutation.mutate({ id: item.id })
}
disabled={deleteMutation.isPending}
>
Delete
</Button>
</div>
))}
</div>
</CardContent>
</Card>
</div>
</div>
);
}
Step 4: Update Routing
File: apps/client/src/main.tsx
Add your new pages to the router:
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import App from "./App";
import HomePage from "./pages/HomePage";
import DashboardPage from "./pages/DashboardPage";
const router = createBrowserRouter([
{
path: "/",
element: <App />,
children: [
{ index: true, element: <HomePage /> },
{ path: "dashboard", element: <DashboardPage /> },
],
},
]);
File: apps/client/src/App.tsx
Update navigation:
const navItems = [
{ to: "/", label: "Home" },
{ to: "/dashboard", label: "Dashboard" },
];
Phase 4: Polish & Validate
Step 1: Run Type Checking
ALWAYS run this before considering your work complete:
pnpm build
Fix all TypeScript errors. Never use any unless absolutely necessary.
Step 2: Run Linter
pnpm lint
Fix all linting errors.
Step 3: Test in Browser
The dev server should be running. Test:
- All CRUD operations work correctly
- Data updates in real-time
- Error states display properly
- Loading states show appropriate feedback
- UI is responsive and looks great
🎨 Design Guidelines
Visual Excellence Principles
-
No Generic Colors: Never use plain red/blue/green. Use curated HSL palettes.
- ✅
hsl(262 83% 58%)(vibrant purple) - ❌
#0000ff(plain blue)
- ✅
-
Premium Aesthetics: Make it feel high-end
- Use subtle gradients, shadows, and glassmorphism
- Add smooth transitions (
transition-all duration-200) - Implement hover states on interactive elements
- Use proper spacing and visual hierarchy
-
Modern Typography:
- Import Google Fonts (e.g., Inter, Outfit, Manrope)
- Use varied font weights (400, 500, 600, 700)
- Proper text sizing hierarchy
-
Micro-Animations:
- Loading spinners with
lucide-reacticons +animate-spin - Fade-ins on data load
- Smooth transitions on hover
- Button press feedback
- Loading spinners with
-
Dashboard-First Design:
- Card-based layouts
- Clear visual grouping
- Stats/metrics prominently displayed
- Intuitive navigation
Component Structure
Always follow this pattern:
// 1. Imports (external, then internal)
import { useState } from "react";
import { Card } from "@/components/ui/card";
import { trpc } from "@/lib/trpc";
// 2. Type definitions (if needed)
interface ItemFormProps {
onSuccess: () => void;
}
// 3. Component (arrow function)
export default function ItemForm({ onSuccess }: ItemFormProps) {
// 4. State & hooks
const [name, setName] = useState("");
const createMutation = trpc.items.create.useMutation({ onSuccess });
// 5. Event handlers
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
createMutation.mutate({ name });
};
// 6. Render
return <form onSubmit={handleSubmit}>{/* ... */}</form>;
}
📁 File Organization Best Practices
Backend Structure
apps/server/
├── taylordb/
│ ├── types.ts # Generated types (DO NOT EDIT)
│ └── query-builder.ts # All database operations
├── router.ts # tRPC API routes
├── trpc.ts # tRPC configuration
└── index.ts # Server entry point
Frontend Structure
apps/client/src/
├── components/
│ ├── ui/ # shadcn/ui components (auto-generated)
│ └── [custom]/ # Your custom components
├── pages/
│ └── [PageName].tsx # Route pages
├── lib/
│ ├── trpc.ts # tRPC client setup
│ └── utils.ts # Utilities (cn helper, etc.)
├── App.tsx # Layout + navigation
├── main.tsx # Router + app initialization
└── index.css # Global styles + design tokens
Where to Put What
| What | Where |
|---|---|
| Database queries | apps/server/taylordb/query-builder.ts |
| API endpoints | apps/server/router.ts |
| Route pages | apps/client/src/pages/ |
| Reusable UI components | apps/client/src/components/ |
| shadcn/ui components | apps/client/src/components/ui/ (auto) |
| Design tokens | apps/client/src/index.css |
| TypeScript types | Use generated types from taylordb/types.ts |
🔧 TaylorDB Query Builder Reference
Instead of duplicating examples here, use the dedicated docs:
docs/TAYLORDB_QUERY_REFERENCE.md– index for all query builder docsdocs/TAYLORDB_BASIC_QUERIES.md–selectFrom, filtering, ordering, date filtersdocs/TAYLORDB_WRITE_OPERATIONS.md–insertInto,update,deleteFromdocs/TAYLORDB_ADVANCED_PATTERNS.md– aggregations, totals, conditional queries, paginationdocs/TAYLORDB_FIELD_TYPES.md– field type mapping, nullable handling, enumsdocs/TAYLORDB_ATTACHMENTS.md– selecting and writing attachment fieldsdocs/TAYLORDB_PITFALLS_BEST_PRACTICES.md– pitfalls and best practices
When writing queries in apps/server/taylordb/query-builder.ts, mirror the patterns from these docs and keep everything strongly typed using taylordb/types.ts.
✅ Code Style Guidelines
TypeScript
- Never use
any. Use proper types fromtaylordb/types.ts - Strict null checks: handle
nullandundefinedexplicitly - Use type inference where obvious, explicit types for function params/returns
Naming Conventions
- Variables/Functions:
camelCase(e.g.,getUserData) - Components:
PascalCase(e.g.,DashboardPage) - Constants:
UPPER_CASE(e.g.,MAX_ITEMS) - Files: Match component name (e.g.,
DashboardPage.tsx)
Imports
- Group external imports first, then internal
- Use path aliases:
@/components/...not../../components/...
Components
- Use arrow functions:
const MyComponent = () => { ... } - Props typing:
interface MyComponentProps { ... } - Keep components focused (single responsibility)
Error Handling
- Display error states in UI
- Use tRPC's built-in error handling
- Show user-friendly messages
Comments
- Use JSDoc for functions:
/** Description */ - Explain "why", not "what"
- Remove commented-out code
🎓 Example: Building a Task Manager Dashboard
User Request: "Build a task manager with projects and tasks"
1. Analyze Schema
Assume TaylorDB has:
projectstable:id,name,description,statustaskstable:id,title,projectId,status,dueDate
2. Design Decision
- Color: Gradient purple/blue theme
- Style: Modern with glassmorphism cards
- Layout: Projects on left sidebar, tasks on right
3. Backend (apps/server/taylordb/query-builder.ts)
export async function getAllProjects() {
return await queryBuilder
.selectFrom("projects")
.select(["id", "name", "description", "status"])
.execute();
}
export async function getTasksByProject(projectId: number) {
return await queryBuilder
.selectFrom("tasks")
.where("projectId", "=", projectId)
.orderBy("dueDate", "asc")
.execute();
}
4. API (apps/server/router.ts)
export const appRouter = router({
projects: {
getAll: publicProcedure.query(() => db.getAllProjects()),
},
tasks: {
getByProject: publicProcedure
.input(z.object({ projectId: z.number() }))
.query(({ input }) => db.getTasksByProject(input.projectId)),
},
});
5. Frontend (apps/client/src/pages/TasksPage.tsx)
Build the UI with cards, proper loading states, and type-safe tRPC calls.
⚠️ Critical Rules
- NEVER use mock data. Always connect to real TaylorDB.
- NEVER ignore TypeScript errors. Fix them before moving on.
- ALWAYS use shadcn/ui components instead of hand-rolling UI.
- NEVER modify generated types in
taylordb/types.ts. - ALWAYS run
pnpm buildto validate your work. - Design must be modern and premium, not basic MVP.
🎯 Success Criteria
Your implementation is successful when:
- ✅ All TypeScript errors are resolved (
pnpm buildpasses) - ✅ All lint errors are fixed (
pnpm lintpasses) - ✅ UI looks modern and premium (not basic/generic)
- ✅ All CRUD operations work correctly with TaylorDB
- ✅ Loading and error states are handled gracefully
- ✅ Code is well-organized and follows best practices
- ✅ Type safety is maintained from database to UI
Remember: You're building production-quality applications that should impress users from the first glance. Focus on visual excellence, type safety, and solid architecture.