Skip to content

bug(read-only): a read-only window still draws Approve/Reject on approval cards and on the held-reply bubble #2310

Closed

Description

@HugoXK

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)

  1. core/src/client/flybot-slots.js:344/:345/:346 — the ordinary approval card draws its buttons from the status, never from readOnly: Retry only when failed && canRetry(approval), Reject/Approve only when waiting. The component already receives useRoster at :267 (the overlay is registered with inject: face at core/src/client/index.js:928) and does not read it; lockedWindow has zero hits in this file. failed and waiting are 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).
  2. core/src/client/held-reply.js:60-65 — the waiting branch (bt-held-card, "Waiting for your approval") draws Reject/Approve with no readOnly in sight, and HeldReplyActions takes only {held, actions}; core/src/client/cells.js:266 has the roster in hand but does not pass it. lockedWindow has zero hits here too.
  3. core/src/client/held-reply.js:57 — on the settled branch (outside .bt-flybot-approval-actions), the bt-held-note row draws its own Retry when failed && canRetry(held), next to "Not sent: delivery failed"; it calls the same decide('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

Scope

  • flybot-slots.js: take lockedWindow(roster) from the existing useRoster (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: add roster to HeldReplyActions, pass it from cells.js:266; apply the same rule to both branches — the waiting card and the settled bt-held-note Retry 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 call decide from either point.
  • core/client.js and patches/ds-bot/** are regenerated by build-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

  1. Birfy commented on Nov 4, 2026

    @Birfy
    Collaborator

    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。

  2. added
    needs-humanThe agent loop stopped; a person must decide (reason in the last comment)
    on Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-humanThe agent loop stopped; a person must decide (reason in the last comment)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions