Skip to content

joshcox/hello-world

Repository files navigation

Todo Demo

A modern, full-stack todo and note-taking application built with Next.js, demonstrating clean architecture patterns and best practices for React applications.

Overview

This application provides a personal assistant interface for managing todos and notes with rich markdown editing capabilities. It showcases a well-structured architecture following the MVP (Model-View-Presenter) pattern with headless UI hooks, ensuring separation of concerns and maintainability.

Key Features

  • Todo Management: Create, update, complete, and delete todos with due dates
  • Note Taking: Rich markdown editor with Mermaid diagram support
  • Zen Mode: Distraction-free editing experience for notes
  • Search: Filter todos and notes by search query
  • Responsive UI: Clean, modern interface built with Tailwind CSS and Radix UI

Architecture Overview

This application follows a Model-View-Presenter (MVP) architecture pattern, ensuring clear separation of concerns and maintainable code structure.

Architectural Layers

Layer Responsibility Location Pattern
Model Encapsulates business logic and data structures src/lib/models/ Domain entities (Todo, Note)
View Renders UI components (pure functions of props) src/components/ No data fetching or state management
Presenter Handles data fetching, state management, routing, and behavior src/lib/hooks/use-{domain}-page.ts Headless UI hooks

Technology Stack

Technology Purpose Location
Next.js React framework for server-side rendering, routing, and API routes src/app/
React UI component library src/components/
TypeScript Type safety and developer experience All .ts/.tsx files
TanStack Query Data fetching, caching, and synchronization src/lib/query/
TanStack Form Headless form state management and validation Form components
Drizzle ORM Type-safe database queries and migrations src/lib/db/
SQLite (Bun) Embedded database for local data persistence todo.db
Tailwind CSS Utility-first CSS framework for styling src/app/globals.css
Radix UI Headless UI primitives (dialog, checkbox, label) src/components/ui/
MDXEditor Rich markdown editor with plugin support src/components/markdown/
Zod Schema validation and type inference Models and forms

Design Patterns

Headless UI Hooks (MVP-003)

Headless UI hooks are custom React hooks that encapsulate all application concerns (data fetching, state, routing, mutations) without any UI rendering logic. They return a structured object containing data, state, actions, and pending states.

Pattern: Presenter hooks (use-{domain}-page.ts) encapsulate data, state, routing, and behavior. Pages consume hooks and compose pure UI components.

Example Structure:

// src/lib/hooks/use-todos-page.ts
export function useTodosPage() {
  const { data: todos, ... } = useTodos();
  const createMutation = useCreateTodo();
  
  return {
    todos,
    createTodo: createMutation.mutate,
    isPending: createMutation.isPending,
    // ... other state and actions
  };
}

Benefits:

  • Separation of concerns: UI logic separate from business logic
  • Testability: Hooks can be tested independently
  • Reusability: Same hook can power different UI implementations
  • Type safety: Full TypeScript support

Pure Components (VIEW-002)

Pure components receive all data and behavior via props. They have no direct data fetching, state management, or side effects.

Pattern: Components in src/components/{domain}/ or src/components/ui/ are pure functions of their props.

Example:

// Pure component - receives everything via props
function TodoItem({ todo, onToggle, onDelete }: TodoItemProps) {
  return (
    <div>
      <input type="checkbox" checked={todo.completed} onChange={onToggle} />
      <span>{todo.title}</span>
      <button onClick={onDelete}>Delete</button>
    </div>
  );
}

Container Components (VIEW-001)

Container components handle data retrieval and orchestration. Pages (src/app/) are always containers.

Pattern: Pages use presenter hooks and pass data/actions to pure components.

Example:

// Container component - connects presenter to views
export default function TodosPage() {
  const { todos, createTodo, ... } = useTodosPage();
  
  return (
    <div>
      {todos.map(todo => (
        <TodoItem key={todo.id} todo={todo} {...actions} />
      ))}
    </div>
  );
}

High-Level Variants (VIEW-003)

High-Level Variants use discriminated union components to provide a narrow entry point with deep internal variation. Export a single component with a variant prop that dispatches to internal variant implementations.

Pattern: Reduces component proliferation while maintaining type safety.

Example:

// Single component with variant prop
interface NoteProps {
  variant: 'sidebar' | 'view' | 'edit';
  // ... other props
}

export function Note({ variant, ...props }: NoteProps) {
  switch (variant) {
    case 'sidebar': return <SidebarNote {...props} />;
    case 'view': return <ViewNote {...props} />;
    case 'edit': return <EditNote {...props} />;
  }
}

Hook Composition (HOOK-001)

Hook Composition allows composing multiple atomic headless UI hooks into aggregate hooks using an enabled options object. Aggregate hooks default all sub-hooks to false (opt-in pattern).

Pattern: Access composed hooks via namespaced return object.

Example:

const { todos, notes } = useApp({ 
  enabled: { todos: true, notes: true } 
});

Conditional Fetching (HOOK-002)

Conditional Fetching ensures atomic hooks accept enabled?: boolean (defaults to false). Aggregate hooks use nested enabled object with opt-in defaults. Always provide a way to disable fetching to prevent over-fetching.

