// app.jsx — Correx TUI shell: layout, live state, keybind engine, theme + tweaks.
const { useState, useEffect, useRef, useCallback } = React;
const D = window.CORREX;
const { applyTheme, THEMES, ACCENTS } = window.CORREX_THEMES;
const DIR_ORDER = ["boxed", "soft", "bare"];
const ACC_ORDER = ["cyan", "amber", "green", "magenta", "neutral"];
const SPIN = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"direction": "boxed",
"accent": "cyan",
"fontSize": 13,
"motion": true,
"scanlines": false
}/*EDITMODE-END*/;
function Panel({ n, title, right, active, className, footer, bodyRef, children }) {
return (
{n && {n}}
{title}
{right && {right}}
{children}
{footer}
);
}
function App() {
const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
const rootRef = useRef(null);
const evRef = useRef(null);
const [overlay, setOverlay] = useState(null); // 'cmd'|'events'|'context'|'graph'|'models'|'tools'|'approval'|null
const [focusPanel, setFocusPanel] = useState("conv"); // 'conv'|'events'
const [sessionState, setSessionState] = useState("awaiting_approval");
const [activeModel, setActiveModel] = useState(D.SESSION.model);
const [tools, setTools] = useState(D.TOOLS);
const [autoApprove, setAutoApprove] = useState(false);
const [approvalResolved, setApprovalResolved] = useState(false);
const [msgs, setMsgs] = useState(D.CONVERSATION);
const [events, setEvents] = useState(D.EVENTS);
const [stages, setStages] = useState(D.STAGES);
const [toast, setToast] = useState(null);
const [streamTarget, setStreamTarget] = useState(D.CONVERSATION.length - 1);
const [streamLen, setStreamLen] = useState(0);
const [frame, setFrame] = useState(0);
const [draft, setDraft] = useState("");
const [promptFocus, setPromptFocus] = useState(false);
const promptRef = useRef(null);
const convRef = useRef(null);
// theme application
useEffect(() => { if (rootRef.current) applyTheme(rootRef.current, t.direction, t.accent); }, [t.direction, t.accent]);
// spinner / clock frames
useEffect(() => {
if (!t.motion) return;
const id = setInterval(() => setFrame((f) => f + 1), 110);
return () => clearInterval(id);
}, [t.motion]);
// streaming reveal
useEffect(() => {
if (streamTarget == null) return;
const full = (msgs[streamTarget] || {}).text || "";
if (streamLen >= full.length) return;
if (!t.motion) { setStreamLen(full.length); return; }
const id = setTimeout(() => setStreamLen((n) => Math.min(n + 3, full.length)), 16);
return () => clearTimeout(id);
}, [streamTarget, streamLen, msgs, t.motion]);
// autoscroll event panel
useEffect(() => { if (evRef.current) evRef.current.scrollTop = evRef.current.scrollHeight; }, [events]);
// autoscroll conversation
useEffect(() => { if (convRef.current) convRef.current.scrollTop = convRef.current.scrollHeight; }, [msgs, streamLen]);
const flash = (m) => { setToast(m); setTimeout(() => setToast(null), 2600); };
const pushEvent = (e) => setEvents((arr) => [...arr, { id: "x" + arr.length + Math.random().toString(36).slice(2, 5), ...e }]);
const addMsg = (m) => { setMsgs((arr) => { const ni = arr.length; setStreamTarget(ni); setStreamLen(0); return [...arr, m]; }); };
const appendMsg = (m) => setMsgs((arr) => [...arr, m]);
// user steering input → routes back through Correx
const hm = () => new Date().toTimeString().slice(0, 5);
const replyFor = (q) => {
const s = q.toLowerCase();
if (/test|spec|cover/.test(s)) return "Queued — I'll add SegmentStoreTest with an append→read round-trip and a dedup assertion (identical bytes ⇒ one object). Runs at the validation gate.";
if (/perf|bench|fast|speed|throughput/.test(s)) return "Noted. I'll add a JMH harness for append throughput; the mmap read path stays zero-copy so reads aren't affected by compression.";
if (/revert|undo|stop|cancel|rollback/.test(s)) return "Holding edits. Nothing destructive ran — the gated gradle resolve was the only external op. Awaiting your next instruction.";
if (/compress|zstd|gzip|lz4|codec/.test(s)) return "Compression is pluggable behind a SegmentCodec — zstd for cold, raw for hot. I can swap lz4 for lower CPU at a worse ratio; say the word.";
if (/index|offset|seek|lookup/.test(s)) return "SegmentIndex maps logical offset ranges → CAS keys with a binary search over segment bounds; O(log n) seeks. I'll memo the hot range.";
return "Understood — folding that into the implementation plan. I'll surface a revised ImplementationArtifact at the validation gate for review.";
};
const send = () => {
const text = draft.trim(); if (!text) return;
setDraft("");
setStreamTarget(null);
appendMsg({ role: "user", t: hm(), text });
const at = (ms, fn) => seqRef.current.push(setTimeout(fn, ms));
at(320, () => pushEvent({ t: hm() + ":02", cat: "Context", type: "steer.captured", text: "router → agent context (L1)" }));
at(760, () => addMsg({ role: "kernel", kind: "route", text: "Steering synthesized → injected at L1 · agent context updated" }));
at(1500, () => addMsg({ role: "assistant", t: hm(), text: replyFor(text) }));
};
// resolve approval → drive the live sequence
const seqRef = useRef([]);
const clearSeq = () => { seqRef.current.forEach(clearTimeout); seqRef.current = []; };
useEffect(() => () => clearSeq(), []);
const resolveApproval = useCallback((kind, note) => {
setOverlay(null);
setApprovalResolved(true);
setSessionState("active");
setEvents((arr) => arr.map((e) => e.pending ? { ...e, pending: false } : e));
const at = (ms, fn) => seqRef.current.push(setTimeout(fn, ms));
if (kind === "reject") {
flash("Rejected — agent re-plans without the network tool");
pushEvent({ t: "14:12:31", cat: "Approval", type: "approval.rejected", text: "operator rejected T3 gate" });
at(700, () => pushEvent({ t: "14:12:33", cat: "Inference", type: "infer.start", text: "re-planning · no-network constraint" }));
at(1600, () => addMsg({ role: "assistant", t: "14:12", text: "Understood — dropping zstd. I'll compress cold segments with the JDK Deflater instead: no new dependency, no network. Slightly worse ratio, zero supply-chain surface." }));
at(3400, () => pushEvent({ t: "14:12:48", cat: "Domain", type: "artifact.emitted", text: "ImplementationArtifact ✓ (deflater path)" }));
at(4200, () => transition());
return;
}
// approve / auto / steer
const auto = kind === "auto";
if (auto) setAutoApprove(true);
flash(kind === "steer" ? "Steered + approved — note injected into agent context" : auto ? "Auto-approve enabled for session" : "Approved");
pushEvent({ t: "14:12:14", cat: "Approval", type: kind === "steer" ? "approval.steered" : "approval.approved", text: note ? "“" + note + "”" : "operator approved" + (auto ? " · auto-session ON" : "") });
at(500, () => pushEvent({ t: "14:12:15", cat: "Tool", type: "shell.exec", text: "gradle resolving zstd-jni …", running: true }));
at(2100, () => setEvents((arr) => arr.map((e) => e.running ? { id: e.id, t: "14:12:19", cat: "Tool", type: "shell.exec", text: "gradle resolve OK (4.1s) · +1 dep" } : e)));
at(2600, () => addMsg({ role: "assistant", t: "14:12", text: (note ? "Locking the version as asked. " : "") + "Resolved zstd-jni 1.5.6-3. Cold segments now zstd-compress on write; hot segments stay raw for mmap. Emitting the implementation artifact." }));
at(4600, () => pushEvent({ t: "14:12:34", cat: "Domain", type: "artifact.emitted", text: "ImplementationArtifact ✓ valid" }));
at(5400, () => transition());
}, []);
const transition = () => {
pushEvent({ t: "14:12:36", cat: "Lifecycle", type: "stage.transition", text: "implementation → validation" });
setStages((arr) => arr.map((s) => s.id === "implementation" ? { ...s, status: "done", note: "artifact accepted" } : s.id === "validation" ? { ...s, status: "active", enter: "14:12", note: "schema + semantic checks" } : s));
pushEvent({ t: "14:12:36", cat: "Lifecycle", type: "stage.enter", text: "validation (role=reviewer)" });
};
// auto-open the approval gate once (modal interrupt demonstration)
const raised = useRef(false);
useEffect(() => {
if (raised.current) return; raised.current = true;
const id = setTimeout(() => { if (!approvalResolved) setOverlay("approval"); }, 1100);
return () => clearTimeout(id);
}, []);
// ── keybind engine ──
const cycle = (key, order) => setTweak(key, order[(order.indexOf(t[key]) + 1) % order.length]);
const setDirection = (dir) => setTweak({ direction: dir, accent: THEMES[dir].defaultAccent });
const cycleDir = () => setDirection(DIR_ORDER[(DIR_ORDER.indexOf(t.direction) + 1) % DIR_ORDER.length]);
const open = (id) => {
if (id === "appearance") { cycleDir(); return; }
if (id === "approval") { if (approvalResolved) { flash("No pending approvals"); return; } setOverlay("approval"); return; }
setOverlay(id);
};
useEffect(() => {
const onKey = (e) => {
const typing = /^(INPUT|TEXTAREA)$/.test(document.activeElement && document.activeElement.tagName);
// approval modal chords
if (overlay === "approval") {
if (typing) return;
if (e.key === "a") { resolveApproval("approve"); e.preventDefault(); }
else if (e.key === "A") { resolveApproval("auto"); e.preventDefault(); }
else if (e.key === "r") { resolveApproval("reject"); e.preventDefault(); }
else if (e.key === "Escape") setOverlay(null);
return;
}
if (e.key === "Escape") { if (overlay) { setOverlay(null); e.preventDefault(); } return; }
if (typing) return;
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { setOverlay("cmd"); e.preventDefault(); return; }
if (overlay === "cmd") return; // palette owns keys
if (overlay) return; // other overlays: only esc (above)
switch (e.key) {
case "p": case ":": case "/": setOverlay("cmd"); e.preventDefault(); break;
case "e": open("events"); break;
case "c": open("context"); break;
case "g": open("graph"); break;
case "m": open("models"); break;
case "t": open("tools"); break;
case "i": promptRef.current && promptRef.current.focus(); e.preventDefault(); break;
case "a": open("approval"); break;
case "F2": cycleDir(); e.preventDefault(); break;
case "F3": cycle("accent", ACC_ORDER); e.preventDefault(); break;
case "Tab": setFocusPanel((p) => p === "conv" ? "events" : "conv"); e.preventDefault(); break;
default: break;
}
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [overlay, approvalResolved, t.direction, t.accent, resolveApproval]);
// ── footer hints (contextual) ──
const hints = (() => {
if (overlay === "approval") return [["a", "approve"], ["A", "auto-session"], ["s", "steer"], ["r", "reject"], ["esc", "later"]];
if (overlay === "cmd") return [["↑↓", "move"], ["↵", "run"], ["esc", "close"]];
if (overlay) return [["↑↓", "navigate"], ["↵", "select"], ["esc", "back"]];
if (promptFocus) return [["↵", "send to correx"], ["esc", "blur"]];
return [["i", "message"], ["p", "commands"], ["a", "approval"], ["e", "events"], ["c", "context"], ["g", "graph"], ["m", "models"], ["t", "tools"], ["F2", "skin"]];
})();
const tokPct = Math.min(100, Math.round((D.CONTEXT_PACK.used / D.CONTEXT_PACK.budget) * 100) + (approvalResolved ? 4 : 0));
const spin = SPIN[frame % SPIN.length];
return (
{t.scanlines &&
}
{/* top status line */}
correx
·
session {D.SESSION.name}
·
{D.SESSION.branch}
·
{activeModel} ({D.MODELS.find((m) => m.id === activeModel).provider})
{autoApprove &&
auto-approve}
ctx
{tokPct}%
{sessionState === "awaiting_approval" ? "⏸ awaiting approval" : "▸ active"}
{/* main split */}
}>
{msgs.map((m, i) => {
const streaming = i === streamTarget;
const text = streaming ? m.text.slice(0, streamLen) : m.text;
const who = m.role === "user" ? "you" : m.role === "kernel" ? "correx" : "agent";
return (
{who}{m.t}
{text}{streaming && streamLen < m.text.length && }{streaming && streamLen >= m.text.length && sessionState === "awaiting_approval" && }
);
})}
{spin} paused : {spin} live}>
{events.map((e) => (
setOverlay("events")}>
{e.t}
{e.cat}
{e.running && {spin} }{e.type} {e.text}{e.pending && a}
))}
{/* footer keybind bar */}
{hints.map(([k, l], i) => (
{
const map = { p: () => setOverlay("cmd"), a: () => open("approval"), e: () => open("events"), c: () => open("context"), g: () => open("graph"), m: () => open("models"), t: () => open("tools"), i: () => promptRef.current && promptRef.current.focus(), F2: () => cycleDir() };
map[k] && map[k]();
}}>
{k}{l}
))}
{THEMES[t.direction].label} · {t.accent}
{toast && {toast}
}
{/* overlays */}
{overlay === "cmd" && { setOverlay(null); setTimeout(() => open(id), 0); }} onClose={() => setOverlay(null)} />}
{overlay === "approval" && resolveApproval(auto ? "auto" : "approve")} onReject={() => resolveApproval("reject")} onSteer={(n) => resolveApproval("steer", n)} onClose={() => setOverlay(null)} />}
{overlay === "events" && setOverlay(null)} />}
{overlay === "context" && setOverlay(null)} />}
{overlay === "graph" && setOverlay(null)} />}
{overlay === "models" && { setActiveModel(id); setOverlay(null); flash("Switched → " + id + " · re-synthesizing Context Pack"); pushEvent({ t: "14:12", cat: "Context", type: "pack.resynth", text: "for " + id }); }} onClose={() => setOverlay(null)} />}
{overlay === "tools" && setTools((arr) => arr.map((x) => x.id === id ? { ...x, enabled: !x.enabled } : x))} onClose={() => setOverlay(null)} />}
{/* tweaks */}
setDirection(v)} />
{ const map = { "#56c8d8": "cyan", "#e8b765": "amber", "#8fd96a": "green", "#c98fd9": "magenta", "#b9c0c9": "neutral" }; setTweak("accent", map[hex] || "cyan"); }} />
setTweak("fontSize", v)} />
setTweak("motion", v)} />
setTweak("scanlines", v)} />
);
}
ReactDOM.createRoot(document.getElementById("root")).render();