import React, { useEffect, useRef, useState } from "react"; import { apiUrl } from "../utils/api.js"; import SessionItem from "./SessionItem.jsx"; /** * SessionSidebar — Claude-Code-on-Web parity. * * Shows a scrollable list of coding sessions with status indicators, * timestamps, and a "New Session" button. Additive — does not modify * any existing component. */ /** Most recently worked on first; a session with no timestamp sinks. */ function sortNewestFirst(sessions) { const at = (s) => Date.parse(s?.updated_at || s?.created_at || "") || 0; return [...sessions].sort((a, b) => at(b) - at(a)); } export default function SessionSidebar({ repo, activeSessionId, onSelectSession, onNewSession, onDeleteSession, refreshNonce = 0, }) { const [sessions, setSessions] = useState([]); const [loading, setLoading] = useState(false); const pollRef = useRef(null); const repoFullName = repo?.full_name || (repo ? `${repo.owner}/${repo.name}` : null); // Fetch sessions useEffect(() => { if (!repoFullName) { setSessions([]); return; } let cancelled = false; const fetchSessions = async () => { setLoading(true); try { const token = localStorage.getItem("github_token"); const headers = token ? { Authorization: `Bearer ${token}` } : {}; // Filter server-side: the limit is applied after the filter there, so // a long global history can't push this repo's sessions off the list. const res = await fetch( apiUrl(`/api/sessions?repo=${encodeURIComponent(repoFullName)}`), { headers, cache: "no-cache" } ); if (!res.ok) return; const data = await res.json(); if (cancelled) return; // The server already returns newest-first; sorting again costs nothing // and keeps the newest session on top against an older backend. const rows = (data.sessions || []).filter( (s) => !s.repo || s.repo === repoFullName ); setSessions(sortNewestFirst(rows)); } catch (err) { console.warn("Failed to fetch sessions:", err); } finally { if (!cancelled) setLoading(false); } }; fetchSessions(); // Poll every 15s for status updates pollRef.current = setInterval(fetchSessions, 15000); return () => { cancelled = true; if (pollRef.current) clearInterval(pollRef.current); }; }, [repoFullName, refreshNonce]); const handleDelete = async (sessionId) => { try { const token = localStorage.getItem("github_token"); const headers = token ? { Authorization: `Bearer ${token}` } : {}; await fetch(apiUrl(`/api/sessions/${sessionId}`), { method: "DELETE", headers }); setSessions((prev) => prev.filter((s) => s.id !== sessionId)); // Notify parent so it can clear the chat if this was the active session onDeleteSession?.(sessionId); } catch (err) { console.warn("Failed to delete session:", err); } }; return (