From 5bde7c2d7e099fd9c8b2615602ab1d096bd9b6be Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
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) <noreply@anthropic.com>

* 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 `<remote>/<base>` 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 <base_branch>` 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) <noreply@anthropic.com>

* 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) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 .../src/components/document/nodes/StepLogFeed.tsx  | 277 ---------------------
 1 file changed, 277 deletions(-)
 delete mode 100644 frontend/src/components/document/nodes/StepLogFeed.tsx

(limited to 'frontend/src/components/document/nodes/StepLogFeed.tsx')

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<LogEntry[]>([]);
-  const [message, setMessage] = useState('');
-  const [sending, setSending] = useState(false);
-  const [connected, setConnected] = useState(false);
-  const [error, setError] = useState<string | null>(null);
-  const logsEndRef = useRef<HTMLDivElement>(null);
-  const wsRef = useRef<WebSocket | null>(null);
-  const logContainerRef = useRef<HTMLDivElement>(null);
-  const inputRef = useRef<HTMLInputElement>(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<typeof setTimeout>;
-    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 (
-    <div className="step-log-feed">
-      {/* Header */}
-      <div className="step-log-feed-header">
-        <div className="step-log-feed-header-left">
-          <span className="step-log-feed-title">{stepName} - Logs</span>
-          <span className={`step-log-feed-status ${connected ? 'connected' : 'disconnected'}`}>
-            {connected ? 'Live' : 'Disconnected'}
-          </span>
-        </div>
-        <div className="step-log-feed-header-right">
-          {isActive && (
-            <button
-              className="step-log-feed-interrupt-btn"
-              onClick={handleInterrupt}
-              title="Interrupt this contract"
-            >
-              &#x23F9; Interrupt
-            </button>
-          )}
-          <button
-            className="step-log-feed-collapse-btn"
-            onClick={onCollapse}
-            title="Collapse (Esc)"
-          >
-            &#x2715;
-          </button>
-        </div>
-      </div>
-
-      {/* Log content */}
-      <div className="step-log-feed-content" ref={logContainerRef}>
-        {logs.length === 0 && !error && (
-          <div className="step-log-feed-empty">
-            {isActive
-              ? 'Waiting for log output...'
-              : 'No logs available for this step.'}
-          </div>
-        )}
-
-        {error && (
-          <div className="step-log-feed-error">{error}</div>
-        )}
-
-        {logs.map((entry, idx) => (
-          <div key={idx} className={`step-log-entry step-log-entry--${entry.type}`}>
-            <span className="step-log-entry-time">{formatTimestamp(entry.timestamp)}</span>
-            <span className="step-log-entry-content">{entry.content}</span>
-          </div>
-        ))}
-        <div ref={logsEndRef} />
-      </div>
-
-      {/* Message input (comment/interrupt controls) */}
-      {isActive && (
-        <div className="step-log-feed-input">
-          <input
-            ref={inputRef}
-            type="text"
-            className="step-log-feed-input-field"
-            placeholder="Send a message to this contract..."
-            value={message}
-            onChange={(e) => setMessage(e.target.value)}
-            onKeyDown={handleKeyDown}
-            disabled={sending}
-          />
-          <button
-            className="step-log-feed-send-btn"
-            onClick={handleSendMessage}
-            disabled={!message.trim() || sending}
-            title="Send message (Enter)"
-          >
-            {sending ? '...' : '&#x27A4;'}
-          </button>
-        </div>
-      )}
-    </div>
-  );
-}
-- 
cgit v1.2.3

