Skip to content

The Project board reads decisions from the Project folder only, so a decision archived in the Session folder reads as "decision file gone" #1809

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: f8187c1a5d4046ad9ac678cfeac77d1f
Branch: dev/1-project-board-decisions-union
Base: 84de666
At: 2026-10-30T07:05:05Z

Symptom

A user holds a meeting in a room that belongs to a Project and closes it (decide). The decision file is written — decisions/<date>-<slug>.md exists on disk — but the Project board's Decisions section stays empty and the same meeting, under Meetings, is drawn with decision file gone. Nothing was deleted: the file is in the Session's working folder, and the board only looks in the Project's own folder.

Root cause (read side; the write side is the documented behaviour)

  • meeting decide writes through core/src/host/meetings.js:277 const dir = args.dir ?? workDirFor(rt, args.exec); workDirFor (core/src/host/project-state.js:99-105) is the Session cwd → workDirOf({botId, roomId}) → teamFolder(). That is "the Session's working folder", which is what the docs and #1767 say a decision is archived in. This issue does not move it.
  • The board resolves a folder of its own — core/src/host/project-board.js:72 folderOf(project): project.workDir, else a member room's or Bot's workDir, else '' — and reads only that folder (:93 listProjectState(dir)). Its own comment at :69-71 says this is "the same folder a meeting resolves through workDirFor before it archives a decision, so the reader and the writer look in one place"; that sentence is the assumption this issue disproves.
  • So the two agree only by accident (Project folder == Session folder). In a default install the Project names no folder at all (project-create writes none), the Session archives in the team folder, and the board answers no working folder: this Project names none (project-board.js:90).
  • Second, independent half: even when the two folders are the same, listProjectState refuses a folder that is not a git repository (core/src/host/project-state.js:60-61, repositoryAt at :29), so the board answers not a repository: <dir> and lists nothing. A decision file is the board's own artifact; requiring .git to display it is what makes the default install empty.
  • Client side: a decided meeting whose named file is not in the list gets path: '' and is rendered as decision file gone (core/src/client/project-board.js:212), which reads as "someone deleted it".

Reproduction (real host, no sleeps)

Probe: core/test/probe-p216-decisions-union.test.js (scratch probe, not for the PR; the PR test is the same cases in core/test/project-board.test.js).

Run on a clean 30c04907 tree:

DSH_HARNESS=/home/xuke/.local/lib/node_modules/@deepseek-ai/dsh TMPDIR=/tmp TZ=UTC \
  node --import ./test/harness-resolve.mjs --test test/probe-p216-decisions-union.test.js

Every case is driven through the real meeting tool (open → three position → decide) and then rt.projectBoard('project-p1'). Red on untouched main — 4 fail, 1 pass (case E is the guard that must keep passing):

Case A — the Project names folder A, the room names folder B, the deciding Session works in B:

# PROBE A recorded decisionFile: decisions/2026-10-30-where-do-the-decisions-of-p1-live.md | decisionDir: undefined
# PROBE A Project folder decisions/: absent | Session folder decisions/: holds decisions/2026-10-30-where-do-the-decisions-of-p1-live.md
# PROBE A board.decisions.dir: /tmp/ds-p216a-project-QWV4tY | reason: "not a repository: /tmp/ds-p216a-project-QWV4tY"
# PROBE A board.decisions.files: [] | decided: [ [ 'meeting-cce50e35e1', '' ] ]
not ok 1 - probe P-216 A: the Project names folder A, the member names folder B - the decision stays in B and the board reads it
  error: |-
    the meeting records the folder it archived in
    + actual - expected
    + undefined
    - '/tmp/ds-p216a-room-0Xlb6U'

Case B — default install: Project, room and Bot name no folder; the Session works in the team folder:

# PROBE B project workDir: undefined | decisionDir: undefined
# PROBE B team folder decisions/: holds decisions/2026-10-30-which-folder-holds-the-record.md
# PROBE B board.decisions.dir: null | reason: "no working folder: this Project names none"
# PROBE B board.decisions.files: [] | decided: [ [ 'meeting-62e6327552', '' ] ]
not ok 2 - probe P-216 B: default install (no folder named anywhere) - the board lists the team-folder decision
  error: |-
    the meeting records the team folder it archived in
    + actual - expected
    + undefined
    - '/tmp/ds-p216b-team-RTnZRk'

