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
8 changes: 6 additions & 2 deletions agents/conductors/intake/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,13 @@ schema — light structure over free-form prose.
| **reconcile** | `intake reconcile [prefix]` | rank backlog prompts that look already-shipped (vs the `complete/` records / `active/`); always read-only — retiring stays human |
| **reconcile --repo** | `intake reconcile --repo <target> [prefix]` | **also** read the target repo's source for identifiers the prompts name — the one signal that sees a prompt with no Mind-side trace. Opt-in; the default path is offline |

**Recent** is the one section laid out by *date* rather than by state: the 20
**Recent** is the one section laid out by *date* rather than by state: the 50
newest events on the **work in hand** — issued, parked, filed — merged across
the live buckets and sitting between the Backlog and the Epics. Every other
the live buckets and sitting between the Backlog and the Epics. It *holds* 50
and *shows* 10 (`RECENT_MAX` / `RECENT_PAGE`): the table is a glance, not a
log, so the rest is one tap away — a `…` button on the Pages twin, and nested
`<details>` on the markdown page, which GitHub renders where it strips the
script. Every other
section answers "what should I do now?"; recency is orthogonal to state, so
none of them can answer "what has been happening?". Dates come from the
registry key that names the event (`issued:` / `parked:` / `filed:`, PyAutoMind
Expand Down
91 changes: 81 additions & 10 deletions agents/conductors/intake/_intake.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,12 @@ def _clip(text: str, limit: int = 130) -> str:
# records deep and ships ~200 a month, so including it made the table a list of
# receipts — twenty things nobody can act on, on the page whose whole job is
# work in hand. `complete/index.md` is where shipped work is read.
RECENT_MAX = 20
# How deep the feed goes, and how much of it is on screen at once. The table
# is a glance, not a log: ten rows answer "what has been happening?" without
# pushing the Epics below a scroll, and the rest is one tap away — so a quiet
# week still shows a fortnight of context and a busy one does not bury it.
RECENT_MAX = 50
RECENT_PAGE = 10

# The verb each event reads as in the feed. Past tense throughout — every row
# is something that already happened.
Expand Down Expand Up @@ -820,6 +825,9 @@ def _epic_members(c: dict) -> dict:
"`complete/index.md`, and a thousand records deep it would crowd out "
"everything anyone can still act on.")

RECENT_PAGING_NOTE = (
" Showing the newest {page}; \u2026 opens the next {page}.")


def _dated(row: dict) -> str:
"""`— issued 2026-08-19`, the facet every live task row now carries.
Expand All @@ -834,6 +842,47 @@ def _dated(row: dict) -> str:
return f" — {event} {row['date']}"


def _recent_blurb(rows: list) -> str:
"""The section's prose — the paging sentence only when there IS paging."""
text = RECENT_BLURB.format(n=len(rows))
if len(rows) > RECENT_PAGE:
text += RECENT_PAGING_NOTE.format(page=RECENT_PAGE)
return text


RECENT_TABLE_HEAD = ["| Date | Event | Task |", "|------|-------|------|"]


def _recent_rows(rows: list) -> list:
return [f"| {r['date']} | {r['event']} | {_cell(_recent_link(r))} |"
for r in rows]


def _recent_pages(rows: list, page: int = RECENT_PAGE) -> list:
"""The feed as nested `<details>`: a page on screen, the rest one tap in.

GitHub strips the JavaScript the Pages twin uses for this, so the markdown
page reveals with the one interactive element it does render — `<details>`,
NESTED, so each tap shows the next page and leaves another `…` behind it.
Sibling blocks would let a reader open page 4 without page 3, which is not
what "show me more" means when the list is ordered by date.

Each page carries its own header row: a markdown table cannot span an HTML
block boundary, so the alternative is a headerless slab of pipes. The blank
lines are load-bearing — without them GitHub treats the table as raw text
inside the `<details>` (same rule as `_task_row`).
"""
head, rest = rows[:page], rows[page:]
block = RECENT_TABLE_HEAD + _recent_rows(head)
if not rest:
return block
shown = min(page, len(rest))
return block + ["",
f"<details><summary>… {shown} more "
f"({len(rest)} left)</summary>",
""] + _recent_pages(rest, page) + ["", "</details>"]


def _recent_link(e: dict) -> str:
"""The task cell of a Recent row — its title, linked to where it lives."""
return f"<a href=\"{e['path']}\">{_summary_label(_clip(e['title'], 70))}</a>"
Expand Down Expand Up @@ -969,12 +1018,8 @@ def render_dashboard(c: dict) -> str:
# it is picked up.
recent = c.get("recent") or []
if recent:
L += ["## Recent", "",
RECENT_BLURB.format(n=len(recent)), "",
"| Date | Event | Task |",
"|------|-------|------|"]
L += [f"| {r['date']} | {r['event']} | {_cell(_recent_link(r))} |"
for r in recent]
L += ["## Recent", "", _recent_blurb(recent), ""]
L += _recent_pages(recent)
L += ["", "_Dates come from each task's registry entry — "
"`lifecycle.py dates` reports anything undated._", ""]

