Skip to content

Project board: past the 200-decision cap a decided meeting on a dropped file reads "decision file gone" #2033

Closed

Description

@HugoXK

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 — decisionsOf also returns the un-truncated deduped path set
    (paths only, no hash/bytes), and meetingsOf falls back to that set when known misses.
  • 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.js mirrors the host file (0002 carries it);
    regenerate rather than hand-editing, then check / 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

  1. A decided meeting naming the oldest file, outside the 200-row cut, gets path set to that file
    and the row no longer reads decision file gone.
  2. A decided meeting naming a file inside the cut behaves exactly as today (no regression).
  3. A meeting naming a file that is really absent still yields path: '' and the row still reads
    decision file gone (existing cases unchanged).
  4. A name such as ../outside.md never yields a path — the fallback accepts only names present in
    this read's dedupe result.
  5. 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 f6fcb7ff tree 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:
    • #2021 changed 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.
    • #2023 added the count (decisions.total) so a cut list is visible as cut, and closed. It
      added a number; meetingsOf still reads only the truncated row list.
    • This one is the meeting → file lookup: a decided meeting's path is 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 (path non-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_MAX matches only #2021 (wrong end of the cut) and #2023 (the
    total count). Neither touches meetingsOf'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 own decisions.total.

Activity

  1. HugoXK commented on Nov 1, 2026

    @HugoXK
    ContributorAuthor

    Release

    Written-by: FlyBot Lead
    Released: 54212b20793c48f49106050770fe9007
    Reason: merged as #2034, merge sha 5528769
    At: 2026-11-01T12:35:41Z

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions