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:
+30
-6
@@ -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));
|
||||
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user