docs: add visual design reference (TUI mockups)

This commit is contained in:
2026-06-01 23:23:29 +04:00
parent 91ededa4ca
commit 198cf95725
8 changed files with 2225 additions and 0 deletions
+243
View File
@@ -0,0 +1,243 @@
// overlays.jsx — Correx overlays: command palette, approval gate, and inspectors.
const { useState, useEffect, useRef, useMemo } = React;
/* ── generic overlay shell ── */
function Overlay({ tag, title, esc = "esc", center, width, onClose, children }) {
return (
<div className={"scrim" + (center ? " center" : "")} onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}>
<div className="ovl" style={width ? { width } : null} onMouseDown={(e) => e.stopPropagation()}>
<div className="ovl-h">
<span className="tag">{tag}</span>
<span className="ti">{title}</span>
<span className="esc">{esc} to close</span>
</div>
<div className="ovl-b">{children}</div>
</div>
</div>
);
}
/* ── command palette ── */
function CommandPalette({ commands, onRun, onClose }) {
const [q, setQ] = useState("");
const [i, setI] = useState(0);
const inp = useRef(null);
useEffect(() => { inp.current && inp.current.focus(); }, []);
const score = (c) => {
const s = (c.label + " " + c.hint).toLowerCase(), t = q.toLowerCase().trim();
if (!t) return 1;
if (s.includes(t)) return 2;
let qi = 0; for (const ch of s) if (ch === t[qi]) qi++;
return qi === t.length ? 1 : 0;
};
const list = useMemo(() => commands.map((c) => [c, score(c)]).filter(([, s]) => s > 0)
.sort((a, b) => b[1] - a[1]).map(([c]) => c), [q]);
useEffect(() => { setI(0); }, [q]);
const key = (e) => {
if (e.key === "ArrowDown") { setI((x) => Math.min(x + 1, list.length - 1)); e.preventDefault(); }
else if (e.key === "ArrowUp") { setI((x) => Math.max(x - 1, 0)); e.preventDefault(); }
else if (e.key === "Enter") { list[i] && onRun(list[i].id); e.preventDefault(); }
else if (e.key === "Escape") { onClose(); }
e.stopPropagation();
};
return (
<div className="scrim" onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}>
<div className="ovl" style={{ width: "min(560px,94%)" }} onMouseDown={(e) => e.stopPropagation()}>
<div className="cmd-input">
<span className="pr"></span>
<input ref={inp} value={q} placeholder="Jump to a capability…" onChange={(e) => setQ(e.target.value)} onKeyDown={key} />
</div>
<div className="cmd-list">
{list.length === 0 && <div className="hint-empty">no matches</div>}
{list.map((c, n) => (
<div key={c.id} className={"cmd-it" + (n === i ? " sel" : "")} onMouseEnter={() => setI(n)} onClick={() => onRun(c.id)}>
<span className="ci-k">{c.key}</span>
<span className="ci-l">{c.label}</span>
<span className="ci-h">{c.hint}</span>
</div>
))}
</div>
</div>
</div>
);
}
/* ── approval gate (modal interrupt) ── */
function ApprovalModal({ data, onApprove, onReject, onSteer, onClose }) {
const [steer, setSteer] = useState("");
const sref = useRef(null);
return (
<div className="scrim center" onMouseDown={(e) => e.stopPropagation()}>
<div className="ovl appr" onMouseDown={(e) => e.stopPropagation()}>
<div className="appr-tier">
<span className="appr-badge">{data.tier}</span>
<div>
<div className="lab">Approval required · <b>{data.tierLabel}</b></div>
<div className="sub">{data.intent}</div>
</div>
<span className="esc" style={{ marginLeft: "auto" }}>execution paused</span>
</div>
<div className="ovl-b">
<div className="appr-cmd"><span className="pr">$ </span>{data.command}</div>
<div style={{ color: "var(--dim)", fontSize: 12.5, marginBottom: 6 }}>{data.detail}</div>
<dl className="kv">
<dt>tool</dt><dd>{data.tool} <span className="tag-tier tier-T3">{data.tier}</span></dd>
<dt>reversible</dt><dd>{data.reversible ? "yes" : "no"}</dd>
<dt>policy</dt><dd style={{ color: "var(--dim)" }}>{data.policy}</dd>
</dl>
<div style={{ margin: "10px 0 4px", color: "var(--dim)", fontSize: 11, letterSpacing: ".08em", textTransform: "uppercase" }}>semantic risk scan</div>
{data.risks.map((r, n) => <div className="risk" key={n}>{r}</div>)}
<div className="steer">
<span style={{ color: "var(--accent)" }}></span>
<input ref={sref} value={steer} placeholder='steer + approve, e.g. "ok, but lock the version"'
onChange={(e) => setSteer(e.target.value)}
onKeyDown={(e) => { e.stopPropagation(); if (e.key === "Enter" && steer.trim()) onSteer(steer.trim()); if (e.key === "Escape") sref.current.blur(); }} />
</div>
</div>
<div className="appr-acts">
<button className="btn ok" onClick={() => onApprove(false)}>approve <kbd>a</kbd></button>
<button className="btn ghost" onClick={() => onApprove(true)}>auto-approve session <kbd>A</kbd></button>
<button className="btn" onClick={() => steer.trim() ? onSteer(steer.trim()) : sref.current.focus()}>steer <kbd>s</kbd></button>
<button className="btn bad" onClick={onReject}>reject <kbd>r</kbd></button>
</div>
</div>
</div>
);
}
/* ── event stream inspector ── */
function EventInspector({ events, onClose }) {
const cats = ["All", "Lifecycle", "Context", "Inference", "Tool", "Domain", "Approval"];
const [cat, setCat] = useState("All");
const [sel, setSel] = useState(events[events.length - 1]);
const list = events.filter((e) => cat === "All" || e.cat === cat);
const payload = {
id: sel.id, type: sel.type, category: sel.cat, ts: "2026-05-29T" + sel.t,
session_id: window.CORREX.SESSION.id, correlation_id: "cor-7f2a", causation_id: sel.id === "e49" ? "e48" : null,
payload: { detail: sel.text },
};
return (
<Overlay tag="events" title="Event stream inspector" onClose={onClose} width="min(820px,95%)">
<div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginBottom: 10 }}>
{cats.map((c) => <span key={c} className={"pill" + (c === cat ? " live" : "")} style={{ cursor: "pointer" }} onClick={() => setCat(c)}>{c}</span>)}
</div>
<div style={{ display: "grid", gridTemplateColumns: "1.3fr 1fr", gap: 14 }}>
<div>
{list.map((e) => (
<div key={e.id} className={"ev" + (e === sel ? " sel" : "")} onClick={() => setSel(e)}>
<span className="ev-t">{e.t}</span>
<span className={"ev-cat cat-" + e.cat}>{e.cat}</span>
<span className="ev-tx"><b>{e.type}</b> {e.text}</span>
</div>
))}
</div>
<div className="appr-cmd" style={{ margin: 0, alignSelf: "start", fontSize: 12 }}>
<pre style={{ whiteSpace: "pre-wrap", margin: 0, color: "var(--fg)" }}>{JSON.stringify(payload, null, 2)}</pre>
</div>
</div>
</Overlay>
);
}
/* ── context pack inspector ── */
function ContextPackInspector({ pack, onClose }) {
const pct = Math.round((pack.used / pack.budget) * 100);
return (
<Overlay tag="context" title="Context Pack — what the model receives" onClose={onClose} width="min(760px,95%)">
<div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 12 }}>
<div style={{ color: "var(--fg-strong)", fontSize: 15 }}>{pack.used.toLocaleString()} <span style={{ color: "var(--dim)", fontSize: 12 }}>/ {pack.budget.toLocaleString()} tok</span></div>
<div className="mono-bar" style={{ flex: 1 }}><i style={{ width: pct + "%" }} /></div>
<div style={{ color: "var(--accent)" }}>{pct}%</div>
</div>
{pack.layers.map((l) => (
<div className="row" key={l.id} style={{ gridTemplateColumns: "38px 150px 1fr 64px" }}>
<span style={{ color: "var(--accent)", fontWeight: 600 }}>{l.id}</span>
<span style={{ color: "var(--fg)" }}>{l.name}<br /><span style={{ color: "var(--faint)", fontSize: 11 }}>{l.mode}</span></span>
<span style={{ color: "var(--dim)", fontSize: 12 }}>{l.items.join(" · ")}</span>
<span style={{ color: "var(--dim)", textAlign: "right" }}>{l.tok}t</span>
</div>
))}
<div style={{ marginTop: 12, color: "var(--dim)", fontSize: 11, letterSpacing: ".08em", textTransform: "uppercase" }}>excluded by synthesis</div>
{pack.dropped.map((d, n) => <div key={n} style={{ color: "var(--faint)", fontSize: 12.5, padding: "2px 0" }}> {d}</div>)}
</Overlay>
);
}
/* ── stage / transition graph ── */
function StageGraph({ stages, transitions, onClose }) {
const glyph = { done: "✔", active: "▸", pending: "·" };
return (
<Overlay tag="graph" title="Stage / transition graph" onClose={onClose} width="min(720px,95%)">
<div style={{ display: "grid", gridTemplateColumns: "1.4fr 1fr", gap: 18 }}>
<div>
{stages.map((s, n) => (
<div key={s.id}>
<div style={{ display: "flex", alignItems: "baseline", gap: 10, padding: "5px 0" }}>
<span style={{ width: 16, color: s.status === "active" ? "var(--accent)" : s.status === "done" ? "var(--ok)" : "var(--faint)" }}>{glyph[s.status]}</span>
<span style={{ color: s.status === "pending" ? "var(--dim)" : "var(--fg-strong)", fontWeight: s.status === "active" ? 600 : 400, minWidth: 120 }}>{s.id}</span>
<span style={{ color: "var(--faint)", fontSize: 11 }}>role:{s.role}</span>
{s.status === "active" && <span className="pill live">running · {s.enter}</span>}
<span style={{ color: "var(--dim)", fontSize: 12, marginLeft: "auto" }}>{s.note}</span>
</div>
{n < stages.length - 1 && <div style={{ marginLeft: 7, color: "var(--faint)", lineHeight: 1 }}></div>}
</div>
))}
</div>
<div>
<div style={{ color: "var(--dim)", fontSize: 11, letterSpacing: ".08em", textTransform: "uppercase", marginBottom: 8 }}>transition rules</div>
<div style={{ color: "var(--faint)", fontSize: 11, marginBottom: 10 }}>no hardcoded graph rules eval per artifact</div>
{transitions.map((t, n) => (
<div key={n} className="appr-cmd" style={{ margin: "0 0 8px", fontSize: 12 }}>
<span style={{ color: "var(--accent-2)" }}>when</span> {t.when}<br />
<span style={{ color: "var(--accent)" }}> goto</span> {t.goto}
</div>
))}
</div>
</div>
</Overlay>
);
}
/* ── model registry / provider switch ── */
function ModelRegistry({ models, active, onSwitch, onClose }) {
return (
<Overlay tag="models" title="Model registry" onClose={onClose} width="min(800px,95%)">
<div style={{ color: "var(--faint)", fontSize: 12, marginBottom: 8 }}>models are stateless engines switching re-synthesizes the Context Pack for the new window.</div>
{models.map((m) => (
<div className={"row"} key={m.id} style={{ gridTemplateColumns: "1fr auto", cursor: m.id === active ? "default" : "pointer", opacity: m.status === "cold" ? 0.7 : 1 }} onClick={() => m.id !== active && onSwitch(m.id)}>
<div>
<div style={{ display: "flex", alignItems: "center", gap: 9 }}>
<span style={{ color: m.id === active ? "var(--accent)" : "var(--fg-strong)", fontWeight: 600 }}>{m.id}</span>
<span className={"pill " + (m.provider === "local" ? "live" : "")}>{m.provider}</span>
{m.id === active && <span className="pill on">active</span>}
<span className="pill off">{m.status}</span>
</div>
<div style={{ color: "var(--dim)", fontSize: 11.5, marginTop: 3 }}>{m.caps.join(" · ")} ctx {m.ctx.toLocaleString()} · t={m.temp} · {m.gpu}</div>
</div>
<div style={{ textAlign: "right", color: "var(--faint)", fontSize: 11 }}>
sessions {m.parallel}{m.id !== active && <div style={{ color: "var(--accent)" }}>switch </div>}
</div>
</div>
))}
</Overlay>
);
}
/* ── tool palette ── */
function ToolPalette({ tools, onToggle, onClose }) {
return (
<Overlay tag="tools" title="Tool palette" onClose={onClose} width="min(720px,95%)">
<div style={{ color: "var(--faint)", fontSize: 12, marginBottom: 8 }}>config-driven · capability-scoped. tier sets the approval gate; disabled tools are invisible to the agent.</div>
{tools.map((t) => (
<div className="row" key={t.id} style={{ gridTemplateColumns: "auto 1fr auto auto", cursor: "pointer", opacity: t.enabled ? 1 : 0.55 }} onClick={() => onToggle(t.id)}>
<span className={"tag-tier tier-" + t.tier}>{t.tier}</span>
<span><b style={{ color: "var(--fg-strong)", fontWeight: 600 }}>{t.id}</b> <span style={{ color: "var(--dim)", fontSize: 12 }}> {t.desc}</span></span>
<span className="pill" style={{ color: t.mode === "deny" ? "var(--bad)" : t.mode === "prompt" ? "var(--warn)" : "var(--ok)" }}>{t.mode}</span>
<span className={"pill " + (t.enabled ? "on" : "off")}>{t.enabled ? "enabled" : "disabled"}</span>
</div>
))}
</Overlay>
);
}
Object.assign(window, { Overlay, CommandPalette, ApprovalModal, EventInspector, ContextPackInspector, StageGraph, ModelRegistry, ToolPalette });