From 8351c394633a53275e3593d44108291ac36d226a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 21:56:22 +0000 Subject: [PATCH 1/3] The boards wear their organ's logo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every one-tap board carried its own copy of the same GitHub-grey stylesheet, so the family read as six unrelated pages that happened to share a layout — and none of them looked like the organ whose logo sits at the top of its README. board/_theme.py is now the single answer to "what does a board look like": the stylesheet, the hero, the facet pills, the family footer. Presentation only — no state, no collection, no policy — and shared rather than copied, so the look moves for the whole family at once. The Brain board and the Mind dashboard (rendered by the intake conductor) both draw from it; Heart, Hands, Memory and the umbrella adopt it when their renderers next change. Each organ has an accent sampled from its own logo, and every board now opens with a dark hero reproducing that logo's wordmark — white PyAuto, the organ name in its accent, the logo's tagline underneath, inside the logo's ring. Below the hero the page goes back to being a plain readable document: these are lists people scan on a phone before breakfast. Colour is information, never decoration: - the accent is organ identity — links, section rules, stats, hero; - pills are the task facets, toned so only the *exception* is coloured. `supervised` is 136 of 154 prompts in the Mind, so it stays neutral; tinting it would paint the whole backlog and say nothing. Green go, amber caution, red handle-with-care; - ok/warn/bad stay reserved for the verdict semantics they already carried, and the Brain board's verdict is now a banner rather than a coloured word in a paragraph; - the family footer is one chip per sibling board in that organ's own colour, so the six pages read as one system. Every accent is checked for >= 4.5:1 contrast against its own background in both colour schemes — these pages are read outdoors on phones. tests/test_board_theme.py pins that, the palette's completeness against config/policy.yaml `board: boards:`, and the exception-not-default toning rule. Mind palette from PyAutoMind/logo.png, Brain from PyAutoBrain/logo.png; the other four are placeholders in the right hue family, to be confirmed against each logo on adoption. PyAutoMind/dashboard.html is generated by this renderer, so it is left to dashboard_refresh.yml's self-heal — the nightly rebuild exists for exactly this case (a generator change in the Brain fires no Mind path). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Fjjkrhp7xbEqf6Nh1EhodV --- agents/conductors/intake/_intake.py | 114 +++------- board/AGENTS.md | 23 +++ board/_board.py | 90 +++----- board/_theme.py | 309 ++++++++++++++++++++++++++++ tests/test_board.py | 6 +- tests/test_board_theme.py | 117 +++++++++++ 6 files changed, 515 insertions(+), 144 deletions(-) create mode 100644 board/_theme.py create mode 100644 tests/test_board_theme.py diff --git a/agents/conductors/intake/_intake.py b/agents/conductors/intake/_intake.py index 9206add..b930804 100755 --- a/agents/conductors/intake/_intake.py +++ b/agents/conductors/intake/_intake.py @@ -41,6 +41,16 @@ policy as _sizing_policy, BODY_MAP_PATH, ) +# The shared board theme: the one place that answers "what does a one-tap board +# look like". Presentation only — the stylesheet, the hero, the pills — so this +# page and the Brain board are visibly the same family (board/_theme.py). +sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "board")) +from _theme import ( # noqa: E402 + JS as _THEME_JS, boards_footer, css as _theme_css, hero, pills, stats, +) + +THEME_ORGAN = "mind" # whose logo this page wears + # --- work-type classification ------------------------------------------------- # Keyword signals per work-type. The classifier scores each type by keyword hits # (word-boundary prefix match via _sizing._hits) and picks the strongest; ties / @@ -1121,63 +1131,10 @@ def render_dashboard(c: dict) -> str: return "\n".join(L).rstrip("\n") + "\n" -_HTML_CSS = """\ -:root{color-scheme:light dark;--bg:#fff;--fg:#1f2328;--muted:#59636e; - --line:#d1d9e0;--btn:#f6f8fa;--ok:#1a7f37;--accent:#0969da} -@media(prefers-color-scheme:dark){:root{--bg:#0d1117;--fg:#f0f6fc; - --muted:#9198a1;--line:#3d444d;--btn:#151b23;--ok:#3fb950;--accent:#4493f8}} -*{box-sizing:border-box} -body{margin:0 auto;max-width:44rem;padding:1rem 1rem 4rem;background:var(--bg); - color:var(--fg);font:16px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI", - Helvetica,Arial,sans-serif} -h1{font-size:1.35rem;margin:.4rem 0} -h2{font-size:1.15rem;margin-top:2rem;border-bottom:1px solid var(--line); - padding-bottom:.3rem} -h3{font-size:1rem;margin:1.2rem 0 .2rem} -a{color:var(--accent);text-decoration:none} -a:hover{text-decoration:underline} -.muted{color:var(--muted)} -.facets{color:var(--muted);font-size:.85em} -.mdsrc{font-size:.85em} -code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.92em; - background:var(--btn);padding:.1em .3em;border-radius:4px} -.task{display:flex;gap:.6rem;align-items:flex-start;padding:.45rem 0; - border-bottom:1px solid var(--line)} -.task p{margin:.25rem 0 0;flex:1;overflow-wrap:anywhere} -button.copy{flex:0 0 auto;width:2.6rem;height:2.6rem;font-size:1.1rem; - border:1px solid var(--line);border-radius:8px;background:var(--btn); - cursor:pointer;color:var(--fg)} -button.copy.ok{color:var(--ok);border-color:var(--ok)} -details{margin:.5rem 0} -summary{cursor:pointer;font-weight:600;padding:.4rem 0} -table.recent{width:100%;border-collapse:collapse;font-size:.95em} -table.recent td{border-bottom:1px solid var(--line);padding:.45rem .4rem .45rem 0; - vertical-align:top;overflow-wrap:anywhere} -table.recent td.when{white-space:nowrap;color:var(--muted);font-variant-numeric: - tabular-nums} -table.recent td.what{white-space:nowrap;color:var(--muted);font-size:.85em; - padding-top:.58rem} -table.recent td.pick{width:2.6rem;padding-right:0} -table.recent button.copy{width:2.2rem;height:2.2rem;font-size:.95rem} -button.more{display:block;width:100%;margin:.6rem 0;padding:.5rem; - border:1px solid var(--line);border-radius:8px;background:var(--btn); - color:var(--muted);cursor:pointer;font:inherit;font-size:.9em} -button.more:hover{color:var(--fg)} -""" - -# One tap on 📋 → the command is on the clipboard; the button flashes ✓. The -# textarea path covers browsers without the async clipboard API. -_HTML_JS = """\ -async function copyCmd(b){ - const cmd=b.dataset.cmd; - try{await navigator.clipboard.writeText(cmd);} - catch(e){const t=document.createElement("textarea");t.value=cmd; - document.body.appendChild(t);t.select();document.execCommand("copy"); - t.remove();} - b.textContent="\\u2713";b.classList.add("ok"); - setTimeout(()=>{b.textContent="\\ud83d\\udccb";b.classList.remove("ok");},1200);} -document.addEventListener("click",e=>{ - const b=e.target.closest("button.copy");if(b)copyCmd(b);}); +# The dashboard-only half of the page script: the shared clipboard +# handler lives in the board theme, this reveals the Recent feed a page +# at a time. +_MORE_JS = """\ // Recent shows one page and reveals the next on each tap of the \u2026 button, // which retires itself once the feed is exhausted. Every row is already in the // DOM, so this never re-renders or re-sorts anything. @@ -1227,12 +1184,9 @@ def link(path, text_html): return f'{text_html}' def record_row(r): - text = link(r["path"], _summary_label(r["title"])) - facets = " · ".join(_summary_label(x) for x in - (r["target"], r["difficulty"], - r["autonomy"], r["priority"]) if x != "-") - if facets: - text += f' — {facets}' + text = link(r["path"], _summary_label(r["title"])) + pills( + *(_summary_label(x) for x in (r["target"], r["difficulty"], + r["autonomy"], r["priority"]))) return _html_task(text, f"/start_dev {r['path']}") H = [ @@ -1244,21 +1198,22 @@ def record_row(r): "PyAutoMind Dashboard", f"", - f"", + f"", "", "", - "

