Repository navigation
workflow: count only the held ids the wider page reaches - #2263
Conversation
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
|
@Birfy — please review this tip: Coordinates: head 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 ( Written-by: FlyBot Lead |
|
Verdict: PASS at 9f8e6e9 Ran at this tip: Red first holds: with only
|
… (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 -->
…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>
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 asTreeabove, pushed as9f8e6e936621d69c830c1d6f0c5fdbf072c6fe5e)Pushed head: 9f8e6e9
Files: 4 changed, +74/−4
The defect
A lane can hold more ids than one
workflow/stateread serves:heldLimitis capped at 200 (HELD_LIMIT,plugins/workflow/client.jsbase :1436,MAX_HELD_LIMITinservice.js). With 250 held, the folded stage control readShow 230 more held tasks— computed fromheldTaskCount - heldShown.lengthalone (base :605) — while pressing it drew 200 rows and becameShow less. Ids 201-250 were named and then dropped: the dead line#2256had 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
4a9d3eddClean tree at
4a9d3edd2dcac112797e1d028b3f5f790a4f467ewith onlyplugins/workflow/test/client.test.jscarrying the new case (base sources):plugins/workflow/test/client.test.jsrun on its own → 167 tests / 166 pass / 1 fail (the wholeplugins/workflowpackage 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 isnot ok 215.workflow/stateanswers the 20-id page by default andMath.min(heldLimit, 200)when asked, withheldTaskCount: 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.jsat the tip:heldRest(:614), so 250 held under a 200 cap readsShow 180 more held tasks— 20 rows drawn, 180 still reachable.heldTaskCountstill above what the host returned draws onewf-held-notelineShowing {shown} of {count} held tasks(:607–:609), the shapefeedback.jsuses for a truncated window; it folds away with the rows it describes.HELD_LIMIT(:1445), theheldLimit: 200wide read, thedata-more="held"control,aria-expanded,Show less, the rows and theirRe-matchaction are untouched. With 200 held ids or fewer the Stage text is byte-identical: the#2256case that pins the 23-id block still readsShow 3 more held tasks, and both#2257cases are unchanged.Delta, file by file
plugins/workflow/client.jsplugins/workflow/test/client.test.jsdocs/subsystems/workflow.mdheldLimitrule)changelog.d/2260-held-cap-remainder.fixed.mdplugins/carries no generated bundle, so nothing here is produced bynpm run buildords-bot-patches generate;patches/ds-bot/**andcore/client.jsare untouched, and the run below leftpatches/byte-identical.The docs change is the second half of the issue:
heldLimitOfreads the value throughNumber()and keeps it when it is a positive integer, so'10'reads 10 ids andtruereads 1, while0,-5,3.5,'many'andnullfall 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 as9f8e6e936621d69c830c1d6f0c5fdbf072c6fe5e)TZ=UTC ARCH_DOCS_BASE=4a9d3edd2dcac112797e1d028b3f5f790a4f467e npm run verify→ exit 0.github/scriptsplugins/workflow/test/client.test.json its own: 167 / 167 / 0 (ok 167); the wholeplugins/workflowpackage 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 --checksilent.Tier, by path (not by a label)
createGate(risk-rules).judge(files)over exactly these four paths, from4a9d3edd'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=T078 changed lines in 4 files, no generated file.
Not run
dev/bench.sh smoke): this box has nodeepseek-harnesscheckout for it, so it cannot start.core/index.jsandcore/src/host/are untouched by this change.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 realSectionrender.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"
}