Skip to content

board: a read-only window draws no task-board write (#2248) - #2261

Merged
Birfy merged 2 commits into
mainfrom
dev/2248-readonly-task-board
Nov 3, 2026
Merged

Birfy merged 2 commits into
mainfrom
dev/2248-readonly-task-board

Conversation

@Birfy

@Birfy Birfy commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Written-by: Dev
Fixes #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 (#2215 #2221 #2223 #2233 #2236 #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:

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 — client: Settings takes focus when it opens and returns it to the opener (#2251) #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

#2262 and #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 #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 #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

{
"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."
}

@Birfy

Birfy commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator Author

Checkpoint summary rewritten to four sentences after the first enqueue-guard read (that run read the body before the edit; the gate re-reads at queue entry). All other checks at 890e47a947ee80cefb246a0b2eb4705bc0c9e5e2 are green: tests-ok, package, plugins (social/tree/timeline/workflow), agent-audience, merge-gate.

@Birfy
Birfy added this pull request to the merge queue Nov 3, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch 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.
@Birfy
Birfy force-pushed the dev/2248-readonly-task-board branch from 890e47a to 4cd7d3e Compare November 3, 2026 05:22
Replay follow-up: drop the stray core-test.log the replay commit carried.
@Birfy
Birfy added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit 5008116 Nov 3, 2026
14 checks passed
@Birfy
Birfy deleted the dev/2248-readonly-task-board branch November 3, 2026 05:31
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Nov 3, 2026
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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

core: a read-only window's task board still draws Add task, Start, Complete and drag, and every one is refused

1 participant