Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion board/_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,23 @@ def collect_devbox():
AUTONOMY_ROW_CAP = 5


def _judgement_chip(value, cap=28):
"""The log's two judgement cells, as a chip-sized label.

The log writes them as `<verdict> (<why>)` — "safe (feature medium cap;
same resumed acknowledged launch ...)" — because it is a record, read as a
table. A pill is not a table cell: it cannot wrap, so the whole sentence
became one chip a thousand pixels wide and the board scrolled sideways on
a phone. The verdict is the part that belongs on a chip; the why stays in
the log, which is where the row's link sends you.

It also restores the colour: `_LEVEL_TONES`/`_OUTCOME_TONES` key off the
bare verdict, so every one of these rows was silently rendering neutral.
"""
head = value.split(" (", 1)[0].strip()
return head if len(head) <= cap else head[:cap - 1].rstrip() + "\u2026"


def collect_autonomy():
"""The tail of the Mind's autonomy calibration log — what ran unattended
lately and how it ended (read verbatim; the log stays the record)."""
Expand All @@ -623,7 +640,8 @@ def collect_autonomy():
r"^\|\s*(\d{4}-\d{2}-\d{2})\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|"
r"[^|]*\|\s*([^|]+?)\s*\|",
log.read_text(encoding="utf-8"), re.M)
return [{"date": d, "task": t[:70], "level": lvl, "outcome": o}
return [{"date": d, "task": t[:70],
"level": _judgement_chip(lvl), "outcome": _judgement_chip(o)}
for d, t, lvl, o in rows[-AUTONOMY_ROW_CAP:]]


Expand Down
12 changes: 10 additions & 2 deletions board/_theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,9 +381,17 @@ def mark(key):
/* --- facet pills: the backlog, scannable by colour --------------------- */
.facets{color:var(--muted);font-size:.85em}
.tags{display:block;margin-top:.32rem;line-height:1.9}
.pill{display:inline-block;padding:.06em .5em;border-radius:999px;
/* A pill is a LABEL, and a label that will not fit is elided, never allowed
to push the page sideways. `nowrap` is what makes a chip read as a chip, so
it is also the one thing the page-wide wrap guard above cannot reach: an
over-long value (a board handing a whole log sentence to a facet) used to
run a chip a thousand pixels wide. The row's prose carries the meaning; the
chip carries the word. `vertical-align:bottom` because `overflow:hidden`
moves an inline-block's baseline to its bottom edge. */
.pill{display:inline-block;max-width:100%%;padding:.06em .5em;border-radius:999px;
font-size:.74em;font-weight:650;letter-spacing:.015em;white-space:nowrap;
vertical-align:.09em;border:1px solid var(--edge);background:var(--tint);
overflow:hidden;text-overflow:ellipsis;
vertical-align:bottom;border:1px solid var(--edge);background:var(--tint);
color:var(--accent)}
.pill+.pill{margin-left:.28rem}
.pill.n{border-color:var(--line);background:var(--btn);color:var(--muted)}
Expand Down
28 changes: 28 additions & 0 deletions tests/test_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -837,3 +837,31 @@ def test_missing_mind_degrades_honestly(tmp_path):
assert any("community" in d for d in s["degraded"])
assert any("resume" in d for d in s["degraded"])
assert s["resume"]["tasks"] == []


# ------------------------------------------------------- the autonomy chips --


def test_a_judgement_cell_becomes_a_chip_sized_label(tmp_path):
"""The log writes its two judgement columns as `<verdict> (<why>)`, because
it is a record read as a table. A pill cannot wrap, so the whole sentence
used to render as one chip a thousand pixels wide — the board scrolled
sideways on a phone, and the tone lookups (which key off the bare verdict)
silently fell through to neutral on every such row."""
log = AUTONOMY_LOG + (
"| 2026-08-03 | third-task (#3) "
"| safe (feature medium cap; same resumed acknowledged launch and "
"campaign merge authorization) | tests pass "
"| merged-unchanged (RepoA#535 merge b9d9927f; issue left open) |\n"
"| 2026-08-04 | fourth-task (#4) "
"| human-authorized merge after accepted smoke exception and "
"independent review | tests pass | amended |\n")
stub = _fabricate(tmp_path, _default_fixtures())
(tmp_path / "PyAutoMind" / "autonomy_log.md").write_text(log)
page = _run(["--html"], tmp_path, stub).stdout
# The verdict is the chip; the why stays in the log.
assert '<span class="pill g">safe</span>' in page
assert '<span class="pill n">merged-unchanged</span>' in page
assert "campaign merge authorization" not in page
# No parenthetical to cut? Then the head is elided, never left full length.
assert '"pill n">human-authorized merge afte…</span>' in page
14 changes: 14 additions & 0 deletions tests/test_board_theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,17 @@ def test_no_component_rule_re_declares_the_wrap():
css = re.sub(r"/\*.*?\*/", "", _theme.css("mind"), flags=re.S)
body_rule = re.search(r"^body\{.*?\}", css, re.S | re.M).group(0)
assert css.replace(body_rule, "").count("overflow-wrap") == 0


def test_a_pill_is_bounded_because_nowrap_is_outside_the_wrap_guard():
"""`white-space:nowrap` is what makes a chip read as a chip, and it is the
one thing `body{overflow-wrap}` cannot reach. A board that hands a pill a
sentence must get an ellipsis, not a chip wider than the phone."""
rule = re.search(r"^\.pill\{.*?\}", _theme.css("mind"), re.S | re.M).group(0)
flat = rule.replace("\n ", "")
assert "max-width:100%" in flat
assert "overflow:hidden" in flat
assert "text-overflow:ellipsis" in flat
# overflow:hidden moves an inline-block's baseline to its bottom edge, so
# the old optical nudge would drop every chip half a line.
assert "vertical-align:bottom" in flat
Loading