From 60a04e48c18c5b5c97925aeab172e6a1b5b45256 Mon Sep 17 00:00:00 2001 From: Mark Henderson Date: Thu, 30 Apr 2026 13:44:50 -0700 Subject: [PATCH] feat(plan): Objective/Sessions/Tasks tab with markdown + AI scribe - Objective: full markdown document editor with Write/Preview tabs - Sessions: project-scoped chat threads with AI-generated summaries - Tasks: master-detail view with markdown spec, status pills, agent delegation placeholder - Chat threads now scoped per-project and auto-summarised after each assistant turn (powers Sessions list) - AI MCP scribe tools: plan_get / plan_vision_set / plan_idea_add / plan_task_add (title + markdown desc) / plan_task_complete / plan_decision_log - Chat panel clears stale project threads when navigating to workspace Made-with: Cursor --- .../project/[projectId]/(home)/plan/page.tsx | 970 +++++++++++++++--- app/api/chat/route.ts | 57 + app/api/chat/threads/route.ts | 34 +- app/api/mcp/route.ts | 59 +- app/api/projects/[projectId]/plan/route.ts | 71 +- components/vibn-chat/chat-panel.tsx | 4 + lib/ai/vibn-tools.ts | 9 +- package-lock.json | 1 + package.json | 1 + 9 files changed, 1008 insertions(+), 198 deletions(-) diff --git a/app/[workspace]/project/[projectId]/(home)/plan/page.tsx b/app/[workspace]/project/[projectId]/(home)/plan/page.tsx index 713f5a06..d384542a 100644 --- a/app/[workspace]/project/[projectId]/(home)/plan/page.tsx +++ b/app/[workspace]/project/[projectId]/(home)/plan/page.tsx @@ -19,22 +19,45 @@ import { useCallback, useEffect, useState } from "react"; import { useParams } from "next/navigation"; +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; import { - Loader2, AlertCircle, Lightbulb, ListTodo, GitBranch, - Sparkles, Plus, Trash2, Check, RotateCcw, Pencil, X, + Loader2, AlertCircle, MessageSquare, ListTodo, GitBranch, + Target, Plus, Trash2, Check, RotateCcw, Pencil, X, Eye, FileText, } from "lucide-react"; interface Idea { id: string; text: string; createdAt: string } -interface Task { id: string; text: string; status: "open" | "done"; createdAt: string; doneAt?: string } +type TaskStatus = "open" | "in_progress" | "done" | "blocked"; +interface Task { + id: string; + title: string; + description?: string; + status: TaskStatus; + agent?: { runId: string; startedAt: string; finishedAt?: string; status: "queued" | "running" | "succeeded" | "failed" } | null; + createdAt: string; + startedAt?: string; + doneAt?: string; + // Legacy single-line tasks (pre-markdown migration) carry text instead of title. + text?: string; +} interface Decision { id: string; title: string; choice: string; why?: string; createdAt: string } interface Plan { - vision?: string; - ideas: Idea[]; + vision?: string; // stored as `vision` server-side, surfaced as "Objective" in the UI + ideas: Idea[]; // legacy bin — no longer surfaced; kept on the model for backward compat tasks: Task[]; decisions: Decision[]; } -type Section = "vision" | "ideas" | "tasks" | "decisions"; +interface Session { + id: string; + title: string; + summary: string | null; + messageCount: number; + updatedAt: string; + createdAt: string; +} + +type Section = "objective" | "sessions" | "tasks" | "decisions"; interface SectionDef { key: Section; @@ -44,10 +67,10 @@ interface SectionDef { } const SECTIONS: SectionDef[] = [ - { key: "vision", label: "Vision", icon: Sparkles, blurb: "What you're building, in one sentence." }, - { key: "tasks", label: "Tasks", icon: ListTodo, blurb: "What needs to happen next." }, - { key: "decisions", label: "Decisions", icon: GitBranch, blurb: "Choices already made — so we stop re-litigating them." }, - { key: "ideas", label: "Ideas", icon: Lightbulb, blurb: "Raw capture. Park half-thoughts here." }, + { key: "objective", label: "Objective", icon: Target, blurb: "What you're building, who it's for, and why." }, + { key: "tasks", label: "Tasks", icon: ListTodo, blurb: "What needs to happen next." }, + { key: "decisions", label: "Decisions", icon: GitBranch, blurb: "Choices already made — so we stop re-litigating them." }, + { key: "sessions", label: "Sessions", icon: MessageSquare, blurb: "Past chat sessions for this project." }, ]; // ────────────────────────────────────────────────── @@ -57,11 +80,13 @@ const SECTIONS: SectionDef[] = [ export default function PlanTab() { const params = useParams(); const projectId = params.projectId as string; + const workspace = params.workspace as string; const [plan, setPlan] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const [active, setActive] = useState
("vision"); + const [active, setActive] = useState
("objective"); + const [sessionCount, setSessionCount] = useState(0); const load = useCallback(async () => { try { @@ -100,14 +125,103 @@ export default function PlanTab() { } const counts: Record = { - vision: plan.vision ? 1 : 0, - ideas: plan.ideas.length, - tasks: plan.tasks.filter((t) => t.status === "open").length, + objective: plan.vision ? 1 : 0, + sessions: sessionCount, + tasks: plan.tasks.filter((t) => t.status !== "done").length, decisions: plan.decisions.length, }; return (
+