Let the agent's report open from the Focus view

The Focus 'Right now' well advertised a report with the › chevron but was
a static div. When the rendered event carries detail it is now a real
disclosure — a details.fold whose summary is the well itself, keyed into
S.openFolds like the Sessions timeline's folds so the open state survives
the SSE-driven re-renders, with the report in the fold's mono <pre>,
scroll-bounded when long.

Wells where clicking does nothing no longer wear the chevron: the board
cards' live line and the empty-focus well lead with · instead (the drive
wells' ✳ already meant something else). One glyph, one meaning.

tests/test_focus_well.py encodes the invariants as source-level checks:
› only inside the wellfold summary, detail rendered preformatted, fold
state keyed rather than DOM-only, Sessions folds untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
istos
2026-07-30 07:00:28 +02:00
co-authored by Claude Fable 5
parent 59e84f3070
commit 5261b55685
2 changed files with 113 additions and 6 deletions
+30 -6
View File
@@ -309,6 +309,14 @@
font-family:var(--mono);font-size:11.5px;line-height:1.5;color:var(--muted);
white-space:pre-wrap;overflow-x:auto;
}
/* a well as a real disclosure: the summary IS the well, so its is earned
— it rotates open instead of the fold's usual ▸ prefix */
.fold.wellfold{max-width:none}
.fold.wellfold summary::before{content:none}
.fold.wellfold summary::-webkit-details-marker{display:none}
.fold.wellfold .lead{transition:transform .12s ease}
.fold.wellfold[open] .lead{transform:rotate(90deg)}
.fold.wellfold pre{max-height:300px;overflow-y:auto}
.stopbtn:hover{border-color:var(--alarm);color:var(--alarm)}
.f-empty{padding:30px 20px;font-size:12.5px;color:var(--dim)}
@@ -857,11 +865,11 @@ function cardFor(task) {
const last = run || (smeta && smeta.lastSummary ? { summary: smeta.lastSummary, ok: null } : null);
if (last && last.summary) {
const active = run || (smeta && smeta.status === 'active');
liveLine = `<div class="well"><span class="lead"></span>` +
liveLine = `<div class="well"><span class="lead">·</span>` +
`<span class="wbody">${esc(last.summary)}${active ? '<span class="caret">▌</span>' : ''}</span></div>`;
}
} else if (agent) {
liveLine = `<div class="well"><span class="lead"></span><span class="wbody">warming up<span class="caret">▌</span></span></div>`;
liveLine = `<div class="well"><span class="lead">·</span><span class="wbody">warming up<span class="caret">▌</span></span></div>`;
}
// tool chips: destinations, not statuses — they live in the card's footer
@@ -1448,10 +1456,21 @@ function renderFocus() {
const last = run || events[events.length - 1];
const wellCls = last && last.ok === false ? ' bad' : '';
const act = last
? `<div class="well${wellCls}"><span class="lead"></span><span class="wbody">${esc(last.summary)}` +
`${(run || live) ? '<span class="caret">▌</span>' : ''}</span></div>`
: '<div class="well"><span class="lead"></span><span class="wbody">no activity yet</span></div>';
const caret = (run || live) ? '<span class="caret">▌</span>' : '';
let act;
if (last && last.detail) {
// only when clicking does something: the well is the summary of a
// fold, keyed into S.openFolds so it survives the SSE re-renders
const wkey = `w${last.ts}`;
act = `<details class="fold wellfold" data-key="${esc(wkey)}"${S.openFolds.has(wkey) ? ' open' : ''}>` +
`<summary><div class="well${wellCls}"><span class="lead"></span>` +
`<span class="wbody">${esc(last.summary)}${caret}</span></div></summary>` +
`<pre>${esc(last.detail)}</pre></details>`;
} else if (last) {
act = `<div class="well${wellCls}"><span class="lead">·</span><span class="wbody">${esc(last.summary)}${caret}</span></div>`;
} else {
act = '<div class="well"><span class="lead">·</span><span class="wbody">no activity yet</span></div>';
}
const plan = [...events].reverse().find(e => e.kind === 'plan' && e.detail);
let steps = '';
@@ -1526,6 +1545,11 @@ function renderFocus() {
`<div class="minifeed">${recent || '<span style="color:var(--dim);font-size:12px">nothing yet</span>'}</div></div>`;
$('#cgrid').innerHTML = nowPanel + `<div class="c-side">${checksPanel}${filesPanel}${recentPanel}</div>`;
document.querySelectorAll('#cgrid details.fold').forEach(d => {
d.addEventListener('toggle', () => {
if (d.open) S.openFolds.add(d.dataset.key); else S.openFolds.delete(d.dataset.key);
});
});
const cstop = $('#cstop');
if (cstop) cstop.addEventListener('click', () => stopAgent(cstop.dataset.aid));
+83
View File
@@ -0,0 +1,83 @@
"""The Focus "Right now" well and the wells' visual grammar (task 08).
board.html is a single file with inline JS and no frontend test runner, so
these are source-level invariants: the ones that, if broken, would bring
back the decorative chevron or lose the fold's keyed state across SSE
re-renders.
python3 -m unittest discover -s tests -v
"""
from __future__ import annotations
import re
import unittest
from pathlib import Path
BOARD = Path(__file__).resolve().parents[1] / "manager" / "core" / "board.html"
class FocusWellTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.html = BOARD.read_text(encoding="utf-8")
def test_chevron_only_where_clicking_does_something(self):
"""One glyph, one meaning: every worn by a well's lead span must
sit inside the wellfold disclosure — no static well may wear it."""
chevrons = [m.start() for m in
re.finditer(r'<span class="lead">', self.html)]
self.assertTrue(chevrons, "the disclosure well lost its chevron")
for pos in chevrons:
window = self.html[max(0, pos - 300):pos]
self.assertIn('class="fold wellfold"', window,
"a well outside the wellfold disclosure wears , "
"but clicking it does nothing")
self.assertIn("<summary>", window,
"the must be inside the disclosure's summary")
def test_static_wells_wear_a_neutral_lead(self):
"""The board cards' live line and the empty-focus well are plain
divs; after the audit they lead with · (or the drive's ✳)."""
for m in re.finditer(r'<div class="well[^>]*"><span class="lead">(.)',
self.html):
if self.html[:m.start()].rstrip("` +\n").endswith("<summary>"):
continue # the wellfold's own summary — the earned
self.assertIn(m.group(1), "·✳",
f"static well leads with {m.group(1)!r}; "
"only the wellfold summary may use ")
def test_report_detail_renders_preformatted(self):
"""The full report opens as machine-adjacent prose: a <pre> right
after the wellfold's summary, scrollable when long."""
self.assertRegex(self.html,
r"</summary>` \+\s*`<pre>\$\{esc\(last\.detail\)\}</pre></details>",
"the wellfold must render last.detail in a <pre>")
self.assertRegex(self.html, r"\.fold\.wellfold pre\{[^}]*max-height",
"long reports need a scroll bound on the fold's pre")
def test_open_state_is_keyed_not_dom_only(self):
"""Focus re-renders every few seconds over SSE; the fold must be
re-created from S.openFolds and write back on toggle, exactly like
the Sessions timeline's folds."""
m = re.search(r"if \(last && last\.detail\) \{(.*?)\}\s*else",
self.html, re.S)
self.assertIsNotNone(m, "the Focus well no longer branches on detail")
for needle in ("data-key", "S.openFolds.has"):
self.assertIn(needle, m.group(1),
f"the wellfold lost {needle}: open state would "
"snap shut on the next re-render")
self.assertIn("#cgrid details.fold", self.html,
"renderFocus must re-attach toggle listeners so "
"toggles land back in S.openFolds")
def test_sessions_timeline_fold_unchanged(self):
"""The timeline's own fold mechanism is the pattern being reused,
not replaced."""
self.assertIn("function fold(key, label, body)", self.html)
self.assertIn("ev.kind === 'report' ? 'the report'", self.html)
self.assertIn("#ftl details", self.html)
if __name__ == "__main__":
unittest.main()