Files
taskpile/CLAUDE.md
2026-04-08 11:24:36 +00:00

3.9 KiB
Raw Blame History

Taskpile

A task manager with force-directed graph visualization.

Architecture

  • Frontend: Next.js 14 (App Router) + React 18 + Tailwind CSS 3 + TypeScript
  • Backend: Rust (Axum 0.7) + SQLite (via SQLx)
  • Graph: react-force-graph-2d for force-directed visualization

Project Structure

frontend/
  src/app/page.tsx          — Main page: tabs, panels, task state management
  src/app/layout.tsx        — Root layout
  src/components/
    GraphView.tsx            — Force graph with node selection, drag-to-center
    TaskList.tsx             — Pending/completed task list with selection
    TaskItem.tsx             — Individual task card
    AddTaskForm.tsx          — New task form
    TaskDetailPanel.tsx      — Right panel: selected task details
    UserPanel.tsx            — Left panel: user profile (example data)
    ForceGraphClient.tsx     — ForceGraph2D ref wrapper for dynamic import
  src/lib/
    api.ts                   — API client (fetch wrappers)
    types.ts                 — TypeScript interfaces
  src/__tests__/
    unit/                    — Jest unit tests (API, TaskItem)
    e2e/                     — Jest integration tests (full user flows)

backend/
  src/main.rs               — Axum server on port 3001
  src/models.rs             — Task, GraphNode, GraphEdge structs
  src/db.rs                 — SQLite pool + migrations
  src/graph.rs              — Deterministic edge generation (~30% pairs)
  src/routes/tasks.rs       — CRUD: GET/POST /api/tasks, PATCH/DELETE /api/tasks/:id
  src/routes/graph.rs       — GET /api/graph
  tests/integration_test.rs — Axum integration tests

Running

# Backend (port 3001)
cd backend && cargo run

# Frontend (port 3003, proxies /api to backend)
cd frontend && npm run dev -- -p 3003

Port 3000 is used by Gitea on this machine — use 3003 for the frontend.

Testing

# Frontend tests
cd frontend && npx jest

# Backend tests
cd backend && cargo test

Key Design Decisions

  • Task IDs are UUIDs (TEXT in SQLite, string from backend). Frontend Task.id is typed as number but actually receives strings — selection uses string comparison throughout.
  • Graph tab and task list are switched via tabs in the center area. Left panel (user info) and right panel (task details) are independently foldable.
  • Selecting a task (from list or graph node click) triggers a 3-phase animation: (1) charge force jumps to -600 so other nodes repel outward immediately, (2) after 80ms the selected node slides to canvas center over 500ms with cubic ease-out, (3) charge restores to -120 and the graph stabilizes. The node stays pinned (fx/fy) until a different task is selected.
  • Both views (task list and graph) are always mounted using absolute inset-0 with opacity/pointer-events toggle — never hidden. This ensures GraphView always has real canvas dimensions from page load, so the force simulation runs correctly in the background.
  • ForceGraph2D canvas dimensions are driven by a ResizeObserver on the container div. Canvas is only mounted after the first measurement to avoid the 300×300 default size.
  • Graph re-fits on tab switch (isVisible effect) and on panel resize (dimensions effect). When a node is selected, zoomToFit is suppressed to avoid fighting the pin animation.
  • Panel transitions are 150ms CSS opacity. When switching to graph tab with a pending node selection, animation is delayed 400ms to let the re-fit settle first.

API

All endpoints under /api:

Method Path Description
GET /tasks List all tasks
POST /tasks Create task {title, description?}
PATCH /tasks/:id Update task {title?, description?, completed?}
DELETE /tasks/:id Delete task
GET /graph Get nodes + weighted edges

Proxy

Do not use system proxy env vars when testing the app locally — curl --noproxy '*' or equivalent.