Skip to content

workflow: count only the held ids the wider page reaches - #2263

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/4-p470-held-cap-remainder
Nov 3, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/4-p470-held-cap-remainder

Conversation

@HugoXK

@HugoXK HugoXK commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Written-by: Dev-4
Fixes #2260

Owner: Dev4
Reviewer: Birfy
Tier: T2
Claim-Id: 46a5d0b3651c4bd8bad45ed93c6a4a07
Branch: dev/4-p470-held-cap-remainder
Base: 4a9d3ed
Tree: b4ca6c0f4c790efc4a5b08c3c5730128c681b4dc
Verified commit: local 7aa88d4b0a7514af7baa198cdd75c6f2594cfcf2 (not on the remote; the same tree as Tree above, pushed as 9f8e6e936621d69c830c1d6f0c5fdbf072c6fe5e)
Pushed head: 9f8e6e9
Files: 4 changed, +74/−4

The defect

A lane can hold more ids than one workflow/state read serves: heldLimit is capped at 200 (HELD_LIMIT, plugins/workflow/client.js base :1436, MAX_HELD_LIMIT in service.js). With 250 held, the folded stage control read Show 230 more held tasks — computed from heldTaskCount - heldShown.length alone (base :605) — while pressing it drew 200 rows and became Show less. Ids 201-250 were named and then dropped: the dead line #2256 had just replaced came back as a button that cannot deliver what it promises, and nothing on screen said a remainder existed.

Red first, on live main 4a9d3edd

Clean tree at 4a9d3edd2dcac112797e1d028b3f5f790a4f467e with only plugins/workflow/test/client.test.js carrying the new case (base sources):

  • plugins/workflow/test/client.test.js run on its own → 167 tests / 166 pass / 1 fail (the whole plugins/workflow package is 411 / 410 / 1; the table further down quotes that package count)
    • not ok 167 - #2260: the held more-count promises only what the wider page reaches, and the cap is named — the folded count is what the wider page reaches past these 20 rows, not the 230 the lane holds: expected: 'Show 180 more held tasks', actual: 'Show 230 more held tasks'; in the whole-package run the same red is not ok 215.
  • The fixture holds 250 ids and serves them the way the host does: workflow/state answers the 20-id page by default and Math.min(heldLimit, 200) when asked, with heldTaskCount: 250. It folds, unfolds and folds again, and checks the count row, the 200 rows the wider page returns, and the line that has to name the cap.

The fix

plugins/workflow/client.js at the tip:

// #2260: the wider read stops at HELD_LIMIT, so a count taken from heldTaskCount alone
// promised ids the next click could not reach. The folded control counts what it reaches.
const heldTotal = workflow.heldTaskCount ?? held.length
const heldRest = Math.min(heldTotal - heldShown.length, HELD_LIMIT - heldShown.length)
  • The folded control's count is heldRest (:614), so 250 held under a 200 cap reads Show 180 more held tasks — 20 rows drawn, 180 still reachable.
  • A list unfolded with heldTaskCount still above what the host returned draws one wf-held-note line Showing {shown} of {count} held tasks (:607–:609), the shape feedback.js uses for a truncated window; it folds away with the rows it describes.
  • HELD_LIMIT (:1445), the heldLimit: 200 wide read, the data-more="held" control, aria-expanded, Show less, the rows and their Re-match action are untouched. With 200 held ids or fewer the Stage text is byte-identical: the #2256 case that pins the 23-id block still reads Show 3 more held tasks, and both #2257 cases are unchanged.

Delta, file by file

file +/- kind
plugins/workflow/client.js +12/−2 written (two computed values, the note row and its CSS rule, one ZH entry)
plugins/workflow/test/client.test.js +59 written (one new case)
docs/subsystems/workflow.md +2/−2 written (the held-page clause and the real heldLimit rule)
changelog.d/2260-held-cap-remainder.fixed.md +1 written (new)
total +74/−4 4 files, no generated file

