feat(web): collapsible hideable left+right debug panes

Each debug pane (#rail-left, #affect-console) gains an independent
collapse control: a chevron button in the pane header folds it to a
22px re-open strip, and #center (flex:1) reflows to fill. State
persists per-pane in localStorage (ratatoskr-left-collapsed /
-right-collapsed), matching the theme/cot-toggle idiom; the right-side
drag-resizer hides with its pane. Collapsing both yields a clean
chat-only surface.

Playwright-verified end-to-end (default-open, collapse-to-strip,
center reflow, independent left/right, reload persistence, re-open).
This commit is contained in:
2026-07-17 13:27:01 -07:00
parent 021fb5265f
commit c6788539ac
3 changed files with 50 additions and 3 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "ratatoskr"
version = "0.20.16"
version = "0.20.17"
description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard"
readme = "README.md"
requires-python = ">=3.12"
+48 -1
View File
@@ -178,6 +178,29 @@ kbd {
#app.live { display: flex; }
#cols { flex: 1; display: flex; min-height: 0; }
/* ---- collapsible debug panes (independent left/right; #center reflows to fill) ---- */
.pane-collapse {
width: 22px; height: 22px; display: flex; align-items: center; justify-content: center;
background: transparent; border: 1px solid var(--border-subtle); border-radius: var(--radius-sm);
color: var(--fg-3); cursor: pointer; flex: 0 0 auto; font-size: 13px; line-height: 1; padding: 0;
}
.pane-collapse:hover { background: var(--bg-2); color: var(--fg-0); border-color: var(--border-default); }
.ac-head-row { display: flex; align-items: center; justify-content: space-between; position: relative; }
/* re-open strip: hidden while the pane is open, becomes the full-height handle when collapsed */
.pane-reopen { display: none; }
#cols.left-collapsed #rail-left { width: 22px !important; flex-basis: 22px !important; overflow: hidden; }
#cols.right-collapsed #affect-console { width: 22px !important; flex-basis: 22px !important; overflow: hidden; }
#cols.left-collapsed #rail-left > *:not(.pane-reopen) { display: none; }
#cols.right-collapsed #affect-console > *:not(.pane-reopen) { display: none; }
#cols.left-collapsed #rail-left .pane-reopen,
#cols.right-collapsed #affect-console .pane-reopen {
display: flex; flex: 1; width: 100%; align-items: center; justify-content: center;
background: transparent; border: none; color: var(--fg-3); cursor: pointer; font-size: 13px;
}
#cols.left-collapsed #rail-left .pane-reopen:hover,
#cols.right-collapsed #affect-console .pane-reopen:hover { background: var(--bg-2); color: var(--fg-0); }
#cols.right-collapsed #affect-resizer { display: none; }
/* eyebrow — mono uppercase wide-tracked (Australis signature) */
.eyebrow {
font-family: var(--font-mono); font-size: 9.5px;
@@ -554,6 +577,7 @@ body.cot-hidden #cot-toggle { border-color: rgba(66,220,209,0.55); color: var(--
<svg class="i-sun" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"></path></svg>
<svg class="i-moon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"></path></svg>
</button>
<button class="pane-collapse" id="collapse-left" title="Collapse debug rail"></button>
</div>
<div id="conn" data-state="offline">
@@ -576,6 +600,7 @@ body.cot-hidden #cot-toggle { border-color: rgba(66,220,209,0.55); color: var(--
<span class="eyebrow off" id="bifrost-head">⇄ bifrost · not bound</span>
<div id="bifrost-body"><div class="bf-note">observe-only session</div></div>
</div>
<button class="pane-reopen" id="reopen-left" title="Expand debug rail"></button>
</aside>
<!-- ══════════ CENTER · conversation ══════════ -->
@@ -613,7 +638,10 @@ body.cot-hidden #cot-toggle { border-color: rgba(66,220,209,0.55); color: var(--
</defs></svg>
<div class="ac-sec" id="ac-dominant">
<div class="glow"></div>
<div class="eyebrow">affect · dominant</div>
<div class="ac-head-row">
<div class="eyebrow">affect · dominant</div>
<button class="pane-collapse" id="collapse-right" title="Collapse affect console"></button>
</div>
<div id="dominant-emotion" class="muted"></div>
<div id="emotions-active"></div>
</div>
@@ -711,6 +739,7 @@ body.cot-hidden #cot-toggle { border-color: rgba(66,220,209,0.55); color: var(--
<span class="grow"></span>
<span id="version">ratatoskr —</span>
</div>
<button class="pane-reopen" id="reopen-right" title="Expand affect console"></button>
</aside>
</div>
</div>
@@ -1948,6 +1977,24 @@ async function cancelTurn() {
});
})();
// ---- collapsible debug panes (independent left / right; state persists) ----
(function () {
const cols = $("cols");
function apply(side, collapsed) {
cols.classList.toggle(side + "-collapsed", collapsed);
try { localStorage.setItem("ratatoskr-" + side + "-collapsed", collapsed ? "1" : "0"); } catch (_) {}
}
let left = false, right = false;
try { left = localStorage.getItem("ratatoskr-left-collapsed") === "1"; } catch (_) {}
try { right = localStorage.getItem("ratatoskr-right-collapsed") === "1"; } catch (_) {}
cols.classList.toggle("left-collapsed", left);
cols.classList.toggle("right-collapsed", right);
$("collapse-left").addEventListener("click", () => apply("left", true));
$("reopen-left").addEventListener("click", () => apply("left", false));
$("collapse-right").addEventListener("click", () => apply("right", true));
$("reopen-right").addEventListener("click", () => apply("right", false));
})();
// ---- composer auto-grow + keyboard ----
$("prompt-input").addEventListener("input", (e) => {
const el = e.target;
Generated
+1 -1
View File
@@ -1052,7 +1052,7 @@ wheels = [
[[package]]
name = "ratatoskr"
version = "0.20.16"
version = "0.20.17"
source = { editable = "." }
dependencies = [
{ name = "httpx" },