import React, { useCallback, useEffect, useState } from 'react'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from '@/components/ui/collapsible'; import { ChevronDown, ChevronsUpDown, Map, FileText, Loader2, Users, FileCode, Clock } from 'lucide-react'; import { api } from '@/api'; import { APP_VERSION } from '@/lib/version'; /* ─── Priority Configuration ───────────────────────────── */ const PRIORITY_LANES = [ { key: 'critical', emoji: '🔴', label: 'CRITICAL', borderColor: 'border-t-red-500', bgColor: 'bg-red-500/10', textColor: 'text-red-600 dark:text-red-400', badgeClass: 'bg-red-500/15 text-red-600 dark:text-red-400 border-red-500/20' }, { key: 'high', emoji: '🟠', label: 'HIGH', borderColor: 'border-t-orange-500', bgColor: 'bg-orange-500/10', textColor: 'text-orange-600 dark:text-orange-400', badgeClass: 'bg-orange-500/15 text-orange-600 dark:text-orange-400 border-orange-500/20' }, { key: 'medium', emoji: '🟡', label: 'MEDIUM', borderColor: 'border-t-yellow-500', bgColor: 'bg-yellow-500/10', textColor: 'text-yellow-600 dark:text-yellow-400', badgeClass: 'bg-yellow-500/15 text-yellow-600 dark:text-yellow-400 border-yellow-500/20' }, { key: 'low', emoji: '🔵', label: 'LOW', borderColor: 'border-t-blue-500', bgColor: 'bg-blue-500/10', textColor: 'text-blue-600 dark:text-blue-400', badgeClass: 'bg-blue-500/15 text-blue-600 dark:text-blue-400 border-blue-500/20' }, { key: 'niceToHave', emoji: '💭', label: 'NICE TO HAVE', borderColor: 'border-t-gray-400', bgColor: 'bg-gray-400/10', textColor: 'text-gray-600 dark:text-gray-400', badgeClass: 'bg-gray-500/15 text-gray-600 dark:text-gray-400 border-gray-500/20' }, ]; function laneForPriority(priority) { const key = typeof priority === 'string' ? priority.toLowerCase().replace(/\s+/g, '').replace(/to/ig, 'To') : ''; // Map API priority keys to lane keys const mapping = { critical: 'critical', high: 'high', medium: 'medium', low: 'low', nicetohave: 'niceToHave', 'nice to have': 'niceToHave', }; return mapping[key] || 'low'; } /* ─── Roadmap Item Card ────────────────────────────────── */ function RoadmapItemCard({ item, defaultOpen, onToggle }) { const lane = PRIORITY_LANES.find(l => l.key === laneForPriority(item.priority)) || PRIORITY_LANES[3]; const [open, setOpen] = useState(defaultOpen); const handleOpenChange = useCallback((value) => { setOpen(value); onToggle?.(value); }, [onToggle]); const effortLabel = item.effort || ''; return (
{item.added && ( {item.added} )} {item.addedBy && ( <> {item.addedBy} )} {effortLabel && ( <> {effortLabel} )}
{item.description && (

Description

{item.description}

)} {item.rationale && (

Rationale

{item.rationale}

)} {item.implementationNotes && (

Implementation Notes

{item.implementationNotes}
)}
); } /* ─── Priority Lane ─────────────────────────────────────── */ function PriorityLane({ lane, items, defaultOpenCards }) { if (items.length === 0) return null; return (

{lane.label}

{items.length}
{items.map((item) => ( ))}
); } /* ─── Dev Log Entry ─────────────────────────────────────── */ function DevLogEntry({ entry }) { const [open, setOpen] = useState(false); return (
{/* Timeline line */}
{entry.agents?.length > 0 && (

Agents

{entry.agents.map((agent, idx) => ( {agent.status === 'COMPLETED' ? '✅' : agent.status === 'IN PROGRESS' ? '⏳' : '❓'}{' '} {agent.name} {agent.time ? ` · ${agent.time}` : ''} ))}
)} {entry.filesModified?.length > 0 && (

Files Modified

{entry.filesModified.map((file, idx) => ( {file} ))}
)} {entry.workCompleted?.length > 0 && (

Work Completed

    {entry.workCompleted.map((work, idx) => (
  • {work}
  • ))}
)}
); } /* ─── Main Page ─────────────────────────────────────────── */ export default function RoadmapPage() { const [roadmapData, setRoadmapData] = useState(null); const [devLogData, setDevLogData] = useState(null); const [roadmapLoading, setRoadmapLoading] = useState(true); const [devLogLoading, setDevLogLoading] = useState(false); const [roadmapError, setRoadmapError] = useState(null); const [devLogError, setDevLogError] = useState(null); const [allExpanded, setAllExpanded] = useState(true); // Detect desktop for default expand state const [isDesktop, setIsDesktop] = useState( typeof window !== 'undefined' ? window.matchMedia('(min-width: 1024px)').matches : true ); useEffect(() => { const mq = window.matchMedia('(min-width: 1024px)'); const handler = (e) => setIsDesktop(e.matches); mq.addEventListener('change', handler); setIsDesktop(mq.matches); return () => mq.removeEventListener('change', handler); }, []); // Fetch roadmap on mount useEffect(() => { let cancelled = false; setRoadmapLoading(true); api.roadmap() .then((data) => { if (!cancelled) setRoadmapData(data); }) .catch((err) => { if (!cancelled) setRoadmapError(err.message || 'Failed to load roadmap'); }) .finally(() => { if (!cancelled) setRoadmapLoading(false); }); return () => { cancelled = true; }; }, []); const fetchDevLog = useCallback(() => { if (devLogData) return; // Already loaded let cancelled = false; setDevLogLoading(true); api.devLog() .then((data) => { if (!cancelled) setDevLogData(data); }) .catch((err) => { if (!cancelled) setDevLogError(err.message || 'Failed to load activity log'); }) .finally(() => { if (!cancelled) setDevLogLoading(false); }); return () => { cancelled = true; }; }, [devLogData]); const version = roadmapData?.version || APP_VERSION; const items = roadmapData?.items || []; const counts = roadmapData?.counts || {}; const devLogEntries = devLogData?.entries || []; // Group items by priority lane const grouped = PRIORITY_LANES.map(lane => ({ ...lane, items: items.filter(item => laneForPriority(item.priority) === lane.key), })); const defaultOpenCards = isDesktop && allExpanded; return (
{/* Page Header */}

Roadmap

Current and upcoming features by priority

v{version}
{/* Tabs */} { if (value === 'activity') fetchDevLog(); }}> Roadmap Activity Log {/* ─── Roadmap Tab ─── */} {roadmapLoading ? (
Loading roadmap…
) : roadmapError ? (

Failed to load roadmap

{roadmapError}

) : items.length === 0 ? ( No roadmap items found. ) : ( <> {/* Expand/Collapse All toggle */}
{/* Desktop: 5-column grid */}
{grouped.map(lane => ( ))}
{/* Tablet: 2-column grid */}
{/* Left column: Critical + High */}
{grouped.filter(l => l.key === 'critical' || l.key === 'high').map(lane => ( ))}
{/* Right column: Medium + Low + Nice to Have */}
{grouped.filter(l => l.key === 'medium' || l.key === 'low' || l.key === 'niceToHave').map(lane => ( ))}
{/* Mobile: single column */}
{grouped.map(lane => ( ))}
)}
{/* ─── Activity Log Tab ─── */} {devLogLoading ? (
Loading activity log…
) : devLogError ? (

Failed to load activity log

{devLogError}

) : devLogEntries.length === 0 ? ( No activity log entries found. ) : (
{devLogEntries.map((entry, idx) => ( ))}
)}
); }