|
| 1 | +TypeScript full-stack template with tRPC for type-safe API communication between React frontend and Node.js backend. Use this when building type-safe TypeScript applications with the following structure: |
| 2 | +- server/: Node.js backend with tRPC API |
| 3 | +- client/: React frontend with tRPC client |
| 4 | + |
| 5 | +## Testing Guidelines: |
| 6 | + |
| 7 | +**CRITICAL**: Use Node.js native test runner only. Do NOT import vitest, jest, or supertest. |
| 8 | +Put tests next to the code (e.g. src/*.test.ts) |
| 9 | + |
| 10 | +```typescript |
| 11 | +import { test } from "node:test"; |
| 12 | +import { strict as assert } from "node:assert"; |
| 13 | +``` |
| 14 | + |
| 15 | +## Databricks Type Handling: |
| 16 | + |
| 17 | +- **executeQuery REQUIRES Zod schema**: Pass the Zod schema object as second parameter, NOT a TypeScript type annotation |
| 18 | + ```typescript |
| 19 | + // ❌ WRONG - Do NOT use generic type parameter |
| 20 | + const result = await client.executeQuery<MyType>(sql); |
| 21 | + |
| 22 | + // ✅ CORRECT - Pass Zod schema as parameter |
| 23 | + const mySchema = z.object({ id: z.number(), name: z.string() }); |
| 24 | + const result = await client.executeQuery(sql, mySchema); |
| 25 | + ``` |
| 26 | +- **QueryResult access**: `executeQuery()` returns `{rows: T[], rowCount: number}`. Always use `.rows` property: `const {rows} = await client.executeQuery(...)` or `result.rows.map(...)` |
| 27 | +- **Type imports**: Use `import type { T }` (not `import { T }`) when `verbatimModuleSyntax` is enabled |
| 28 | +- **Column access**: Use bracket notation `row['column_name']` (TypeScript strict mode requirement) |
| 29 | +- **DATE/TIMESTAMP columns**: Databricks returns Date objects. Use `z.coerce.date()` in schemas (never `z.string()` for dates) |
| 30 | +- **Dynamic properties**: Cast explicitly `row['order_id'] as number` |
| 31 | + |
| 32 | +### Helper Utilities: |
| 33 | + |
| 34 | +**mapRows<T>(rows, schema)** - Validates and maps raw SQL rows using Zod schema: |
| 35 | +```typescript |
| 36 | +import { mapRows } from './databricks'; |
| 37 | + |
| 38 | +// When you have raw rows and need manual mapping |
| 39 | +const rawRows = [{id: 1, name: "Alice"}, {id: 2, name: "Bob"}]; |
| 40 | +const userSchema = z.object({ id: z.number(), name: z.string() }); |
| 41 | +const users = mapRows(rawRows, userSchema); |
| 42 | +// users is now typed as { id: number; name: string }[] |
| 43 | +``` |
| 44 | + |
| 45 | +Use this when: |
| 46 | +- Processing nested query results |
| 47 | +- Manually mapping row data before returning from tRPC |
| 48 | +- Need to validate data from non-Databricks sources |
| 49 | + |
| 50 | +## Frontend Styling Guidelines: |
| 51 | + |
| 52 | +### Component Structure Pattern: |
| 53 | +- Use container with proper spacing: `<div className="container mx-auto p-4">` |
| 54 | +- Page titles: `<h1 className="text-2xl font-bold mb-4">Title</h1>` |
| 55 | +- Forms: Use `space-y-4` for vertical spacing between inputs |
| 56 | +- Cards: Use shadcn Card components or `border p-4 rounded-md` for item display |
| 57 | +- Grids: Use `grid gap-4` for list layouts |
| 58 | + |
| 59 | +### Example App Structure: |
| 60 | +```tsx |
| 61 | +<div className="container mx-auto p-4"> |
| 62 | + <h1 className="text-2xl font-bold mb-4">Page Title</h1> |
| 63 | + <form className="space-y-4 mb-8">{/* form inputs */}</form> |
| 64 | + <div className="grid gap-4">{/* list items */}</div> |
| 65 | +</div> |
| 66 | +``` |
| 67 | + |
| 68 | +### Tailwind Usage: |
| 69 | +- Use Tailwind classes directly in JSX |
| 70 | +- Avoid @apply unless creating reusable component styles |
| 71 | +- When using @apply, only in @layer components (never @layer base) |
| 72 | +- Template has CSS variables defined - use via Tailwind (bg-background, text-foreground, etc.) |
| 73 | + |
| 74 | +### Typography & Spacing: |
| 75 | +- Headings: text-2xl font-bold with mb-4 |
| 76 | +- Secondary text: text-foreground/70 |
| 77 | +- Card titles: text-xl font-semibold |
| 78 | +- Form spacing: space-y-4 between inputs, mb-8 after forms |
| 79 | +- Grid/list spacing: gap-4 for consistent item spacing |
| 80 | + |
| 81 | +### Component Organization: |
| 82 | +Create separate components when: |
| 83 | +- Logic exceeds ~100 lines |
| 84 | +- Component is reused in multiple places |
| 85 | +- Component has distinct responsibility (e.g., ProductForm, ProductList) |
| 86 | +File structure: |
| 87 | +- Shared UI: client/src/components/ui/ |
| 88 | +- Feature components: client/src/components/FeatureName.tsx |
| 89 | + |
| 90 | +### Visual Design: |
| 91 | +- Adjust visual mood to match user prompt, prefer clean and modern visually appealing aesthetics, but avoid overly flashy designs - keep it professional and user-friendly; |
| 92 | +- Use shadcn/radix components (Button, Input, Card, etc.) for consistent UI |
| 93 | +- Forms should have loading states: `disabled={isLoading}` |
| 94 | +- Show empty states with helpful text when no data exists |
| 95 | + |
| 96 | +### Best Practices: |
| 97 | +- Always fetch real data from tRPC (never use mock/hardcoded data) |
| 98 | +- Handle nullable fields: `value={field || ''}` for inputs |
| 99 | +- Type all callbacks explicitly: `onChange={(e: React.ChangeEvent<HTMLInputElement>) => ...}` |
| 100 | +- Use proper relative imports for server types: `import type { Product } from '../../server/src/schema'` |
| 101 | + |
| 102 | +## Data Visualization with Recharts |
| 103 | + |
| 104 | +The template includes Recharts for data visualization. Use Databricks brand colors for chart elements: `['#40d1f5', '#4462c9', '#EB1600', '#0B2026', '#4A4A4A', '#353a4a']` (apply via `stroke` or `fill` props). |
| 105 | + |
| 106 | +### Basic Chart Pattern: |
| 107 | +```tsx |
| 108 | +import { useState, useEffect } from 'react'; |
| 109 | +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; |
| 110 | +import { Card, CardContent, CardHeader, CardTitle } from './components/ui/card'; |
| 111 | +import { trpc } from './utils/trpc'; |
| 112 | + |
| 113 | +function MyDashboard() { |
| 114 | + const [data, setData] = useState<{ name: string; value: number }[]>([]); |
| 115 | + const [error, setError] = useState<string | null>(null); |
| 116 | + |
| 117 | + useEffect(() => { |
| 118 | + // fetch from Databricks via tRPC |
| 119 | + trpc.getMetrics.query() |
| 120 | + .then(setData) |
| 121 | + .catch((err) => setError(err.message)); |
| 122 | + }, []); |
| 123 | + |
| 124 | + return ( |
| 125 | + <Card> |
| 126 | + <CardHeader> |
| 127 | + <CardTitle>My Metrics</CardTitle> |
| 128 | + </CardHeader> |
| 129 | + <CardContent> |
| 130 | + <ResponsiveContainer width="100%" height={300}> |
| 131 | + <LineChart data={data}> |
| 132 | + <CartesianGrid strokeDasharray="3 3" /> |
| 133 | + <XAxis dataKey="name" /> |
| 134 | + <YAxis /> |
| 135 | + <Tooltip /> |
| 136 | + <Line type="monotone" dataKey="value" stroke="hsl(var(--primary))" /> |
| 137 | + </LineChart> |
| 138 | + </ResponsiveContainer> |
| 139 | + </CardContent> |
| 140 | + </Card> |
| 141 | + ); |
| 142 | +} |
| 143 | +``` |
0 commit comments