Keep/docs/development-guide-keep-notes.md
sepehr 640fcb26f7 fix: improve note interactions and markdown LaTeX support
## 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
2026-01-09 22:13:49 +01:00

14 KiB

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
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

git clone <repository-url>
cd Keep

2. Install Dependencies

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

# Generate Prisma client
npm run db:generate

# Run migrations (if needed)
npx prisma migrate dev

# Open Prisma Studio (optional)
npx prisma studio

Prisma Studio:

4. Environment Configuration

Create .env file in keep-notes/:

# 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:

openssl rand -base64 32

Development Workflow

Start Development Server

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

# Create component file
touch components/my-component.tsx
// components/my-component.tsx
export default function MyComponent() {
  return (
    <div>
      <h1>Hello from Memento!</h1>
    </div>
  )
}

Usage:

import MyComponent from '@/components/my-component'

export default function Page() {
  return <MyComponent />
}

2. Add a New API Route

# Create API route directory
mkdir -p app/api/my-resource
touch app/api/my-resource/route.ts
// 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

# Create action file
touch app/actions/my-action.ts
'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:

import { myAction } from '@/app/actions/my-action'

export default function MyComponent() {
  async function handleSubmit() {
    await myAction('note-id')
  }
}

4. Modify Database Schema

# 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:

model Note {
  // ... existing fields
  newField String?  // Add new optional field
}

5. Run Tests

# 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

npx prisma studio
# Opens at http://localhost:5555

Features:

  • Browse tables
  • Edit records
  • Add records
  • Filter and query

Manual Database Queries

# 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

# 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:

{
  "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:

console.log('Server log', data)  // Appears in terminal

Client Components:

console.log('Client log', data)  // Appears in browser console

Debug API Routes

Add logging to API routes:

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

# Type check all files
npx tsc --noEmit

# Type check with watch mode
npx tsc --noEmit --watch

Common Type Issues

1. Prisma Client Types:

import prisma from '@/lib/prisma'

// Use Prisma types
type Note = Prisma.NoteGetPayload<{ include: {} }>

2. Server Actions:

'use server'

// Server actions must be async
export async function myAction() {
  // Action logic
}

3. Component Props:

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:

// 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:

// 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:

const notes = await prisma.note.findMany({
  where: { userId: session.user.id },
  orderBy: { updatedAt: 'desc' }
})

Create a note:

const note = await prisma.note.create({
  data: {
    title: 'My Note',
    content: 'Note content',
    color: 'blue',
    userId: session.user.id
  }
})

Update a note:

const note = await prisma.note.update({
  where: { id: noteId },
  data: {
    title: 'Updated Title',
    content: 'Updated content'
  }
})

Delete a note:

await prisma.note.delete({
  where: { id: noteId }
})

Search notes:

const notes = await prisma.note.findMany({
  where: {
    OR: [
      { title: { contains: query, mode: 'insensitive' } },
      { content: { contains: query, mode: 'insensitive' } }
    ]
  }
})

Transaction Support

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:

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:

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:

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:

touch lib/ai/providers/my-provider.ts

2. Implement Provider:

// lib/ai/providers/my-provider.ts
export function createMyProvider() {
  return {
    generateText: async (prompt) => {
      // Call AI API
      return response
    }
  }
}

3. Register in Factory:

// lib/ai/factory.ts
export function getProvider(provider: string) {
  switch (provider) {
    case 'my-provider':
      return createMyProvider()
    // ... existing providers
  }
}

Use AI SDK

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

import Image from 'next/image'

<Image
  src="/image.png"
  alt="Description"
  width={500}
  height={300}
  priority // For above-fold images
/>

2. Code Splitting

// 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)

// 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:

'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

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

npx playwright test tests/my-feature.spec.ts

Debug Tests

# 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:

npm run db:generate

Issue: Port Already in Use

Solution:

# 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:

# Clear cache and reinstall
rm -rf node_modules .next
npm install

Issue: Database Locked

Solution:

# Close Prisma Studio or other DB connections
# Or wait for SQLite to release lock

Build & Production

Build for Production

npm run build

Output: .next/ directory

Start Production Server

npm start
# Runs on port 3000 (or PORT env var)

Environment Variables for Production

# .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:

{
  "compilerOptions": {
    "strict": true
  }
}

2. ESLint & Prettier

# Lint code
npm run lint

# Fix linting issues
npm run lint -- --fix

3. Git Hooks (Optional)

Install husky for pre-commit hooks:

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

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.