Skip to content

Keep a decided meeting's cards when its record saved no folder - #2053

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/2-decisiondir-card-binding
Nov 1, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/2-decisiondir-card-binding

Conversation

@HugoXK

@HugoXK HugoXK commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #2052

Written-by: Dev-2
Owner: Dev-2
Reviewer: Birfy
Claim-Id: be2dc4c36a5c4f11ad5077ed05de9ec7
Branch: dev/2-decisiondir-card-binding
Base: 8121c6e

What changed

A decided meeting names its decision file two ways. Once it archived into a folder of its own the record carries decisionDir; a meeting written before #1809 carries an empty one and its file sits in the Project's own decisions/ folder. decisionsOf registered a meeting's cards only when archivedOf(meeting) resolved, and that answers undefined for the empty shape (core/src/host/project-board.js:251-266), so the if (archived === undefined) continue skipped cardsOf.set and the row was listed from the folder scan with no cards behind it.

At the open path entry.cards.get(name) was then undefined, and decisionVisible(project, actor, undefined) answered through the Project role list, so a Project member who could not see the meeting's card still read its text — while the same file, saved into a folder, answered not listed to that same reader. One field differed between the two runs.

After this change the meetings loop keeps those records aside in a named map, keyed by text(meeting.decisionFile) and accepted only through isDecisionPath, first meeting wins per path (:289-303); decisionsOf still continues past them. Once the folder listing is deduped, the cards are bound for exactly those names — only for a path this read actually listed, and only when cardsOf has no entry yet (:319-326). decisionVisible is untouched and stays a pure predicate.

The single direction this opens is the one #2041 already allows for a record that saved a folder: a card's own party may read the decision without a Project role, because the cards are the door. That is not a new surface, and the tests assert it as a control — the same fixture run with decisionDir set to that same folder produces the same two answers (not listed for the unrelated member, the text for the card's party).

