UI: fold finished steps to their title line with a toggle triangle
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 5s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 295952@bnt-lap001
Assistant-Session: e93f64ad-516c-46eb-9666-aad8d300c477
This commit is contained in:
tegwick 2026-09-29 11:05:48 +02:00
parent 7d0067a324
commit 7d0249ed51
2 changed files with 24 additions and 7 deletions

View file

@ -19,7 +19,7 @@ h1,h2{font-weight:600}a{color:inherit}hr{border:0;border-top:1px solid #8886}
.step{border:1px solid #8886;padding:.8rem 1rem;margin:.8rem 0}.step.running{border-color:currentColor}
.pill{font:12px ui-monospace,monospace;border:1px solid #8886;padding:0 .4rem}
pre{border:1px solid #8886;padding:.5rem;overflow:auto;margin:.4rem 0}
button,input,select{font:inherit;padding:.25rem .5rem}.head{display:flex;justify-content:space-between;align-items:baseline;gap:1rem}.row{display:flex;gap:.5rem;align-items:center;flex-wrap:wrap}.grow{flex:1;min-width:12rem}[hidden]{display:none}#current{scroll-margin-top:1rem}.note{opacity:.75}.bad{font-weight:600}"""
button,input,select{font:inherit;padding:.25rem .5rem}.head{display:inline-flex;width:calc(100% - 1.6rem);justify-content:space-between;align-items:baseline;gap:1rem;vertical-align:top}summary{cursor:pointer}.row{display:flex;gap:.5rem;align-items:center;flex-wrap:wrap}.grow{flex:1;min-width:12rem}[hidden]{display:none}#current{scroll-margin-top:1rem}.note{opacity:.75}.bad{font-weight:600}"""
def page(title: str, body: str) -> bytes:
@ -106,7 +106,7 @@ class App:
if cmd and state == "running": # the command and Copy appear only once the step is started
block = f"<pre>{e(cmd)}</pre><button type=button data-copy>Copy</button>"
elif cmd and state == "done":
block = f"<details><summary class=note>Command</summary><pre>{e(cmd)}</pre></details>"
block = f"<pre>{e(cmd)}</pre>" # kept as a record; no Copy once done
url = f"/run/{i}/{e(rid)}/{e(s['id'])}"
v = st.get("verification")
if state == "todo" and not blocked:
@ -148,11 +148,12 @@ class App:
waiting_for = f"step {n}, {s['title']}"
rb = f"<p class=note>Rollback: {e(s['rollback'])}</p>" if s.get("rollback") and state in ("running", "failed") else ""
anchor = " id=current" if s["id"] == current else ""
out.append(f"<div{anchor} class='step {state}'><div class=head>"
fold = "" if state in ("done", "skipped") else " open" # finished steps start folded; the triangle toggles
out.append(f"<details{anchor} class='step {state}'{fold}><summary><span class=head>"
f"<span><b>{n}. {e(s['title'])}</b> "
f"<span class=note>[{e(s['owner'])}] {e(s.get('risk', 'read-only'))}</span></span>"
f"<span class=pill>{state}</span></div>{block}"
f"<p class=note>Done when: {e(s['verify']['done_when'])}</p>{rb}{act}</div>")
f"<span class=pill>{state}</span></span></summary>{block}"
f"<p class=note>Done when: {e(s['verify']['done_when'])}</p>{rb}{act}</details>")
if r.complete:
out.append("<p><b>All steps done.</b> To record this run as exercising the pack, review and apply: "
f"<code>python -m rtut run finish {e(p.path)} {e(rid)} --apply</code></p>")

View file

@ -131,7 +131,7 @@ def test_command_and_copy_appear_only_when_step_is_started(srv):
req(srv, "POST", base + "/read-the-sign/done", {"_t": t, "note": "read it twice"})
_, _, body = req(srv, "GET", base)
assert "<button type=button data-copy>" not in body # done: no copy button
assert "<details>" in body and "Comment: read it twice" in body # command kept as a record, comment shown
assert "<pre>printf" in body and "Comment: read it twice" in body # command kept as a record, comment shown
def test_action_buttons_left_of_the_comment(srv):
@ -159,6 +159,22 @@ def test_fail_without_comment_warns_first_then_is_allowed(srv):
def test_state_pill_is_top_right_after_the_numbered_title(srv):
t, loc, _ = _showcase_run(srv)
_, _, body = req(srv, "GET", loc.split("#")[0])
head = body[body.index("<div class=head>"):]
head = body[body.index("<span class=head>"):]
assert head.index("1. Read the poem") < head.index("class=pill") # number and title first, state last
assert "justify-content:space-between" in body # pill pushed to the right edge
def test_done_steps_fold_to_the_title_line_and_others_stay_open(srv):
t, loc, _ = _showcase_run(srv)
base = loc.split("#")[0]
_, _, body = req(srv, "GET", base)
assert "<details id=current class='step todo' open>" in body # active step is open
req(srv, "POST", base + "/read-the-sign/start", {"_t": t})
req(srv, "POST", base + "/read-the-sign/verify", {"_t": t, "ok": "1"})
_, _, body = req(srv, "GET", base)
assert "<details id=current class='step running' open>" in body
req(srv, "POST", base + "/read-the-sign/done", {"_t": t, "note": "fine"})
_, _, body = req(srv, "GET", base)
assert "<details class='step done'>" in body # done: no 'open' attribute, so folded
assert "1. Read the poem" in body.split("<details class='step done'>")[1].split("</summary>")[0] # title line stays in the summary
assert "Comment: fine" in body.split("<details class='step done'>")[1].split("</details>")[0] # the rest is inside the fold