From 5bde7c2d7e099fd9c8b2615602ab1d096bd9b6be Mon Sep 17 00:00:00 2001 From: soryu Date: Tue, 28 Apr 2026 21:26:11 +0100 Subject: revert PRs #93-#98; enforce strict-linear-DAG + mandatory directive verify (#100) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * revert: roll back PRs #93-#98 to pre-Lexical baseline Reverts the entire chain of directive document UI work and the homepage redesign, restoring the working tree to the state at 3679ceb (before c8b169d / PR #93). PRs reverted: - #93 c8b169d feat: Document UI for directive orchestration with Lexical editor - #94 d6f01a6 fix: compilation error and warnings already merged via PR #93 - #95 5aa3faf fix: resolve compilation error and warnings in Rust backend - #97 d513f93 feat: document UI with contract blocks, expandable logs, and interaction controls - #96 6366941 feat: Redesign homepage with professional PC-98 styling - #98 d1fdfb1 feat: revert broken directive PRs, re-implement Lexical document orchestrator The directive Document UI experiments produced fragile output and merge artifacts; follow-up commits in this PR change orchestration to favor strictly linear DAGs and add goal/conflict verification so future runs do not require this kind of cleanup. Co-Authored-By: Claude Opus 4.7 (1M context) * feat(directive): strict-linear-DAG planning + mandatory `directive verify` Tightens directive orchestration so the final PR almost never needs a hand-merge: 1. Planning prompts now strongly bias toward strictly linear DAGs. Parallel steps are reserved for genuinely independent work (e.g. disjoint modules); the default for "in doubt" is sequential. Linear chains inherit each previous step's worktree, so the final merge is typically just a rebase against the base branch. 2. New CLI command `makima directive verify` does a local in-memory `git merge-tree` of HEAD against `/` and exits non-zero with a list of conflicting files if the PR would not merge cleanly. Pure-local — no API call, no working-tree mutation. 3. Completion / PR-creation prompts now mandate three pre-push checks: a. build (`cargo check` and/or `tsc --noEmit`), b. `makima directive verify --base ` must exit 0, and c. an explicit goal-alignment self-check against the diff. The orchestrator is told NOT to push, create the PR, or call `makima directive update` until all three pass. Skipping any of them is documented as a directive failure. The combination means that with a linear DAG the final PR-creation task should almost never see a real conflict — when it does, that is treated as a planning bug to escalate rather than something to paper over with `-X theirs`. Co-Authored-By: Claude Opus 4.7 (1M context) * fix(frontend): TS errors pre-existing on master - TaskSlideOutPanel: declare missing `selectedFileDiff` / `selectedFilePath` state hooks that were referenced everywhere but never created, and re-balance the JSX so the `<>...` fragment in the non-diff branch is closed (the previous indentation/braces would not parse). - api.ts: add a `getWorktreeDiff` thin wrapper around `getTaskDiff` so TaskDetail's per-file click handler type-checks (the per-file slice is a future improvement; today both return the full task diff). - WorktreeFilesPanel: remove unused `isClickable` local; the gating already reads `onFileClick` directly inline. Run after revert: `npx tsc --noEmit` exits 0. Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- frontend/src/components/document/index.ts | 14 -- .../src/components/document/nodes/StepLogFeed.tsx | 277 --------------------- .../document/nodes/StepsDiagramComponent.tsx | 239 ------------------ 3 files changed, 530 deletions(-) delete mode 100644 frontend/src/components/document/index.ts delete mode 100644 frontend/src/components/document/nodes/StepLogFeed.tsx delete mode 100644 frontend/src/components/document/nodes/StepsDiagramComponent.tsx (limited to 'frontend/src/components/document') diff --git a/frontend/src/components/document/index.ts b/frontend/src/components/document/index.ts deleted file mode 100644 index 906c1dc..0000000 --- a/frontend/src/components/document/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -export { default as DocumentLayout } from './DocumentLayout' -export { default as DocumentEditor } from './DocumentEditor' -export { DirectiveFileTree } from './DirectiveFileTree' -export { default as DocumentSettings } from './DocumentSettings' - -// Lexical Nodes -export { StepsDiagramNode, $createStepsDiagramNode, $isStepsDiagramNode } from './nodes/StepsDiagramNode' -export { ContractBlockNode, $createContractBlockNode, $isContractBlockNode } from './nodes/ContractBlockNode' - -// Sub-components -export { StepsDiagramComponent } from './nodes/StepsDiagramComponent' -export { ContractBlockComponent } from './nodes/ContractBlockComponent' -export { StepLogFeed } from './nodes/StepLogFeed' -export { ContractLogFeed } from './nodes/ContractLogFeed' diff --git a/frontend/src/components/document/nodes/StepLogFeed.tsx b/frontend/src/components/document/nodes/StepLogFeed.tsx deleted file mode 100644 index 2f2f553..0000000 --- a/frontend/src/components/document/nodes/StepLogFeed.tsx +++ /dev/null @@ -1,277 +0,0 @@ -import React, { useEffect, useRef, useState, useCallback } from 'react'; - -interface StepLogFeedProps { - taskId: string; - stepName: string; - stepStatus: string; - onCollapse: () => void; -} - -interface LogEntry { - timestamp: string; - content: string; - type: 'stdout' | 'stderr' | 'system' | 'user'; -} - -/** - * Live log feed for an expanded step row. - * Connects via WebSocket to stream task output and allows - * sending messages (comments) and interrupting the task. - */ -export function StepLogFeed({ taskId, stepName, stepStatus, onCollapse }: StepLogFeedProps) { - const [logs, setLogs] = useState([]); - const [message, setMessage] = useState(''); - const [sending, setSending] = useState(false); - const [connected, setConnected] = useState(false); - const [error, setError] = useState(null); - const logsEndRef = useRef(null); - const wsRef = useRef(null); - const logContainerRef = useRef(null); - const inputRef = useRef(null); - - const isActive = ['running', 'starting'].includes(stepStatus.toLowerCase()); - - // Auto-scroll to bottom when new logs arrive - useEffect(() => { - logsEndRef.current?.scrollIntoView({ behavior: 'smooth' }); - }, [logs]); - - // Connect to WebSocket for live streaming - useEffect(() => { - if (!taskId) return; - - const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - const wsUrl = `${protocol}//${window.location.host}/api/v1/mesh/tasks/subscribe`; - - let ws: WebSocket; - let reconnectTimer: ReturnType; - let shouldReconnect = true; - - function connect() { - try { - ws = new WebSocket(wsUrl); - wsRef.current = ws; - - ws.addEventListener('open', () => { - setConnected(true); - setError(null); - // Subscribe to this specific task - ws.send(JSON.stringify({ type: 'subscribe', taskId })); - }); - - ws.addEventListener('message', (evt) => { - try { - const data = JSON.parse(evt.data); - // Handle different message formats from the backend - if (data.taskId === taskId || data.task_id === taskId) { - const entry: LogEntry = { - timestamp: data.timestamp || new Date().toISOString(), - content: data.content || data.output || data.message || JSON.stringify(data), - type: data.type || data.stream || 'stdout', - }; - setLogs(prev => [...prev, entry]); - } - } catch { - // Non-JSON message, treat as raw log - setLogs(prev => [...prev, { - timestamp: new Date().toISOString(), - content: evt.data, - type: 'stdout', - }]); - } - }); - - ws.addEventListener('close', () => { - setConnected(false); - wsRef.current = null; - if (shouldReconnect && isActive) { - reconnectTimer = setTimeout(connect, 3000); - } - }); - - ws.addEventListener('error', () => { - setConnected(false); - setError('WebSocket connection failed'); - }); - } catch (err) { - setError('Failed to connect to log stream'); - } - } - - connect(); - - return () => { - shouldReconnect = false; - clearTimeout(reconnectTimer); - if (wsRef.current) { - wsRef.current.close(); - wsRef.current = null; - } - }; - }, [taskId, isActive]); - - // Keyboard shortcut: Escape to collapse - useEffect(() => { - const handler = (e: KeyboardEvent) => { - if (e.key === 'Escape') { - onCollapse(); - } - }; - document.addEventListener('keydown', handler); - return () => document.removeEventListener('keydown', handler); - }, [onCollapse]); - - // Send a message/comment to the task - const handleSendMessage = useCallback(async () => { - if (!message.trim() || !taskId || sending) return; - - setSending(true); - try { - const response = await fetch(`/api/v1/mesh/tasks/${taskId}/message`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ message: message.trim() }), - }); - - if (!response.ok) { - const body = await response.json().catch(() => ({ message: response.statusText })); - throw new Error(body.message || body.error || `HTTP ${response.status}`); - } - - // Add as a user message in the log - setLogs(prev => [...prev, { - timestamp: new Date().toISOString(), - content: message.trim(), - type: 'user', - }]); - setMessage(''); - inputRef.current?.focus(); - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to send message'); - } finally { - setSending(false); - } - }, [message, taskId, sending]); - - const handleKeyDown = useCallback((e: React.KeyboardEvent) => { - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault(); - handleSendMessage(); - } - // Prevent Escape from bubbling when input is focused - if (e.key === 'Escape') { - e.stopPropagation(); - inputRef.current?.blur(); - } - }, [handleSendMessage]); - - // Interrupt the running task - const handleInterrupt = useCallback(async () => { - if (!taskId) return; - try { - // Send a special interrupt message - const response = await fetch(`/api/v1/mesh/tasks/${taskId}/message`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ message: '/interrupt' }), - }); - if (!response.ok) { - throw new Error(`HTTP ${response.status}`); - } - setLogs(prev => [...prev, { - timestamp: new Date().toISOString(), - content: 'Interrupt signal sent', - type: 'system', - }]); - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to interrupt'); - } - }, [taskId]); - - const formatTimestamp = (ts: string) => { - try { - return new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); - } catch { - return ''; - } - }; - - return ( -
- {/* Header */} -
-
- {stepName} - Logs - - {connected ? 'Live' : 'Disconnected'} - -
-
- {isActive && ( - - )} - -
-
- - {/* Log content */} -
- {logs.length === 0 && !error && ( -
- {isActive - ? 'Waiting for log output...' - : 'No logs available for this step.'} -
- )} - - {error && ( -
{error}
- )} - - {logs.map((entry, idx) => ( -
- {formatTimestamp(entry.timestamp)} - {entry.content} -
- ))} -
-
- - {/* Message input (comment/interrupt controls) */} - {isActive && ( -
- setMessage(e.target.value)} - onKeyDown={handleKeyDown} - disabled={sending} - /> - -
- )} -
- ); -} diff --git a/frontend/src/components/document/nodes/StepsDiagramComponent.tsx b/frontend/src/components/document/nodes/StepsDiagramComponent.tsx deleted file mode 100644 index ac1cb83..0000000 --- a/frontend/src/components/document/nodes/StepsDiagramComponent.tsx +++ /dev/null @@ -1,239 +0,0 @@ -import React, { useEffect, useState, useRef, useCallback } from 'react'; -import { getDirective, DirectiveStep, DirectiveWithSteps } from '../../../services/directiveApi'; -import { StepLogFeed } from './StepLogFeed'; -import './StepsDiagram.css'; - -interface StepsDiagramComponentProps { - directiveId: string; - onExpandContract?: (step: DirectiveStep) => void; -} - -type StepStatus = 'pending' | 'ready' | 'running' | 'completed' | 'failed' | 'skipped'; - -const STATUS_LABELS: Record = { - pending: 'Queued', - ready: 'Ready', - running: 'Executing', - completed: 'Fulfilled', - failed: 'Failed', - skipped: 'Skipped', -}; - -function formatTime(dateStr: string): string { - if (!dateStr) return ''; - const d = new Date(dateStr); - return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); -} - -interface StepCardProps { - step: DirectiveStep; - isExpanded: boolean; - onToggleExpand: () => void; - onCollapse: () => void; -} - -function StepCard({ step, isExpanded, onToggleExpand, onCollapse }: StepCardProps) { - const status = (step.status || 'pending').toLowerCase() as StepStatus; - const hasTask = !!step.taskId || !!step.contractId; - const canExpand = hasTask && ['running', 'completed', 'failed'].includes(status); - - return ( -
-
{ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onToggleExpand(); } } : undefined} - > - {step.name} -
- - {STATUS_LABELS[status] || status} - - {canExpand && ( - - ▶ - - )} -
-
- {step.description && !isExpanded && ( -

{step.description}

- )} -
- #{step.orderIndex} - {status === 'running' && ( - In progress... - )} - {status === 'completed' && step.completedAt && ( - - Completed {formatTime(step.completedAt)} - - )} -
- - {/* Expandable log feed */} - {isExpanded && hasTask && ( - - )} -
- ); -} - -export function StepsDiagramComponent({ directiveId, onExpandContract }: StepsDiagramComponentProps) { - const [steps, setSteps] = useState([]); - const [directiveStatus, setDirectiveStatus] = useState(''); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [expandedStepId, setExpandedStepId] = useState(null); - const intervalRef = useRef | null>(null); - const prevStepCountRef = useRef(0); - - const fetchSteps = useCallback(async () => { - try { - const data: DirectiveWithSteps = await getDirective(directiveId); - setSteps(data.steps || []); - setDirectiveStatus(data.status || ''); - setError(null); - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load contracts'); - } finally { - setLoading(false); - } - }, [directiveId]); - - useEffect(() => { - fetchSteps(); - intervalRef.current = setInterval(fetchSteps, 5000); - return () => { - if (intervalRef.current) clearInterval(intervalRef.current); - }; - }, [fetchSteps]); - - // Track when new steps appear for animation - useEffect(() => { - prevStepCountRef.current = steps.length; - }, [steps.length]); - - // Keyboard shortcut: Escape to collapse expanded step - useEffect(() => { - const handler = (e: KeyboardEvent) => { - if (e.key === 'Escape' && expandedStepId) { - setExpandedStepId(null); - } - }; - document.addEventListener('keydown', handler); - return () => document.removeEventListener('keydown', handler); - }, [expandedStepId]); - - const toggleExpand = useCallback((stepId: string) => { - setExpandedStepId(prev => prev === stepId ? null : stepId); - }, []); - - const collapseExpanded = useCallback(() => { - setExpandedStepId(null); - }, []); - - const completedCount = steps.filter(s => s.status?.toLowerCase() === 'completed').length; - const totalCount = steps.length; - const isActive = ['active', 'running', 'planning'].includes(directiveStatus.toLowerCase()); - const isBuilding = isActive && steps.length === 0; - - // Group steps by orderIndex - const groupedSteps: Map = new Map(); - const sortedSteps = [...steps].sort((a, b) => a.orderIndex - b.orderIndex); - for (const step of sortedSteps) { - const idx = step.orderIndex; - if (!groupedSteps.has(idx)) groupedSteps.set(idx, []); - groupedSteps.get(idx)!.push(step); - } - const orderGroups = Array.from(groupedSteps.entries()).sort((a, b) => a[0] - b[0]); - - if (loading) { - return ( -
-
- Contract Steps - Authored by Makima -
-
-
- Loading contracts... -
-
- ); - } - - if (error) { - return ( -
-
- Contract Steps - Authored by Makima -
-
Failed to load contracts: {error}
-
- ); - } - - return ( -
-
-
- Contract Steps - {totalCount > 0 && ( - - {completedCount}/{totalCount} fulfilled - - )} -
- Authored by Makima -
- - {isBuilding && ( -
-
- -
- Makima is drafting contracts... -
- )} - - {totalCount === 0 && !isBuilding && ( -
No contract steps defined yet.
- )} - - {totalCount > 0 && ( -
- {orderGroups.map(([orderIndex, groupSteps], groupIdx) => ( - - {groupIdx > 0 && ( -
-
-
-
- )} -
- {groupSteps.map((step) => ( - toggleExpand(step.id)} - onCollapse={collapseExpanded} - /> - ))} -
- - ))} -
- )} -
- ); -} -- cgit v1.2.3