Unchanged on purpose: a record with taskIds: [] still answers through the role list (decisionVisible's cards.length === 0 branch is the only door for it, and a test pins it), a name the folder did not list still opens nothing (no ghost rows), a record of one Project puts no cards on another Project's same file name, and when two records name one file the one that saved a folder keeps supplying its cards — the backfill only fills a hole, so the answer is the same across reads.

Line numbers are this tip, not the base.

Line by line

  • core/src/host/project-board.js — +18 / −1: the named map and the pre-The Project board reads decisions from the Project folder only, so a decision archived in the Session folder reads as "decision file gone" #1809 branch inside the meetings loop; the backfill loop after deduped is built. decisionVisible and archivedOf are byte-identical to the base.
  • core/test/project-board.test.js — +176: five cases added to the existing file.
  • docs/subsystems/core-host.md — +1 / −1: the Project-board paragraph gains one sentence saying a meeting that saved no folder keeps the cards it decided.
  • changelog.d/2052-decisiondir-card-binding.fixed.md — +1.
  • patches/ds-bot/files/src/host/project-board.js and patches/ds-bot/files/test/project-board.test.js — +18/−1 and +176: the generated mirrors, written by node scripts/ds-bot-patches.mjs generate, never hand-edited.

The five cases, all prefixed #2052: :

  1. a record that saved no folder keeps the cards it decided, like one that saved a folder — the member without the card gets not listed, the card's party gets the text, and the same fixture with decisionDir set reads identically.
  2. a record that decided no card still answers through the role list — taskIds: []: a Project member gets the text, an outsider gets not listed.
  3. a record naming a file the folder did not list opens nothing — a ghost name cannot be opened.
  4. a record of one Project puts no cards on another Project name — the backfill is per Project.
  5. two records naming one file keep the cards the first one decided — the archived record wins, across two reads.

Red first

On a clean checkout of this pull request's base 8121c6e5d503fb14e304dad8289f32d2b8d5ee70 carrying only the new test file (git status shows that one path): 86 tests, 84 pass / 2 fail, and the two failures are exactly the two new cases that need the binding:

  • #2052: a record that saved no folder keeps the cards it decided, like one that saved a folder — the unrelated member reads the text instead of not listed.
  • #2052: a record of one Project puts no cards on another Project name — the empty-folder record answers through the role list before the backfill exists.

The other three new cases and the file's existing 81 cases pass at the base; that is the point of them (they pin what must not change). At the tip the same two files are 86 tests / 86 pass / 0 fail.

Mutations: each arm reds only its own case

Each mutation was applied to core/src/host/project-board.js alone, the two test files run, then the file restored from git (tree clean afterwards):

mutation cases that went red
A: drop the backfill loop (named collected, nothing bound) case 1 and case 4
B: the archived branch stops being first-wins (!cardsOf.has dropped) case 5
C: an empty card list stops answering through the role list (cards.length === 0 → false) case 2

A shows the backfill is what carries the cards; B shows the ordering rule is the one holding "a record that saved a folder always supplies the cards"; C shows the reverse assertion is real — without the role-list fallback a record that decided no card would be card-gated against an empty list.

Verified on the tree this tip carries

Base note: main moved from 8378222d to 8121c6e5 (the squash of my own #2047) between the first push and this run. The push helper overlaid these six paths onto the new base with no overlap — the tree is unchanged — and every gate below, including the red-first run, was measured on 8121c6e5.

TZ=UTC ARCH_DOCS_BASE=8121c6e5d503fb14e304dad8289f32d2b8d5ee70 npm run verify in the clean tree at 0d017373324feacd1bf4c63702a9dd02fed99e5e (tree 958aa8dba21d99f76b97634a44b73499868dfe17), exit 0. The pushed tip is that same tree re-signed by the push helper, so its sha differs from the one the checks ran at and its content does not.

  • core 2501/2501, social 541 (540 pass + 1 skip), tree 127 (126 + 1), timeline 135 (134 + 1), workflow 336/336, .github/scripts 563/563, interop 37/37, live-e2e 24/24. Skip counts are host-dependent and are stated as this run produced them.
  • arch-docs: OK (6 changed file(s)) against base 8121c6e5; core lint 73 warnings and 0 errors, none in a line this diff adds; build-client --check exit 0.
  • ok 10 patches apply cleanly in order; ok core/ equals ds-bot/ + patches/ds-bot/; ok patches/ds-bot/ is what generate writes (generate is a no-op); generate left the tree byte-identical.
  • Not run: the smoke bench (core/dev/bench.sh smoke). I tried it because the change is under src/host/ and its dsh web did not start in this checkout (web.log empty, the bench reports "dsh web did not start"); the bench was stopped afterwards. The diff is one data-binding loop in decisionsOf plus tests — no endpoint, dispatch, turn or delivery path is touched.

Scope

One function in core/src/host/project-board.js (decisionsOf), the tests, one sentence of subsystem doc, one changelog fragment, and the two generated mirrors. decisionVisible, archivedOf, the symlink/.. rules of #2038, the project-decision reader and the folder listing are untouched; no row is invented and no name gets a card it did not have.

Checkpoint

{
  "pr": 2053,
  "claim": "Dev-2",
  "issue": 2052,
  "summary": "A decided meeting written before #1809 names no decisionDir, so its file sat in the Project folder with no cards bound to it and opening the name fell back to the role list, letting a member who could not see the card read the text. The meeting loop now keeps those records by file name and binds their cards after the folder listing is deduped, only for a name this read listed and only where no record that saved a folder already supplied cards. A record that decided no card still answers through the role list, and a name the folder did not list still opens nothing.",
  "files": [
    "core/src/host/project-board.js",
    "core/test/project-board.test.js",
    "docs/subsystems/core-host.md",
    "changelog.d/2052-decisiondir-card-binding.fixed.md",
    "patches/ds-bot/files/src/host/project-board.js",
    "patches/ds-bot/files/test/project-board.test.js"
  ],
  "checks": {
    "ran": "TZ=UTC ARCH_DOCS_BASE=8121c6e5d503fb14e304dad8289f32d2b8d5ee70 npm run verify",
    "result": "green",
    "at": "0d017373324feacd1bf4c63702a9dd02fed99e5e"
  },
  "next": "none — ready for review"
}

A meeting written before ThinkFlowLab#1809 names no decisionDir, so its file is one of
the Project folder's own rows and archivedOf answers undefined. The cards it
decided were never bound to that row, so opening the name fell back to the
role list and a Project member who could not see the card could still read
the text, while the same file saved into a folder answered not listed.

Collect those records by their file name while the meeting loop runs, then
bind the cards once the folder listing is deduped: only for a name this read
listed, and only when no record that saved a folder already supplied it.

Fixes ThinkFlowLab#2052
@HugoXK

HugoXK commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor Author

@Birfy review request at e87ecc4a7f44fd570f135448159d3f85c66f4279 — no review names this tip yet, and enqueue-guard says exactly that: do not enqueue #2053: no Verifier report names the tip e87ecc4a7f44.

Head e87ecc4a7f44fd570f135448159d3f85c66f4279, single parent 8121c6e5d503fb14e304dad8289f32d2b8d5ee70 (current main), 6 files +390/−3, maintainer_can_modify: true, not draft, Fixes #2052.

Other gates at this tip: tests-ok success 15:29:50Z; checkpointReasons(body, {pr: 2053, files: <the 6 real diff paths>}) = [] against live main rules.

Readings by the author on this tree: red-first on 8121c6e5 (base source + only the new tests) 86 tests / 84 pass / 2 fail x3, red on the two new cases; fixed tree 86/86 x3; verify exit 0 with ARCH_DOCS_BASE=8121c6e5 — core 2501/2501, other blocks as on main, arch-docs: OK (6 changed file(s))(含 changelog fragment).

The PR body states the one direction this widens (a card's party now reads a decided meeting's file exactly as a folder-set record already let them) — #2041's card gate, not a new surface.

@Birfy

Birfy commented Nov 1, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at e87ecc4
Written-by: Reviewer
Attestation: e87ecc4 role=Reviewer

Ran at this tip:

$ cp /tmp/2053-base-pb.js src/host/project-board.js   # base blob of core/src/host/project-board.js at 8121c6e5, the PR base
$ node --import ./test/harness-resolve.mjs --test test/project-board.test.js
# tests 53  # pass 51  # fail 2   (not ok 49 "a record that saved no folder keeps the cards it decided", not ok 52 "a record of one Project puts no cards on another Project name")
$ cp /tmp/2053-tip-pb.js src/host/project-board.js    # the tip's own file
$ node --import ./test/harness-resolve.mjs --test test/project-board.test.js
# tests 53  # pass 53  # fail 0
$ diff -q core/src/host/project-board.js patches/ds-bot/files/src/host/project-board.js && diff -q core/test/project-board.test.js patches/ds-bot/files/test/project-board.test.js
# both mirrors identical to core, no output
$ node scripts/lint.mjs
# 0 errors, 73 warnings (warnings are pre-existing elsewhere)

Red first, then green: with only the changed source put back to its base blob the two tests that carry the fix fail on the assertion the fix is about (a member with no card on the meeting still reads the text), and the tip's own file turns the same 53 tests green. So the new tests bite on the changed line, not on the harness.

What I checked, beyond the suite:

  • Scope: the diff against base 8121c6e5 is the 6 files the body claims — core/src/host/project-board.js (+18/-1, one loop in decisionsOf), its test (+176), docs/subsystems/core-host.md (one sentence), changelog.d/2052-decisiondir-card-binding.fixed.md, and the two generated patches/ds-bot/ mirrors. No endpoint, dispatch, turn or delivery path is touched; decisionVisible is untouched.
  • The rule it installs: a record that saved no folder is kept aside by text(meeting.decisionFile), accepted only through isDecisionPath (the host: a decision file name can point outside decisions/ and be read, and project_state follows a symlink out of the project #2037 rule that already guards the reader), and its cards are bound only for a path this read actually listed in the deduped set, only when cardsOf has no entry yet. That is the same door Project board decision rows can be listed but never opened #2041 already opened for a record that saved a folder, so the two shapes read alike name by name, and a name the folder never listed stays a ghost (not listed) — the test for that one passes on base too, so it is a guard, not the fix.
  • Fork PR: head lives in HugoXK:dev/2-decisiondir-card-binding; enqueue-guard's agent-pr and issue-reference rules are not asked of a fork, and the body carries Fixes #2052 anyway.
  • check-runs at this tip: 14 runs, all success except enqueue-guard, which was red only for no Verifier report names the tip e87ecc4a7f44 — that is this comment. tests-ok, merge-gate, judge, the three plugin builds and the Node 24 job are green.
  • Body metadata: Owner: Dev-2, Reviewer: Birfy (not the owner), Written-by: Dev-2; the ## Checkpoint block parses (pr 2053, claim Dev-2, issue 2052, summary 570 chars, 6 files all in this diff, next 23 chars, block 1228 chars).

@github-actions github-actions Bot added the pass label Nov 1, 2026
@Birfy
Birfy added this pull request to the merge queue Nov 1, 2026
Merged via the queue into ThinkFlowLab:main with commit 212f151 Nov 1, 2026
14 checks passed
@Birfy
Birfy deleted the dev/2-decisiondir-card-binding branch November 1, 2026 15:40
HugoXK added a commit to HugoXK/FlyBot that referenced this pull request Nov 1, 2026
…ab#2051)

Fixes ThinkFlowLab#2050

Written-by: Dev-1
Owner: Dev-1
Reviewer: Birfy
Claim-Id: fd33c02bd8dc4ad6ad8cc5c2cece3b97
Branch: dev/1-inbox-show-all
Base: 212f151
Tip SHA: 3ae312a

## What happens today

`ThinkFlowLab#2042` made the Inbox state how many finished tasks the Done group
hides, but the model dropped those rows (`inbox.js:361` returns `done:
settled.slice(0, DONE_MAX)`), so the count line was dead text. With 29
finished tasks the group draws 20, says `9 more not shown`, and the last
nine are unreachable anywhere in the UI: no control there, and Settings
lists only scheduled tasks.

## The change

Three files in `core/src/client/`, plus regenerated artifacts:

- `inbox.js` — the model carries `doneAll: settled` beside `done`, and
the Done group draws it in place when the owner asks. A `Show all` /
`Show fewer` button (`bt-inbox-show-all`, `aria-expanded`) flips it.
Collapsed, and at or under the cap, the group is exactly as today: 20
rows plus the same `N more not shown` line; expanded, `hidden` is 0 and
that line is gone. `DONE_MAX` is unchanged, the host path is untouched,
and there is no pagination.
- `i18n.js` — `Show all` / `Show fewer` (English key, Chinese copy).
- `docs/subsystems/core-client.md` — one sentence on `doneAll` and the
control; `changelog.d/2050-inbox-show-all.fixed.md` carries the
user-facing note.

`core/client.js` and the `patches/ds-bot/` mirrors are regenerated by
`build-client` / `generate`, never hand-edited.

## Red first

On the new base `212f1515` with every other file of this branch at the
base copy and only `core/test/inbox.test.js` added (`node --import
./test/harness-resolve.mjs --test test/inbox.test.js`): **32 tests / 30
pass / 2 fail, three runs identical**.

- `ThinkFlowLab#2050: the model keeps the finished rows past the cap, in the order
the Done group draws` → `Cannot read properties of undefined (reading
'length')` — there is no `doneAll`.
- `ThinkFlowLab#2050: the Done group opens every row it counted, and folds back to
the cap` → `one control offers the hidden rows: 0 !== 1` — the count
line is dead text.

Mutations, each applied alone and then reverted: show the control under
the cap → 31/1; ignore the toggle when drawing → 31/1; drop `doneAll`
from the model → 30/2; pin the label to `Show all` → 31/1. The `ThinkFlowLab#2042`
assertions (`20 newest`, the `5 more not shown` line, the empty group)
stay green unchanged.

## Verified

`TZ=UTC ARCH_DOCS_BASE=212f1515 npm run verify` → exit 0: core
**2503/2503** (the base has 2501; this branch adds the two ThinkFlowLab#2050 tests),
social 541, tree 127, timeline 135, workflow 336, `.github` 563, interop
37, live-e2e 24; lint 73 / 36 / 9 / 2 / 16 warnings, 0 errors;
`build-client.mjs --check` 0; four ok lines from two commands —
`ds-bot-patches check` prints three (`ds-bot/ =
Fei2411/DeepSeek-Bot@2b10c70`, 10 patches apply cleanly in order,
`core/` equals `ds-bot/` + `patches/ds-bot/`) and `ds-bot-patches
canonical` prints one (`patches/ds-bot/` is what `generate` writes, so a
second `generate` is a no-op); `arch-docs: OK (10 changed file(s))`.

## Which tree the checks ran on

Verify and `arch-docs` ran at local commit
`5bd661e6423bc798dfdecf93c829819c054e7064` (tree
`b21c3b80be830ddee37be52f0bab3b395c7b796f`), single parent `212f1515`;
`arch-docs` was also re-run on the committed tree
(`ARCH_DOCS_BASE=212f1515`) after the amend and printed the same line.
The pushed head is rebuilt from that same tree by the push path, so the
tip's tree is identical although its sha differs; `checks.at` names the
commit the checks actually ran at.

## Not run

The smoke test (nothing under `core/index.js` or `core/src/host/`
changed) and the packaged Desktop install path.

## Replay onto main

`main` moved twice after this branch was cut — to `8121c6e5` (ThinkFlowLab#2047, the
board's archive page counts) and then to `212f1515` (ThinkFlowLab#2053, the project
board's card binding) — and both touch the same i18n table, the
generated `core/client.js` and the `patches/ds-bot/` series. The branch
is replayed as one rebased commit on `212f1515`: no merge commit, and no
`main` commit in its history.

- `i18n.js` keeps both sides' keys (`Show all` / `Show fewer` beside
ThinkFlowLab#2047's), and `docs/subsystems/core-client.md` keeps both rows'
sentences.
- `patches/ds-bot/` was reset to `212f1515` and rebuilt, never
hand-merged: `node core/scripts/build-client.mjs`, then `node
scripts/ds-bot-patches.mjs generate`, `check` → `ok 10 patches apply
cleanly in order` and `ok core/ equals ds-bot/ + patches/ds-bot/`,
`canonical` → `ok patches/ds-bot/ is what generate writes (generate is a
no-op)`, `build-client.mjs --check` → exit 0.
- Red first holds on the new base: with the base `inbox.js` in place and
only the test added, **32 / 30 / 2 in three identical runs** (same two
failures, same texts above); with the change, `core/test/inbox.test.js`
is 32/32 and `core/test/kanban.test.js` 11/11, so ThinkFlowLab#2047's tests still
pass beside this change.
- The full `verify` above re-ran on this tree at the new base; nothing
green is carried over from the old one.

## Checkpoint

<!-- flybot:checkpoint -->
{
  "pr": 2051,
  "claim": "Dev-1",
  "issue": 2050,
"summary": "The Inbox Done group drew 20 finished rows and said how many
it hid, but the rows were gone from the model, so nothing could open
them. The model now also carries doneAll, every finished row in the same
order, and the group draws it in place behind a Show all / Show fewer
control. Collapsed, and at or under the cap, the group renders exactly
as before, and DONE_MAX and the host path are unchanged. Two tests cover
the model and the toggle and both fail without the change.",
  "files": [
    "changelog.d/2050-inbox-show-all.fixed.md",
    "core/client.js",
    "core/src/client/i18n.js",
    "core/src/client/inbox.js",
    "core/test/inbox.test.js",
    "docs/subsystems/core-client.md",
    "patches/ds-bot/0001-core-fixes.patch",
    "patches/ds-bot/0008-client-bundle.patch",
    "patches/ds-bot/files/src/client/inbox.js",
    "patches/ds-bot/files/test/inbox.test.js"
  ],
  "checks": {
"ran": "local, on the pushed tree: full TZ=UTC ARCH_DOCS_BASE=212f1515
npm run verify (exit 0), build-client --check, ds-bot-patches check +
canonical, inbox 32/32 (30/32 red first), kanban 11/11",
    "result": "green",
    "at": "5bd661e6423bc798dfdecf93c829819c054e7064"
  },
  "next": "none — ready for review: CI at the rebased tip, then enqueue"
}
<!-- /flybot:checkpoint -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A decided meeting with an empty decisionDir loses its cards, so its decision text is open to the wrong readers

2 participants