diff --git a/frontend/src/components/l1/L1WalkAdhocVariant.tsx b/frontend/src/components/l1/L1WalkAdhocVariant.tsx new file mode 100644 index 00000000..7f2b203d --- /dev/null +++ b/frontend/src/components/l1/L1WalkAdhocVariant.tsx @@ -0,0 +1,156 @@ +import { useEffect, useRef, useState } from 'react' +import { ChevronLeft } from 'lucide-react' +import { Link } from 'react-router-dom' +import { l1Api } from '@/api/l1' +import type { AdhocNote, WalkSession } from '@/types/l1' +import { EscalateModal, ResolveModal } from '@/components/l1/WalkModals' + +interface Props { + session: WalkSession + onSessionUpdate: (s: WalkSession) => void + onDone: () => void +} + +export function L1WalkAdhocVariant({ session, onSessionUpdate, onDone }: Props) { + const [showResolve, setShowResolve] = useState(false) + const [showEscalate, setShowEscalate] = useState(false) + // Show prior notes as joined paragraphs so the L1 sees an editable timeline. + const [notesText, setNotesText] = useState(() => + session.walk_notes.map((n) => n.content).join('\n\n') + ) + const [savedAt, setSavedAt] = useState(null) + const [saving, setSaving] = useState(false) + const saveTimer = useRef(null) + + // Debounced autosave: 300ms after the last keystroke, send to the backend. + useEffect(() => { + if (session.status !== 'active') return + if (saveTimer.current) window.clearTimeout(saveTimer.current) + saveTimer.current = window.setTimeout(async () => { + // Split paragraphs into structured notes. Empty paragraphs are skipped. + const parts = notesText + .split('\n\n') + .map((c) => c.trim()) + .filter(Boolean) + const notes: AdhocNote[] = parts.map((content) => ({ + timestamp: new Date().toISOString(), + content, + })) + try { + setSaving(true) + const updated = await l1Api.notes(session.id, notes) + onSessionUpdate(updated) + setSavedAt(new Date()) + } catch (err) { + console.error('notes save failed:', err) + } finally { + setSaving(false) + } + }, 300) + return () => { + if (saveTimer.current) window.clearTimeout(saveTimer.current) + } + }, [notesText, session.id, session.status, onSessionUpdate]) + + const savedAgo = savedAt ? Math.max(1, Math.round((Date.now() - savedAt.getTime()) / 1000)) : null + + return ( +
+ {/* Header */} +
+ + + #{session.id.slice(0, 8)} + Ad-hoc walk + +
+ + +
+
+ + {/* Single-pane body */} +
+
+ {session.status !== 'active' ? ( +
+

+ This session is {session.status}. +

+ +
+ ) : ( + <> +

+ Take notes as you work through the call. They're auto-saved. +

+