// Top-level app, orchestration loop, theme, tweaks
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"theme": "light",
"density": "comfortable",
"accentHue": 280,
"autoFlow": true,
"showAgentChip": true
}/*EDITMODE-END*/;
function CreateTaskModal({ onCreate, onClose }) {
const projects = window.ORC_DATA.PROJECTS || [];
const agents = window.ORC_DATA.AGENTS || [];
const [form, setForm] = React.useState({
title: '',
project: projects[0]?.id || '',
type: 'module-install',
agent: agents[0]?.id || 'installer-agent',
priority: 'medium',
version: '',
repo: 'sandbox/app',
prompt: '',
affectsDb: false,
requiresApproval: true,
});
const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
const valid = form.title.trim() && form.project;
const isCli = (window.ORC_DATA.AGENTS || []).find(a => a.id === form.agent)?.kind === 'codex'
|| (window.ORC_DATA.AGENTS || []).find(a => a.id === form.agent)?.kind === 'gemini';
function submit() {
if (!valid) return;
const payload = {
repo: form.repo || 'sandbox/app',
branch: 'main',
module: 'hello',
targetVersion: form.version || undefined,
prompt: form.prompt.trim() || undefined,
};
onCreate({
title: form.title.trim(),
project: form.project,
agent: form.agent,
type: form.type,
priority: form.priority,
version: form.version,
affectsDb: form.affectsDb,
requiresApproval: form.requiresApproval,
payload,
});
onClose();
}
return (
e.stopPropagation()}>
New task
);
}
function App() {
const [tweaks, setTweak] = window.useTweaks(TWEAK_DEFAULTS);
const [view, setView] = React.useState('board');
const [boardTab, setBoardTab] = React.useState('kanban');
const [projectId, setProjectId] = React.useState(null);
const [tasks, setTasks] = React.useState([]);
const [selectedId, setSelectedId] = React.useState(null);
const [animMap, setAnimMap] = React.useState({});
const [toasts, setToasts] = React.useState([]);
const [loadErr, setLoadErr] = React.useState(null);
const [showCreate, setShowCreate] = React.useState(false);
const [live, setLive] = React.useState(false);
const [logsByTask, setLogsByTask] = React.useState({});
// Theme
React.useEffect(() => {
document.documentElement.dataset.theme = tweaks.theme;
}, [tweaks.theme]);
// Accent
React.useEffect(() => {
const root = document.documentElement;
root.style.setProperty('--c-accent', `oklch(0.58 0.19 ${tweaks.accentHue})`);
root.style.setProperty('--c-accent-hi', `oklch(0.66 0.19 ${tweaks.accentHue})`);
root.style.setProperty('--c-accent-bg', `oklch(0.96 0.03 ${tweaks.accentHue})`);
root.style.setProperty('--c-accent-line', `oklch(0.88 0.06 ${tweaks.accentHue})`);
if (tweaks.theme === 'dark') {
root.style.setProperty('--c-accent', `oklch(0.7 0.18 ${tweaks.accentHue})`);
root.style.setProperty('--c-accent-bg', `oklch(0.28 0.06 ${tweaks.accentHue})`);
}
}, [tweaks.accentHue, tweaks.theme]);
// Density
React.useEffect(() => {
document.documentElement.style.setProperty(
'--header-h',
tweaks.density === 'compact' ? '42px' : tweaks.density === 'spacious' ? '56px' : '48px'
);
}, [tweaks.density]);
const selected = tasks.find(t => t.id === selectedId);
const project = projectId ? window.ORC_DATA.PROJECTS.find(p => p.id === projectId) : null;
// Toast helpers
const pushToast = React.useCallback((toast) => {
const id = Math.random().toString(36).slice(2);
setToasts(t => [...t, { ...toast, id }]);
setTimeout(() => {
setToasts(t => t.map(x => x.id === id ? { ...x, leaving: true } : x));
setTimeout(() => setToasts(t => t.filter(x => x.id !== id)), 250);
}, 3600);
}, []);
// Animate column transitions
const animateMove = React.useCallback((id) => {
setAnimMap(m => ({ ...m, [id]: 'entering' }));
setTimeout(() => setAnimMap(m => { const n = { ...m }; delete n[id]; return n; }), 420);
}, []);
// Load board from backend; hydrate ORC_DATA so child components keep working.
const refresh = React.useCallback(async () => {
try {
const board = await window.ORC_API.getBoard();
window.ORC_DATA.COLUMNS = board.columns;
window.ORC_DATA.PROJECTS = board.projects;
window.ORC_DATA.AGENTS = board.agents;
setTasks(board.tasks);
setLoadErr(null);
} catch (e) {
setLoadErr(e.message || 'failed to reach API');
}
}, []);
// Initial load
React.useEffect(() => { refresh(); }, [refresh]);
// Live stream (SSE) — autoFlow toggle pauses/resumes the connection
React.useEffect(() => {
if (!tweaks.autoFlow) { setLive(false); return; }
const es = window.ORC_API.stream(ev => {
if (ev.type === 'task') {
setTasks(curr => {
const i = curr.findIndex(t => t.id === ev.task.id);
if (i < 0) return [ev.task, ...curr];
if (curr[i].column !== ev.task.column) animateMove(ev.task.id);
const n = [...curr]; n[i] = ev.task; return n;
});
} else if (ev.type === 'log') {
setLogsByTask(m => {
const arr = m[ev.taskId] || [];
if (arr.some(l => String(l.id) === String(ev.log.id))) return m;
return { ...m, [ev.taskId]: [...arr, ev.log] };
});
}
});
es.onopen = () => setLive(true);
es.onerror = () => setLive(false);
return () => es.close();
}, [tweaks.autoFlow, animateMove]);
// Seed log history when a task is opened
React.useEffect(() => {
if (!selectedId) return;
window.ORC_API.getLogs(selectedId).then(rows => {
setLogsByTask(m => {
const liveExtra = (m[selectedId] || []).filter(l => !rows.some(r => String(r.id) === String(l.id)));
return { ...m, [selectedId]: [...rows, ...liveExtra].sort((a, b) => Number(a.id) - Number(b.id)) };
});
}).catch(() => {});
}, [selectedId]);
// Action handlers — call API, then refresh + toast
const runAction = React.useCallback(async (fn, ok) => {
try {
await fn();
await refresh();
if (ok) pushToast(ok);
} catch (e) {
pushToast({ tone: 'fail', title: 'Action failed', msg: e.message || 'error' });
}
}, [refresh, pushToast]);
const handleApprove = React.useCallback((id) =>
runAction(() => window.ORC_API.approve(id), { tone: 'ok', title: 'Approved', msg: `${id} → Done` }),
[runAction]);
const handleReject = React.useCallback((id) =>
runAction(() => window.ORC_API.reject(id), { tone: 'fail', title: 'Rejected', msg: `${id} → Failed` }),
[runAction]);
const handleTransition = React.useCallback((id, to) => {
animateMove(id);
return runAction(() => window.ORC_API.transition(id, to), { tone: to === 'failed' ? 'fail' : 'info', title: 'Moved', msg: `${id} → ${to}` });
}, [runAction, animateMove]);
const handleCreate = React.useCallback((body) =>
runAction(() => window.ORC_API.createTask(body), { tone: 'info', title: 'Task created', msg: `${body.title} → Backlog` }),
[runAction]);
const actions = { approve: handleApprove, reject: handleReject, transition: handleTransition };
const visibleTasks = React.useMemo(() => {
if (view === 'board' && projectId) return tasks.filter(t => t.project === projectId);
return tasks;
}, [tasks, view, projectId]);
function handleNav(id, pid) {
setView(id);
if (id === 'projects' && pid) setProjectId(pid);
if (id === 'board') {} // keep filter
setSelectedId(null);
}
return (
{}}
onThemeToggle={() => setTweak('theme', tweaks.theme === 'dark' ? 'light' : 'dark')}
theme={tweaks.theme}
/>
{view === 'board' && (
<>
Project · {project?.name || 'all'}
Agents · all
Priority · any
{live ? 'orchestrator live' : tweaks.autoFlow ? 'connecting…' : 'paused'}
{loadErr && (
API unreachable — {loadErr} · base {window.ORC_API.base}
)}
{boardTab === 'kanban' && (
)}
{boardTab === 'timeline' && (
)}
{boardTab === 'activity' && (
)}
setSelectedId(null)} actions={actions} logs={logsByTask[selectedId]} />
>
)}
{view === 'projects' && (
{ setView('board'); }}
/>
)}
{view === 'modules' && }
{view === 'agents' && }
{view === 'kb' && }
{view === 'settings' && }
{showCreate && setShowCreate(false)} />}
setTweak('theme', v)}
options={[{ value: 'light', label: 'Light' }, { value: 'dark', label: 'Dark' }]}
/>
setTweak('density', v)}
options={[{ value: 'compact', label: 'Compact' }, { value: 'comfortable', label: 'Default' }, { value: 'spacious', label: 'Spacious' }]}
/>
setTweak('accentHue', v)}
/>
setTweak('autoFlow', v)}
/>
setTweak('showAgentChip', v)}
/>
);
}
ReactDOM.createRoot(document.getElementById('root')).render();