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/LandingPage.tsx | 262 +++++++++++------ frontend/src/components/VNInterface.tsx | 28 +- frontend/src/components/document/index.ts | 14 - .../src/components/document/nodes/StepLogFeed.tsx | 277 ------------------ .../document/nodes/StepsDiagramComponent.tsx | 239 --------------- frontend/src/main.tsx | 10 - frontend/src/services/directiveApi.ts | 185 ------------ frontend/src/stores/index.ts | 37 --- frontend/src/styles/mobile.css | 108 ------- frontend/src/styles/pc98.css | 323 --------------------- 10 files changed, 170 insertions(+), 1313 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 delete mode 100644 frontend/src/services/directiveApi.ts (limited to 'frontend/src') diff --git a/frontend/src/components/LandingPage.tsx b/frontend/src/components/LandingPage.tsx index e7579b5..f126d6f 100644 --- a/frontend/src/components/LandingPage.tsx +++ b/frontend/src/components/LandingPage.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react' +import React, { useState, useEffect, useRef } from 'react' import { LoadingScreen } from './LoadingScreen' import { HeartLogo } from './HeartLogo' @@ -9,7 +9,12 @@ interface LandingPageProps { export function LandingPage({ onLogin }: LandingPageProps) { const [loading, setLoading] = useState(false) const [showLanding, setShowLanding] = useState(false) + const [isStandby, setIsStandby] = useState(true) // false = LIVE, true = STDBY + const [velocity, setVelocity] = useState(0) + const [energy, setEnergy] = useState(0) + const [ramped, setRamped] = useState(false) const [pendingAction, setPendingAction] = useState(null) + const [activePanel, setActivePanel] = useState(null) // Fade-in landing page content after mount useEffect(() => { @@ -17,6 +22,62 @@ export function LandingPage({ onLogin }: LandingPageProps) { return () => clearTimeout(timer) }, []) + // Ramp up stats, then keep them fluctuating near max + useEffect(() => { + const VELOCITY_MAX = 603 + const ENERGY_MAX = 32 + + let rampInterval: number | undefined + let fluctuateInterval: number | undefined + + // Ramp-up for ~2 seconds + const rampDurationMs = 2000 + const tickMs = 30 + const vStep = VELOCITY_MAX / (rampDurationMs / tickMs) + const eStep = ENERGY_MAX / (rampDurationMs / tickMs) + + rampInterval = window.setInterval(() => { + setVelocity((v) => { + const next = v + vStep + return next >= VELOCITY_MAX ? VELOCITY_MAX : next + }) + setEnergy((e) => { + const next = e + eStep + return next >= ENERGY_MAX ? ENERGY_MAX : next + }) + }, tickMs) + + const stopRamp = window.setTimeout(() => { + if (rampInterval) window.clearInterval(rampInterval) + setVelocity(VELOCITY_MAX) + setEnergy(ENERGY_MAX) + setRamped(true) + + // Fluctuate near the top + fluctuateInterval = window.setInterval(() => { + setVelocity((v) => { + const min = VELOCITY_MAX - 18 + const max = VELOCITY_MAX + const delta = (Math.random() - 0.5) * 6 // ±3 + const next = Math.max(min, Math.min(max, v + delta)) + return next + }) + setEnergy((e) => { + const min = ENERGY_MAX - 2 + const max = ENERGY_MAX + const delta = (Math.random() - 0.5) * 0.25 // ±0.125 + const next = Math.max(min, Math.min(max, e + delta)) + return next + }) + }, 220) + }, rampDurationMs + 60) + + return () => { + if (rampInterval) window.clearInterval(rampInterval) + if (fluctuateInterval) window.clearInterval(fluctuateInterval) + } + }, []) + const handleLoadingComplete = () => { if (pendingAction === 'makimaRedirect') { window.location.assign('https://makima.jp') @@ -30,129 +91,144 @@ export function LandingPage({ onLogin }: LandingPageProps) { setLoading(true) } - const scrollTo = (id: string) => { - document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' }) + const handleMission = () => { + setActivePanel((mode) => (mode === 'mission' ? null : 'mission')) + } + + const handleMakimaPanel = () => { + setActivePanel((mode) => (mode === 'makima' ? null : 'makima')) } return (
{loading && } - {/* Professional floating header */} {!loading && ( -
-
-
+
+
+
Soryu { const img = e.currentTarget as HTMLImageElement img.onerror = null img.src = '/logo/crane-logo.png' }} /> - SORYU
- -
+
- +
+
+ System: + setIsStandby(!isStandby)} + title="Click to toggle between LIVE and STANDBY" + > + + {isStandby ? 'STDBY' : 'LIVE'} + +
+
+ Version: + v1.0.0 +
+
)} - {/* Main professional landing layout */} -
- - {/* Hero section */} -
-
-
そりゅう
-