Form Management (FORM-001)

TanStack Form is used for all form state management, validation, and submission handling. It provides headless form state management with type-safe APIs, field-level validation, and optimized re-renders.

Pattern: All forms use useForm from @tanstack/react-form and form.Field components for field rendering.

Getting Started

Prerequisites

  • Bun (latest) - Runtime and package manager
  • Node.js 18+ (if not using Bun)
  • Git

Installation

  1. Clone the repository:

    git clone <repository-url>
    cd todo-demo
  2. Install dependencies:

    bun install

    Or with npm/yarn/pnpm:

    npm install
    # or
    yarn install
    # or
    pnpm install
  3. Set up the database:

    bun run db:create

    This creates the SQLite database file (todo.db) and initializes the schema.

  4. Run database migrations (if needed):

    bun run db:push

    Or generate migrations:

    bun run db:generate
    bun run db:migrate
  5. Start the development server:

    bun dev

    Or with npm/yarn/pnpm:

    npm run dev
    # or
    yarn dev
    # or
    pnpm dev
  6. Open your browser: Navigate to http://localhost:3000

Development Scripts

Command Description
bun dev Start development server
bun build Build for production
bun start Start production server
bun lint Run ESLint
bun db:generate Generate Drizzle migrations
bun db:migrate Run database migrations
bun db:push Push schema changes to database
bun db:studio Open Drizzle Studio (database GUI)
bun db:create Create database and tables

Project Structure

todo-demo/
├── .cursor/                    # Cursor IDE rules and architecture docs
│   └── rules/
│       ├── architecture.mdc   # Architecture decisions
│       ├── design.mdc          # Design patterns and strategies
│       └── strategy/           # Detailed strategy documentation
├── src/
│   ├── app/                    # Next.js App Router pages (containers)
│   │   ├── layout.tsx          # Root layout
│   │   ├── page.tsx            # Todos page
│   │   ├── todos/              # Todos routes
│   │   └── notes/              # Notes routes
│   ├── components/             # React components
│   │   ├── markdown/           # Markdown editor components
│   │   ├── navigation/         # Navigation components
│   │   └── ui/                 # Reusable UI primitives
│   └── lib/                    # Core application logic
│       ├── actions/            # Server actions
│       ├── db/                 # Database schema and connection
│       ├── hooks/              # Presenter hooks (use-*-page.ts)
│       ├── models/             # Domain models
│       └── query/              # TanStack Query hooks
├── drizzle/                    # Database migrations
├── scripts/                    # Utility scripts
│   └── create-db.ts           # Database initialization
├── drizzle.config.ts          # Drizzle ORM configuration
├── next.config.ts             # Next.js configuration
├── package.json               # Dependencies and scripts
└── todo.db                    # SQLite database file

Key Directories

  • src/app/: Next.js pages (container components) that use presenter hooks
  • src/components/: Pure UI components organized by domain
  • src/lib/hooks/: Presenter hooks that encapsulate data fetching and state
  • src/lib/models/: Domain models with business logic
  • src/lib/actions/: Server actions for mutations
  • src/lib/query/: TanStack Query hooks for data fetching
  • src/lib/db/: Database schema and connection logic

Development Workflow

Adding a New Feature

  1. Define the Model (src/lib/models/):

    • Create domain model with business logic
    • Define Zod schemas for validation
  2. Create Query Hooks (src/lib/query/):

    • Create TanStack Query hooks for data fetching
    • Use conditional fetching pattern
  3. Create Server Actions (src/lib/actions/):

    • Define mutations for create/update/delete operations
    • Use Zod for validation
  4. Create Presenter Hook (src/lib/hooks/):

    • Compose query hooks and mutations
    • Return structured object with data, state, and actions
  5. Create UI Components (src/components/):

    • Build pure components that receive props
    • Use high-level variants pattern if needed
  6. Create Page (src/app/):

    • Use presenter hook
    • Compose pure components
    • Handle routing and URL state

Database Changes

  1. Update Schema (src/lib/db/schema.ts):

    export const newTable = sqliteTable('new_table', {
      // ... columns
    });
  2. Generate Migration:

    bun run db:generate
  3. Apply Migration:

    bun run db:push

    Or use migrations:

    bun run db:migrate

Code Style

  • Follow TypeScript strict mode
  • Use functional components with hooks
  • Prefer composition over inheritance
  • Keep components small and focused
  • Use meaningful variable and function names
  • Document complex logic with comments

Architecture Documentation

For detailed architecture decisions and patterns, see:

  • Architecture: .cursor/rules/architecture.mdc
  • Design Patterns: .cursor/rules/design.mdc
  • Strategy Guides: .cursor/rules/strategy/

Each architectural decision is uniquely identified (e.g., MVP-001, HOOK-001) and linked to detailed strategy documentation.

Contributing

When contributing to this project:

  1. Review relevant architecture patterns in .cursor/rules/
  2. Follow the established patterns (MVP, Headless UI, etc.)
  3. Ensure consistency with existing code structure
  4. Update architecture documentation when introducing new patterns
  5. Assign unique IDs to new architectural decisions

License

[Add your license here]

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published