Case C (delete the file for real → still decision file gone), case D (two folder-less Projects sharing the team folder do not list each other's decision) and case E (project_state still answers not a repository: <dir>; the one case green before any fix) are the same run — C and D red for the same listing reason, E green.

By hand, in the running app: Settings → Projects → create a Project (set its folder or leave it empty) → in a room bound to that Project run a meeting and decide → open the Project board: Decisions is empty, the meeting shows decision file gone, while the file exists in the Session's working folder (the team folder by default).

Expected

  1. The decision file stays where decide archived it — the Session's working folder. Nothing is moved, so an existing install's files keep their place.
  2. The board lists that file in Decisions and links the meeting to it (path non-empty), with no decision file gone.
  3. This holds when the Project names one folder and the deciding member/Session works in another.
  4. A file that really was deleted still shows decision file gone (existing #1767 case stays green).
  5. A folder shared by two Projects that name none still shows each Project only its own decisions (isolation by the file a meeting names, not by the folder).
  6. The project_state tool keeps its .git gate and its not a repository answers; only the board's own read is ungated.

Minimal fix (verified on 30c04907)

Three host files, no client change, no new endpoint, no new storage, no write-side move:

  1. core/src/host/meetings.js — in decide, record the folder beside the name: decisionDir: dir in the decided record (closed, next to decisionFile at :286-293). One line; workDirFor and the write itself are unchanged.
  2. core/src/host/project-board.js — decisionsOf reads two sources and returns their union: the Project folder (folderOf untouched, still read whole, so a file put there by hand stays listed) and, for each decided meeting of this Project, the folder in its decisionDir — from a meeting folder only the file that meeting names is listed. That is what keeps two folder-less Projects sharing the team folder apart. Files already listed are de-duplicated by path; a record with no decisionDir (written before this change) resolves through the Project folder exactly as today.
  3. core/src/host/project-state.js — listProjectState(dir, { repository = true } = {}): the gate is repository && !repositoryAt(dir). The project_state tool and readProjectState keep the default; only project-board passes { repository: false }.

Verified in a scratch tree (source restored after the run; repository untouched): probe 5/5 pass — case A # PROBE A board.decisions.files: [ 'decisions/2026-10-30-where-do-the-decisions-of-p1-live.md' ], reason: ""; case B # PROBE B board.decisions.dir: null | reason: "" with the file listed; case C after delete files: [] and path: ''; case D P1 and P2 each list only their own file; case E unchanged not a repository: <dir>. Related suites project-board + project-board-view + project-board-entry + project-state + projects + project-settings-page + project-owner-gate + project-assistant + meetings + meetings-decide-paths + meetings-persist = 99/99, and the whole core suite 2145 tests / 2139 pass / 6 fail — the same six environment failures as untouched main (5 × Cannot find module 'jsdom', 1 × check-arch-docs empty diff passes).

Residual, not in this slice: two decided meetings in different folders that name the same relative path (decisions/<same>.md) de-duplicate to one listed row. Nothing in the record is lost; the rows are one per path by design (the client's row key is the path).

Scope

  • core/src/host/meetings.js, core/src/host/project-board.js, core/src/host/project-state.js — about 45 lines added or changed in total.
  • core/test/project-board.test.js — the same cases as end-to-end tests: Project folder ≠ Session folder, default install (no folder named anywhere), real delete still decision file gone, two folder-less Projects in one folder, and the unchanged not a repository answer of project_state.
  • patches/ds-bot/files/src/host/{project-state,project-board,meetings}.js are verbatim mirrors of files upstream does not carry, so node scripts/ds-bot-patches.mjs generate must run after the source edit (check then proves core/ = ds-bot/ + patches/ds-bot/). No patch is hand-edited. Regeneration note: any core change rewrites the whole patch chain, and #1805 is regenerating the same chain; land this after #1805, or backfill the patches this slice does not carry with main's bytes (the approach #1805 used). Open at the then-current main as base.
  • No client change (the board already renders whatever files it is handed) and no docs change: the write location is still "the Session's working folder", so the existing sentence stays true.

Acceptance

  1. The file is still archived in the Session's working folder (its location does not change), and the test asserts that.
  2. Default install: the board lists the decision, meeting.path non-empty, no decision file gone.
  3. The same holds when the member folder differs from the Project folder.
  4. A file that is really deleted still reads decision file gone (the #1767 deletion case stays green).
  5. Two folder-less Projects sharing one team folder: the other Project's decision never appears on this board.
  6. project_state still answers not a repository: <dir> for a plain folder (its answers are byte-for-byte unchanged); only project-board reads without .git.

Duplicate check (GitHub, open and closed, 2026-10-30 ≈05:3xZ)

decision file project board 22 hits, none the same topic (#1767/#1772 are this feature, #1774 is "a decision never reached disk"); decisions folder workDir board 2 hits = #1767/#1772; meeting decision file not listed 2 hits, same two. No duplicate.

Activity

  1. HugoXK commented on Oct 30, 2026

    @HugoXK
    ContributorAuthor

    Claim

    Written-by: FlyBot Lead
    Claimed-by: FlyBot Lead
    Claim-Id: f8187c1a5d4046ad9ac678cfeac77d1f
    Branch: dev/1-project-board-decisions-union
    Base: 84de666
    At: 2026-10-30T07:05:05Z

    The record is the Claim-Id line in this comment and in the issue body; the label and assignee could not be written by our account, so they are absent by permission, not by intent.

  2. HugoXK commented on Oct 30, 2026

    @HugoXK
    ContributorAuthor

    pickup-ok — assigned to FlyBot Dev-1, branch dev/1-project-board-decisions-union, base = the then-current main (live now 3f8cbb25c42316e84d3d21f5f55e0fe80c8b0a98; the Base: line in the claim comment says 84de6663 from the day it was filed).

    Slot: this is the third PR in our single push queue (#1801 → #1804 → this → #1788 → #1810), because a whole-chain generate rewrites every patch face and two PRs in flight would overwrite each other's shape. Until its slot opens it stays local: no build, no generate, no push.

    Work already done locally at that base, for the record: red first re-taken on 3f8cbb25 (three cases red — A the record names the folder it archived in, B the team-folder decision is listed, D the other Project's decision is never listed here), rebased with no manual conflict (#1770 did not touch decisionsOf/archivedOf), 11 related suites 110/110, core 2172/2172.

    The label and assignee could not be written by our account (permission), so they stay empty by capability, not by intent.

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