- Real‑Time Systems for
Mission‑Critical Observability -

-

- Low‑latency streaming infrastructure that turns live data into reliable, secure insight. -

-
- - -
-
-
- - {/* Content grid: Mission + Makima cards */} -
-
-
-

Mission

-
+
+ {/* Background GIF fills the main body */} + + + {/* Minimal overlay: masthead, issue badge, and CTA */} +
+
- - {/* Footer */} -
-
- SORYU - - Real‑time systems & infrastructure -
-
+ )}
+ {/* MissionDrawer removed in favor of mission screen transformation */}
) } diff --git a/frontend/src/components/VNInterface.tsx b/frontend/src/components/VNInterface.tsx index 0a77f39..318a9b9 100644 --- a/frontend/src/components/VNInterface.tsx +++ b/frontend/src/components/VNInterface.tsx @@ -9,11 +9,9 @@ import { showSettingsModalStore, isVisibleStore, yenBalanceStore, - documentEditorEnabledStore, toggleStandby, toggleShowChoices, - updateTime, - setDocumentEditorEnabled + updateTime } from '../stores' interface VNInterfaceProps { @@ -28,7 +26,6 @@ export function VNInterface({ onLogout }: VNInterfaceProps) { const showSettingsModal = useStore(showSettingsModalStore) const isVisible = useStore(isVisibleStore) const yenBalance = useStore(yenBalanceStore) - const documentEditorEnabled = useStore(documentEditorEnabledStore) // Fade in effect on mount useEffect(() => { @@ -116,14 +113,6 @@ export function VNInterface({ onLogout }: VNInterfaceProps) { Contracts
- {documentEditorEnabled && ( -
- - Edit: - Directives - -
- )}
@@ -208,21 +197,6 @@ export function VNInterface({ onLogout }: VNInterfaceProps) { -
-

Feature Flags

-
- -
- Enable the directive document editor interface -
-
-

Audio

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} - /> - ))} -
- - ))} -
- )} -
- ); -} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 7688159..04b8cde 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -14,8 +14,6 @@ import './styles/mobile.css' // - /contracts - List all contracts // - /contracts/:id - View contract details with tabs (including Files tab) // - /contracts/:contractId/files/:fileId - View a specific file within contract context -// - /directives - Document editor layout (file tree sidebar) -// - /directives/:id - Document editor for a specific directive // // Note: Standalone file routes (/files, /files/:id) have been removed. // Files are now only accessible through their parent contract. @@ -37,14 +35,6 @@ const router = createBrowserRouter([ path: '/contracts/:contractId/files/:fileId', element: , }, - { - path: '/directives', - element: , - }, - { - path: '/directives/:id', - element: , - }, { path: '/daemons', element: , diff --git a/frontend/src/services/directiveApi.ts b/frontend/src/services/directiveApi.ts deleted file mode 100644 index fcb6636..0000000 --- a/frontend/src/services/directiveApi.ts +++ /dev/null @@ -1,185 +0,0 @@ -// API service for directive operations - -export interface DirectiveStepCounts { - pending: number - ready: number - running: number - completed: number - failed: number - skipped: number -} - -export interface DirectiveSummary { - id: string - title: string - goal: string - status: string - repositoryUrl: string - prUrl: string - prBranch: string - createdAt: string - updatedAt: string - goalUpdatedAt: string - stepCounts: DirectiveStepCounts - version?: number - pr_branch?: string | null -} - -export interface DirectiveStep { - id: string - directiveId: string - directive_id?: string - name: string - title?: string - description: string | null - taskPlan: string - dependsOn: string[] - status: string - contractId: string - /** @deprecated Use contractId instead */ - taskId: string - orderIndex: number - sort_order?: number - completedAt: string -} - -export interface DirectiveWithSteps extends DirectiveSummary { - steps: DirectiveStep[] - reconcileMode: string -} - -// Alias for compatibility with context-menu branch types -export type Directive = DirectiveSummary - -async function apiFetch(path: string, options?: RequestInit): Promise { - const response = await fetch(path, { - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers, - }, - }) - if (!response.ok) { - const body = await response.json().catch(() => ({ message: response.statusText })) - throw new Error(body.message ?? body.error ?? `API error ${response.status}: ${response.statusText}`) - } - return response -} - -export async function listDirectives(): Promise { - const response = await apiFetch('/api/v1/directives') - const data = await response.json() - return data.directives || [] -} - -export async function getDirective(id: string): Promise { - const response = await apiFetch(`/api/v1/directives/${id}`) - return response.json() -} - -export async function getDirectiveSteps(id: string): Promise { - const response = await apiFetch(`/api/v1/directives/${id}/steps`) - return response.json() -} - -export async function updateGoal(id: string, goal: string): Promise { - const response = await apiFetch(`/api/v1/directives/${id}/goal`, { - method: 'PUT', - body: JSON.stringify({ goal }), - }) - return response.json() -} - -export async function updateDirective( - id: string, - updates: { title?: string; goal?: string; version?: number }, -): Promise { - const response = await apiFetch(`/api/v1/directives/${id}`, { - method: 'PUT', - body: JSON.stringify(updates), - }) - return response.json() -} - -export async function cleanupDirective(id: string): Promise { - await apiFetch(`/api/v1/directives/${id}/cleanup`, { method: 'POST' }) -} - -export async function createPr(id: string): Promise<{ prUrl: string }> { - const response = await apiFetch(`/api/v1/directives/${id}/create-pr`, { method: 'POST' }) - return response.json() -} - -export async function pickUpOrders(id: string): Promise { - await apiFetch(`/api/v1/directives/${id}/pick-up-orders`, { method: 'POST' }) -} - -export async function startDirective(id: string): Promise { - const response = await apiFetch(`/api/v1/directives/${id}/start`, { method: 'POST' }) - return response.json() -} - -export async function pauseDirective(id: string): Promise { - const response = await apiFetch(`/api/v1/directives/${id}/pause`, { method: 'POST' }) - return response.json() -} - -export async function getUserSetting(key: string): Promise { - const response = await apiFetch(`/api/v1/user-settings/${key}`) - return response.json() -} - -export async function upsertUserSetting(key: string, value: any): Promise { - await apiFetch('/api/v1/user-settings', { - method: 'PUT', - body: JSON.stringify({ key, value }), - }) -} - -// ---- Task control APIs ---- - -export async function sendTaskMessage(taskId: string, message: string): Promise { - await apiFetch(`/api/v1/mesh/tasks/${taskId}/message`, { - method: 'POST', - body: JSON.stringify({ message }), - }) -} - -export async function stopTask(taskId: string): Promise { - await apiFetch(`/api/v1/mesh/tasks/${taskId}/stop`, { - method: 'POST', - }) -} - -export async function continueTask(taskId: string): Promise { - await apiFetch(`/api/v1/mesh/tasks/${taskId}/continue`, { - method: 'POST', - }) -} - -export async function startTask(taskId: string): Promise { - await apiFetch(`/api/v1/mesh/tasks/${taskId}/start`, { - method: 'POST', - }) -} - -// ---- Contract interaction APIs ---- - -export async function sendContractMessage(taskId: string, message: string): Promise { - await apiFetch(`/api/v1/mesh/tasks/${taskId}/message`, { - method: 'POST', - body: JSON.stringify({ message }), - }) -} - -export async function interruptContract(taskId: string): Promise { - await apiFetch(`/api/v1/mesh/tasks/${taskId}/message`, { - method: 'POST', - body: JSON.stringify({ message: '/interrupt' }), - }) -} - -export async function getContractOutput(taskId: string): Promise<{ output: string }> { - const response = await apiFetch(`/api/v1/mesh/tasks/${taskId}/output`) - return response.json() -} diff --git a/frontend/src/stores/index.ts b/frontend/src/stores/index.ts index 1853f4f..58f461c 100644 --- a/frontend/src/stores/index.ts +++ b/frontend/src/stores/index.ts @@ -1,29 +1,5 @@ import { atom } from 'nanostores' import { ChatMessage, Choice } from '../types' -import { upsertUserSetting } from '../services/directiveApi' - -// Document UI feature flag -export const documentUiEnabledStore = atom( - (() => { - try { - const saved = localStorage.getItem('document_ui_enabled') - return saved === 'true' - } catch { - return false - } - })() -) - -export const setDocumentUiEnabled = async (enabled: boolean) => { - documentUiEnabledStore.set(enabled) - localStorage.setItem('document_ui_enabled', enabled.toString()) - // Persist to backend (best-effort) - try { - await upsertUserSetting('document_ui_enabled', enabled) - } catch (err) { - console.error('Failed to persist document UI setting:', err) - } -} // Authentication state export const isLoggedInStore = atom(false) @@ -53,14 +29,6 @@ export const nameStore = atom('???') export const backgroundStore = atom('/__gaogao__56242cbde8f18ac64522e410bad04e68_waifu2x_art_noise2.png') export const locationStore = atom('Tokyo') export const configModalOpenStore = atom(false) - -// Feature flags -export const documentEditorEnabledStore = atom( - (() => { - const saved = localStorage.getItem('documentEditorEnabled') - return saved === 'true' - })() -) export const skipIntroStore = atom( (() => { const saved = localStorage.getItem('skipIntro') @@ -118,11 +86,6 @@ export const setSkipIntro = (skip: boolean) => { localStorage.setItem('skipIntro', skip.toString()) } -export const setDocumentEditorEnabled = (enabled: boolean) => { - documentEditorEnabledStore.set(enabled) - localStorage.setItem('documentEditorEnabled', enabled.toString()) -} - export const setLoadingComplete = (complete: boolean) => { loadingCompleteStore.set(complete) if (complete) { diff --git a/frontend/src/styles/mobile.css b/frontend/src/styles/mobile.css index 7d1f282..c1c524d 100644 --- a/frontend/src/styles/mobile.css +++ b/frontend/src/styles/mobile.css @@ -51,112 +51,4 @@ .makima-inline-icon { width: 16px; height: 16px; margin-left: 6px; } .makima-list { gap: 4px; } .makima-list li { font-size: 12px; line-height: 1.45; } - - /* ================== Professional Landing Page – Mobile ================== */ - - /* Compact header */ - .pro-header-content { - padding: 0.45rem 1rem; - } - - .pro-company-name { - font-size: 0.85rem; - letter-spacing: 0.12em; - } - - .pro-crane-logo { - height: 28px; - } - - /* Hide center heart on small screens to save space */ - .pro-header-center { - display: none; - } - - /* Full-width nav buttons */ - .pro-header-nav { - gap: 0.15rem; - } - - .pro-nav-link { - font-size: 0.7rem; - padding: 0.3rem 0.5rem; - } - - .pro-nav-login { - margin-left: 0.25rem; - } - - /* Hero – reduce heading size and padding */ - .pro-hero { - min-height: 85vh; - padding: 5rem 1.25rem 3rem; - } - - .pro-hero-headline { - font-size: clamp(1.4rem, 5.5vw, 2rem); - } - - .pro-hero-sub { - font-size: 0.9rem; - margin-bottom: 2rem; - } - - .pro-hero-tagline-jp { - font-size: 0.75rem; - letter-spacing: 0.35em; - } - - .pro-hero-cta { - flex-direction: column; - align-items: stretch; - } - - .pro-btn-primary, - .pro-btn-secondary { - width: 100%; - text-align: center; - justify-content: center; - } - - /* Stack cards to single column */ - .pro-content-grid { - grid-template-columns: 1fr; - padding: 0 1rem 3rem; - gap: 1.5rem; - } - - .pro-card-header { - padding: 1rem 1.25rem 0.6rem; - } - - .pro-card-body { - padding: 0.6rem 1.25rem 1.25rem; - } - - .pro-card-title { - font-size: 1rem; - } - - .pro-card-subtitle { - font-size: 0.88rem; - } - - .pro-card-text { - font-size: 0.8rem; - } - - .pro-makima-logo { - width: 48px; - height: 48px; - } - - /* Footer compact */ - .pro-footer { - padding: 1.25rem 1rem; - } - - .pro-footer-inner { - font-size: 0.7rem; - } } diff --git a/frontend/src/styles/pc98.css b/frontend/src/styles/pc98.css index e591cdc..7ec0d1c 100644 --- a/frontend/src/styles/pc98.css +++ b/frontend/src/styles/pc98.css @@ -4680,326 +4680,3 @@ button:focus-visible { .capacity-fill.high { background: #ffcc66; } .capacity-fill.full { background: #ff4466; } .refresh-indicator { font-size: 11px; color: rgba(255, 255, 255, 0.3); margin-left: auto; } - -/* ================== Professional Landing Page ================== */ - -/* ── Header ── */ - -.pro-header { - position: fixed; - top: 0; - left: 0; - right: 0; - z-index: 100; - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - background: rgba(10, 14, 28, 0.85); - border-bottom: 1px solid rgba(0, 200, 255, 0.15); -} - -.pro-header-content { - max-width: 1100px; - margin: 0 auto; - display: flex; - align-items: center; - justify-content: space-between; - padding: 0.6rem 1.5rem; -} - -.pro-header-left { - display: flex; - align-items: center; - gap: 0.65rem; -} - -.pro-crane-logo { - display: block; -} - -.pro-company-name { - font-family: var(--font-family, 'IBM Plex Mono', 'MS Gothic', monospace); - font-size: 1rem; - font-weight: 600; - letter-spacing: 0.18em; - color: #e0e8f0; -} - -.pro-header-center { - display: flex; - align-items: center; - justify-content: center; -} - -.pro-header-nav { - display: flex; - align-items: center; - gap: 0.25rem; -} - -.pro-nav-link { - background: none; - border: 1px solid transparent; - color: #8fa8c8; - font-family: var(--font-family, 'IBM Plex Mono', 'MS Gothic', monospace); - font-size: 0.8rem; - letter-spacing: 0.08em; - padding: 0.35rem 0.75rem; - cursor: pointer; - border-radius: 3px; - transition: color 0.2s, border-color 0.2s; -} - -.pro-nav-link:hover { - color: #00c8ff; - border-color: rgba(0, 200, 255, 0.3); -} - -.pro-nav-login { - color: #00c8ff; - border-color: rgba(0, 200, 255, 0.35); - margin-left: 0.5rem; -} - -.pro-nav-login:hover { - background: rgba(0, 200, 255, 0.08); - border-color: #00c8ff; -} - -/* ── Landing Container ── */ - -.pro-landing { - min-height: 100vh; - background: #080c1a; - color: #c8d4e0; - font-family: var(--font-family, 'IBM Plex Mono', 'MS Gothic', monospace); - overflow-x: hidden; -} - -/* ── Hero ── */ - -.pro-hero { - display: flex; - align-items: center; - justify-content: center; - min-height: 100vh; - padding: 6rem 1.5rem 4rem; - text-align: center; - position: relative; -} - -.pro-hero::before { - content: ''; - position: absolute; - inset: 0; - background: - radial-gradient(ellipse 60% 50% at 50% 40%, rgba(0, 100, 180, 0.08) 0%, transparent 100%), - radial-gradient(ellipse 40% 30% at 50% 60%, rgba(0, 200, 255, 0.04) 0%, transparent 100%); - pointer-events: none; -} - -.pro-hero-inner { - position: relative; - max-width: 700px; -} - -.pro-hero-tagline-jp { - font-size: 0.85rem; - letter-spacing: 0.5em; - color: rgba(0, 200, 255, 0.45); - margin-bottom: 1.5rem; - text-transform: uppercase; -} - -.pro-hero-headline { - font-size: clamp(1.8rem, 4vw, 2.8rem); - font-weight: 600; - line-height: 1.25; - color: #e8f0fa; - margin: 0 0 1.25rem; - letter-spacing: -0.01em; -} - -.pro-hero-sub { - font-size: 1rem; - line-height: 1.7; - color: #8fa8c8; - margin: 0 auto 2.5rem; - max-width: 520px; -} - -.pro-hero-cta { - display: flex; - gap: 1rem; - justify-content: center; - flex-wrap: wrap; -} - -.pro-btn-primary, -.pro-btn-secondary { - font-family: var(--font-family, 'IBM Plex Mono', 'MS Gothic', monospace); - font-size: 0.85rem; - letter-spacing: 0.06em; - padding: 0.65rem 1.5rem; - border-radius: 3px; - cursor: pointer; - transition: all 0.2s; -} - -.pro-btn-primary { - background: rgba(0, 200, 255, 0.1); - color: #00c8ff; - border: 1px solid rgba(0, 200, 255, 0.35); -} - -.pro-btn-primary:hover { - background: rgba(0, 200, 255, 0.18); - border-color: #00c8ff; -} - -.pro-btn-secondary { - background: transparent; - color: #8fa8c8; - border: 1px solid rgba(140, 170, 200, 0.25); -} - -.pro-btn-secondary:hover { - color: #e0e8f0; - border-color: rgba(140, 170, 200, 0.5); -} - -.pro-btn-icon { - margin-right: 0.35rem; - font-size: 0.75rem; -} - -/* ── Content Grid ── */ - -.pro-content-grid { - max-width: 1100px; - margin: 0 auto; - padding: 0 1.5rem 5rem; - display: grid; - grid-template-columns: repeat(auto-fit, minmax(min(100%, 440px), 1fr)); - gap: 2rem; -} - -.pro-card { - background: rgba(12, 18, 36, 0.7); - border: 1px solid rgba(0, 200, 255, 0.12); - border-radius: 4px; - overflow: hidden; - scroll-margin-top: 5rem; - transition: border-color 0.3s ease, box-shadow 0.3s ease; -} - -.pro-card:hover { - border-color: rgba(0, 200, 255, 0.28); - box-shadow: 0 0 20px rgba(0, 200, 255, 0.06); -} - -.pro-card-header { - padding: 1.25rem 1.5rem 0.75rem; - display: flex; - align-items: center; - gap: 0.75rem; - flex-wrap: wrap; -} - -.pro-card-title { - font-size: 1.1rem; - font-weight: 600; - letter-spacing: 0.12em; - color: #e0e8f0; - margin: 0; - text-transform: uppercase; -} - -.pro-card-badge { - font-size: 0.65rem; - letter-spacing: 0.1em; - text-transform: uppercase; - padding: 0.2rem 0.5rem; - background: rgba(0, 200, 255, 0.1); - border: 1px solid rgba(0, 200, 255, 0.25); - border-radius: 2px; - color: #00c8ff; -} - -.pro-card-accent { - flex: 1; - height: 1px; - background: linear-gradient(90deg, rgba(0, 200, 255, 0.3), transparent); -} - -.pro-card-body { - padding: 0.75rem 1.5rem 1.5rem; -} - -.pro-card-subtitle { - font-size: 0.95rem; - font-weight: 500; - color: #c0d0e0; - margin: 0 0 0.75rem; - line-height: 1.5; -} - -.pro-card-text { - font-size: 0.85rem; - line-height: 1.7; - color: #7a90a8; - margin: 0 0 0.6rem; -} - -.pro-card-text:last-child { - margin-bottom: 0; -} - -.pro-makima-logo { - display: block; - width: 64px; - height: 64px; - margin-bottom: 1rem; - opacity: 0.75; -} - -/* ── Footer ── */ - -.pro-footer { - border-top: 1px solid rgba(0, 200, 255, 0.1); - padding: 1.5rem; - text-align: center; -} - -.pro-footer-inner { - font-size: 0.75rem; - color: #4a6080; - letter-spacing: 0.08em; -} - -.pro-footer-brand { - letter-spacing: 0.18em; - color: #5a7898; -} - -.pro-footer-sep { - margin: 0 0.5rem; - opacity: 0.4; -} - -.pro-footer-text { - color: #4a6080; -} - -/* ── Shared transitions ── */ - -.pro-landing.hidden, -.pro-header.hidden { - opacity: 0; - pointer-events: none; -} - -.pro-landing.fade-in, -.pro-header.fade-in { - opacity: 1; - transition: opacity 0.8s ease; -} -- cgit v1.2.3