📋 PyAutoMind Dashboard

", - "

Every task the Mind is holding. Tap a task's 📋 and its " - "/start_dev command is on your clipboard — paste it into " - "a Claude Code chat to route Claude straight to that task. " - 'Recent is the same work by date — what has been ' - "happening rather than what to do next.

", - f'

In flight {c["issued_count"]} · ' - f'Parked {len(c["parked"])} · Planned {len(c["planned"])} · ' - f'Backlog {c["total"]}' - + (f' · {link("dashboard.md", "markdown version")}' if home else "") - + "

", + hero(THEME_ORGAN, "Dashboard", + "Every task the Mind is holding. Tap a task's 📋 and its " + "/start_dev command is on your clipboard — paste it " + "into a Claude Code chat to route Claude straight to that task. " + 'Recent is the same work by date — what has ' + "been happening rather than what to do next."), + # The four numbers a human wants before reading a single row. + stats((c["issued_count"], "In flight"), (len(c["parked"]), "Parked"), + (len(c["planned"]), "Planned"), (c["total"], "Backlog")), ] + if home: + H.append(f'

' + f'{link("dashboard.md", "markdown version")}

') if c.get("drift"): H += ['

⚠️ Needs lifecycle reconciliation — draft prompts ' "whose body records a fix PR (done, never advanced):

", "