docs: add Trellis planning and project specs

This commit is contained in:
2026-07-01 06:27:55 -07:00
parent 7f227c5f2a
commit aafd9caaac
349 changed files with 55801 additions and 0 deletions

View File

@@ -0,0 +1,289 @@
# Pre-Implementation Checklist
> **Purpose**: Ask the right questions **before** writing code to avoid common architectural mistakes.
---
## Why This Checklist?
Most code quality issues aren't caught during implementation--they're **designed in** from the start:
| Problem | Root Cause | Cost |
| -------------------------------------- | --------------------------------------------- | -------------------------- |
| Constants duplicated across 5 files | Didn't ask "will this be used elsewhere?" | Refactoring + testing |
| Same logic repeated in multiple hooks | Didn't ask "does this pattern exist?" | Creating abstraction later |
| Cross-layer type mismatches | Didn't ask "who else consumes this?" | Debugging + fixing |
| Zod schema redefined in frontend | Didn't ask "is this type already exported?" | Inconsistent validation |
| oRPC procedure duplicates existing one | Didn't ask "does a similar endpoint exist?" | API surface bloat |
**This checklist catches these issues before they become code.**
---
## The Checklist
### 1. Constants & Configuration
Before adding any constant or config value:
- [ ] **Cross-package usage?** Will this value be used in both frontend app and API package?
- If yes -> Put in a shared package (e.g., `@your-app/utils` or `@your-app/config`)
- Example: `MAX_UPLOAD_SIZE` used by both file upload UI and oRPC validation
- [ ] **Multiple consumers?** Will this value be used in 2+ files within the same package?
- If yes -> Put in a shared constants file for that package
- Example: Don't define `DEBOUNCE_MS = 300` in each hook file
- [ ] **Magic number?** Is this a hardcoded value that could change?
- If yes -> Extract to named constant with comment explaining why
- Example: `PAGINATION_LIMIT: 50 // oRPC default page size`
- [ ] **Environment-dependent?** Does this differ between dev/staging/production?
- If yes -> Use environment variables with proper validation
- Example: API URLs, feature flags, third-party API keys
### 2. Logic & Patterns
Before implementing any logic:
- [ ] **Pattern exists?** Search for similar patterns in the codebase first
```bash
# Example: Before implementing debounced search
rg "debounce" src/ packages/
rg "useDebounce" src/ packages/
```
- [ ] **Will repeat?** Will this exact logic be needed in 2+ places?
- If yes -> Create a shared hook/utility **first**, then use it
- Example: `useDebounce` instead of repeating debounce logic in 5 hooks
- [ ] **React Query pattern?** Is there an existing query/mutation hook for this data?
- Search before creating: `rg "orpc.items" src/`
- Check if you can extend an existing hook rather than creating a new one
- [ ] **Server or client?** Does this logic need interactivity?
- If no -> Keep it in a Server Component (default)
- If yes -> Extract only the interactive part into a Client Component
### 3. Types & Schemas
Before defining types:
- [ ] **Zod schema exists?** Is there already a Zod schema for this data shape?
- Check the API module's `types.ts`: `rg "Schema = z.object" packages/api/`
- Derive TypeScript types from Zod schemas with `z.infer<typeof schema>`
- Never manually define a TypeScript interface that duplicates a Zod schema
- [ ] **Existing type?** Does a similar type already exist?
- Search before creating: `rg "interface.*YourTypeName\|type.*YourTypeName" src/ packages/`
- [ ] **Cross-layer type?** Is this type used across the oRPC boundary?
- If yes -> Define the Zod schema in the API module's `types.ts`, export the inferred type
- Frontend should import types from the API package, not redefine them
- [ ] **Derived from client?** Can you infer the type from the oRPC client?
```typescript
// Prefer this over manually defining types
type ItemResult = Awaited<ReturnType<(typeof orpcClient)["items"]["get"]>>;
```
### 4. UI Components
Before creating UI components:
- [ ] **Server or Client Component?** Does this component need:
- Event handlers (onClick, onChange)? -> `'use client'`
- React hooks (useState, useEffect)? -> `'use client'`
- Browser APIs (window, document)? -> `'use client'`
- None of the above? -> Keep as Server Component (default)
- [ ] **Similar component exists?** Search before creating
- `rg "function.*YourComponent\|export.*YourComponent" src/`
- [ ] **Visual-logic consistency?** If there's already a visual distinction (icon, color, label) for a concept, does your logic match?
- [ ] **State lifecycle?** Will this component unmount during normal user flow?
- If yes -> Consider where state should persist (URL params with nuqs, parent, context)
### 5. API Routes & oRPC Procedures
Before writing an API route or oRPC procedure:
- [ ] **Existing procedure?** Does a similar oRPC procedure already exist?
- Check the module's `router.ts`: `rg "Router = {" packages/api/`
- Can you extend an existing procedure rather than creating a new one?
- [ ] **Correct HTTP method?**
- GET for read operations (queries)
- POST for create operations (mutations)
- PUT/PATCH for update operations
- DELETE for removal operations
- [ ] **Authentication level?** Which base procedure to use?
- Public data -> `publicProcedure`
- User-specific data -> `protectedProcedure`
- Admin operations -> `adminProcedure`
- [ ] **Input/output schemas defined?** Both should be Zod schemas in `types.ts`
### 6. Dependencies
Before adding a dependency:
- [ ] **Already installed?** Check `package.json` across all packages
```bash
rg "\"dependency-name\"" package.json packages/*/package.json
```
- [ ] **Built-in alternative?** Can you use a native API or existing utility instead?
- Example: `structuredClone()` instead of `lodash.cloneDeep`
- [ ] **Bundle impact?** Will this significantly increase the client bundle?
- If yes -> Consider dynamic import or server-only usage
---
## Quick Decision Tree
```
Adding a value/constant?
|-- Used in both app AND api package? -> shared package (@your-app/utils)
|-- Used in 2+ files within same package? -> shared constants file
+-- Single file only? -> Local constant is fine
Adding logic/behavior?
|-- Similar pattern exists? -> Extend or reuse existing
|-- Will be used in 2+ places? -> Create shared hook/utility first
+-- Single use only? -> Implement directly (but document pattern)
Adding a type?
|-- Zod schema exists? -> Use z.infer<typeof schema>
|-- Crosses oRPC boundary? -> Define in API types.ts, import elsewhere
|-- Can derive from client? -> Use Awaited<ReturnType<...>>
+-- Local only? -> Define locally
Adding a component?
|-- Needs interactivity? -> 'use client'
|-- Pure display? -> Server Component (default)
+-- Mix of both? -> Split into Server wrapper + Client interactive part
```
---
## What to Verify Across Layers
When implementing a feature that spans Server Component -> API -> Database, verify:
| Layer | Check |
| ---------------- | ------------------------------------------------------------------ |
| Server Component | Data fetched correctly? Props serializable? No client-only APIs? |
| Client Component | Loading/error states handled? React Query cache invalidated? |
| oRPC Procedure | Input validated? Auth checked? Output schema matches? |
| Database Query | No N+1 queries? Proper indexes? Transactions where needed? |
| Zod Schemas | Input and output schemas consistent? Date/null handling correct? |
---
## Anti-Patterns to Avoid
### Redefining Backend Types
```typescript
// DON'T: Manually define types that mirror Zod schemas
interface Item {
id: string;
name: string;
createdAt: Date;
}
// DO: Import or infer from the source of truth
import type { Item } from "@your-app/api/modules/items/types";
// or
type Item = Awaited<ReturnType<(typeof orpcClient)["items"]["get"]>>["item"];
```
### Manual Query Keys
```typescript
// DON'T: Manually construct query keys
queryClient.invalidateQueries({ queryKey: ["items", "list"] });
// DO: Use oRPC generated keys
queryClient.invalidateQueries({ queryKey: orpc.items.list.key() });
```
### Unnecessary Client Components
```typescript
// DON'T: Mark everything as 'use client'
'use client';
export function ItemCard({ item }) {
return <div>{item.name}</div>; // No interactivity needed!
}
// DO: Keep as Server Component when possible
export function ItemCard({ item }) {
return <div>{item.name}</div>;
}
```
### Fetch in Client Components When Server Would Work
```typescript
// DON'T: Fetch in Client Component when data could come from Server Component
'use client';
export function ItemList() {
const { data } = useQuery(orpc.items.list.queryOptions({ input: {} }));
return <ul>{data?.items.map(...)}</ul>;
}
// DO: Fetch in Server Component, pass as props (when no interactivity needed)
export async function ItemList() {
const data = await orpcClient.items.list({});
return <ul>{data.items.map(...)}</ul>;
}
```
---
## When to Use This Checklist
| Trigger | Action |
| ------------------------------------------ | ------------------------- |
| About to add a constant | Run through Section 1 |
| About to implement logic | Run through Section 2 |
| About to define a type or schema | Run through Section 3 |
| About to create a component | Run through Section 4 |
| About to add an oRPC procedure | Run through Section 5 |
| About to add a dependency | Run through Section 6 |
| Feels like you've seen similar code before | **STOP** and search first |
---
## Relationship to Other Guides
| Guide | Focus | Timing |
| ------------------------------------------------------------- | ------------------------- | --------------------------- |
| **Pre-Implementation Checklist** (this) | Questions before coding | Before writing code |
| [Cross-Layer Thinking Guide](./cross-layer-thinking-guide.md) | Data flow across layers | Complex feature planning |
**Ideal workflow:**
1. Read this checklist before coding
2. Use Cross-Layer guide for features spanning multiple layers
---
## Lessons Learned
| Date | Issue | Lesson |
| ---- | ---------------------------------------------- | --------------------------------------------------------------------- |
| - | Zod schema redefined in frontend and backend | Always derive frontend types from the API package's Zod schemas |
| - | `useQuery` used where Server Component sufficed | Ask "does this need interactivity?" before reaching for React Query |
| - | Manual query keys diverged from oRPC keys | Always use `orpc.xxx.key()` or `orpc.xxx.queryKey()` for cache ops |
| - | Type defined in both app and api package | Cross-boundary types must be defined once in the API module |
---
**Core Principle**: 5 minutes of checklist thinking saves 50 minutes of refactoring.