## Bug Fixes ### Note Card Actions - Fix broken size change functionality (missing state declaration) - Implement React 19 useOptimistic for instant UI feedback - Add startTransition for non-blocking updates - Ensure smooth animations without page refresh - All note actions now work: pin, archive, color, size, checklist ### Markdown LaTeX Rendering - Add remark-math and rehype-katex plugins - Support inline equations with dollar sign syntax - Support block equations with double dollar sign syntax - Import KaTeX CSS for proper styling - Equations now render correctly instead of showing raw LaTeX ## Technical Details - Replace undefined currentNote references with optimistic state - Add optimistic updates before server actions for instant feedback - Use router.refresh() in transitions for smart cache invalidation - Install remark-math, rehype-katex, and katex packages ## Testing - Build passes successfully with no TypeScript errors - Dev server hot-reloads changes correctly
854 lines
14 KiB
Markdown
854 lines
14 KiB
Markdown
# Development Guide - keep-notes (Memento Web App)
|
|
|
|
## Overview
|
|
|
|
Complete development guide for the Memento web application. Covers setup, development workflow, debugging, testing, and common tasks.
|
|
|
|
---
|
|
|
|
## Prerequisites
|
|
|
|
### Required Software
|
|
|
|
| Tool | Version | Purpose |
|
|
|------|---------|---------|
|
|
| **Node.js** | 20+ | JavaScript runtime |
|
|
| **npm** | Latest | Package manager |
|
|
| **Git** | Latest | Version control |
|
|
|
|
### Recommended Tools
|
|
|
|
| Tool | Purpose |
|
|
|------|---------|
|
|
| VS Code | IDE with great TypeScript/React support |
|
|
| Prisma Studio | Database GUI for viewing/editing data |
|
|
| Postman/Insomnia | API testing |
|
|
| Playwright VS Code | E2E test debugging |
|
|
|
|
---
|
|
|
|
## Initial Setup
|
|
|
|
### 1. Clone Repository
|
|
|
|
```bash
|
|
git clone <repository-url>
|
|
cd Keep
|
|
```
|
|
|
|
### 2. Install Dependencies
|
|
|
|
```bash
|
|
cd keep-notes
|
|
npm install
|
|
```
|
|
|
|
**Expected packages:**
|
|
- React 19.2.3
|
|
- Next.js 16.1.1
|
|
- Prisma 5.22.0
|
|
- 100+ dependencies
|
|
|
|
### 3. Database Setup
|
|
|
|
```bash
|
|
# Generate Prisma client
|
|
npm run db:generate
|
|
|
|
# Run migrations (if needed)
|
|
npx prisma migrate dev
|
|
|
|
# Open Prisma Studio (optional)
|
|
npx prisma studio
|
|
```
|
|
|
|
**Prisma Studio:**
|
|
- Opens at http://localhost:5555
|
|
- View and edit database records
|
|
- Visual database schema
|
|
|
|
### 4. Environment Configuration
|
|
|
|
Create `.env` file in `keep-notes/`:
|
|
|
|
```bash
|
|
# Database
|
|
DATABASE_URL="file:./prisma/dev.db"
|
|
|
|
# NextAuth
|
|
NEXTAUTH_SECRET="your-secret-key-here"
|
|
NEXTAUTH_URL="http://localhost:3000"
|
|
|
|
# Email (optional - for password reset)
|
|
SMTP_HOST="smtp.example.com"
|
|
SMTP_PORT="587"
|
|
SMTP_USER="your-email@example.com"
|
|
SMTP_PASS="your-password"
|
|
SMTP_FROM="noreply@memento.app"
|
|
|
|
# AI Providers (optional)
|
|
OPENAI_API_KEY="sk-..."
|
|
OLLAMA_API_URL="http://localhost:11434"
|
|
```
|
|
|
|
**Generate NEXTAUTH_SECRET:**
|
|
```bash
|
|
openssl rand -base64 32
|
|
```
|
|
|
|
---
|
|
|
|
## Development Workflow
|
|
|
|
### Start Development Server
|
|
|
|
```bash
|
|
npm run dev
|
|
```
|
|
|
|
**Server starts at:** http://localhost:3000
|
|
|
|
**Features:**
|
|
- Hot reload (Fast Refresh)
|
|
- TypeScript checking
|
|
- Source maps for debugging
|
|
- API routes available
|
|
|
|
### File Watching
|
|
|
|
Next.js automatically watches for changes in:
|
|
- `app/` directory
|
|
- `components/` directory
|
|
- `lib/` directory
|
|
- `public/` directory
|
|
|
|
**Automatic Actions:**
|
|
- Recompile changed files
|
|
- Refresh browser (HMR)
|
|
- Regenerate Prisma client if schema changes
|
|
|
|
---
|
|
|
|
## Common Development Tasks
|
|
|
|
### 1. Create a New Component
|
|
|
|
```bash
|
|
# Create component file
|
|
touch components/my-component.tsx
|
|
```
|
|
|
|
```typescript
|
|
// components/my-component.tsx
|
|
export default function MyComponent() {
|
|
return (
|
|
<div>
|
|
<h1>Hello from Memento!</h1>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
**Usage:**
|
|
```tsx
|
|
import MyComponent from '@/components/my-component'
|
|
|
|
export default function Page() {
|
|
return <MyComponent />
|
|
}
|
|
```
|
|
|
|
### 2. Add a New API Route
|
|
|
|
```bash
|
|
# Create API route directory
|
|
mkdir -p app/api/my-resource
|
|
touch app/api/my-resource/route.ts
|
|
```
|
|
|
|
```typescript
|
|
// app/api/my-resource/route.ts
|
|
import { NextResponse } from 'next/server'
|
|
|
|
export async function GET() {
|
|
return NextResponse.json({ message: 'Hello' })
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
const body = await request.json()
|
|
return NextResponse.json({ received: body })
|
|
}
|
|
```
|
|
|
|
**Access:** http://localhost:3000/api/my-resource
|
|
|
|
### 3. Add a New Server Action
|
|
|
|
```bash
|
|
# Create action file
|
|
touch app/actions/my-action.ts
|
|
```
|
|
|
|
```typescript
|
|
'use server'
|
|
|
|
import { prisma } from '@/lib/prisma'
|
|
import { revalidatePath } from 'next/cache'
|
|
|
|
export async function myAction(id: string) {
|
|
const result = await prisma.note.update({
|
|
where: { id },
|
|
data: { /* updates */ }
|
|
})
|
|
|
|
revalidatePath('/')
|
|
return result
|
|
}
|
|
```
|
|
|
|
**Usage in Component:**
|
|
```tsx
|
|
import { myAction } from '@/app/actions/my-action'
|
|
|
|
export default function MyComponent() {
|
|
async function handleSubmit() {
|
|
await myAction('note-id')
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. Modify Database Schema
|
|
|
|
```bash
|
|
# 1. Edit schema
|
|
nano prisma/schema.prisma
|
|
|
|
# 2. Create migration
|
|
npx prisma migrate dev --name my_changes
|
|
|
|
# 3. Update client
|
|
npm run db:generate
|
|
```
|
|
|
|
**Example Schema Change:**
|
|
```prisma
|
|
model Note {
|
|
// ... existing fields
|
|
newField String? // Add new optional field
|
|
}
|
|
```
|
|
|
|
### 5. Run Tests
|
|
|
|
```bash
|
|
# Run all E2E tests
|
|
npm test
|
|
|
|
# Run tests with UI
|
|
npm run test:ui
|
|
|
|
# Run tests in headed mode (see browser)
|
|
npm run test:headed
|
|
```
|
|
|
|
**Test Reports:**
|
|
- HTML: `playwright-report/index.html`
|
|
- Results: `test-results/.last-run.json`
|
|
|
|
---
|
|
|
|
## Database Operations
|
|
|
|
### View Data with Prisma Studio
|
|
|
|
```bash
|
|
npx prisma studio
|
|
# Opens at http://localhost:5555
|
|
```
|
|
|
|
**Features:**
|
|
- Browse tables
|
|
- Edit records
|
|
- Add records
|
|
- Filter and query
|
|
|
|
### Manual Database Queries
|
|
|
|
```bash
|
|
# Open SQLite CLI
|
|
sqlite3 keep-notes/prisma/dev.db
|
|
|
|
# Query notes
|
|
SELECT * FROM Note LIMIT 10;
|
|
|
|
# Query users
|
|
SELECT * FROM User;
|
|
|
|
# Exit
|
|
.quit
|
|
```
|
|
|
|
### Reset Database
|
|
|
|
```bash
|
|
# Delete database file
|
|
rm keep-notes/prisma/dev.db
|
|
|
|
# Re-run migrations
|
|
npx prisma migrate dev
|
|
|
|
# Seed data (if you have a seed script)
|
|
npx prisma db seed
|
|
```
|
|
|
|
---
|
|
|
|
## Debugging
|
|
|
|
### Server-Side Debugging
|
|
|
|
**VS Code Launch Config:**
|
|
|
|
Create `.vscode/launch.json`:
|
|
```json
|
|
{
|
|
"version": "0.2.0",
|
|
"configurations": [
|
|
{
|
|
"name": "Next.js: debug server-side",
|
|
"type": "node-terminal",
|
|
"request": "launch",
|
|
"command": "npm run dev"
|
|
},
|
|
{
|
|
"name": "Next.js: debug client-side",
|
|
"type": "chrome",
|
|
"request": "launch",
|
|
"url": "http://localhost:3000"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Console Logging
|
|
|
|
**Server Components:**
|
|
```typescript
|
|
console.log('Server log', data) // Appears in terminal
|
|
```
|
|
|
|
**Client Components:**
|
|
```typescript
|
|
console.log('Client log', data) // Appears in browser console
|
|
```
|
|
|
|
### Debug API Routes
|
|
|
|
Add logging to API routes:
|
|
```typescript
|
|
export async function GET(request: NextRequest) {
|
|
console.log('GET /api/notes called')
|
|
console.log('Query params:', request.nextUrl.searchParams)
|
|
|
|
const notes = await prisma.note.findMany()
|
|
console.log('Found notes:', notes.length)
|
|
|
|
return NextResponse.json({ data: notes })
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## TypeScript Configuration
|
|
|
|
### Type Checking
|
|
|
|
```bash
|
|
# Type check all files
|
|
npx tsc --noEmit
|
|
|
|
# Type check with watch mode
|
|
npx tsc --noEmit --watch
|
|
```
|
|
|
|
### Common Type Issues
|
|
|
|
**1. Prisma Client Types:**
|
|
```typescript
|
|
import prisma from '@/lib/prisma'
|
|
|
|
// Use Prisma types
|
|
type Note = Prisma.NoteGetPayload<{ include: {} }>
|
|
```
|
|
|
|
**2. Server Actions:**
|
|
```typescript
|
|
'use server'
|
|
|
|
// Server actions must be async
|
|
export async function myAction() {
|
|
// Action logic
|
|
}
|
|
```
|
|
|
|
**3. Component Props:**
|
|
```typescript
|
|
interface MyComponentProps {
|
|
title: string
|
|
count?: number // Optional
|
|
}
|
|
|
|
export default function MyComponent({ title, count = 0 }: MyComponentProps) {
|
|
return <div>{title}: {count}</div>
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Styling Guide
|
|
|
|
### Tailwind CSS Classes
|
|
|
|
**Documentation:** https://tailwindcss.com/docs
|
|
|
|
**Common Patterns:**
|
|
```tsx
|
|
// Spacing
|
|
<div className="p-4 m-2"> // Padding 4, margin 2
|
|
<div className="gap-4"> // Gap between children
|
|
|
|
// Colors
|
|
<div className="bg-blue-500 text-white">
|
|
<div className="text-gray-700 dark:text-gray-300">
|
|
|
|
// Typography
|
|
<h1 className="text-2xl font-bold">
|
|
<p className="text-sm leading-relaxed">
|
|
|
|
// Layout
|
|
<div className="flex flex-col md:flex-row">
|
|
<div className="grid grid-cols-3 gap-4">
|
|
|
|
// Responsive
|
|
<div className="hidden md:block">
|
|
<div className="w-full md:w-1/2">
|
|
```
|
|
|
|
### Custom CSS
|
|
|
|
**Global Styles:** `app/globals.css`
|
|
|
|
**Component-Specific:**
|
|
```tsx
|
|
// Use Tailwind @apply or inline styles
|
|
<div style={{ customProperty: 'value' }} />
|
|
|
|
// Or CSS modules
|
|
import styles from './MyComponent.module.css'
|
|
<div className={styles.container}>
|
|
```
|
|
|
|
---
|
|
|
|
## Working with Prisma
|
|
|
|
### Common Queries
|
|
|
|
**Find all notes for a user:**
|
|
```typescript
|
|
const notes = await prisma.note.findMany({
|
|
where: { userId: session.user.id },
|
|
orderBy: { updatedAt: 'desc' }
|
|
})
|
|
```
|
|
|
|
**Create a note:**
|
|
```typescript
|
|
const note = await prisma.note.create({
|
|
data: {
|
|
title: 'My Note',
|
|
content: 'Note content',
|
|
color: 'blue',
|
|
userId: session.user.id
|
|
}
|
|
})
|
|
```
|
|
|
|
**Update a note:**
|
|
```typescript
|
|
const note = await prisma.note.update({
|
|
where: { id: noteId },
|
|
data: {
|
|
title: 'Updated Title',
|
|
content: 'Updated content'
|
|
}
|
|
})
|
|
```
|
|
|
|
**Delete a note:**
|
|
```typescript
|
|
await prisma.note.delete({
|
|
where: { id: noteId }
|
|
})
|
|
```
|
|
|
|
**Search notes:**
|
|
```typescript
|
|
const notes = await prisma.note.findMany({
|
|
where: {
|
|
OR: [
|
|
{ title: { contains: query, mode: 'insensitive' } },
|
|
{ content: { contains: query, mode: 'insensitive' } }
|
|
]
|
|
}
|
|
})
|
|
```
|
|
|
|
### Transaction Support
|
|
|
|
```typescript
|
|
await prisma.$transaction(async (tx) => {
|
|
// Multiple operations
|
|
await tx.note.create({ data: note1 })
|
|
await tx.note.create({ data: note2 })
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## Authentication Development
|
|
|
|
### NextAuth Configuration
|
|
|
|
**Config File:** `auth.config.ts`
|
|
|
|
**Add OAuth Provider:**
|
|
```typescript
|
|
export const { handlers, signIn, signOut, auth } = NextAuth({
|
|
providers: [
|
|
Google({
|
|
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
}),
|
|
// ... other providers
|
|
],
|
|
})
|
|
```
|
|
|
|
### Protected Routes
|
|
|
|
**Server Component:**
|
|
```tsx
|
|
import { auth } from '@/auth'
|
|
import { redirect } from 'next/navigation'
|
|
|
|
export default async function ProtectedPage() {
|
|
const session = await auth()
|
|
|
|
if (!session) {
|
|
redirect('/login')
|
|
}
|
|
|
|
return <div>Welcome {session.user.name}</div>
|
|
}
|
|
```
|
|
|
|
**API Route:**
|
|
```typescript
|
|
import { auth } from '@/auth'
|
|
|
|
export async function GET() {
|
|
const session = await auth()
|
|
|
|
if (!session?.user) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
}
|
|
|
|
return NextResponse.json({ data: 'secret' })
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## AI Integration Development
|
|
|
|
### Add New AI Provider
|
|
|
|
**1. Create Provider File:**
|
|
```bash
|
|
touch lib/ai/providers/my-provider.ts
|
|
```
|
|
|
|
**2. Implement Provider:**
|
|
```typescript
|
|
// lib/ai/providers/my-provider.ts
|
|
export function createMyProvider() {
|
|
return {
|
|
generateText: async (prompt) => {
|
|
// Call AI API
|
|
return response
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**3. Register in Factory:**
|
|
```typescript
|
|
// lib/ai/factory.ts
|
|
export function getProvider(provider: string) {
|
|
switch (provider) {
|
|
case 'my-provider':
|
|
return createMyProvider()
|
|
// ... existing providers
|
|
}
|
|
}
|
|
```
|
|
|
|
### Use AI SDK
|
|
|
|
```typescript
|
|
import { generateText } from 'ai'
|
|
import { openai } from '@ai-sdk/openai'
|
|
|
|
const response = await generateText({
|
|
model: openai('gpt-4'),
|
|
prompt: 'Generate tags for this note...',
|
|
})
|
|
|
|
console.log(response.text)
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Optimization
|
|
|
|
### 1. Image Optimization
|
|
|
|
```tsx
|
|
import Image from 'next/image'
|
|
|
|
<Image
|
|
src="/image.png"
|
|
alt="Description"
|
|
width={500}
|
|
height={300}
|
|
priority // For above-fold images
|
|
/>
|
|
```
|
|
|
|
### 2. Code Splitting
|
|
|
|
```tsx
|
|
// Dynamic import for heavy components
|
|
import dynamic from 'next/dynamic'
|
|
|
|
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
|
|
loading: () => <div>Loading...</div>,
|
|
ssr: false // Client-only
|
|
})
|
|
```
|
|
|
|
### 3. Server Components (Default)
|
|
|
|
```tsx
|
|
// Server components are default (no 'use client')
|
|
export default async function Page() {
|
|
const data = await fetch('https://api.example.com/data')
|
|
return <div>{data}</div>
|
|
}
|
|
```
|
|
|
|
**Client Component:**
|
|
```tsx
|
|
'use client' // Required for interactivity
|
|
|
|
export default function InteractiveComponent() {
|
|
const [count, setCount] = useState(0)
|
|
return <button onClick={() => setCount(count + 1)}>{count}</button>
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Testing Guide
|
|
|
|
### Write E2E Test
|
|
|
|
**File:** `tests/my-feature.spec.ts`
|
|
|
|
```typescript
|
|
import { test, expect } from '@playwright/test'
|
|
|
|
test('my feature test', async ({ page }) => {
|
|
await page.goto('http://localhost:3000')
|
|
|
|
await page.fill('input[name="email"]', 'test@example.com')
|
|
await page.click('button[type="submit"]')
|
|
|
|
await expect(page).toHaveURL('/dashboard')
|
|
})
|
|
```
|
|
|
|
### Run Specific Test
|
|
|
|
```bash
|
|
npx playwright test tests/my-feature.spec.ts
|
|
```
|
|
|
|
### Debug Tests
|
|
|
|
```bash
|
|
# Run with UI
|
|
npm run test:ui
|
|
|
|
# Run headed mode
|
|
npm run test:headed
|
|
|
|
# Debug mode
|
|
npx playwright test --debug
|
|
```
|
|
|
|
---
|
|
|
|
## Common Issues & Solutions
|
|
|
|
### Issue: Prisma Client Not Generated
|
|
|
|
**Solution:**
|
|
```bash
|
|
npm run db:generate
|
|
```
|
|
|
|
### Issue: Port Already in Use
|
|
|
|
**Solution:**
|
|
```bash
|
|
# Kill process on port 3000
|
|
npx kill-port 3000
|
|
|
|
# Or find and kill manually
|
|
lsof -ti:3000 | xargs kill -9
|
|
```
|
|
|
|
### Issue: Module Not Found
|
|
|
|
**Solution:**
|
|
```bash
|
|
# Clear cache and reinstall
|
|
rm -rf node_modules .next
|
|
npm install
|
|
```
|
|
|
|
### Issue: Database Locked
|
|
|
|
**Solution:**
|
|
```bash
|
|
# Close Prisma Studio or other DB connections
|
|
# Or wait for SQLite to release lock
|
|
```
|
|
|
|
---
|
|
|
|
## Build & Production
|
|
|
|
### Build for Production
|
|
|
|
```bash
|
|
npm run build
|
|
```
|
|
|
|
**Output:** `.next/` directory
|
|
|
|
### Start Production Server
|
|
|
|
```bash
|
|
npm start
|
|
# Runs on port 3000 (or PORT env var)
|
|
```
|
|
|
|
### Environment Variables for Production
|
|
|
|
```bash
|
|
# .env.production
|
|
DATABASE_URL="file:./prisma/dev.db"
|
|
NEXTAUTH_SECRET="production-secret"
|
|
NEXTAUTH_URL="https://your-domain.com"
|
|
```
|
|
|
|
---
|
|
|
|
## Development Tips
|
|
|
|
### 1. Use TypeScript Strict Mode
|
|
|
|
Already enabled in `tsconfig.json`:
|
|
```json
|
|
{
|
|
"compilerOptions": {
|
|
"strict": true
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. ESLint & Prettier
|
|
|
|
```bash
|
|
# Lint code
|
|
npm run lint
|
|
|
|
# Fix linting issues
|
|
npm run lint -- --fix
|
|
```
|
|
|
|
### 3. Git Hooks (Optional)
|
|
|
|
Install husky for pre-commit hooks:
|
|
```bash
|
|
npm install -D husky lint-staged
|
|
npx husky install
|
|
```
|
|
|
|
### 4. VS Code Extensions
|
|
|
|
Recommended:
|
|
- Prisma
|
|
- ESLint
|
|
- Prettier
|
|
- Tailwind CSS IntelliSense
|
|
- TypeScript Vue Plugin (Volar)
|
|
|
|
---
|
|
|
|
## Resources
|
|
|
|
### Documentation
|
|
- Next.js: https://nextjs.org/docs
|
|
- Prisma: https://www.prisma.io/docs
|
|
- React: https://react.dev
|
|
- Tailwind CSS: https://tailwindcss.com/docs
|
|
- Radix UI: https://www.radix-ui.com/primitives
|
|
|
|
### Community
|
|
- Next.js GitHub Discussions
|
|
- Prisma Slack
|
|
- Stack Overflow
|
|
|
|
### Project-Specific
|
|
- API Contracts: `docs/api-contracts-keep-notes.md`
|
|
- Data Models: `docs/data-models.md`
|
|
- Component Inventory: `docs/component-inventory.md`
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
The Memento web application uses a modern Next.js 16 stack with:
|
|
- **App Router** for routing
|
|
- **Server Components** by default
|
|
- **Prisma ORM** for database access
|
|
- **NextAuth** for authentication
|
|
- **Tailwind CSS** for styling
|
|
- **Playwright** for E2E testing
|
|
|
|
This guide provides everything needed for local development, debugging, and testing.
|