import { readFile } from "node:fs/promises" import path from "node:path" import Link from "next/link" export const dynamic = "force-dynamic" type TodoState = "done" | "partial" | "planned" type TodoTask = { state: TodoState text: string } type TodoBlock = { title: string tasks: TodoTask[] notes: string[] } type TodoSection = { title: string blocks: TodoBlock[] notes: string[] } type ProgressCounts = { done: number partial: number planned: number } const metaSections = new Set(["Status Legend"]) function isMetaSection(section: TodoSection | null): boolean { return section ? metaSections.has(section.title) : false } function toTaskState(token: string): TodoState { const value = token.toLowerCase() if (value === "x") { return "done" } if (value === "~") { return "partial" } return "planned" } function parseTodo(content: string): TodoSection[] { const sections: TodoSection[] = [] let currentSection: TodoSection | null = null let currentBlock: TodoBlock | null = null const ensureSection = (title = "General"): TodoSection => { if (!currentSection) { currentSection = { title, blocks: [], notes: [], } sections.push(currentSection) } return currentSection } const ensureBlock = (title = "Overview"): TodoBlock => { const section = ensureSection() if (!currentBlock) { currentBlock = { title, tasks: [], notes: [], } section.blocks.push(currentBlock) } return currentBlock } for (const rawLine of content.split("\n")) { const line = rawLine.trim() if (!line) { continue } if (line.startsWith("# ")) { continue } if (line.startsWith("## ")) { currentSection = { title: line.replace("## ", "").trim(), blocks: [], notes: [], } sections.push(currentSection) currentBlock = null continue } if (line.startsWith("### ")) { const section = ensureSection() currentBlock = { title: line.replace("### ", "").trim(), tasks: [], notes: [], } section.blocks.push(currentBlock) continue } const taskMatch = line.match(/^- \[([ x~X])\] (.+)$/) if (taskMatch) { if (isMetaSection(currentSection)) { ensureSection().notes.push(taskMatch[0].replace("- ", "")) continue } ensureBlock().tasks.push({ state: toTaskState(taskMatch[1]), text: taskMatch[2].trim(), }) continue } const bulletMatch = line.match(/^- (.+)$/) if (bulletMatch) { if (currentBlock) { currentBlock.notes.push(bulletMatch[1].trim()) } else { ensureSection().notes.push(bulletMatch[1].trim()) } } } return sections } function getProgressCounts(sections: TodoSection[]): ProgressCounts { const counts: ProgressCounts = { done: 0, partial: 0, planned: 0, } for (const section of sections) { if (isMetaSection(section)) { continue } for (const block of section.blocks) { for (const task of block.tasks) { counts[task.state] += 1 } } } return counts } function getProgressPercent(progress: ProgressCounts): number { const total = progress.done + progress.partial + progress.planned if (total === 0) { return 0 } return Math.round(((progress.done + progress.partial * 0.5) / total) * 100) } async function getTodoMarkdown(): Promise { const candidates = [ path.resolve(process.cwd(), "TODO.md"), path.resolve(process.cwd(), "../TODO.md"), path.resolve(process.cwd(), "../../TODO.md"), ] for (const filePath of candidates) { try { return await readFile(filePath, "utf8") } catch { // Try next candidate path. } } return "# TODO file not found\n\nCreate `/TODO.md` in the repository root to track progress." } function statusBadgeClass(state: TodoState): string { if (state === "done") { return "bg-emerald-50 text-emerald-700 border-emerald-200" } if (state === "partial") { return "bg-amber-50 text-amber-700 border-amber-200" } return "bg-slate-50 text-slate-700 border-slate-200" } function statusLabel(state: TodoState): string { if (state === "done") { return "Done" } if (state === "partial") { return "Partial" } return "Planned" } export default async function AdminTodoPage() { const content = await getTodoMarkdown() const sections = parseTodo(content) const progress = getProgressCounts(sections) const completionPercent = getProgressPercent(progress) return (

Admin App

Roadmap and Progress

Structured view from root `TODO.md` (single source of truth).

Back to dashboard

Weighted completion

{completionPercent}%

Completed items

{progress.done}

Partially done items

{progress.partial}

Planned items

{progress.planned}

{sections.map((section) => (

{section.title}

{section.notes.length > 0 ? (
    {section.notes.map((note) => (
  • {note}
  • ))}
) : null}
{section.blocks.map((block) => (

{block.title}

    {block.tasks.map((task) => (
  • {task.text}

    {statusLabel(task.state)}
  • ))}
{block.notes.length > 0 ? (
    {block.notes.map((note) => (
  • {note}
  • ))}
) : null}
))}
))}
View raw markdown source (`TODO.md`)
          {content}
        
) }