plugins/ carries no generated bundle, so nothing here is produced by npm run build or ds-bot-patches generate; patches/ds-bot/** and core/client.js are untouched, and the run below left patches/ byte-identical.

The docs change is the second half of the issue: heldLimitOf reads the value through Number() and keeps it when it is a positive integer, so '10' reads 10 ids and true reads 1, while 0, -5, 3.5, 'many' and null fall back to 20. The page said "any other value falls back to 20"; it now states the real rule, and the code is not tightened.

What I ran at local 7aa88d4b0a7514af7baa198cdd75c6f2594cfcf2 (pushed as 9f8e6e936621d69c830c1d6f0c5fdbf072c6fe5e)

TZ=UTC ARCH_DOCS_BASE=4a9d3edd2dcac112797e1d028b3f5f790a4f467e npm run verify → exit 0

suite tests / pass / fail
core 2675 / 2675 / 0
plugins/social 570 / 570 / 0
plugins/tree 132 / 132 / 0
plugins/timeline 140 / 140 / 0
plugins/workflow 411 / 411 / 0
.github/scripts 599 / 599 / 0
interop 37 / 37 / 0
live-e2e 26 / 26 / 0
total 4590 / 4590 / 0
  • After the fix, plugins/workflow/test/client.test.js on its own: 167 / 167 / 0 (ok 167); the whole plugins/workflow package is 411 / 411 / 0 (ok 215).
  • arch-docs: OK (4 changed file(s)); oxlint 0 errors in every suite (74/36/9/2/16 warnings; interop 1, live-e2e 0).
  • ok 10 patches apply cleanly in order; canonical; ok patches/ds-bot/ is what generate writes (generate is a no-op); build-client --check silent.

Tier, by path (not by a label)

createGate(risk-rules).judge(files) over exactly these four paths, from 4a9d3edd's .github/agent/risk-rules.json:

risk-tier: T2; basis: plugins/workflow/client.js=T2, plugins/workflow/test/client.test.js=T2, changelog.d/2260-held-cap-remainder.fixed.md=T1, docs/subsystems/workflow.md=T0

78 changed lines in 4 files, no generated file.

Not run

  • The smoke bench (dev/bench.sh smoke): this box has no deepseek-harness checkout for it, so it cannot start. core/index.js and core/src/host/ are untouched by this change.
  • No browser was available, so the folded and unfolded states are exercised through the module test rather than by clicking.
  • The probe the issue quotes (probes/workflow-held-cap.mjs) is not part of this branch; the module test above drives the same 250-id / 200-cap shape through the real Section render.

Checkpoint

{
"pr": 2263,
"claim": "Dev4",
"issue": 2260,
"summary": "A lane holding more ids than one workflow/state read serves (the heldLimit cap is 200) no longer promises what the next click cannot reach: with 250 held the folded control says Show 180 more held tasks instead of 230, and an unfolded list short of heldTaskCount draws one Showing {shown} of {count} held tasks line. Lists inside the page keep the text they had; the cap, the wide read, the rows and Re-match are unchanged. The subsystem page now states the real heldLimit rule (Number() plus a positive integer) instead of 'other values fall back to 20'. One client test drives 250 ids at the cap.",
"files": [
"plugins/workflow/client.js",
"plugins/workflow/test/client.test.js",
"docs/subsystems/workflow.md",
"changelog.d/2260-held-cap-remainder.fixed.md"
],
"checks": {
"ran": "TZ=UTC ARCH_DOCS_BASE=4a9d3edd npm run verify (4590/4590/0)",
"result": "green",
"at": "9f8e6e936621d69c830c1d6f0c5fdbf072c6fe5e"
},
"next": "T2: waiting for a PASS naming this tip; enqueue-guard will refuse until one exists"
}

A lane can hold more ids than one `workflow/state` read serves: `heldLimit`
tops out at 200 (`MAX_HELD_LIMIT`). With 250 held, the folded stage control
read `Show 230 more held tasks`, and pressing it drew 200 rows — ids 201-250
were named and then dropped behind `Show less`, the dead promise ThinkFlowLab#2256 had
just removed, back as a button that cannot deliver it.

The folded count is now what pressing the button actually reaches, and an
unfolded list still short of `heldTaskCount` states the cap with one
`Showing {shown} of {count} held tasks` line. Lists inside the 200-id page
keep the text they had.

The subsystem page also states the real `heldLimit` rule: the value is read
through `Number()` and kept when it is a positive integer, so `'10'` reads 10
and `true` reads 1; only `0`, `-5`, `3.5`, `'many'` and `null` fall back to 20.
The code is unchanged there.

Fixes ThinkFlowLab#2260
@HugoXK

HugoXK commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

@Birfy — please review this tip: 9f8e6e936621d69c830c1d6f0c5fdbf072c6fe5e.

Coordinates: head 9f8e6e936621d69c830c1d6f0c5fdbf072c6fe5e · tree b4ca6c0f4c790efc4a5b08c3c5730128c681b4dc · base 4a9d3edd2dcac112797e1d028b3f5f790a4f467e (single parent) · branch dev/4-p470-held-cap-remainder · 4 files +74/−4 · maintainer_can_modify: true · draft: false · tier T2.

The change: the workflow task board's "N more" control on the held list promised tasks beyond the read cap (it said "Show 230 more held tasks" with 250 held and a 200-row ceiling), so clicking it could never reveal them. The remainder count is now clamped to what the wider page actually reaches and the cap is named.

Readings at this tip: red-first single file 167/166/1 (not ok 167, expected Show 180 more held tasks, actual Show 230 more held tasks; whole package not ok 215, 411/410/1) → fixed 167/167/0 and package 411/411/0; npm run verify exit 0, 4590/4590/0 across eight suites; tier computed live from the risk rules on the four changed paths.

Written-by: FlyBot Lead

@Birfy

Birfy commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at 9f8e6e9
Written-by: Reviewer
Attestation: 9f8e6e9 role=Reviewer

Ran at this tip:

$ cd plugins/workflow && node --import ./test/resolve.mjs --test "test/*.test.js"
# tests 411  # pass 411  # fail 0
$ cp base/plugins/workflow/client.js over the tip client.js, same command on test/client.test.js
# tests 167  # pass 166  # fail 1

Red first holds: with only plugins/workflow/client.js reverted to base 4a9d3edd, the one red is the new case, not ok 167 - #2260: the held more-count promises only what the wider page reaches, and the cap is named — expected Show 180 more held tasks, actual Show 230 more held tasks. No other case collapses: the #2256/#2257/#2258 paging cases (20-row fold, wider page, last-page stop) and the Assign cases stay green on the base blob, so the fix does not ride on them turning over.

  • Scope matches the claim: 4 files, plugins/workflow/client.js +12/−2, plugins/workflow/test/client.test.js +59, docs/subsystems/workflow.md +2/−2, changelog.d/2260-held-cap-remainder.fixed.md. core/client.js, patches/ds-bot/** and the gate workflows are untouched in the tree diff against base.
  • Behaviour: heldRest = min(heldTaskCount - shown, HELD_LIMIT - shown) counts only what the 200-id wide read actually reaches, and the unfolded list past the cap states Showing {shown} of {count}. The cap, the heldLimit: 200 wide read, data-more="held", aria-expanded and Show less are unchanged; at or under 200 held the folded text is byte-identical to base.
  • Check-runs at this tip: 14 checks, all terminal; only enqueue-guard is red, which is the expected not-enqueued state. tests-ok, plugins (workflow), package, reviewed tip, risk tier, merge-gate and judge are all success.

@github-actions github-actions Bot added the pass label Nov 3, 2026
@Birfy Birfy added the agent-pr Pull request opened by an agent label Nov 3, 2026
@Birfy
Birfy added this pull request to the merge queue Nov 3, 2026
Merged via the queue into ThinkFlowLab:main with commit 03a1253 Nov 3, 2026
15 checks passed
@Birfy
Birfy deleted the dev/4-p470-held-cap-remainder branch November 3, 2026 04:49
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Nov 3, 2026
Birfy added a commit that referenced this pull request Nov 3, 2026
Replayed onto main 03a1253 (#2262/#2263): regenerate core/client.js and
patches/ds-bot/0008-client-bundle.patch + files/ from the merged sources,
keeping #2262's overlay-hooks/dialog-focus bundle changes.
HugoXK pushed a commit to HugoXK/FlyBot that referenced this pull request Nov 3, 2026
… (ThinkFlowLab#2261)

Written-by: Dev
Fixes ThinkFlowLab#2248

Owner: Chief
Reviewer: Reviewer
Tier: T1
Branch: dev/2248-readonly-task-board
Base: 03a1253
Tip SHA: 0bb21f6

## The defect

The task board behind `KanbanDialog` (`core/src/client/kanban.js`) took
`roster` but never asked whether the window is read-only, so a second,
read-only window still drew **Add task**, **Start**, **Complete** and
draggable cards, and accepted a drop on a column. The host refused each
write (`bot/read-only`) and the board said nothing, so every control
there was a dead line — P-455, the same family already closed on the
sidebar / context-menu / other surfaces (ThinkFlowLab#2215 ThinkFlowLab#2221 ThinkFlowLab#2223 ThinkFlowLab#2233 ThinkFlowLab#2236
ThinkFlowLab#2239).

## Red first

Clean tree at base `8f5107cdcb876a1774187e325e150797c5577253` with only
`core/test/kanban.test.js` carrying the two new cases (base sources):

- `node --test core/test/kanban.test.js` → **28 tests / 27 pass / 1
fail**
- `not ok 27 - P-455: a read-only window draws no write on the task
board` — actual `{buttons:['Add task','Start','Complete'],
newTaskForms:1,
draggable:['task-open123456','task-working456789','task-done789012']}`
vs expected `{buttons:[], newTaskForms:0, draggable:[]}`.
- `ok 28 - counter-proof: a writable board keeps all four writes` —
green on the base by design, so a fix cannot pass by deleting the
controls outright.
- Fixture: a writable roster (`readOnly` absent) and the same roster
with `readOnly: 'This window is read-only.'` over one board with three
cards.

## The fix

`KanbanDialog` reads the lock once, with the predicate the sidebar
already uses (`sidebar.js:371`: a non-empty string), and passes it down:

```js
const locked = typeof roster?.readOnly === 'string' && roster.readOnly.trim() !== ''
```

- `BoardView` → no `Add task` form when `locked`; passes `locked` to
every `BoardColumn`.
- `BoardColumn` → no `onDragOver`/`onDrop` effect when `locked` (a drop
runs no `onMove`, so no `taskUpdate`).
- `BoardCard` via `cardProps` → `movable: !locked && canMoveTask(...)`,
so no card is `draggable` and Start/Complete are not drawn.
- `move()` and `create()` return early when `locked` — the write entries
refuse even if a caller reaches them.
- Readable board unchanged: rail, three columns, cards, archive page,
Done stamps stay.

## Delta, file by file

| file | +/- | kind |
| --- | --- | --- |
| `core/src/client/kanban.js` | +19/−13 | written |
| `core/test/kanban.test.js` | +71/−2 | written (two cases) |
| `docs/subsystems/core-client.md` | +1/−1 | written |
| `changelog.d/2248-readonly-task-board.fixed.md` | +1 | written (new) |
| `core/client.js` | +18/−12 | generated (`build-client`) |
| `patches/ds-bot/0008-client-bundle.patch` | +1/−13 | generated |
| `patches/ds-bot/files/src/client/kanban.js` | +19/−13 | generated
(copy) |
| `patches/ds-bot/files/test/kanban.test.js` | +71/−2 | generated (copy)
|
| **total** | **+201/−56** | 8 files |

Generated by `node scripts/ds-bot-patches.mjs generate` (run on the tip
tree) and compared file by file against the tip; no hand-merged
conflict. `node core/scripts/build-client.mjs --check` → exit 0; `node
scripts/ds-bot-patches.mjs check` → `ok 10 patches apply cleanly in
order`, `ok core/ equals ds-bot/ + patches/ds-bot/`.

## What I ran, at tree `0bb21f646de45f550ebc417e9c70d2ac11eae1a2`
(replayed onto main `03a125306226158ae13c1b3a1185e4db30c7ca67`)

- `node --test core/test/kanban.test.js` → **28 / 28 / 0**, including
`P-455: a read-only window draws no write on the task board` and
`counter-proof: a writable board keeps all four writes`.
- `node --test core/test/dialog-focus.test.js` → **3 / 3 / 0** — ThinkFlowLab#2262's
Settings-dialog focus is still in the regenerated bundle.
- `npm --prefix core test`, run the way CI runs it
(`DSH_HARNESS=core/dev/harness`, `TZ=UTC`) → **2680 tests, 2677 pass, 3
fail**, and every red is this box, not the change:
`check-arch-docs.test.js:39` needs a git checkout (green once a local
repo exists), `check-arch-docs.test.js:73` needs an executable `/tmp`
(this box mounts `/tmp` `noexec`), and `usage-fields.test.js:88` shells
out to `python3`, which this box does not have. No kanban/client test is
red.
- `node core/scripts/build-client.mjs --check` → exit 0; `node
scripts/ds-bot-patches.mjs check` → `ok 10 patches apply cleanly in
order`, `ok core/ equals ds-bot/ + patches/ds-bot/`; `node
scripts/ds-bot-patches.mjs generate` on the merged tree → no-op against
what is committed.
- `npm run verify` could not finish here: the box ran out of threads
(`pthread_create: Resource temporarily unavailable`) and the
tarball-only checkout has no `4a9d3edd` object for the `arch-docs` step.
CI is the reference for the full chain.

## The replay

ThinkFlowLab#2262 and ThinkFlowLab#2263 landed after this tip was cut and both touch generated
files, so `update-branch` was refused (422 `merge conflict between base
and head`). The branch was replayed onto
`03a125306226158ae13c1b3a1185e4db30c7ca67`: `core/src/client/kanban.js`,
`core/test/kanban.test.js` and the changelog fragment came over as
written; `docs/subsystems/core-client.md` is a semantic merge (main's
ThinkFlowLab#2251 bullet plus this PR's kanban row); **every generated file was
regenerated, never hand-merged** — `core/client.js`,
`patches/ds-bot/0008-client-bundle.patch`,
`patches/ds-bot/files/src/client/kanban.js`,
`patches/ds-bot/files/test/kanban.test.js`. Against `main`'s tree the
branch differs in exactly these 8 files, `0008` differs only by the
read-only gate (`cardProps` / `BoardColumn` / `BoardView` `locked`), and
ThinkFlowLab#2262's overlay-hooks / dialog-focus bundle changes are intact.

## Tier, by path

`createGate(risk-rules).judge(files)` over exactly these 8 paths:
`core/src/client/kanban.js`=T1, `core/test/kanban.test.js`=T1,
`core/client.js`=T1, `patches/**`=T1, `changelog.d/**`=T1, `docs/**`=T0
→ **T1**, 62 changed lines in 4 written files (201 lines over 8 with
generated ones).

## Checkpoint

<!-- flybot:checkpoint -->
{
  "pr": 2261,
  "claim": "Chief",
  "issue": 2248,
"summary": "A read-only window no longer draws task-board writes the
host would refuse. KanbanDialog reads roster.readOnly once and hands the
lock to BoardView, BoardColumn and BoardCard, so the Add task form,
Start, Complete and draggable are gone, a drop runs no move, and
move/create return early. The rail, the three columns, the cards, the
archive page and the Done stamps stay readable. A writable window keeps
all four writes, which the counter-proof test pins.",
  "files": [
    "core/src/client/kanban.js",
    "core/test/kanban.test.js",
    "docs/subsystems/core-client.md",
    "changelog.d/2248-readonly-task-board.fixed.md",
    "core/client.js",
    "patches/ds-bot/0008-client-bundle.patch",
    "patches/ds-bot/files/src/client/kanban.js",
    "patches/ds-bot/files/test/kanban.test.js"
  ],
  "checks": {
"ran": "node --test core/test/kanban.test.js (28/28/0); node --test
core/test/dialog-focus.test.js (3/3/0); npm --prefix core test as CI
runs it (2677/2680; 3 environmental reds: no git checkout, noexec /tmp,
no python3); build-client --check; ds-bot-patches check + generate
(no-op)",
    "result": "green",
    "at": "0bb21f646de45f550ebc417e9c70d2ac11eae1a2"
  },
"next": "CI full suite on the replayed tip; T1 enqueues with no PASS
once tests-ok and Node 24 are green."
}
<!-- /flybot:checkpoint -->
HugoXK added a commit to HugoXK/FlyBot that referenced this pull request Nov 3, 2026
…d-task form (ThinkFlowLab#2246)

Written-by: Dev-4
Fixes ThinkFlowLab#2243

Owner: Dev4
Reviewer: Birfy
Tier: T1
Claim-Id: a41089f1cdd34475b08a8e4188440002
Branch: dev/4-p457-schedules-create-entries
Base: 5008116
Tree: 93e92c8
Verified commit: local `51040d46c2fe287320e520b860213bc6f9486f91` (the
same commit that was pushed)
Pushed head: 51040d4
Files: 12 changed, +6975/−6064 (six written, six generated)

Rebased onto `50081167e81ac5ee182e6cc5d1a25ab248329e32` (live main:
`ThinkFlowLab#2263`, then `ThinkFlowLab#2261`); every reading in this revision is from that
base. Two earlier revisions of this body quoted `5a21d5b4` and then
`03a125306` — the code and the case names are the same, the counts move
with main.

## What the rebase onto `50081167` had to resolve

`ThinkFlowLab#2261` (`ThinkFlowLab#2248`, the other team's task-board change, merged upstream)
rewrites `core/src/client/kanban.js` and its tests. This change touches
`schedules.js`, `composer.js` and `details-drawer.js`; the two change
sets meet in three generated or shared files and overlap in no source
line:

- `core/client.js` — generated bundle, both change sets in it;
regenerated by `npm run build` (not hand-merged).
- `patches/ds-bot/0008-client-bundle.patch` — generated mirror of that
bundle; the rebase conflicted here (generated text), and the committed
version is what `generate` writes.
- `docs/subsystems/core-client.md` — one table row each: `kanban.js`
(theirs) and `schedules.js` (this change); both rows stand as written.

Both behaviours are kept: `ThinkFlowLab#2261`'s read-only task board is in
`kanban.js`, tests and bundle exactly as it landed, and this change's
read-only create entries are on top of it. `build-client --check` is
silent, `node scripts/ds-bot-patches.mjs check` answers `ok core/ equals
ds-bot/ + patches/ds-bot/`, and `canonical` answers `ok patches/ds-bot/
is what generate writes (generate is a no-op)` — the mirrors are
generated output, not a resolution by hand.

## The defect

ThinkFlowLab#2235 made a read-only window draw no create form (`settings.js`:
`locked ? null : adding ? TaskForm : …`), but three entries still lead
into it, and each one lands on a page that draws nothing:

- `core/src/client/schedules.js:461` — `BotSchedules` draws its `New
task` row unconditionally (it is `TaskList`'s `extra` row), and the row
calls `actions.openSettings('schedules', bot.id)`.
- `core/src/client/schedules.js:485` — `ScheduleSummary` never read the
roster at all, so its empty state's `Add one` (`No scheduled tasks yet.
Add one`) called the same thing.
- `core/src/client/composer.js:170` — the `+` menu's `Scheduled task`
row calls `openSettings('schedules', target)`.

`settings.js` turns the `start` id into `adding`, so the settings page
really does believe a form is open and then draws no form: a click that
looked live ends on an empty page.

## Red first, on live main `50081167`

Clean tree at `50081167e81ac5ee182e6cc5d1a25ab248329e32` with only the
two test files touched.

- `core/test/schedules-client.test.js` alone → **52 tests / 50 pass / 2
fail**
- `not ok 50 - P-457: a read-only Bot card draws no New task row` — `a
read-only Bot card still offers New task, saw New task`
- `ok 51 - P-457: a locked page opened with a start id draws no create
form` — green on the base, the counter-case that must stay green
- `not ok 52 - P-457: a read-only details line offers no Add one, and a
writable one does` — `the locked details line draws no button`, `actual
[ 'No scheduled tasks yet. Add one' ]`, `expected []`
- `core/test/composer-locked.test.js` alone → **2 / 1 / 1**
- `not ok 2 - read-only window: no menu row opens the schedules page` —
`a read-only window still offers the row: ["Add files","Reference a
chat","Scheduled task"]`, `expected: true`, `actual: false`

The composer case is the one Product's earlier probe could not mount; it
is a real test now, with the three stubs the probe needed
(`createPortal: node => node`, `useState` fed `{left:0,bottom:0}` then
`null`, `useRef: current => ({current})`).

The whole `core` package on the same red tree (both files at their
package numbers): **2685 / 2682 / 3** — `not ok 277`, `not ok 1990`,
`not ok 1992`, the same three cases.

After the fix, on this tip: `schedules-client.test.js` alone **52 / 52 /
0**, `composer-locked.test.js` alone **2 / 2 / 0**; the whole `core`
package **2685 / 2685 / 0**.

## The fix

- `core/src/client/schedules.js`: `lockedWindow(roster)` (the
module-private rule that was already at `:122`) is **exported once**
instead of copied a fifth time — the same predicate still sits in
`workflows.js:570/639`, `sidebar.js:371` and `settings.js:232`, and this
change does not touch those four. `BotSchedules` draws `extra: locked ?
[] : [ … New task … ]`. `ScheduleSummary` takes `roster` and, when the
window is locked and the Bot has no tasks, returns `null`; with tasks it
still draws the read half, because those rows only open the settings
list (`openSettings('schedules')`, no Bot id) and open no form.
- `core/src/client/composer.js`: the local `schedule()` helper is gone
(`:147-151` on the base, call site `:150`); the menu row itself (`:170`)
becomes `locked ? null : …` and imports `lockedWindow` from
`./schedules.js` instead of growing a fifth copy.
- `core/src/client/details-drawer.js:350`: passes the `roster` it
already holds into `ScheduleSummary`.
- `docs/subsystems/core-client.md`: the two sentences that contradicted
each other are now one rule — a read-only window draws neither the empty
state's button nor a Bot card's `New task` row.
- New tests: 3 in `core/test/schedules-client.test.js` (read-only card,
read-only details line, plus the locked page that stays empty), and
`core/test/composer-locked.test.js` in full (read-only draws no row,
writable still posts `('schedules', 'b1')`).

Writable counter-cases live in the same tests: the writable Bot card
still has its `New task` row, the writable details line still draws `Add
one`, and the writable composer row still calls
`openSettings('schedules', <bot id>)`.

## Which six files are written and which six are generated

Written by hand — six:

- `core/src/client/schedules.js` (+10/−6)
- `core/src/client/composer.js` (+5/−5)
- `core/src/client/details-drawer.js` (+1/−1)
- `core/test/schedules-client.test.js` (+32)
- `core/test/composer-locked.test.js` (+82, new)
- `docs/subsystems/core-client.md` (1 line changed)

Generated, never hand-edited — six:

- `core/client.js` (`npm run build`; +1688/−1687)
- `patches/ds-bot/0008-client-bundle.patch` (4993/4354)
- `patches/ds-bot/0001-core-fixes.patch` (12/7)
- `patches/ds-bot/0012-room-membership.patch` (37/3)
- `patches/ds-bot/files/test/schedules-client.test.js` (+32)
- `patches/ds-bot/files/test/composer-locked.test.js` (+82)

## The generated diff is module order, not a hand edit

`core/client.js` and `patches/ds-bot/0008-client-bundle.patch` —
**+1688/−1687 and +4993/−4354** — are **esbuild module order, not a
logic rewrite**: `composer.js` now imports `./schedules.js`, so the
`schedules` block moved in front of the `composer` block in the bundle.
Generated only by `npm run build` and `node scripts/ds-bot-patches.mjs
generate`; `build-client --check` and a no-op `generate` prove it.
`composer.js` imports `./schedules.js`, so the `schedules` block moved
in front of the `composer` block in the bundle. Both files are written
only by `npm run build` and `node scripts/ds-bot-patches.mjs generate`;
neither was edited by hand, and `build-client --check` plus `generate`
being a no-op prove it. `patches/ds-bot/0001-core-fixes.patch` and
`0012-room-membership.patch` follow the same two source files. There is
no `patches/ds-bot/files/src/client/schedules.js` mirror, and none was
invented.

## What I ran at `51040d46c2fe287320e520b860213bc6f9486f91`

`TZ=UTC ARCH_DOCS_BASE=50081167e81ac5ee182e6cc5d1a25ab248329e32 npm run
verify` → **exit 0**

| suite | tests / pass / fail |
| --- | --- |
| core | 2685 / 2685 / 0 |
| plugins/social | 570 / 570 / 0 |
| plugins/tree | 132 / 132 / 0 |
| plugins/timeline | 140 / 140 / 0 |
| plugins/workflow | 411 / 411 / 0 |
| `.github/scripts` | 599 / 599 / 0 |
| interop | 37 / 37 / 0 |
| live-e2e | 26 / 26 / 0 |
| **total** | **4600 / 4600 / 0** |

- `arch-docs: OK (12 changed file(s))`; oxlint **0 errors** in every
suite (74/36/9/2/16 warnings, the same counts as live main; interop 1,
live-e2e 0).
- `ok ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70 (tree 5834c00, v0.1.5)`;
`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)`; `build-client --check` silent.
- The five tests these two files add are the only difference between the
red tree's 2685 and a base tree without them; every other core test is
`ThinkFlowLab#2261`'s and the ones before it.

## Tier, by path (not by a label)

`createGate(risk-rules).judge(files)` over exactly these 12 paths
answers:

`risk-tier: T1; basis: core/client.js=T1, core/src/client/**=T1,
core/test/**=T1, patches/ds-bot/**=T1,
docs/subsystems/core-client.md=T1`

## Not run

- The smoke bench (`dev/bench.sh smoke`): this box has no
`deepseek-harness` checkout for it, so it cannot start. `core/index.js`
and `core/src/host/` are untouched by this change.
- No browser was available, so the window states are exercised through
the modules and the tests rather than by clicking.

## Checkpoint

<!-- flybot:checkpoint -->
{
  "pr": 2246,
  "claim": "Dev4",
  "issue": 2243,
"summary": "The three entries ThinkFlowLab#2235 left in a read-only window —
BotSchedules' New task row, ScheduleSummary's empty-state Add one, and
the composer's + menu Scheduled task row — now draw nothing, so no click
lands on a create form the locked page hides. lockedWindow is exported
once from schedules.js and reused by composer.js rather than copied a
fifth time, the details line passes the roster, and a Bot that has tasks
still lists them. Tests pin both read-only shapes and keep the writable
entry points and their openSettings arguments unchanged. client.js/0008
is module order, not a hand edit.",
  "files": [
    "core/src/client/schedules.js",
    "core/src/client/composer.js",
    "core/src/client/details-drawer.js",
    "core/test/schedules-client.test.js",
    "core/test/composer-locked.test.js",
    "docs/subsystems/core-client.md"
  ],
  "checks": {
"ran": "TZ=UTC ARCH_DOCS_BASE=50081167 npm run verify (4600/4600/0)",
    "result": "green",
    "at": "51040d46c2fe287320e520b860213bc6f9486f91"
  },
  "next": "T1: waiting for tests-ok at this tip; no PASS required"
}
<!-- /flybot:checkpoint -->

Co-authored-by: FlyBot Dev-4 <dev4@flybot.local>
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.

workflow: the held list promises 230 more but the host caps one read at 200

2 participants