import { createFileRoute, Link } from "@tanstack/react-router"; import { useEffect, useState } from "react"; import { api, type SessionDB, type SessionMeta } from "@/lib/api"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { timeAgo, formatDuration } from "@/components/analytics"; import { Zap, Clock, Activity, ChevronRight, AlertTriangle } from "lucide-react"; export const Route = createFileRoute("/sessions")({ component: Sessions }); function Sessions() { const [dbs, setDbs] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { api.sessions().then(d => { setDbs(d); setLoading(false); }); }, []); if (loading) return

Loading sessions...

; const nonEmpty = dbs.filter(db => db.sessions.length > 0); if (nonEmpty.length === 0) { return (

Sessions

No sessions with events found

Sessions appear here once your AI coding tools start generating events.
); } // Flatten all sessions const allSessions: { session: SessionMeta; dbHash: string }[] = []; for (const db of nonEmpty) { for (const s of db.sessions) { allSessions.push({ session: s, dbHash: db.hash }); } } // Compute KPIs const totalSessions = allSessions.length; const totalEvents = allSessions.reduce((a, s) => a + s.session.eventCount, 0); // Compute average duration from startedAt to lastEventAt let totalDurationMin = 0; let durCount = 0; for (const { session: s } of allSessions) { if (s.startedAt && s.lastEventAt) { const start = new Date(s.startedAt).getTime(); const end = new Date(s.lastEventAt).getTime(); if (!isNaN(start) && !isNaN(end) && end > start) { totalDurationMin += (end - start) / 60000; durCount++; } } } const avgDuration = durCount > 0 ? Math.round(totalDurationMin / durCount) : 0; // Sort by startedAt descending allSessions.sort((a, b) => (b.session.startedAt || "").localeCompare(a.session.startedAt || "")); return (

Sessions

All recorded AI coding sessions

{/* KPI Strip */}
Sessions
{totalSessions}

across {nonEmpty.length} db{nonEmpty.length > 1 ? "s" : ""}

Avg Duration
{formatDuration(avgDuration)}

per session

Events
{totalEvents}

{totalSessions > 0 ? `~${Math.round(totalEvents / totalSessions)} per session` : "none"}

{/* Session cards */}
{allSessions.map(({ session: s, dbHash }) => { // Compute duration let durationMin = 0; if (s.startedAt && s.lastEventAt) { const start = new Date(s.startedAt).getTime(); const end = new Date(s.lastEventAt).getTime(); if (!isNaN(start) && !isNaN(end) && end > start) { durationMin = (end - start) / 60000; } } // Project name: last 2 path segments const projectName = s.projectDir ? s.projectDir.split("/").filter(Boolean).slice(-2).join("/") : "Unknown"; return (
{/* Project name */}
{projectName} {s.compactCount > 0 && ( {s.compactCount} compact{s.compactCount > 1 ? "s" : ""} )}
{s.id.slice(0, 16)}
{/* Duration badge */} {durationMin > 0 && ( {formatDuration(durationMin)} )} {/* Event count */} {s.eventCount} event{s.eventCount !== 1 ? "s" : ""} {/* Started time */} {timeAgo(s.startedAt)}
); })}
); }