Kanban App — Architecture
Last verified: 2026-03-12 Target:
apps/kanbanCompanion:packages/app-kanban
Overview
apps/kanban is a Next.js 15 App Router application that provides an internal project-management board for the HappyClient monorepo. It has two distinct responsibilities:
- Viewing and editing the kanban board — tasks and milestones stored as Markdown files in
.kanbn/ - Task claiming API — a REST endpoint agents call to atomically mark a task "In Progress" via the GitHub REST API
The app is deployed on Vercel at port 3011. It runs in Node.js runtime (not Edge) to support filesystem I/O.
Directory Structure
apps/kanban/
├── app/
│ ├── page.tsx # Root — board view (redirects to same as /tasks)
│ ├── layout.tsx # Root layout with HeaderNav
│ ├── globals.css
│ ├── health/route.ts # GET /health — full health check (core/server)
│ ├── actions/
│ │ └── tasks.ts # Server Actions: updateTaskAction, updateTaskFrontmatterAction
│ ├── api/
│ │ ├── health/route.ts # GET /api/health — simple "ok" health probe
│ │ ├── info/route.ts # GET /api/info — service info (build SHA, env)
│ │ └── tasks/[id]/claim/ # POST /api/tasks/{id}/claim — GitHub REST-based claiming
│ ├── tasks/
│ │ ├── page.tsx # Task board with milestone filter
│ │ └── [id]/page.tsx # Task detail (3-level ID lookup)
│ └── milestones/
│ ├── page.tsx # Milestones board
│ ├── new/page.tsx # New milestone form (Server Action)
│ └── [id]/page.tsx # Milestone detail with embedded task board
├── components/
│ ├── HeaderNav.tsx # Sticky nav (Tasks | Milestones | New milestone)
│ ├── RoadmapBoardClient.tsx # "use client" shell for dynamic import
│ ├── RoadmapBoard.tsx # Main board UI: filters + tab switcher + DnD
│ ├── KanbanBoard.tsx # Status / priority / phase column view
│ ├── OwnerBoard.tsx # Finisher (owner) column view
│ ├── ExecutorBoard.tsx # Executor column view
│ ├── ReviewBoard.tsx # Review column view
│ ├── DeliveryBoard.tsx # Delivery mode column view
│ ├── MilestoneBoardClient.tsx # "use client" shell for milestone board
│ ├── MilestoneBoard.tsx # Milestone cards grid
│ ├── TaskCard.tsx # Task card with app-color badges
│ └── TaskDetail.tsx # Task detail panel (markdown body + metadata)
└── lib/
└── env.ts # getKanbanEnvironment() — lazy singleton
packages/app-kanban/
├── src/
│ ├── types/index.ts # Zod schemas + TypeScript types
│ ├── server/index.ts # FS operations: load, create, update tasks/milestones
│ └── web/
│ ├── index.ts # Public exports
│ ├── kanban-board.tsx # KanbanBoard component
│ ├── task-card.tsx # TaskCard component
│ ├── owner-board.tsx # OwnerBoard component
│ ├── executor-board.tsx # ExecutorBoard component
│ ├── review-board.tsx # ReviewBoard component
│ ├── delivery-board.tsx # DeliveryBoard component
│ ├── badges.tsx # BadgePill, getBadgeConfig
│ └── utils.ts # groupTasksByStatus, sortTasksByPriority, appColorClasses
└── package.json
Module Overview
Primary Data Flow
Integration Map
Task Data Model
Tasks are stored as Markdown files with YAML frontmatter. The schema is enforced by TaskFrontmatterSchema (Zod) in packages/app-kanban/src/types/index.ts.
---
id: fix-video-upload-bug
title: "Fix video upload race condition"
status: In Progress # Backlog | Ready | In Progress | Done | Archived
priority: P1 # P0 | P1 | P2 | P3
executor: agent # agent | Toon | Jonathan
finisher: Toon # Toon | Jonathan
review: required # none | optional | required | Jonathan
deliveryMode: kiss-first # kiss-first | refined
phase: ready # requirements | prd | ready | implementation
summary: >-
One-line summary shown on cards
milestone: agentic-simple-work-v1-verify
impactedApps:
- branded-video-flow
assignees: []
dependsOn: []
prd:
needed: maybe
owner: agent
createdAt: '2026-03-01T00:00:00.000Z'
updatedAt: '2026-03-12T00:00:00.000Z'
---
## Context
...
## Why
...
Milestone Data Model
Milestones live in .kanbn/milestones/*.md and carry richer metadata than tasks. Milestone summaries are computed on-the-fly from task statistics if a summary field is not set.
---
id: agentic-simple-work-v1-verify
title: "Agentic Simple Work v1 — Verify"
status: Backlog
priority: P1
endDate: '2026-04-01T00:00:00.000Z'
forWho: "Jonathan"
why: "..."
definitionOfDone: "..."
requirements: "..."
createdAt: '2026-03-01T00:00:00.000Z'
updatedAt: '2026-03-12T00:00:00.000Z'
---
Board Views
The RoadmapBoard component supports 7 view tabs, toggled client-side:
| Tab | Component | Groups by |
|---|---|---|
status | KanbanBoard | Backlog → Ready → In Progress → Done |
priority | KanbanBoard | P0 → P1 → P2 → P3 |
phase | KanbanBoard | requirements → prd → ready → implementation |
owner | OwnerBoard | finisher (Toon / Jonathan) |
executor | ExecutorBoard | executor (agent / Toon / Jonathan) |
review | ReviewBoard | review level |
delivery | DeliveryBoard | delivery mode (kiss-first / refined) |
Filters available (multi-select):
- By impacted app (colored badges per app)
- By milestone
- By assignee
Drag-and-drop (via @dnd-kit/core) is enabled only when NODE_ENV === "development". In production, the board is read-only. Task drops call updateTaskAction (Server Action), which calls updateTaskFrontmatterById to write the .md file in place.
Task Claiming API
POST /api/tasks/[id]/claim atomically marks a task as "In Progress" on the main branch using the GitHub REST API. It runs on Vercel (no git CLI needed). Full protocol documented in kanban-task-claiming.md.
Key points:
- Requires
GITHUB_TOKENenv var (PAT withcontents:write+pull_requests:write) - Creates a
kanban-claim/{id}-{date}-{shortId}branch - Commits updated frontmatter (status → In Progress, updatedAt → now, optional assignee)
- Opens a PR with
skip-deploy+auto-mergelabels → merges silently - Returns
{ ok, taskId, branch, prUrl, prNumber }
Environment Variables
| Variable | Required | Purpose |
|---|---|---|
ENVIRONMENT | No | local or cloud |
DEPLOYMENT_ENVIRONMENT | No | local|preview|staging|prod |
GITHUB_TOKEN | Required for claim API | PAT for GitHub REST API (repo contents + PRs) |
GITHUB_REPOSITORY | No | owner/repo — auto-detected from VERCEL_GIT_* or defaults to happy-client/happyclient-monorepo |
BUILD_SHA, BUILD_REF, BUILD_TIME | No | Build metadata for /api/info |
VERCEL_GIT_COMMIT_SHA, VERCEL_GIT_COMMIT_REF | No | Vercel-provided fallbacks |
VERCEL_GIT_REPO_OWNER, VERCEL_GIT_REPO_SLUG | No | Auto-detection of GITHUB_REPOSITORY |
File Loading Strategy
packages/app-kanban/src/server/index.ts uses fast-glob + gray-matter for all file I/O:
loadKanbanTasks()— globs.kanbn/tasks/**/*.md, parses each file, validates frontmatter with Zod, skips invalid files (resilient)loadKanbanMilestones()— globs.kanbn/milestones/**/*.md, enriches with computed task summariesloadKanbanMilestoneById(id)— loads all milestones, finds by idcreateMilestoneFile()— validates frontmatter, uses gray-matter to write to.kanbn/milestones/{id}.mdupdateTaskFrontmatterById()— globs all task files, finds by id, re-writes with updated frontmatter
Repo root discovery: findRepoRootDir() walks up directories looking for .kanbn/ folder, pnpm-workspace.yaml, or .git. Used automatically when repoRootDir is not passed explicitly.
Limitation: No caching — every page load re-reads all task files from disk. On Vercel, the filesystem is the deployment snapshot, so reads are fast but unbounded as task count grows.
Nightly Maintenance Interactions
The kanban app interacts with nightly workers in two ways:
- As the claim endpoint: Claude agents call
POST /api/tasks/{id}/claimbefore starting any task (see CLAUDE.md Kanban Task Lifecycle) - As a data source: The
.kanbn/directory is read/written by all nightly workers:- ④ Kanban Hygiene — cross-references task statuses with merged PRs
- ⑤ Task Worker — reads
agent-queue.json, picks tasks to implement - ⑦ Planning Worker — writes
agent-queue.jsonproposals - ⑥ Architecture Review — writes new tasks to
.kanbn/tasks/
See nightly-maintenance-architecture.md for the full pipeline.
Known Limitations & Gaps
| Area | Current State | Notes |
|---|---|---|
| Read-only in production | D&D task updates gated behind NODE_ENV === "development" | Production board cannot be edited through the UI |
| No file read caching | Every request globs and reads all .md files | Acceptable at current scale; may become slow with 500+ tasks |
path.resolve(cwd, "../../") in milestone creation | New milestone form hardcodes repo root as process.cwd() two levels up | Fragile if deployment CWD changes; findRepoRootDir() would be safer |
| Dual health routes | /health (full) and /api/health (simple) both exist | /api/health is the simple probe; /health returns structured core health response |
appColorClasses incomplete | Color map covers 7 apps; hello, admin, demo, links missing | Missing apps display neutral badge styling |