diff --git a/scripts/board.py b/scripts/board.py
index 1f94735..7fc17cb 100644
--- a/scripts/board.py
+++ b/scripts/board.py
@@ -92,7 +92,6 @@
#: holds everything that was missed — git history does, which is exactly what
#: ``scripts/catch_up.py`` reads back.
CATCH_UP_DAYS = 7
-CATCH_UP_LYRIC = "…so… I've been lost for a while" # Fred again..
CATCH_UP_CMD = "Read PyAutoMemory/skills/catch_up/SKILL.md and follow the catch_up skill with arguments: lensing."
# The family look lives once, in the Brain (``board/_theme.py``): the
@@ -701,27 +700,23 @@ def _catch_up_due(snapshot: dict) -> int | None:
return model["age_days"] if model["state"] == "stale" else None
-def _catch_up_md(snapshot: dict) -> list[str]:
+def _catch_up_summary(snapshot: dict) -> str:
model = _lensing_catch_up(snapshot)
- if model["state"] == "healthy":
- return []
- title = "Strong-lensing catch-up due" if model["state"] == "stale" else "Strong-lensing freshness unknown"
- return [f"> **{title}**", f"> {model['reason']}",
- f"> Next: `{CATCH_UP_CMD}` — human-reviewed paper selection.", ""]
+ if model["state"] == "unknown":
+ return "Catch-up start date unknown."
+ due = model["due_at"][:10]
+ status = "due" if model["state"] == "stale" else "next due"
+ return f"Catch up from {model['last_activity']} · {status} {due}."
+
+
+def _catch_up_md(snapshot: dict) -> list[str]:
+ return ["## Catch up", "", _catch_up_summary(snapshot), "",
+ f"`{CATCH_UP_CMD}`", ""]
def _catch_up_html(snapshot: dict) -> str:
- model = _lensing_catch_up(snapshot)
- if model["state"] == "healthy":
- return ""
- title = "Strong-lensing catch-up due" if model["state"] == "stale" else "Strong-lensing freshness unknown"
- lyric = (f"
{_html.escape(CATCH_UP_LYRIC)}
"
- if model["state"] == "stale" else "")
- return (f"{lyric}{title}
"
- f"{_html.escape(model['reason'])}
"
- f"{_html.escape(CATCH_UP_CMD)} "
- f"{_copy_btn(CATCH_UP_CMD, 'copy: catch up on strong lensing')} "
- "Human-reviewed paper selection
")
+ return (f"{_html.escape(_catch_up_summary(snapshot))}
"
+ f"{_copy_btn(CATCH_UP_CMD, 'Copy catch-up prompt')}")
def _totals(snapshot: dict) -> dict:
@@ -1163,9 +1158,6 @@ def _bar(statuses: dict) -> str:
color:var(--bg);opacity:0;transition:opacity .2s;pointer-events:none;z-index:9}
#toast.show{opacity:1}
#toast.bad{background:var(--warn)}
-.catchup{border-left:3px solid var(--warn);padding:.4em .9em;margin:0 0 1em}
-.catchup p{margin:.25em 0}
-.catchup .lyric{color:var(--warn)}
"""
# The shared copy handler is delegated, so a chip inside a would
@@ -1592,9 +1584,7 @@ def _fresh_span(fresh: dict, now_text: str, stale_text: str,
filing_items.append(
f"{issue_link} "
f"{_html.escape(f['branch'])} {pr} ")
- # Above everything else when present, absent otherwise: it is the one
- # section that is a human's merge, and an empty "nothing waiting" line
- # would only teach the eye to skip it.
+ # Keep pending filings in their own navigable section when present.
filings_block = (
f"Filings awaiting merge ({len(filing_items)} "
f"on a branch, not yet in memory — merge the PR to finish the "
@@ -1608,10 +1598,9 @@ def _fresh_span(fresh: dict, now_text: str, stale_text: str,
{"href": "#citation-work", "label": "To cite", "count": t["todo"]},
{"href": "#sub-wikis", "label": "Pages", "count": t["pages"], "context": f"{pct}% cited"},
]
- if _catch_up_html(snapshot):
- navigation.insert(0, {"href": "#catch-up", "label": "Catch up"})
+ navigation.append({"href": "#catch-up", "label": "Catch up"})
if filing_items:
- navigation.insert(0, {"href": "#filings", "label": "Filings awaiting merge", "count": len(filing_items)})
+ navigation.append({"href": "#filings", "label": "Filings awaiting merge", "count": len(filing_items)})
hero = t_.hero(BOARD_KEY, "Dashboard", _LEDE, navigation=navigation)
repo_url = _repo_url(snapshot)
panel = t_.orchestration_panel(
@@ -1637,12 +1626,9 @@ def _fresh_span(fresh: dict, now_text: str, stale_text: str,
{hero}
{panel}
-{_catch_up_html(snapshot)}
-markdown version {onetap}
-
-{filings_block}
arXiv inbox (suggested overnight — un-acted papers
lapse after {inbox_actions.INBOX_WINDOW_DAYS} days)
+{onetap.removeprefix(" · ")}
{inbox_block}
arXiv interests (everything that is not strong
lensing — one day's ten at a time; \U0001f9f9 clears the day and shows the
@@ -1658,8 +1644,12 @@ def _fresh_span(fresh: dict, now_text: str, stale_text: str,
Sub-wikis ({snapshot.get('bib_entries', 0)} bibliography
entries · {snapshot.get('links', {}).get('wanted', 0)} wanted pages)
+Catch up
+{_catch_up_html(snapshot)}
+{filings_block}
{_boards_nav(snapshot)}
-Rendered by scripts/board.py from the checkout —
+
+markdown version · Rendered by scripts/board.py from the checkout —
nothing here is committed; generated
{_html.escape(str(snapshot.get('generated') or '?'))}.
diff --git a/tests/test_board.py b/tests/test_board.py
index e63e483..059d774 100644
--- a/tests/test_board.py
+++ b/tests/test_board.py
@@ -752,8 +752,8 @@ def boom(*a, **k):
assert board._pending_filings() == []
-def test_pending_filings_render_above_the_inbox_with_a_pr_link(tmp_path):
- """A filing on a branch is not in memory: the board says so, first, and
+def test_pending_filings_render_in_their_own_section_with_a_pr_link(tmp_path):
+ """A filing on a branch is not in memory: the board says so and
hands out the one tap that finishes it."""
snap = _snap_with_remote(tmp_path)
snap["filings"] = [{"issue": 91, "branch": "queue-filing/issue-91"}]
@@ -762,7 +762,7 @@ def test_pending_filings_render_above_the_inbox_with_a_pr_link(tmp_path):
assert "compare/main...queue-filing/issue-91?expand=1" in text
assert "/issues/91" in text
if '' in text:
- assert text.index("id='filings'") < text.index("id='arxiv-inbox'")
+ assert text.index("id='filings'") > text.index("id='arxiv-inbox'")
else:
assert text.index("Filings awaiting merge") < text.index("arXiv inbox")
@@ -946,11 +946,12 @@ def test_last_ingested_is_collected_from_a_scope_section(tmp_path):
assert board.collect(root)["last_ingested"] == "2026-09-01"
-def test_no_banner_under_the_threshold(tmp_path):
+def test_catchup_remains_available_under_the_threshold(tmp_path):
snap = _banner_snap(tmp_path, "2026-09-24") # 6 days
assert board._days_since_ingest(snap) == 6
assert "lost for a while" not in board.render(snap, "md")
- assert "class='catchup'" not in board.render(snap, "html")
+ assert "Catch up from 2026-09-24 · next due 2026-10-01." in board.render(snap, "html")
+ assert 'href="#catch-up"' in board.render(snap, "html")
assert board.to_state(snap)["days_since_ingest"] == 6
@@ -958,21 +959,20 @@ def test_banner_at_the_threshold_carries_the_day_count(tmp_path):
snap = _banner_snap(tmp_path, "2026-09-23") # exactly CATCH_UP_DAYS
assert board.CATCH_UP_DAYS == 7
md = board.render(snap, "md")
- assert "**Strong-lensing catch-up due**" in md
- assert "2026-09-23 (7 days ago)" in md
- assert "after 7 days" in md
+ assert "## Catch up" in md
+ assert "Catch up from 2026-09-23 · due 2026-09-30." in md
assert "`Read PyAutoMemory/skills/catch_up/SKILL.md and follow the catch_up skill with arguments: lensing.`" in md
# Top of the markdown: before the contents line.
- assert md.index("Strong-lensing catch-up due") < md.index("_Contents")
+ assert md.index("## Catch up") < md.index("_Contents")
def test_catchup_html_follows_banner_and_navigation_with_a_copy_button(tmp_path):
snap = _banner_snap(tmp_path, "2026-09-11")
page = board.render(snap, "html")
- assert "…so… I've been lost for a while " in page
- assert "2026-09-11 (19 days ago)" in page
+ assert "lost for a while" not in page
+ assert "Catch up from 2026-09-11 · due 2026-09-18." in page
assert 'data-cmd="Read PyAutoMemory/skills/catch_up/SKILL.md and follow the catch_up skill with arguments: lensing."' in page
- assert page.index('class="hero"') < page.index('class="board-nav"') < page.index("class='catchup'")
+ assert page.index('class="hero"') < page.index('class="board-nav"') < page.index('id="catch-up"')
assert board.to_state(snap)["days_since_ingest"] == 19
@@ -980,7 +980,7 @@ def test_unknown_banner_when_nothing_is_on_record(tmp_path):
snap = _banner_snap(tmp_path, None)
assert board._days_since_ingest(snap) is None
assert "lost for a while" not in board.render(snap, "md")
- assert "Strong-lensing freshness unknown" in board.render(snap, "html")
+ assert "Catch-up start date unknown." in board.render(snap, "html")
assert board.to_state(snap)["days_since_ingest"] is None
@@ -998,8 +998,8 @@ def test_lensing_catch_up_threshold_has_one_model_for_every_surface(tmp_path, ag
assert not rows
else:
assert len(rows) == 1 and rows[0]["reason"] == model["reason"]
- assert model["reason"] in board.render(snap, "md")
- assert model["reason"] in board.render(snap, "html")
+ assert board._catch_up_summary(snap) in board.render(snap, "md")
+ assert board._catch_up_summary(snap) in board.render(snap, "html")
assert rows[0]["actions"][0]["target"] == "Read PyAutoMemory/skills/catch_up/SKILL.md and follow the catch_up skill with arguments: lensing."
assert rows[0]["actions"][0]["safety"] == "scientific_judgement"
assert "requires_human_decision" not in rows[0] # No invented choice.
@@ -1182,3 +1182,15 @@ def test_panel_refresh_uses_capture_not_ingest_date(tmp_path, monkeypatch):
board.render(snap, "html")
assert calls[0]["refreshed_at"] == snap["generated"]
assert calls[0]["refresh_url"] == "https://github.com/SomeOrg/MemoryWork/actions/workflows/knowledge_board.yml"
+
+
+def test_inbox_is_first_content_after_checkin(tmp_path):
+ page = board.render(_snap_with_remote(tmp_path), "html")
+ between = page.split('id="orchestration-memory"', 1)[1].split("id='arxiv-inbox'", 1)[0]
+ assert 'id="catch-up"' not in between
+ assert "mdsrc" not in between
+ assert "id='onetap'" not in between
+ assert "id='filings'" not in between
+ assert page.index("id='arxiv-inbox'") < page.index("id='onetap'") < page.index("id='arxiv-interests'")
+ assert "Read PyAutoMemory/skills/catch_up" not in page
+ assert "Copy catch-up prompt" in page