All docs/kanban

docs/architecture/kanban-app-architecture.md

Kanban App — Architecture

Last verified: 2026-03-12 Target: apps/kanban Companion: 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:

  1. Viewing and editing the kanban board — tasks and milestones stored as Markdown files in .kanbn/
  2. 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:

TabComponentGroups by
statusKanbanBoardBacklog → Ready → In Progress → Done
priorityKanbanBoardP0 → P1 → P2 → P3
phaseKanbanBoardrequirements → prd → ready → implementation
ownerOwnerBoardfinisher (Toon / Jonathan)
executorExecutorBoardexecutor (agent / Toon / Jonathan)
reviewReviewBoardreview level
deliveryDeliveryBoarddelivery 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_TOKEN env var (PAT with contents: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-merge labels → merges silently
  • Returns { ok, taskId, branch, prUrl, prNumber }

Environment Variables

VariableRequiredPurpose
ENVIRONMENTNolocal or cloud
DEPLOYMENT_ENVIRONMENTNolocal|preview|staging|prod
GITHUB_TOKENRequired for claim APIPAT for GitHub REST API (repo contents + PRs)
GITHUB_REPOSITORYNoowner/repo — auto-detected from VERCEL_GIT_* or defaults to happy-client/happyclient-monorepo
BUILD_SHA, BUILD_REF, BUILD_TIMENoBuild metadata for /api/info
VERCEL_GIT_COMMIT_SHA, VERCEL_GIT_COMMIT_REFNoVercel-provided fallbacks
VERCEL_GIT_REPO_OWNER, VERCEL_GIT_REPO_SLUGNoAuto-detection of GITHUB_REPOSITORY

File Loading Strategy

packages/app-kanban/src/server/index.ts uses fast-glob + gray-matter for all file I/O:

  1. loadKanbanTasks() — globs .kanbn/tasks/**/*.md, parses each file, validates frontmatter with Zod, skips invalid files (resilient)
  2. loadKanbanMilestones() — globs .kanbn/milestones/**/*.md, enriches with computed task summaries
  3. loadKanbanMilestoneById(id) — loads all milestones, finds by id
  4. createMilestoneFile() — validates frontmatter, uses gray-matter to write to .kanbn/milestones/{id}.md
  5. updateTaskFrontmatterById() — 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:

  1. As the claim endpoint: Claude agents call POST /api/tasks/{id}/claim before starting any task (see CLAUDE.md Kanban Task Lifecycle)
  2. 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.json proposals
    • ⑥ Architecture Review — writes new tasks to .kanbn/tasks/

See nightly-maintenance-architecture.md for the full pipeline.


Known Limitations & Gaps

AreaCurrent StateNotes
Read-only in productionD&D task updates gated behind NODE_ENV === "development"Production board cannot be edited through the UI
No file read cachingEvery request globs and reads all .md filesAcceptable at current scale; may become slow with 500+ tasks
path.resolve(cwd, "../../") in milestone creationNew milestone form hardcodes repo root as process.cwd() two levels upFragile 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 incompleteColor map covers 7 apps; hello, admin, demo, links missingMissing apps display neutral badge styling