diff --git a/app/(app)/sessions/[id]/page.tsx b/app/(app)/sessions/[id]/page.tsx
index afc16e8..d252da0 100644
--- a/app/(app)/sessions/[id]/page.tsx
+++ b/app/(app)/sessions/[id]/page.tsx
@@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { SessionTimelineChart } from "@/components/sessions/SessionTimelineChart";
import { SessionTitleEditor } from "@/components/sessions/SessionTitleEditor";
+import { SessionDescriptionEditor } from "@/components/sessions/SessionDescriptionEditor";
import { STATUS_VARIANT } from "@/components/sessions/SessionsTable";
import { getSessionDetail, getSessionName } from "@/lib/db/queries/sessions";
import { getSessionTimeline } from "@/lib/db/queries/stats";
@@ -52,9 +53,7 @@ export default async function SessionDetailPage({ params }: { params: Promise<{
)}
)}
- {detail.session.description && (
- {detail.session.description}
- )}
+
{detail.session.status}
diff --git a/components/sessions/SessionDescriptionEditor.tsx b/components/sessions/SessionDescriptionEditor.tsx
new file mode 100644
index 0000000..413df18
--- /dev/null
+++ b/components/sessions/SessionDescriptionEditor.tsx
@@ -0,0 +1,81 @@
+"use client";
+
+import { useEffect, useRef, useState } from "react";
+import { useRouter } from "next/navigation";
+import { Check, Loader2 } from "lucide-react";
+import { toast } from "sonner";
+import { cn } from "@/lib/utils";
+
+const AUTOSAVE_DELAY_MS = 600;
+
+export function SessionDescriptionEditor({
+ sessionId,
+ initialDescription,
+}: {
+ sessionId: number;
+ initialDescription: string | null;
+}) {
+ const router = useRouter();
+ const initial = initialDescription ?? "";
+ const [value, setValue] = useState(initial);
+ const [status, setStatus] = useState<"idle" | "saving" | "saved">("idle");
+ const lastSaved = useRef(initial);
+
+ useEffect(() => {
+ // Unlike the session name, an empty description is a valid, saveable
+ // state (it just means "no description"), so no non-empty guard here.
+ if (value === lastSaved.current) return;
+
+ setStatus("saving");
+ const timer = setTimeout(async () => {
+ const res = await fetch(`/api/sessions/${sessionId}`, {
+ method: "PATCH",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ description: value }),
+ });
+ if (res.ok) {
+ lastSaved.current = value;
+ setStatus("saved");
+ router.refresh();
+ } else {
+ setStatus("idle");
+ toast.error("Could not save description");
+ }
+ }, AUTOSAVE_DELAY_MS);
+
+ return () => clearTimeout(timer);
+ }, [value, sessionId, router]);
+
+ useEffect(() => {
+ if (status !== "saved") return;
+ const timer = setTimeout(() => setStatus("idle"), 1500);
+ return () => clearTimeout(timer);
+ }, [status]);
+
+ return (
+
+ );
+}