diff --git a/agents/conductors/intake/AGENTS.md b/agents/conductors/intake/AGENTS.md
index 660a7d0..1ef6dd9 100644
--- a/agents/conductors/intake/AGENTS.md
+++ b/agents/conductors/intake/AGENTS.md
@@ -83,11 +83,22 @@ schema — light structure over free-form prose.
| **classify** | `intake ""` / `intake classify --file P` | classify one raw input; `--apply` writes the prompt |
| **ideas** | `intake ideas` | scan `ideas.md`, propose one prompt per bullet; `--apply` writes them |
| **census** | `intake census` | inventory every filed prompt (work-type/target/difficulty/status + hygiene flags); always read-only |
-| **dashboard** | `intake dashboard` | render the census as the Mind **task** page — picks, in flight, parked, planned, backlog; `--apply` writes `PyAutoMind/dashboard.md`, `--check` exits 1 on drift |
+| **dashboard** | `intake dashboard` | render the census as the Mind **task** page — picks, in flight, parked, planned, backlog, recent, epics; `--apply` writes `PyAutoMind/dashboard.md`, `--check` exits 1 on drift |
| **formalise** | `intake formalise [prefix]` | retroactively header the prompts census flags — derive the missing fields, insert in place, prose untouched; `--apply` writes |
| **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 [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
+newest task events merged across every bucket — issued, parked, filed,
+completed — sitting between the Backlog and the Epics. 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 REFERENCE.md
+"Task dates"), a prompt's own `Issued:` header, or a record's `completed:`.
+Live work is selected first and completions fill the rest — on a Mind that
+ships two hundred records a month a straight date sort is twenty receipts and
+no work.
+
Census/dashboard are the Mind *backlog* view — deliberately distinct from
Heart's `/health status` health view (see "must never do"). The prompt-taxonomy
folder is authoritative for a prompt's work-type/target; header fields are
diff --git a/agents/conductors/intake/_intake.py b/agents/conductors/intake/_intake.py
index 7bad3a0..06b7893 100755
--- a/agents/conductors/intake/_intake.py
+++ b/agents/conductors/intake/_intake.py
@@ -337,13 +337,14 @@ def parse_header(text: str) -> dict:
Only scans the top of the file so a stray "Status:" deep in prose does not
fire; first occurrence of each field wins. No YAML — the blessed convention.
- `Epic:`/`Phase:` are optional epic-membership fields (dashboard grouping);
- they are not in HEADER_FIELDS, so their absence is never header hygiene.
+ `Epic:`/`Phase:` are optional epic-membership fields (dashboard grouping)
+ and `Issued:` is the prompt's own copy of its registry date; none are in
+ HEADER_FIELDS, so their absence is never header hygiene.
"""
fields = {}
for line in text.splitlines()[:30]:
m = re.match(r"(Type|Target|Difficulty|Autonomy|Priority|Status|"
- r"Epic|Phase):\s*(\S.*)",
+ r"Issued|Epic|Phase):\s*(\S.*)",
line.strip())
if m:
fields.setdefault(m.group(1).lower(), m.group(2).strip())
@@ -364,6 +365,25 @@ def _prefix_match(path: str, prefix: str) -> bool:
_REG_HEAD = re.compile(r"^##\s+(\S.*?)\s*$")
_REG_FIELD = re.compile(r"^-\s+([a-z][a-z-]*):\s*(.*)$")
_ISSUE_URL = re.compile(r"https://github\.com/[^/\s]+/[^/\s]+/issues/(\d+)")
+_ISO_DATE = re.compile(r"\b(\d{4}-\d{2}-\d{2})\b")
+
+# The Mind dates a task the moment it leaves the backlog (PyAutoMind
+# REFERENCE.md "Task dates"). The KEY names the event, so a merged feed can say
+# what each date means; most-specific event first, so a task that was filed and
+# later issued dates from its issue. A date sitting in some other field's prose
+# is deliberately not read — that is the un-parseable habit the convention
+# replaced.
+DATE_KEYS = ("issued", "registered", "started", "planned", "filed", "parked",
+ "found", "completed", "shipped")
+
+
+def _entry_date(fields: dict) -> tuple:
+ """(date, event) for a registry entry, or ('', '') when it carries none."""
+ for key in DATE_KEYS:
+ m = _ISO_DATE.search(fields.get(key) or "")
+ if m:
+ return m.group(1), key
+ return "", ""
def parse_registry(path: Path) -> list:
@@ -380,7 +400,8 @@ def parse_registry(path: Path) -> list:
head = _REG_HEAD.match(line)
if head:
cur = {"slug": head.group(1), "issue": "", "issue_no": "",
- "status": "", "prompt": ""}
+ "status": "", "prompt": "", "fields": {},
+ "date": "", "event": ""}
entries.append(cur)
continue
if cur is None:
@@ -389,6 +410,7 @@ def parse_registry(path: Path) -> list:
if not field:
continue
key, value = field.group(1), field.group(2).strip()
+ cur["fields"].setdefault(key, value)
if key in ("issue", "status", "prompt") and not cur[key]:
cur[key] = value
if key == "issue":
@@ -397,6 +419,8 @@ def parse_registry(path: Path) -> list:
m = _ISSUE_URL.search(value)
if m:
cur["issue"], cur["issue_no"] = m.group(0), m.group(1)
+ for e in entries:
+ e["date"], e["event"] = _entry_date(e["fields"])
return entries
@@ -466,6 +490,146 @@ def _clip(text: str, limit: int = 130) -> str:
return text[:limit].rsplit(" ", 1)[0].rstrip(" .,;:—-") + "…"
+# --- the recent feed -----------------------------------------------------------
+# The dashboard answers "what should I do now?" everywhere else on the page.
+# This one section answers "what has been happening?" — the single question a
+# task page laid out by STATE cannot answer, because a task's recency is
+# orthogonal to which bucket it sits in. Merging the buckets by date puts the
+# week's issuing, parking and shipping in one place, which is also the fastest
+# way to spot a Mind that has drifted (three weeks of `filed` and nothing
+# `completed` is visible at a glance and invisible section by section).
+RECENT_MAX = 20
+
+# How many month folders of `complete/` to open. The records are the bulk of
+# the Mind (1000+ files), and only the newest handful can reach a 20-row feed,
+# so the scan is bounded by folder rather than reading the whole ledger on
+# every render. Two months is comfortably more than 20 records in practice;
+# the scan widens on its own if they are not.
+RECENT_MONTHS = 2
+
+# Records open with their fields (`- completed: …`) but the ledger is a decade
+# of hand-written prose, so a fifth of them say it another way. Read the head
+# of the file only: a date deep in a narrative is something the task mentions,
+# not when it shipped.
+_RECORD_HEAD_BYTES = 4096
+_RECORD_DATE = re.compile(r"^-\s+(completed|shipped):\s*(.*)$", re.MULTILINE)
+
+# The verb each event reads as in the feed. Past tense throughout — every row
+# is something that already happened.
+EVENT_LABEL = {"issued": "issued", "registered": "issued", "started": "started",
+ "planned": "planned", "filed": "filed", "parked": "parked",
+ "found": "found", "completed": "completed", "shipped": "completed"}
+
+
+def _anchor(heading: str) -> str:
+ """GitHub's heading anchor for an `## ` registry entry."""
+ return re.sub(r"[^a-z0-9-]+", "-", heading.lower()).strip("-")
+
+
+def _record_date(path: Path, text: str) -> str:
+ """The day a completion record's task shipped.
+
+ `completed:`/`shipped:` when the record states one; otherwise the record's
+ own `complete///` folder, which the lifecycle engine files it
+ into by completion month — coarse, but never wrong about the month, and
+ the alternative is dropping a fifth of the ledger out of the feed."""
+ m = _RECORD_DATE.search(text)
+ if m:
+ d = _ISO_DATE.search(m.group(2))
+ if d:
+ return d.group(1)
+ parts = path.parts
+ if len(parts) >= 3 and parts[-3].isdigit() and parts[-2].isdigit():
+ return f"{parts[-3]}-{parts[-2]}"
+ return ""
+
+
+def completed_records(mind: Path, months: int = RECENT_MONTHS,
+ want: int = RECENT_MAX) -> list:
+ """The newest completion records, newest first — `[{date, slug, path}]`.
+
+ Walks `complete///` newest-first and stops one month after it has
+ enough, so the whole ledger is never read to render a 20-row table. A month
+ is opened whole: records inside one are unordered, and a record can carry a
+ date from the month before it was filed under."""
+ root = mind / "complete"
+ if not root.is_dir():
+ return []
+ folders = sorted(
+ (d for y in root.iterdir() if y.is_dir() and y.name.isdigit()
+ for d in y.iterdir() if d.is_dir() and d.name.isdigit()),
+ key=lambda d: (d.parent.name, d.name), reverse=True)
+ out, opened = [], 0
+ for folder in folders:
+ opened += 1
+ for f in folder.glob("*.md"):
+ text = f.read_text(encoding="utf-8", errors="replace")[:_RECORD_HEAD_BYTES]
+ out.append({"date": _record_date(f, text),
+ "slug": f.stem,
+ "path": str(f.relative_to(mind))})
+ if opened >= months and len(out) >= want:
+ break
+ out.sort(key=lambda r: r["date"], reverse=True)
+ return out
+
+
+def recent_events(c: dict, limit: int = RECENT_MAX) -> list:
+ """The Mind's newest task events, newest first — `[{date, event, title, …}]`.
+
+ One row per task, not per event: a task that was filed and later issued
+ appears once, on its latest date (which is what `_entry_date` already picks
+ per entry).
+
+ **Live work is never crowded out by shipped work.** A straight date sort
+ would be all completions — this Mind ships ~200 records a month, so the
+ twenty newest dates in it are twenty records — and the one thing the feed
+ must show is the work still in hand. So the rows are *selected* live-first
+ (in flight, parked, planned), completions fill whatever room is left, and
+ the selected set is then sorted by date like any other feed. On a quiet
+ Mind that changes nothing; on a busy one it is the difference between a
+ table of work and a table of receipts.
+
+ Undated rows are absent rather than sorted to the bottom: `lifecycle.py
+ dates` is where a missing date gets reported, and padding this table with
+ unknowns would bury the answer it exists to give.
+ """
+ live, done = [], []
+ for r in c.get("in_flight") or []:
+ if r.get("date"):
+ live.append({"date": r["date"], "event": r.get("event") or "issued",
+ "title": r["title"], "path": r["path"], "live": True,
+ "payload": f"/start_dev {r['path']}"})
+ for key, verb in (("parked", "resume"), ("planned", "start")):
+ for e in c.get(key) or []:
+ if e.get("date"):
+ live.append({"date": e["date"],
+ "event": e.get("event") or key,
+ "title": e["slug"],
+ # The entry's own heading anchor — a registry file
+ # is long enough that landing at its top is not
+ # the same as landing on the task.
+ "path": f"{key}.md#{_anchor(e['slug'])}",
+ "live": True,
+ "payload": _registry_payload(e, key, verb)})
+ for r in c.get("completed") or []:
+ if r.get("date"):
+ done.append({"date": r["date"], "event": "completed",
+ "title": r["slug"], "path": r["path"], "live": False,
+ "payload": f"/memory recall the completed PyAutoMind "
+ f"task {r['slug']} — its record is "
+ f"{r['path']}"})
+
+ def _newest(rows):
+ return sorted(rows, key=lambda e: (e["date"], e["title"]), reverse=True)
+
+ chosen = _newest(live)[:limit]
+ chosen += _newest(done)[:max(0, limit - len(chosen))]
+ chosen = _newest(chosen)
+ for e in chosen:
+ e["event"] = EVENT_LABEL.get(e["event"], e["event"])
+ return chosen
+
+
def census(mind: Path) -> dict:
"""Inventory the Mind's work — filed prompts plus the registry's live rows.
@@ -546,11 +710,20 @@ def _count(key):
continue
header = parse_header(text)
row = by_prompt.get(rel, {})
+ # The registry row is the live record, so its date wins; the prompt's
+ # own `Issued:` header is the fallback that keeps an orphan (no row
+ # claims it) dated rather than dropping it out of the recent feed.
+ date, event = row.get("date", ""), row.get("event", "")
+ if not date:
+ m = _ISO_DATE.search(header.get("issued", ""))
+ date, event = (m.group(1), "issued") if m else ("", "")
in_flight.append({
"path": rel,
"title": _title(text),
"target": header.get("target", "-"),
"priority": header.get("priority", "-"),
+ "date": date,
+ "event": event,
"issue": row.get("issue", ""),
"issue_no": row.get("issue_no", ""),
# The registry row only. A prompt's own `Status:` header is written
@@ -559,7 +732,7 @@ def _count(key):
"status": row.get("status", ""),
})
- return {
+ c = {
"generated": _dt.date.today().isoformat(),
"home": _mind_home(mind),
"total": len(records),
@@ -576,6 +749,9 @@ def _count(key):
"hygiene": hygiene,
"drift": drift,
}
+ c["completed"] = completed_records(mind)
+ c["recent"] = recent_events(c)
+ return c
def _cell(value: str) -> str:
@@ -718,6 +894,32 @@ def _epic_members(c: dict) -> dict:
return groups
+RECENT_BLURB = (
+ "The Mind's most recent {n} task events, newest first — what was issued, "
+ "parked, filed and shipped, in one place. Every other section on this page "
+ "is laid out by state, which is exactly why none of them can answer "
+ "\u201cwhat has been happening?\u201d. Work still in hand is listed "
+ "first-class here; completed records fill whatever room is left.")
+
+
+def _dated(row: dict) -> str:
+ """`— issued 2026-08-19`, the facet every live task row now carries.
+
+ The date is worth showing where the task IS, not only in the Recent feed:
+ a status line reads very differently against a row issued yesterday than
+ against one issued in May. Empty for an undated row rather than a
+ placeholder — `lifecycle.py dates` is where the gap gets reported."""
+ if not row.get("date"):
+ return ""
+ event = EVENT_LABEL.get(row.get("event", ""), row.get("event") or "dated")
+ return f" — {event} {row['date']}"
+
+
+def _recent_link(e: dict) -> str:
+ """The task cell of a Recent row — its title, linked to where it lives."""
+ return f"{_summary_label(_clip(e['title'], 70))}"
+
+
EPIC_ORDER_CAUTION = ("Members are worked in order through the epic's ledger "
"— continue the epic rather than starting one standalone.")
@@ -751,7 +953,9 @@ def render_dashboard(c: dict) -> str:
L += [
"Every task the Mind is holding, on one page: what is in flight, what "
"is parked, and the whole backlog to pick from. Pick a task and run "
- "its `/start_dev` command in a Claude Code chat to start it.",
+ "its `/start_dev` command in a Claude Code chat to start it. "
+ "[Recent](#recent) is the same work by date — what has been happening "
+ "rather than what to do next.",
"",
"| Where | Count |",
"|-------|------:|",
@@ -794,6 +998,7 @@ def render_dashboard(c: dict) -> str:
head = f"{_summary_label(r['title'])}"
if r["issue_no"]:
head += f" — issue #{r['issue_no']}"
+ head += _dated(r)
if r["status"]:
head += f" — {_summary_label(_clip(r['status']))}"
flight.append(_task_row(head, f"/start_dev {r['path']}"))
@@ -817,6 +1022,7 @@ def render_dashboard(c: dict) -> str:
head = f"{_summary_label(e['slug'])}"
if e["issue_no"]:
head += f" — issue #{e['issue_no']}"
+ head += _dated(e)
if e["status"]:
head += f" — {_summary_label(_clip(e['status']))}"
items.append(_task_row(head, _registry_payload(e, key, verb)))
@@ -838,6 +1044,21 @@ def render_dashboard(c: dict) -> str:
L += _items([_bullet(r) for r in rows])
L += ["", "", ""]
+ # Recency is orthogonal to state, so it gets its own table rather than a
+ # column on any section above. A table, not the page's usual copy rows:
+ # this section is read, not picked from — the task's own section is where
+ # 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 += ["", "_Dates come from each task's registry entry and each record's "
+ "`completed:` — `lifecycle.py dates` reports anything undated._", ""]
+
# Epics live at the bottom, whole: each epic's resume prompt sits with its
# queued member prompts, grouped and phase-ordered, so nobody picks a
# member standalone out of order from a work-type section above.
@@ -913,6 +1134,15 @@ def render_dashboard(c: dict) -> str:
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}
"""
# One tap on 📋 → the command is on the clipboard; the button flashes ✓. The
@@ -988,7 +1218,9 @@ def record_row(r):
"
📋 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.
",
+ "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"]}'
@@ -1031,6 +1263,8 @@ def h2(title, src):
text = link(r["path"], _summary_label(r["title"]))
if r["issue_no"]:
text += f' — issue #{r["issue_no"]}'
+ if r.get("date"):
+ text += f'{_dated(r)}'
if r["status"]:
text += (f' — '
f'{_summary_label(_clip(r["status"]))}')
@@ -1048,6 +1282,8 @@ def h2(title, src):
if e["issue_no"]:
text += (f' — '
f'issue #{e["issue_no"]}')
+ if e.get("date"):
+ text += f'{_dated(e)}'
if e["status"]:
text += (f' — '
f'{_summary_label(_clip(e["status"]))}')
@@ -1070,6 +1306,22 @@ def h2(title, src):
H += [record_row(r) for r in rows]
H += [""]
+ recent = c.get("recent") or []
+ if recent:
+ H += ['' + h2("Recent", "dashboard.md#recent"),
+ f'
"]
+
known = {e["slug"] for e in c.get("epics") or []}
stray = [s for s in members if s not in known]
if c.get("epics") or stray:
diff --git a/tests/test_intake_dashboard.py b/tests/test_intake_dashboard.py
index 6eb0238..4781d53 100644
--- a/tests/test_intake_dashboard.py
+++ b/tests/test_intake_dashboard.py
@@ -33,11 +33,16 @@ def _prompt(title, difficulty="medium", autonomy="supervised", priority="normal"
f"Priority: {priority}\nStatus: {status}\n\nBody prose.\n")
-def _mind(root: Path, drafts=None, active=None, registries=None) -> Path:
+def _mind(root: Path, drafts=None, active=None, registries=None,
+ complete=None) -> Path:
for rel, body in (drafts or {}).items():
p = root / "draft" / rel
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(body, encoding="utf-8")
+ for rel, body in (complete or {}).items():
+ p = root / "complete" / rel
+ p.parent.mkdir(parents=True, exist_ok=True)
+ p.write_text(body, encoding="utf-8")
for name, body in (active or {}).items():
p = root / "active" / name
p.parent.mkdir(parents=True, exist_ok=True)
@@ -452,3 +457,168 @@ def test_html_sections_link_their_markdown_source(tmp_path):
for src in ("active.md", "epics.md", "parked.md", "planned.md"):
assert f'/blob/main/{src}">markdown version' in html, src
assert '/tree/main/draft">markdown version' in html
+
+
+# --------------------------------------------------------------------------- #
+# recent: the one section laid out by date rather than by state
+# --------------------------------------------------------------------------- #
+def _record(slug, date):
+ return f"## {slug}\n- completed: {date}\n- summary: it shipped.\n"
+
+
+def _recent_mind(root, n_records=0, month="08"):
+ """A Mind with one task in each live state plus `n_records` completions."""
+ return _mind(
+ root,
+ active={"sprocket_calibration.md": _prompt("Sprocket calibration")},
+ complete={f"2026/{month}/shipped-{i:02d}.md": _record(f"shipped-{i:02d}",
+ f"2026-{month}-2{i % 10}")
+ for i in range(n_records)},
+ registries={
+ "active.md": "## sprocket-calibration\n- issued: 2026-08-19\n"
+ "- prompt: active/sprocket_calibration.md\n",
+ "parked.md": "## flywheel-balance\n- parked: 2026-08-18 — deferred\n",
+ "planned.md": "## gearbox-survey\n- filed: 2026-07-01\n",
+ })
+
+
+def test_recent_merges_every_state_into_one_dated_feed(tmp_path):
+ """Recency is orthogonal to state, so it is the one question no other
+ section on the page can answer."""
+ page = _page(_recent_mind(tmp_path, n_records=3))
+ recent = page.split("## Recent")[1]
+ for slug in ("sprocket-calibration", "flywheel-balance", "gearbox-survey",
+ "shipped-00"):
+ assert slug in recent or slug.replace("-", " ") in recent.lower()
+ assert "| Date | Event | Task |" in recent
+
+
+def test_recent_names_the_event_each_date_records(tmp_path):
+ """A bare date says nothing; `parked` / `issued` / `filed` says what
+ happened — the whole reason the registry key is the event name."""
+ rows = _intake.census(_recent_mind(tmp_path))["recent"]
+ assert {r["title"]: r["event"] for r in rows} == {
+ "Sprocket calibration": "issued",
+ "flywheel-balance": "parked",
+ "gearbox-survey": "filed",
+ }
+
+
+def test_recent_is_newest_first(tmp_path):
+ rows = _intake.census(_recent_mind(tmp_path))["recent"]
+ assert [r["date"] for r in rows] == sorted(
+ (r["date"] for r in rows), reverse=True)
+
+
+def test_recent_sits_after_the_backlog_and_before_the_epics(tmp_path):
+ mind = _recent_mind(tmp_path)
+ (mind / "epics.md").write_text(_EPICS, encoding="utf-8")
+ page = _page(mind)
+ assert page.index("## Backlog") < page.index("## Recent") < page.index("## Epics")
+
+
+def test_a_busy_ledger_never_crowds_live_work_out_of_the_feed(tmp_path):
+ """A straight date sort on a Mind that ships 200 records a month is 20
+ receipts and no work. Live tasks are selected first; records fill the
+ rest — otherwise the dates on active tasks would be invisible on the very
+ page they were added for."""
+ rows = _intake.census(_recent_mind(tmp_path, n_records=40))["recent"]
+ assert len(rows) == _intake.RECENT_MAX
+ live = [r for r in rows if r["live"]]
+ assert {r["title"] for r in live} == {"Sprocket calibration",
+ "flywheel-balance", "gearbox-survey"}
+
+
+def test_an_undated_task_is_absent_rather_than_sorted_to_the_bottom(tmp_path):
+ """`lifecycle.py dates` is where a missing date gets reported; padding the
+ feed with unknowns would bury the answer it exists to give."""
+ mind = _mind(tmp_path, registries={
+ "planned.md": "## gearbox-survey\n- status: planned\n"})
+ assert _intake.census(mind)["recent"] == []
+ assert "## Recent" not in _page(mind)
+
+
+def test_recent_links_a_registry_row_to_its_own_entry_not_the_file_top(tmp_path):
+ """parked.md is long enough that landing at its top is not the same as
+ landing on the task."""
+ page = _page(_recent_mind(tmp_path))
+ assert 'href="parked.md#flywheel-balance"' in page
+
+
+def test_an_in_flight_prompt_can_be_dated_by_its_own_header(tmp_path):
+ """The prompt's `Issued:` header is its own copy of the registry date, so
+ an orphan (no row claims it) still dates rather than dropping out."""
+ body = _prompt("Sprocket calibration").replace(
+ "Status: formalised", "Status: formalised\nIssued: 2026-08-19")
+ rows = _intake.census(
+ _mind(tmp_path, active={"sprocket_calibration.md": body}))["recent"]
+ assert [(r["date"], r["event"]) for r in rows] == [("2026-08-19", "issued")]
+
+
+def test_a_registry_date_beats_the_prompts_own_copy(tmp_path):
+ """The registry row is the live record; the header is the fallback."""
+ body = _prompt("Sprocket calibration").replace(
+ "Status: formalised", "Status: formalised\nIssued: 2026-07-01")
+ mind = _mind(tmp_path, active={"sprocket_calibration.md": body},
+ registries={"active.md": "## sprocket-calibration\n"
+ "- issued: 2026-08-19\n"
+ "- prompt: active/sprocket_calibration.md\n"})
+ assert _intake.census(mind)["recent"][0]["date"] == "2026-08-19"
+
+
+def test_a_date_in_another_fields_prose_does_not_count_as_a_date(tmp_path):
+ """`- issue: …/1501 (issued 2026-08-19)` is the un-parseable habit the
+ convention replaced — reading it back would re-legitimise it."""
+ mind = _mind(tmp_path, registries={
+ "planned.md": "## gearbox-survey\n"
+ "- issue: https://example.invalid/issues/1 (filed 2026-08-19)\n"})
+ assert _intake.census(mind)["recent"] == []
+
+
+def test_a_record_without_a_completed_field_still_dates_by_its_folder(tmp_path):
+ """A fifth of the ledger states its date in prose. The month folder the
+ lifecycle engine filed it into is coarse but never wrong about the month,
+ and the alternative is dropping those records out of the feed."""
+ mind = _mind(tmp_path, complete={
+ "2026/07/legacy_record.md": "Shipped one July afternoon.\n"})
+ assert _intake.census(mind)["recent"][0]["date"] == "2026-07"
+
+
+def test_the_html_twin_carries_the_same_feed_with_real_copy_buttons(tmp_path):
+ html = _intake.render_dashboard_html(_intake.census(_recent_mind(tmp_path)))
+ assert "
Recent" in html
+ assert html.index("Backlog") < html.index("
Recent") < html.index("
Epics") \
+ if "
Epics" in html else True
+ assert '
' in html
+ assert 'data-cmd="/start_dev active/sprocket_calibration.md"' in html
+
+
+def test_the_completed_scan_does_not_read_the_whole_ledger(tmp_path):
+ """1000+ records is the bulk of the Mind; a 20-row table must not open all
+ of them on every render."""
+ mind = _mind(tmp_path, complete={
+ f"2026/{m:02d}/rec-{m}-{i:02d}.md": _record(f"rec-{m}-{i}", f"2026-{m:02d}-01")
+ for m in range(1, 9) for i in range(30)})
+ scanned = _intake.completed_records(mind)
+ assert len(scanned) < 240
+ assert scanned[0]["date"] == "2026-08-01"
+
+
+def test_a_live_row_wears_its_date_where_the_task_is(tmp_path):
+ """A status line reads very differently against a row issued yesterday
+ than against one issued in May, so the date rides on the row too — not
+ only down in the Recent feed."""
+ page = _page(_recent_mind(tmp_path))
+ flight = page.split("## In flight")[1].split("## Parked")[0]
+ assert "issued 2026-08-19" in flight
+ assert "parked 2026-08-18" in page.split("## Parked")[1].split("## Planned")[0]
+ assert "filed 2026-07-01" in page.split("## Planned")[1].split("## Backlog")[0]
+
+
+def test_an_undated_row_gets_no_placeholder(tmp_path):
+ """`lifecycle.py dates` reports the gap; the page must not invent one."""
+ mind = _mind(tmp_path, active={"sprocket_calibration.md": _prompt("Sprocket")},
+ registries={"active.md": "## sprocket-calibration\n"
+ "- prompt: active/sprocket_calibration.md\n"})
+ row = _page(mind).split("## In flight")[1].split("")[1]
+ assert "—" not in row.split("")[0]