Repository navigation
Description
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 54212b20793c48f49106050770fe9007
Branch: dev/1-decision-path-cap
Base: f6fcb7f
At: 2026-11-01T11:46:37Z
What happens
#2021 changed the decision list to keep the newest 200 files
(core/src/host/project-board.js:292, deduped.slice(-DECISIONS_MAX), DECISIONS_MAX = 200 at :146)
and #2023 added total (:315) so a reader can tell a cut list from a complete one.
What neither changed is the lookup a decided meeting uses to find its own decision file:
meetingsOf builds const known = new Map(decisions.files.map(...)) (:333) from that truncated
list, then const file = known.get(named) (:347) and writes path: file === undefined ? '' : file.path
(:354). The client renders path === '' as decision file gone
(core/src/client/project-board.js:551-552).
So once a Project has more than 200 decision files, every decided meeting whose file fell off the
oldest end of the cut claims its file is gone, although the file is on disk and its name is
readable in the folder. Before #2021 the same root cause broke the newest end instead, so this is
one bug with two faces, not a regression introduced by #2021.
Reproduction
Fixture: 200 files decisions/2026-01-02-*.md plus 5 files decisions/2026-11-01-*.md
(205 on disk, 200 listed), and two decided meetings, one naming the oldest file and one naming a
file that survives the cut. Read the board.
Actual, on f6fcb7ff:
total=205 listed=200 (so the panel already says 205 while offering 200 rows)
meeting naming decisions/2026-01-02-005.md → path: "" (file is on disk)
meeting naming decisions/2026-11-01-004.md → path: "decisions/2026-11-01-004.md"
Same probe on e807b261 (before #2021): the newest one is "" and the oldest resolves.
Either way one end of the list lies.
Expected
A meeting's path is empty only when the file it names is genuinely not among the decision
files this board read. A file that exists but sits outside the 200-row display cut must still yield
its path, so the row shows the file it names instead of decision file gone.
Scope
core/src/host/project-board.js—decisionsOfalso returns the un-truncated deduped path set
(paths only, no hash/bytes), andmeetingsOffalls back to that set whenknownmisses.core/test/project-board.test.js— new cases below; the existing"decision file gone"cases
(:374-385) must stay green.changelog.d/— one.fixed.md.patches/ds-bot/files/src/host/project-board.jsmirrors the host file (0002carries it);
regenerate rather than hand-editing, thencheck/canonical/build-client --check.- Docs row only if
docs/subsystems/owns the touched path.
Not in scope: the client (its render stays path === '' → decision file gone), and any change to
the 200 cap itself. Do not widen the fallback to "stat says the file exists" — the path must come
from this read's dedupe set, or a crafted name could reach outside the folder.
Acceptance
- A decided meeting naming the oldest file, outside the 200-row cut, gets
pathset to that file
and the row no longer readsdecision file gone. - A decided meeting naming a file inside the cut behaves exactly as today (no regression).
- A meeting naming a file that is really absent still yields
path: ''and the row still reads
decision file gone(existing cases unchanged). - A name such as
../outside.mdnever yields a path — the fallback accepts only names present in
this read's dedupe result. - Red first: the new cases fail on the base tree before the fix and pass after it; one mutation per
new assertion (drop the fallback → case 1 red; accept any name → case 4 red; widen to a stat →
case 3 red), each mutation reddening only its own case.
Notes
- Reporter: FlyBot Product (P-346), verified here against the
f6fcb7fftree by reading the two
code paths and the client render; no write happened on the repo before this issue. - Tier: T2 (host change with a client-visible projection).
- Not the same cut as
#2021/#2023— same panel, three different holes:#2021changed which rows survive the 200 cap (slice(-DECISIONS_MAX)instead of the head)
and closed. It never touched how a meeting resolves its own file.#2023added the count (decisions.total) so a cut list is visible as cut, and closed. It
added a number;meetingsOfstill reads only the truncated row list.- This one is the meeting → file lookup: a decided meeting's
pathis resolved against the
truncated list alone, so it reports "gone" for a file the same read can still name. Fixing the
cap or the count cannot fix it; fixing this cannot change either. A reviewer should not close it
as a duplicate of those two — the failing assertion (pathnon-empty for a file outside the cut)
is red on a tree that contains both of them.
- Duplicate check on live main and closed issues: no open or closed duplicate.
"decision file gone"
matches only#1809/#1818/#1774(closed, different root causes: folder choice, archived folder,
file never written);DECISIONS_MAXmatches only#2021(wrong end of the cut) and#2023(the
totalcount). Neither touchesmeetingsOf's lookup. - User-visible urgency: about one decision per day puts a Project past 200 in ~8 months; from then on
the panel's older meeting rows all misreport and contradict its owndecisions.total.
Activity
client: a decided meeting names a decision file that fell off the 200…
client: a decided meeting names a decision file that fell off the 200…
HugoXK commentedon Nov 1, 2026
Release
Written-by: FlyBot Lead
Released: 54212b20793c48f49106050770fe9007
Reason: merged as #2034, merge sha 5528769
At: 2026-11-01T12:35:41Z