"use client" import { useCallback, useEffect, useMemo, useState } from "react" import { useRouter, useSearchParams } from "next/navigation" import { ArrowLeft, ArrowUpRight, Clock3, Link2, MessageCircle, TriangleAlert, X } from "lucide-react" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Input } from "@/components/ui/input" import { ScrollArea } from "@/components/ui/scroll-area" import { Separator } from "@/components/ui/separator" type TaskStatus = "open" | "in_progress" | "blocked" | "deferred" | "closed" type Task = { id: string title: string description: string status: TaskStatus priority: 0 | 1 | 2 | 3 | 4 issueType: string assignee: string owner: string labels: string[] blockedReason: string updatedAgo: string dependencyCount: number blockedByCount: number commentCount: number unread: boolean } type Epic = { id: string name: string progress: number openCount: number tasks: Task[] } const palette = { primary: "#F2A62F", secondary: "#00D1A8", accent: "#0FC5AE", eggplant: "#4A2F63", bg: "#2D2E3C", surface: "#333341", border: "#4A4D5C", text: "#EDEBE5", textSecondary: "#B8B7B1", mutedBg: "#2F2F3E", success: "#0FC5AE", warning: "#D28A2C", error: "#D64545", info: "#00D1A8", atmosphereWarm: "#5A4632", atmosphereCool: "#23484D", } const initialEpics: Epic[] = [ { id: "bb-ui2", name: "Unified UX - Earthy Dark Shell", progress: 69, openCount: 11, tasks: [ { id: "bb-atf", title: "Agent swarm-view-integrator", description: "Integrate swarm view into social workroom shell.", status: "open", priority: 1, issueType: "task", assignee: "sarah.lee", owner: "swarm-team", labels: ["social", "swarm"], blockedReason: "", updatedAgo: "8m", dependencyCount: 1, blockedByCount: 0, commentCount: 3, unread: true }, { id: "bb-z6s", title: "Agent social-view-integrator", description: "Wire social stream cards and panel routing.", status: "in_progress", priority: 0, issueType: "feature", assignee: "alex.chen", owner: "social-team", labels: ["social", "ui"], blockedReason: "", updatedAgo: "14m", dependencyCount: 2, blockedByCount: 0, commentCount: 7, unread: true }, { id: "bb-nuy", title: "Agent swarm-card-builder", description: "Build consistent swarm card visuals and metadata.", status: "blocked", priority: 0, issueType: "bug", assignee: "alex.chen", owner: "swarm-team", labels: ["swarm", "cards"], blockedReason: "Waiting on dependency bb-ui2.0", updatedAgo: "35m", dependencyCount: 3, blockedByCount: 1, commentCount: 5, unread: true }, { id: "bb-3ha", title: "Agent sessions-integrator", description: "Session metrics panel integrated and verified.", status: "closed", priority: 2, issueType: "chore", assignee: "alex.chen", owner: "sessions-team", labels: ["sessions"], blockedReason: "", updatedAgo: "2h", dependencyCount: 0, blockedByCount: 0, commentCount: 4, unread: false }, ], }, { id: "bb-xhm", name: "Timeline and Activity Feed", progress: 80, openCount: 5, tasks: [ { id: "bb-3dv", title: "Agent rightpanel-builder", description: "Implement right rail card stack and compact activity.", status: "open", priority: 2, issueType: "task", assignee: "alex.chen", owner: "layout-team", labels: ["layout", "right-panel"], blockedReason: "", updatedAgo: "11m", dependencyCount: 1, blockedByCount: 0, commentCount: 1, unread: true }, { id: "bb-dwz", title: "Agent leftpanel-builder", description: "Epic->task navigation with search and metadata icons.", status: "in_progress", priority: 1, issueType: "feature", assignee: "sarah.lee", owner: "layout-team", labels: ["layout", "left-panel"], blockedReason: "", updatedAgo: "19m", dependencyCount: 0, blockedByCount: 0, commentCount: 6, unread: true }, { id: "bb-5am", title: "Agent topbar-builder", description: "Topbar controls and filter sync.", status: "blocked", priority: 1, issueType: "bug", assignee: "agent-007", owner: "layout-team", labels: ["topbar"], blockedReason: "Navigation contract mismatch", updatedAgo: "41m", dependencyCount: 2, blockedByCount: 1, commentCount: 2, unread: false }, { id: "bb-z2l", title: "Agent mobile-nav-builder", description: "Mobile drawer flow for three-pane shell.", status: "deferred", priority: 1, issueType: "task", assignee: "sarah.lee", owner: "mobile-team", labels: ["mobile", "navigation"], blockedReason: "", updatedAgo: "52m", dependencyCount: 0, blockedByCount: 0, commentCount: 2, unread: false }, ], }, ] function statusClasses(status: TaskStatus) { if (status === "in_progress") return "border-l-[3px] border-l-[#0FC5AE] bg-[linear-gradient(145deg,#333341,#2F2F3E)]" if (status === "blocked") return "border-l-[3px] border-l-[#D64545] bg-[linear-gradient(145deg,#333341,#302B31)]" if (status === "deferred") return "border-l-[3px] border-l-[#D28A2C] bg-[linear-gradient(145deg,#333341,#342F29)]" if (status === "closed") return "border-l-[3px] border-l-[#6D6F7B] bg-[linear-gradient(145deg,#333341,#2F3039)]" return "border-l-[3px] border-l-[#00D1A8] bg-[linear-gradient(145deg,#333341,#2D313D)]" } function statusBadge(status: TaskStatus) { if (status === "in_progress") return "bg-[#0FC5AE] text-[#0E2220]" if (status === "blocked") return "bg-[#D64545] text-white" if (status === "deferred") return "bg-[#D28A2C] text-[#24190C]" if (status === "closed") return "bg-[#5A5D6A] text-[#D4D6DE]" return "bg-[#00D1A8] text-[#07221C]" } const panelClass = "rounded-2xl border shadow-[0_16px_40px_rgba(0,0,0,0.28)] backdrop-blur-[2px]" const subPanelClass = "rounded-xl border" function updateQuery(searchParams: URLSearchParams, updates: Record) { const next = new URLSearchParams(searchParams.toString()) for (const [key, value] of Object.entries(updates)) { if (!value) next.delete(key) else next.set(key, value) } const qs = next.toString() return qs ? `?${qs}` : "?" } export default function MockupPage() { const searchParams = useSearchParams() const router = useRouter() const [epics, setEpics] = useState(initialEpics) const [query, setQuery] = useState("") const [leftMode, setLeftMode] = useState<"epics" | "tasks">("epics") const urlEpic = searchParams.get("epic") const urlTask = searchParams.get("task") const urlThread = searchParams.get("thread") === "open" const initialEpic = epics.find((epic) => epic.id === urlEpic) ?? epics[0] const [selectedEpicId, setSelectedEpicId] = useState(initialEpic.id) const [selectedTaskId, setSelectedTaskId] = useState(urlTask ?? initialEpic.tasks[0].id) const [threadOpen, setThreadOpen] = useState(urlThread) const [threadEditMode, setThreadEditMode] = useState(false) const [draftTitle, setDraftTitle] = useState("") const [draftDescription, setDraftDescription] = useState("") const [draftStatus, setDraftStatus] = useState("open") const [draftPriority, setDraftPriority] = useState<0 | 1 | 2 | 3 | 4>(2) const [draftIssueType, setDraftIssueType] = useState("") const [draftAssignee, setDraftAssignee] = useState("") const [draftOwner, setDraftOwner] = useState("") const [draftLabels, setDraftLabels] = useState("") const [draftBlockedReason, setDraftBlockedReason] = useState("") const [savePulse, setSavePulse] = useState(false) const closeThread = useCallback(() => { setThreadOpen(false) setThreadEditMode(false) }, []) useEffect(() => { const next = updateQuery(searchParams, { epic: selectedEpicId, task: selectedTaskId, thread: threadOpen ? "open" : null, }) router.replace(next, { scroll: false }) }, [router, searchParams, selectedEpicId, selectedTaskId, threadOpen]) useEffect(() => { if (!threadOpen) { return } const onKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") { closeThread() } } window.addEventListener("keydown", onKeyDown) return () => window.removeEventListener("keydown", onKeyDown) }, [threadOpen, closeThread]) const selectedEpic = epics.find((epic) => epic.id === selectedEpicId) ?? epics[0] const filteredTasks = useMemo(() => { const q = query.trim().toLowerCase() return selectedEpic.tasks.filter((task) => `${task.id} ${task.title}`.toLowerCase().includes(q)) }, [query, selectedEpic.tasks]) const selectedTask = filteredTasks.find((task) => task.id === selectedTaskId) ?? filteredTasks[0] useEffect(() => { if (!selectedTask) return setDraftTitle(selectedTask.title) setDraftDescription(selectedTask.description) setDraftStatus(selectedTask.status) setDraftPriority(selectedTask.priority) setDraftIssueType(selectedTask.issueType) setDraftAssignee(selectedTask.assignee) setDraftOwner(selectedTask.owner) setDraftLabels(selectedTask.labels.join(", ")) setDraftBlockedReason(selectedTask.blockedReason) setThreadEditMode(false) }, [selectedTask, selectedTask?.id]) const saveTaskChanges = () => { if (!selectedTask) return const nextLabels = draftLabels .split(",") .map((part) => part.trim()) .filter(Boolean) setEpics((current) => current.map((epic) => epic.id !== selectedEpicId ? epic : { ...epic, tasks: epic.tasks.map((task) => task.id !== selectedTask.id ? task : { ...task, title: draftTitle, description: draftDescription, status: draftStatus, priority: draftPriority, issueType: draftIssueType, assignee: draftAssignee, owner: draftOwner, labels: nextLabels, blockedReason: draftBlockedReason, updatedAgo: "now", blockedByCount: draftStatus === "blocked" ? Math.max(task.blockedByCount, 1) : 0, } ), } ) ) setSavePulse(true) setTimeout(() => setSavePulse(false), 900) } return (

Social Workroom

Task-first center. Epic drill-in. Live awareness rail.

mockup route
{leftMode === "tasks" ? ( ) : ( Epics )} {selectedEpic.openCount} open
Select an epic, then choose a task.
setQuery(event.target.value)} placeholder={leftMode === "epics" ? "Search epics" : "Search tasks"} className="mb-3" style={{ backgroundColor: palette.mutedBg, borderColor: palette.border }} />
{leftMode === "epics" ? epics .filter((epic) => epic.name.toLowerCase().includes(query.toLowerCase())) .map((epic) => ( )) : filteredTasks.map((task) => ( ))}
{selectedEpic.name} Task cards + thread context
{filteredTasks.map((task) => ( ))}

Conversation: {selectedTask?.id}

alex.chen 2m Need confirmation that detail strip stays sticky while card grid scrolls.
sarah.lee 1m Approved if right rail remains visible at 1280px breakpoint.
Live Context Persistent awareness while working tasks.

Live Agents

swarm-view-integratoronline

social-view-integratoraway

graph-integratorbusy

Recent Activity

5m · bb-z6s moved to in progress

11m · bb-atf received 2 comments

18m · bb-3ha marked closed

33m · bb-nuy dependency changed

Attention

2 blocked tasks in selected epic

{threadOpen ? (
event.stopPropagation()} >

Thread · {selectedTask?.id}

Bead summary and inline edit mode

{threadEditMode ? "Edit task" : "Task summary"}

{savePulse ? "saved" : "ready"}
{!threadEditMode ? (

{selectedTask?.id}

{selectedTask?.title}

{selectedTask?.description}

Status: {selectedTask?.status}
Priority: P{selectedTask?.priority}
Assignee: {selectedTask?.assignee || "-"}
Owner: {selectedTask?.owner || "-"}
Labels: {selectedTask?.labels.join(", ") || "-"}
Blocked reason: {selectedTask?.blockedReason || "None"}
) : ( <>

Title

setDraftTitle(event.target.value)} style={{ backgroundColor: "#323342", borderColor: "#585B6D", color: palette.text }} />

Assignee

setDraftAssignee(event.target.value)} style={{ backgroundColor: "#323342", borderColor: "#585B6D", color: palette.text }} />

Description

setDraftDescription(event.target.value)} style={{ backgroundColor: "#323342", borderColor: "#585B6D", color: palette.text }} />

Issue type

setDraftIssueType(event.target.value)} style={{ backgroundColor: "#323342", borderColor: "#585B6D", color: palette.text }} />

Owner

Labels (comma separated)

setDraftLabels(event.target.value)} style={{ backgroundColor: "#323342", borderColor: "#585B6D", color: palette.text }} />

Status

{(["open", "in_progress", "blocked", "deferred", "closed"] as TaskStatus[]).map((status) => ( ))}

Priority

{([0, 1, 2, 3, 4] as const).map((priority) => ( ))}

Blocked reason

setDraftBlockedReason(event.target.value)} style={{ backgroundColor: "#323342", borderColor: "#585B6D", color: palette.text }} />
)}
) : null}
) }