/* App shell: 4 resizable columns, keyboard shortcuts, copy-reference, status bar */ function Splitter({ orientation = "v", onDelta }) { const [drag, setDrag] = useState(false); function down(e) { e.preventDefault(); let last = { x: e.clientX, y: e.clientY }; setDrag(true); document.body.style.cursor = orientation === "v" ? "col-resize" : "row-resize"; document.body.style.userSelect = "none"; function mv(ev) { onDelta(ev.clientX - last.x, ev.clientY - last.y); last = { x: ev.clientX, y: ev.clientY }; } function up() { setDrag(false); document.body.style.cursor = ""; document.body.style.userSelect = ""; document.removeEventListener("mousemove", mv); document.removeEventListener("mouseup", up); } document.addEventListener("mousemove", mv); document.addEventListener("mouseup", up); } return
; } function RightColumn({ width }) { const [topFrac, setTopFrac] = useState(0.52); const ref = useRef(null); function delta(dx, dy) { const h = ref.current ? ref.current.clientHeight : 600; setTopFrac((f) => Math.max(0.18, Math.min(0.82, (f * h + dy) / h))); } const agent = useMemo(() => agentSeed(), []); const shell = useMemo(() => shellSeed(), []); return (
); } function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); } function ancestors(path) { const parts = path.split("/"); const out = []; for (let i = 1; i < parts.length; i++) out.push(parts.slice(0, i).join("/")); return out; } function initialOpenDirs(node, set) { if (node.type === "dir") { if (node.open && node.path) set.add(node.path); (node.children || []).forEach((c) => initialOpenDirs(c, set)); } return set; } function App() { const changeMap = useMemo(() => Object.fromEntries(PROJECT.changes.map((c) => [c.path, c.status])), []); const changeSet = useMemo(() => new Set(PROJECT.changes.map((c) => c.path)), []); const [tabs, setTabs] = useState([ { path: "src/Http/Controller/UserController.php" }, { path: "public/assets/app.js" }, { path: "src/types/api.ts" }, ]); const [active, setActive] = useState("src/Http/Controller/UserController.php"); const [tabMode, setTabMode] = useState({ "src/Http/Controller/UserController.php": "diff" }); const [openDirs, setOpenDirs] = useState(() => initialOpenDirs(PROJECT.tree, new Set())); const [cursor, setCursor] = useState({ path: "src/Http/Controller/UserController.php", line: 1, col: 1 }); const [selection, setSelection] = useState(null); const [overlay, setOverlay] = useState(null); const [menu, setMenu] = useState(null); const [toasts, setToasts] = useState([]); const [splitFor, setSplitFor] = useState(null); const [staged, setStaged] = useState(() => new Set(["src/Service/PaymentService.php", "config/app.json"])); const [committed, setCommitted] = useState(() => new Set()); const [commitMsg, setCommitMsg] = useState(""); const [passPopup, setPassPopup] = useState(null); const [gitW, setGitW] = useState(232); const [treeW, setTreeW] = useState(244); const [rightW, setRightW] = useState(444); function toast(title, ref) { const id = lid(); setToasts((t) => [...t, { id, title, ref }]); setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 2300); } async function copyText(text, label) { try { await navigator.clipboard.writeText(text); } catch (e) { const ta = document.createElement("textarea"); ta.value = text; ta.style.position = "fixed"; ta.style.opacity = "0"; document.body.appendChild(ta); ta.select(); try { document.execCommand("copy"); } catch (_) {} ta.remove(); } toast(label || "Copied reference", text); } const toggleDir = useCallback((p) => { setOpenDirs((s) => { const n = new Set(s); n.has(p) ? n.delete(p) : n.add(p); return n; }); }, []); function reveal(path) { setOpenDirs((s) => { const n = new Set(s); ancestors(path).forEach((a) => n.add(a)); return n; }); } const stage = (p) => setStaged((s) => { const n = new Set(s); n.add(p); return n; }); const unstage = (p) => setStaged((s) => { const n = new Set(s); n.delete(p); return n; }); const stageAll = () => setStaged(new Set(PROJECT.changes.filter((c) => !committed.has(c.path)).map((c) => c.path))); const unstageAll = () => setStaged(new Set()); function commit() { const list = PROJECT.changes.filter((c) => staged.has(c.path) && !committed.has(c.path)); if (!list.length || !commitMsg.trim()) return; setCommitted((prev) => { const n = new Set(prev); list.forEach((c) => n.add(c.path)); return n; }); setStaged(new Set()); const msg = commitMsg.trim(); setCommitMsg(""); toast(`Committed ${list.length} file${list.length > 1 ? "s" : ""}`, msg.length > 34 ? msg.slice(0, 34) + "…" : msg); } function openFile(path, opts = {}) { const changed = !!PROJECT.diffs[path]; setTabs((t) => t.some((x) => x.path === path) ? t : [...t, { path }]); setActive(path); setTabMode((m) => ({ ...m, [path]: opts.diff && changed ? "diff" : (m[path] || (changed ? "diff" : "code")) })); reveal(path); if (opts.line) { // show the current/updated file so line numbers map to search hits setSplitFor(null); setTabMode((m) => ({ ...m, [path]: changed ? "updated" : "code" })); setCursor({ path, line: opts.line, col: 1 }); setSelection(null); setTimeout(() => { const row = document.querySelector('.editor .ln-row[data-line="' + opts.line + '"]'); if (row) { const ed = row.closest(".editor"); const er = ed.getBoundingClientRect(), rr = row.getBoundingClientRect(); ed.scrollTop += (rr.top - er.top) - ed.clientHeight / 2; } }, 70); } } function closeTab(path) { setTabs((t) => { const ix = t.findIndex((x) => x.path === path); const next = t.filter((x) => x.path !== path); if (path === active) { const fallback = next[ix] || next[ix - 1] || next[next.length - 1]; setActive(fallback ? fallback.path : null); } return next; }); } // ---- context menus ---- function openMenu(e, target) { e.preventDefault(); e.stopPropagation(); const sparkSend = (ref) => ({ icon: Icon.spark({}), label: "Send reference to agent", onClick: () => { window.dispatchEvent(new CustomEvent("agentPaste", { detail: ref })); toast("Passed to agent", ref); } }); if (target.kind === "editor") { const ref = target.sel ? `${target.path}:${target.sel.start}-${target.sel.end}` : `${target.path}:${target.line}`; const mx = e.clientX, my = e.clientY; setMenu({ x: mx, y: my, note: ref, items: [ { primary: true, icon: Icon.copy({}), label: "Copy reference", onClick: () => copyText(ref) }, { icon: Icon.spark({}), label: "Pass on to Agent", onClick: () => setPassPopup({ x: mx, y: my, ref }) }, ], }); } else { const isDir = target.kind === "dir"; const ref = isDir ? target.path + "/" : target.path; const name = target.path.split("/").pop(); const items = [ { primary: true, icon: Icon.copy({}), label: "Copy reference", onClick: () => copyText(ref) }, sparkSend(ref), { icon: Icon.copy({}), label: isDir ? "Copy folder path" : "Copy file name", onClick: () => copyText(isDir ? target.path : name, "Copied") }, ]; if (!isDir) { items.push({ sep: true }); if (target.kind === "git") { const isStaged = staged.has(target.path); items.push(isStaged ? { icon: Icon.minus({}), label: "Unstage changes", onClick: () => unstage(target.path) } : { icon: Icon.plus({}), label: "Stage changes", onClick: () => stage(target.path) }); items.push({ icon: Icon.diff({}), label: "Open diff", onClick: () => openFile(target.path, { diff: true }) }); } items.push({ icon: Icon.file({}), label: "Open file", onClick: () => openFile(target.path) }); items.push({ icon: Icon.reveal({}), label: "Reveal in Explorer", onClick: () => reveal(target.path) }); } setMenu({ x: e.clientX, y: e.clientY, note: ref, items }); } } // ---- shortcuts ---- useEffect(() => { function onKey(e) { const meta = e.metaKey || e.ctrlKey; if (meta && e.key.toLowerCase() === "f") { e.preventDefault(); setOverlay("search"); } else if (meta && e.key.toLowerCase() === "w") { e.preventDefault(); if (active) closeTab(active); } else if (e.key === "Escape") { if (splitFor) setSplitFor(null); else { setOverlay(null); setMenu(null); } } } window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [active, splitFor]); const MODE_LABEL = { original: "orig", updated: "upd", diff: "diff", code: "" }; const MODE_WORD = { original: "Original", updated: "Updated", diff: "Diff" }; const resolvedTabs = tabs.map((t) => { const changed = !!PROJECT.diffs[t.path]; const m = tabMode[t.path] || (changed ? "diff" : "code"); return { ...t, changed, modeLabel: splitFor === t.path ? "split" : MODE_LABEL[m] }; }); const mode = tabMode[active] || (PROJECT.diffs[active] ? "diff" : "code"); const totals = PROJECT.changes.reduce((a, c) => ({ add: a.add + c.add, del: a.del + c.del }), { add: 0, del: 0 }); const activeLang = active ? HL.langLabel(active) : ""; const crumb = active ? active.split("/") : []; return (
{/* title bar */}
{Icon.spark({ style: { color: "var(--accent)" } })}Helder{PROJECT.name}
{active && (
{crumb.map((s, i) => ({i > 0 && }{s}))}
)}
{/* workbench */}
setGitW((w) => clamp(w + dx, 160, 460))} />
setTreeW((w) => clamp(w + dx, 160, 520))} />
{ setTabMode((mm) => ({ ...mm, [active]: m })); setSplitFor(null); }} onActivate={setActive} onClose={closeTab} onContext={openMenu} onSplit={(p) => setSplitFor(p)} splitOpen={splitFor === active} cursor={cursor} selection={selection} setCursor={setCursor} setSelection={setSelection} />
setRightW((w) => clamp(w - dx, 280, 780))} />
{/* status bar */}
{Icon.branch({ width: 12, height: 12 })}{PROJECT.branch}
+{totals.add} −{totals.del}
{active &&
{selection && selection.path === active && selection.start !== selection.end ? `${selection.end - selection.start + 1} lines selected` : `Ln ${cursor.path === active ? cursor.line : 1}, Col ${cursor.path === active ? cursor.col : 1}`}
} {active &&
Spaces: 4
} {active &&
UTF-8
} {active &&
{activeLang}
} {active && PROJECT.diffs[active] &&
{splitFor === active ? "Split" : (MODE_WORD[mode] || "")}
}
{/* overlays */} {splitFor && setSplitFor(null)} onContext={openMenu} />} {passPopup && { const line = (text && text.trim() ? text.trim() + " " : "") + passPopup.ref; window.dispatchEvent(new CustomEvent("agentPaste", { detail: line })); setPassPopup(null); toast("Passed to agent", passPopup.ref); }} onCancel={() => setPassPopup(null)} />} {overlay === "search" && openFile(p, { line: n })} onClose={() => setOverlay(null)} changeSet={changeSet} />} {menu && setMenu(null)} />}
); } ReactDOM.createRoot(document.getElementById("root")).render();