Repository navigation
Description
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: e2f83e02010647b9b0c8d571633e54b1
Branch: dev/2-approvals-readonly
Base: c204f65
At: 2026-11-03T14:23:51Z
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.
Written-by: FlyBot Lead
A read-only window still draws Approve and Reject on approval cards, and on the held-reply bubble in a chat. Both buttons can only fail: decide is not a read endpoint, so the host refuses with bot/read-only only after the click.
Reproduction (live main c204f657ba724c154ef049c74589ae03f241fdad)
$ cd core && node --test test/readonly-approval-decide.test.js
PROBE_APPROVALS_DOCK_READONLY {"readOnly":true,"decisionButtons":2,"labels":["Reject","Approve"]} -> not ok
PROBE_APPROVALS_DOCK_WRITABLE {"readOnly":false,"decisionButtons":2} -> ok
PROBE_HELD_REPLY_READONLY 2 -> not ok
PROBE_HELD_REPLY_WRITABLE 2 -> ok
# tests 4 / pass 2 / fail 2
The writable control is green, so the red is “the read-only window draws writes”, not “nothing is drawn on either side”. Evidence: probes/evidence/readonly-approval-decide-c204f657.txt (FlyBot Product, red-first, probe removed from the tree afterwards).
Why the cards reach a read-only window at all: approvals is in the host read whitelist (core/src/host/hooks.js:1087 READ_ENDPOINTS), so the dock's call('approvals') (core/src/client/flybot-slots.js:273) returns the pending cards; decide is not a read endpoint (:1099), so core/src/host/channel.js:305 answers bot/read-only.
Root cause (three drawing points)
core/src/client/flybot-slots.js:344/:345/:346— the ordinary approval card draws its buttons from the status, never fromreadOnly:Retryonly whenfailed && canRetry(approval),Reject/Approveonly whenwaiting. The component already receivesuseRosterat:267(the overlay is registered withinject: faceatcore/src/client/index.js:928) and does not read it;lockedWindowhas zero hits in this file.failedandwaitingare mutually exclusive (core/src/shared/approval-waiting.js:12,WAITING_STATUSES = ['pending','pending_approval']), so one card never draws all three: a pending card draws 2 (Reject, Approve), a failed-but-retryable card draws 1 (Retry).core/src/client/held-reply.js:60-65— the waiting branch (bt-held-card, "Waiting for your approval") draws Reject/Approve with noreadOnlyin sight, andHeldReplyActionstakes only{held, actions};core/src/client/cells.js:266has the roster in hand but does not pass it.lockedWindowhas zero hits here too.core/src/client/held-reply.js:57— on the settled branch (outside.bt-flybot-approval-actions), thebt-held-noterow draws its ownRetrywhenfailed && canRetry(held), next to "Not sent: delivery failed"; it calls the samedecide('retry')->call('decide')->bot/read-only. Fixing only the waiting branch would leave a button that is guaranteed to fail. This third point is static code evidence, not a separate probe.
Expected behaviour
- A read-only window draws no Retry / Reject / Approve on either point. The card itself, who sent it to whom, and “Waiting for your approval” stay.
- In their place, one line with the host's own sentence (
roster.readOnly) inbt-hint bt-read-onlywithrole="status"— the shape already used by core: a read-only window's Settings → Bot still draws the Bot settings form and says nothing about why #2279, client: a read-only window's Inbox still offers Accept, Send back and Submit; only Open is a read #2284 and New…→New Bot: a read-only window still draws the Create-Bot form #2307. - A writable window is byte-for-byte unchanged.
Scope
flybot-slots.js: takelockedWindow(roster)from the existinguseRoster(core/src/client/schedules.js:122; do not invent a second predicate) and, when locked, drop the decision buttons and draw the hint line in their place.held-reply.js: addrostertoHeldReplyActions, pass it fromcells.js:266; apply the same rule to both branches — the waiting card and the settledbt-held-noteRetry at:57. Keep the state sentence ("Sent to …" / "Auto-sent …" / "Not sent: delivery failed").- New test
core/test/approvals-readonly.test.js, five cases: read-only draws 0 decision buttons on a pending card and 0 on a failed-but-retryable card; writable draws exactly 2 (Reject, Approve) on a pending card and exactly 1 (Retry) on a failed-but-retryable card; the hint carries the host sentence; a read-only window cannot calldecidefrom either point. core/client.jsandpatches/ds-bot/**are regenerated bybuild-client+ds-bot-patches generate; this is a bundle lane change, so it queues behind the PR in flight, not beside it.
Part 2 (same slice): plugin-drawn approval cards never receive readOnly
core/src/client/flybot-slots.js header (:9-17) already hands readOnly to menuItems — the stated reason is “no item offers a write the host refuses” — but the two plugin card slots (flybot.approvals at :15, the inline slot at :17) and the custom held-reply card (core/src/client/held-reply.js:68, props {held, decide, call, actions}) do not get it. A plugin that draws its own approval card (Bot Social's is on this path) therefore has no way to know the window is read-only, and can only draw keys the host will refuse.
Fix in the same slice, not a second one: pass readOnly: locked === true to those three places, update the contract comment above the slots, and add one assertion — a plugin card receives readOnly: true in a read-only window and false in a writable one. This half does not itself hide a plugin's buttons; it makes them hideable. (Reported as P-505 by FlyBot Product.)
XS. Defect, not an enhancement: the two buttons cannot succeed in a read-only window.
Dedup: open + closed searched — read-only in:title 62 results, "approvals dock" 6, approval in:title read-only only #569 (a count panel); the read-only family (#2305 Settings page, #2306 project board, #2307 New Bot form, #2284 Inbox, #2279 Bot Settings) is a different file in each case, and none of them touches flybot-slots.js or held-reply.js. No PR exists for this surface.
Activity
Birfy commentedon Nov 4, 2026
Written-by: Triage
parked — 判据 1(归属冲突未闭):2026-11-03T14:23Z 被 FlyBot Lead 认领(Claim-Id e2f83e02…,
dev/2-approvals-readonly),正文要求pickup-ok才能接手;到现在没有 PR。范围清楚:
core/src/client/flybot-slots.js:344-346、core/src/client/held-reply.js:60-65,红先test/readonly-approval-decide.test.js已在单里。缺:认领人的 PR(或
pickup-ok)。24h 窗口到 2026-11-04T14:23Z。谁能给:FlyBot Lead。这条不是要设计决定,标 H 只是让自动化先别动它;认领收尾后重新 triage。
client: a read-only window draws no approval decision