import { useEffect, useState, useCallback, useRef } from 'react' import { useParams, useNavigate, Link } from 'react-router-dom' import { DirectiveFileTree } from './DirectiveFileTree' import DocumentEditor from './DocumentEditor' import { ToastProvider, useToast } from './Toast' import { type DirectiveWithSteps, type DirectiveStep, getDirective, getDirectiveSteps, updateGoal, updateDirective, cleanupDirective, createPr, pickUpOrders, pauseDirective, startDirective, } from '../../services/directiveApi' import './DocumentLayout.css' function StatusBadge({ status }: { status: string }) { const colors: Record = { active: '#4caf50', running: '#4caf50', idle: '#ffc107', paused: '#ffc107', draft: '#9e9e9e', pending: '#9e9e9e', archived: '#f44336', failed: '#f44336', } const color = colors[status.toLowerCase()] || '#9e9e9e' return ( {status} ) } function DocumentLayoutInner() { const { id: urlDirectiveId } = useParams<{ id: string }>() const navigate = useNavigate() const { addToast } = useToast() const [selectedId, setSelectedId] = useState(urlDirectiveId || null) const [directive, setDirective] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [sidebarWidth, setSidebarWidth] = useState(250) const resizingRef = useRef(false) const startXRef = useRef(0) const startWidthRef = useRef(250) const pollRef = useRef | null>(null) // Sync URL param on mount useEffect(() => { if (urlDirectiveId && urlDirectiveId !== selectedId) { setSelectedId(urlDirectiveId) } }, [urlDirectiveId]) // Handle directive selection - update URL const handleSelectDirective = useCallback((id: string) => { setSelectedId(id) navigate(`/directives/${id}`, { replace: true }) }, [navigate]) // Load directive when selected useEffect(() => { if (!selectedId) { setDirective(null) return } let cancelled = false async function load() { try { setLoading(true) setError(null) const data = await getDirective(selectedId!) if (!cancelled) setDirective(data) } catch (err) { if (!cancelled) { const msg = err instanceof Error ? err.message : 'Failed to load directive' setError(msg) addToast(msg, 'error') } } finally { if (!cancelled) setLoading(false) } } load() return () => { cancelled = true } }, [selectedId, addToast]) // Step polling (after goal update triggers supervisor) const startStepPolling = useCallback(() => { if (pollRef.current) clearInterval(pollRef.current) pollRef.current = setInterval(async () => { if (!selectedId) return try { const data = await getDirective(selectedId) setDirective(data) } catch { // Silently fail for polling } }, 3000) // Stop after 60 seconds setTimeout(() => { if (pollRef.current) { clearInterval(pollRef.current) pollRef.current = null } }, 60000) }, [selectedId]) useEffect(() => { return () => { if (pollRef.current) clearInterval(pollRef.current) } }, []) // Auto-save goal changes const handleGoalChange = useCallback(async (newGoal: string) => { if (!selectedId) return try { const updated = await updateGoal(selectedId, newGoal) setDirective(updated) addToast('Goal saved', 'success') startStepPolling() } catch (err) { addToast(`Failed to save goal: ${(err as Error).message}`, 'error') } }, [selectedId, addToast, startStepPolling]) const handleTitleChange = useCallback(async (newTitle: string) => { if (!selectedId || !directive) return try { const updated = await updateDirective(selectedId, { title: newTitle, version: directive.version, }) setDirective(updated) } catch (err) { addToast(`Failed to update title: ${(err as Error).message}`, 'error') } }, [selectedId, directive, addToast]) const handleCleanup = useCallback(async () => { if (!selectedId) return try { await cleanupDirective(selectedId) addToast('Cleanup task spawned', 'success') startStepPolling() } catch (err) { addToast(`Cleanup failed: ${(err as Error).message}`, 'error') } }, [selectedId, addToast, startStepPolling]) const handleCreatePr = useCallback(async () => { if (!selectedId) return try { await createPr(selectedId) addToast('PR update triggered', 'success') } catch (err) { addToast(`PR update failed: ${(err as Error).message}`, 'error') } }, [selectedId, addToast]) const handlePlanOrders = useCallback(async () => { if (!selectedId) return try { await pickUpOrders(selectedId) addToast('Planning orders...', 'info') startStepPolling() } catch (err) { addToast(`Plan orders failed: ${(err as Error).message}`, 'error') } }, [selectedId, addToast, startStepPolling]) const handleTogglePause = useCallback(async () => { if (!selectedId || !directive) return try { if (directive.status === 'paused') { const result = await startDirective(selectedId) setDirective(result) addToast('Directive resumed', 'success') } else { const updated = await pauseDirective(selectedId) setDirective(updated) addToast('Directive paused', 'info') } } catch (err) { addToast(`Failed to toggle pause: ${(err as Error).message}`, 'error') } }, [selectedId, directive, addToast]) // Sidebar resize handlers const handleMouseDown = useCallback((e: React.MouseEvent) => { resizingRef.current = true startXRef.current = e.clientX startWidthRef.current = sidebarWidth document.body.style.cursor = 'col-resize' document.body.style.userSelect = 'none' const handleMouseMove = (e: MouseEvent) => { if (!resizingRef.current) return const diff = e.clientX - startXRef.current const newWidth = Math.max(180, Math.min(500, startWidthRef.current + diff)) setSidebarWidth(newWidth) } const handleMouseUp = () => { resizingRef.current = false document.body.style.cursor = '' document.body.style.userSelect = '' document.removeEventListener('mousemove', handleMouseMove) document.removeEventListener('mouseup', handleMouseUp) } document.addEventListener('mousemove', handleMouseMove) document.addEventListener('mouseup', handleMouseUp) }, [sidebarWidth]) const handleNewDirective = useCallback(() => { // Placeholder - will be implemented with full directive creation flow console.log('New directive requested') }, []) return (
{/* Sidebar */}
{'\u2190'} Back to Main
{/* Resize handle */}
{/* Main content */}
{directive && (

{directive.title || 'Untitled'}

)}
{loading && (

Loading directive...

)} {error && (

Error: {error}

)} {!loading && !error && !directive && (
{'\u{1F4DD}'}

No directive selected

Select a directive from the sidebar or create a new one to get started.

)} {!loading && !error && directive && ( )}
) } // Wrapper that provides toast context export default function DocumentLayout() { return ( ) }