Expand Down Expand Up @@ -1062,6 +1107,10 @@ def render_dashboard(c: dict) -> str:
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
Expand All @@ -1077,6 +1126,18 @@ def render_dashboard(c: dict) -> str:
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);});
// 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.
document.addEventListener("click",e=>{
const b=e.target.closest("button.more");if(!b)return;
const t=document.querySelector("table.recent");if(!t)return;
const hidden=[...t.querySelectorAll("tr[hidden]")];
const page=Number(b.dataset.page)||10;
hidden.slice(0,page).forEach(r=>r.removeAttribute("hidden"));
const left=hidden.length-Math.min(page,hidden.length);
if(left<=0){b.remove();return;}
b.textContent="\u2026 "+Math.min(page,left)+" more ("+left+" left)";});
"""


Expand Down Expand Up @@ -1228,10 +1289,16 @@ def h2(title, src):
recent = c.get("recent") or []
if recent:
H += ['<a id="recent"></a>' + h2("Recent", "dashboard.md#recent"),
f'<p class="muted">{RECENT_BLURB.format(n=len(recent))}</p>',
# `_summary_label` turns the blurb's `code` spans into <code>;
# markdown backticks render literally on this page.
f'<p class="muted">{_summary_label(_recent_blurb(recent))}</p>',
'<table class="recent">']
for r in recent:
H += ["<tr>",
for i, r in enumerate(recent):
# Every row ships in the DOM; the ones past the first page start
# hidden, so revealing them is a flag flip rather than a re-render
# — and a reader with JS off sees the whole feed rather than ten
# rows and a dead button.
H += ["<tr hidden>" if i >= RECENT_PAGE else "<tr>",
f'<td class="when">{r["date"]}</td>',
f'<td class="what">{_summary_label(r["event"])}</td>',
f'<td>{link(r["path"], _summary_label(_clip(r["title"], 70)))}</td>',
Expand All @@ -1240,6 +1307,10 @@ def h2(title, src):
f'Claude command">📋</button></td>',
"</tr>"]
H += ["</table>"]
rest = len(recent) - RECENT_PAGE
if rest > 0:
H += [f'<button class="more" data-page="{RECENT_PAGE}">'
f'… {min(RECENT_PAGE, rest)} more ({rest} left)</button>']

known = {e["slug"] for e in c.get("epics") or []}
stray = [s for s in members if s not in known]
Expand Down
68 changes: 68 additions & 0 deletions tests/test_intake_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -607,3 +607,71 @@ def test_an_undated_row_gets_no_placeholder(tmp_path):
"- prompt: active/sprocket_calibration.md\n"})
row = _page(mind).split("## In flight")[1].split("<details>")[1]
assert "—" not in row.split("</summary>")[0]


# --------------------------------------------------------------------------- #
# recent: fifty deep, ten on screen
# --------------------------------------------------------------------------- #
def _many(root, n):
"""A Mind whose planned.md holds `n` dated tasks, newest first by slug."""
return _mind(root, registries={"planned.md": "".join(
f"## task-{i:03d}\n- filed: 2026-01-01\n\n" for i in range(n))})


def test_the_feed_runs_deeper_than_the_page(tmp_path):
"""Fifty is what the feed HOLDS; ten is what it SHOWS."""
rows = _intake.census(_many(tmp_path, 80))["recent"]
assert len(rows) == _intake.RECENT_MAX == 50
assert _intake.RECENT_PAGE == 10


def test_markdown_shows_one_page_then_nests_the_rest(tmp_path):
"""GitHub strips the JS the Pages twin uses, so the markdown page reveals
with `<details>` — nested, so each tap shows the next page and leaves
another one behind it."""
page = _page(_many(tmp_path, 80))
section = page.split("## Recent")[1]
before = section.split("<details>")[0]
assert before.count("| 2026-01-01 |") == 10
assert section.count("<details>") == 4
assert "… 10 more (40 left)" in section
assert "… 10 more (10 left)" in section


def test_each_revealed_page_carries_its_own_table_header(tmp_path):
"""A markdown table cannot span an HTML block boundary — without a header
per page the reveal is a headerless slab of pipes."""
section = _page(_many(tmp_path, 80)).split("## Recent")[1]
assert section.count("| Date | Event | Task |") == 5


def test_a_feed_that_fits_on_one_page_has_no_reveal(tmp_path):
page = _page(_many(tmp_path, 6))
section = page.split("## Recent")[1]
assert "<details>" not in section
assert "…" not in section
assert "opens the next" not in section


def test_html_hides_the_overflow_rows_and_offers_a_button(tmp_path):
html = _intake.render_dashboard_html(_intake.census(_many(tmp_path, 80)))
section = html.split("<h2>Recent")[1]
assert section.count("<tr>") == 10
assert section.count("<tr hidden>") == 40
assert '<button class="more" data-page="10">… 10 more (40 left)</button>' in section


def test_html_ships_every_row_so_a_reader_without_js_sees_the_feed(tmp_path):
"""Hidden, not absent: with JS off the whole feed is there rather than ten
rows and a dead button."""
html = _intake.render_dashboard_html(_intake.census(_many(tmp_path, 80)))
section = html.split("<h2>Recent")[1]
assert section.count("<tr") == 50


def test_the_html_blurb_renders_its_code_spans(tmp_path):
"""The blurb is shared with the markdown page; its backticks would
otherwise print literally here."""
html = _intake.render_dashboard_html(_intake.census(_many(tmp_path, 80)))
assert "<code>complete/index.md</code>" in html
assert "`complete/index.md`" not in html
Loading