734 lines
21 KiB
Markdown
734 lines
21 KiB
Markdown
# 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 examples
|
||
- `docs/TAYLORDB_BASIC_QUERIES.md` – basic reads, filtering, dates
|
||
- `docs/TAYLORDB_WRITE_OPERATIONS.md` – inserts, updates, deletes
|
||
- `docs/TAYLORDB_ADVANCED_PATTERNS.md` – aggregations, pagination, conditional queries
|
||
- `docs/TAYLORDB_FIELD_TYPES.md` – field type mapping & enums
|
||
- `docs/TAYLORDB_ATTACHMENTS.md` – working with attachment fields
|
||
- `docs/TAYLORDB_PITFALLS_BEST_PRACTICES.md` – common mistakes & best practices
|
||
|
||
- **shadcn/ui Components & Dashboard Patterns**
|
||
- `docs/SHADCN_COMPONENTS_GUIDE.md` – index for all shadcn/ui docs
|
||
- `docs/SHADCN_INSTALLATION.md` – how to install shadcn/ui components
|
||
- `docs/SHADCN_DASHBOARD_PATTERNS.md` – ready-made dashboard/layout patterns
|
||
- `docs/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 schema
|
||
- `apps/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
|
||
|
||
```typescript
|
||
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 `AppRouter` type 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:
|
||
|
||
```css
|
||
@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:
|
||
|
||
```bash
|
||
pnpm dlx shadcn@latest add <component-name>
|
||
```
|
||
|
||
For concrete install commands and patterns:
|
||
|
||
- See `docs/SHADCN_INSTALLATION.md` for component install snippets
|
||
- See `docs/SHADCN_DASHBOARD_PATTERNS.md` for 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:
|
||
|
||
```typescript
|
||
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:
|
||
|
||
```typescript
|
||
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:
|
||
|
||
```typescript
|
||
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:**
|
||
|
||
```bash
|
||
pnpm build
|
||
```
|
||
|
||
Fix all TypeScript errors. Never use `any` unless absolutely necessary.
|
||
|
||
#### Step 2: Run Linter
|
||
|
||
```bash
|
||
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
|
||
|
||
1. **No Generic Colors**: Never use plain red/blue/green. Use curated HSL palettes.
|
||
- ✅ `hsl(262 83% 58%)` (vibrant purple)
|
||
- ❌ `#0000ff` (plain blue)
|
||
|
||
2. **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
|
||
|
||
3. **Modern Typography**:
|
||
- Import Google Fonts (e.g., Inter, Outfit, Manrope)
|
||
- Use varied font weights (400, 500, 600, 700)
|
||
- Proper text sizing hierarchy
|
||
|
||
4. **Micro-Animations**:
|
||
- Loading spinners with `lucide-react` icons + `animate-spin`
|
||
- Fade-ins on data load
|
||
- Smooth transitions on hover
|
||
- Button press feedback
|
||
|
||
5. **Dashboard-First Design**:
|
||
- Card-based layouts
|
||
- Clear visual grouping
|
||
- Stats/metrics prominently displayed
|
||
- Intuitive navigation
|
||
|
||
### Component Structure
|
||
|
||
**Always follow this pattern:**
|
||
|
||
```typescript
|
||
// 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 docs
|
||
- `docs/TAYLORDB_BASIC_QUERIES.md` – `selectFrom`, filtering, ordering, date filters
|
||
- `docs/TAYLORDB_WRITE_OPERATIONS.md` – `insertInto`, `update`, `deleteFrom`
|
||
- `docs/TAYLORDB_ADVANCED_PATTERNS.md` – aggregations, totals, conditional queries, pagination
|
||
- `docs/TAYLORDB_FIELD_TYPES.md` – field type mapping, nullable handling, enums
|
||
- `docs/TAYLORDB_ATTACHMENTS.md` – selecting and writing attachment fields
|
||
- `docs/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 from `taylordb/types.ts`
|
||
- Strict null checks: handle `null` and `undefined` explicitly
|
||
- 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:
|
||
|
||
- `projects` table: `id`, `name`, `description`, `status`
|
||
- `tasks` table: `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`)
|
||
|
||
```typescript
|
||
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`)
|
||
|
||
```typescript
|
||
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
|
||
|
||
1. **NEVER use mock data.** Always connect to real TaylorDB.
|
||
2. **NEVER ignore TypeScript errors.** Fix them before moving on.
|
||
3. **ALWAYS use shadcn/ui components** instead of hand-rolling UI.
|
||
4. **NEVER modify generated types** in `taylordb/types.ts`.
|
||
5. **ALWAYS run `pnpm build`** to validate your work.
|
||
6. **Design must be modern and premium**, not basic MVP.
|
||
|
||
---
|
||
|
||
## 🎯 Success Criteria
|
||
|
||
Your implementation is successful when:
|
||
|
||
- ✅ All TypeScript errors are resolved (`pnpm build` passes)
|
||
- ✅ All lint errors are fixed (`pnpm lint` passes)
|
||
- ✅ 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.
|