Repository navigation
Held reply to a friend in the replying Bot's own chat: draft looks sent, card detached and names the Bot not the person, no trace after Approve/Reject (regression of #226) #418
Description
Summary
When a friend's line makes my Bot reply and the reply is held for my approval (A side), the held draft already looks like a sent reply, the card floats detached at the bottom right and names the wrong recipient, and after Approve or Reject nothing in the chat says what happened. A rejected reply and a sent reply look the same forever. This is the direct-chat state model #226 asked for (held bubble, controls attached, clear after-state), now seen in the replying Bot's own chat (the #254 path), so it is partly a regression of #226.
Environment
- FlyBot main
a7f51e83ffacc4a6e803669d25fdc9e40eef6c44(Add friend: accept and invite as two labelled groups, Done at the bottom #416), core + plugins/social + plugins/tree on@deepseek-ai/dsh@0.2.0-rc.2, Node 24.21.0. - Two fresh installs on one Linux box: A
:3090(owner SelfA1238,@selfa1238.home) and B:3091(owner SelfB1238,@selfb1238.home). Bot Social prodhttps://47-80-69-220.sslip.io. Both Chiefs onark/deepseek-v4-flash(real model). - Friend link: A made a per-Bot invite for A's Chief; B joined it, talking through B's Chief (relationship
rel_01M597ETM4MFYM90M80ER53J8G, grantgrt_01M597ETM5PK3PNKGZ09R7PC96→@selfb1238.home). B's Chief is not shared back (one-way per-Bot grant, by design Per-Bot grants leak: accepting a Helper invite exposes the joiner's Chief; face-room Chief shows in Friends' Bots; Manage access wrong/raw #138/Regression of #138: joining a friend's invite still puts the joiner's Chief on the inviter's main list #161). A's Chief =@chief.selfa1238.home, B's talk-through Bot =@chief.selfb1238.home. - Auto-send was off on both homes at the start.
- Found by product self-test 2026-10-19 12:38 Asia/Shanghai, focus A Friend DM (browser run about 12:41–12:50 Asia/Shanghai). Screenshots stayed on the test machine; what they show is written out below.
Common setup (B talks to A's Chief)
On B, open the sidebar row for A's Chief (Chief + grey SelfA1238 + globe) and send, one at a time, waiting for each answer:
Hi Chief, this is B. What is 2+2? Reply in one short line.→ A's Chief drafts4.; on A click Reject.Second test: what is 3+3? One line.→ draft6.; on A click Approve.Third test: what is 4+4? One line.→ draft8.; on A click Always approve.Fourth test: 5+5? One line.andFifth test: 6+6? One line.→10.and12.go out without a card.
Repro
- Run the common setup steps 1–2. Stay on A's own Chief chat (where B's line lands, see Friend DM: a shared Bot's reply to the grantee is silently denied (grantee's Bot not shared back), never held or delivered #272/A friend Bot's reply to a typed DM is held on its owner and no approval card ever shows (overlay hook crash, no in-chat card for the replying Bot) #254) while each reply is drafted.
- Before deciding, read the chat and the card. Click Reject on
4., then Approve on6.. - After each decision, read the chat, the sidebar row and B's chat.
Actual (A, the approver)
- The held draft looks sent. After
› Thought it through, the draft4.is an ordinary grey Bot bubble, the same as any delivered reply. No held style, no "waiting" mark on it. - The card is detached and names the wrong party. It floats at the bottom right above the composer, far from the bubble, and reads
Waiting for approval/Chief → Chief/4., with a blueAlways approvetext link on the left and Approve (primary) and Reject on the right. The recipient is B's talk-through Bot; the person who gets the line is SelfB1238, and the card does not say the line goes to SelfB1238. Screenshot06-A-second-card-before-approve.png: A's Chief chat with B's two lines (labelledSelfB1238, grey person icon), theNEWdivider, bubbles4.and6.that look identical, and the card for6.alone at the bottom right. - After Reject: the card shows
Rejectedfor a moment and disappears. The bubble4.stays and still looks sent; the sidebar row preview still reads4.. Screenshot04-A-after-reject.png: chat ends with the plain bubble4., row preview4., no card, no note. - After Approve: the card disappears with no
Senttrace. Screenshot08-A-after-approve-no-status.png: bubbles4.(rejected) and6.(sent) look exactly the same. - No waiting marker in the list. The only mark on the Chief row is a small orange dot on its avatar, which is there in every screenshot before and after the decisions (04, 06, 08, 09), so it does not read as "waiting for you".
- B got nothing for
4.(rejected) and6.arrived once approved. B never sees that a reply is waiting. Per social.md invariant 8 ("The sender sees nothing until it is approved") that part is by design; noted only so a maintainer can confirm.
Why (code read on a7f51e8; hunch)
- The held style exists only for face rooms:
core/src/client/user-message.jsL147 (heldrequiresroom.origin === 'face-to-face') and theGroupMessageheld-line state (Face-to-face join/waiting/leave polish: silent disabled Join, frozen countdown, waiting card twice, Leave next to Auto-send and unconfirmed in waiting, held lines shown twice #306,core/src/client/cells.js). A local hold of a Bot's ownauto-replyin its own chat has no bubble state, so the DSH assistant turn renders the draft as a normal reply. - The card is the
flybot.session.dockslotsocial-approval(plugins/social/client.jsL1927,ChatApproval~L898–931), always drawn in the dock above the composer, not next to the bubble. After a decision it rendersApproved/Rejectedbriefly and then the item leaves the list, so nothing persists. Chief → Chief:holdParties(~L870) usesitem.toName(the receiving Bot) instead of the person (owner of@chief.selfb1238.home).- docs/subsystems/social.md disagrees with itself on card placement (L72 "left-aligned like a bubble", L104 "right-aligned bubbles").
Expected (FlyBot design: in-chat approval with explicit buttons, simplicity/consistency)
- A held reply is visibly held where it is: the draft bubble has the pending style (as face rooms do, fix(social): plain face-room lines do not start Bot turns; UI polish (#167, #166) #168/Face-to-face join/waiting/leave polish: silent disabled Join, frozen countdown, waiting card twice, Leave next to Auto-send and unconfirmed in waiting, held lines shown twice #306) and the Approve / Reject buttons and the standing-choice link sit on that bubble (or the card replaces the bubble in place), owner only. Not a second copy floating elsewhere.
- The card says who will get it, by person:
Chief → SelfB1238(with the friend naming from Friend's Bot rows: two name formats ('Helper · SelfB0023's Bot' vs 'SelfA0023's Chief'), two-line wrap, no preview, inset highlight, faint marker, same avatar as own Chief #384 where a Bot is named). - After Approve: the bubble becomes a normal reply with a quiet
Sentmeta (or nothing, but distinct from rejected). - After Reject: the bubble stays as a muted/struck
Not sent(or is replaced by a one-lineYou didn't send this reply.note); the sidebar preview does not show the rejected text. - While something waits, the Chief row shows the same wait badge that 1:1 friend chats and rooms get (
flybot.chatApprovals/bot.pendingCount, social.md L73), and it clears on decide. - Approval only via explicit buttons ([Feature RFC] Agent inbox board: one place for tasks, approvals and failures (personal first, team second) #349).
Suggested scope / files
core/src/client/user-message.js,core/src/client/cells.js(held state for a local Bot reply held byflybot/approvals; match by approval id likeroom.pendingApprovals).plugins/social/client.js:ChatApproval,ChatApprovalDock,holdParties,approvalsForOpenChat(~L933).core/src/host/hooks.js(release/ reject path) so a decided hold leaves a marker on the turn.docs/subsystems/social.md(one card-placement rule).- Tests:
plugins/social/test/social.test.js, core client tests for the held/decided bubble.
Acceptance criteria
- Before a decision the held reply is visibly different from a sent one and carries the decision buttons in place.
- The card names the person who will receive it (
SelfB1238). - After Reject the reply is shown as not sent, and the list preview does not show it as the last message.
- After Approve the reply looks sent; rejected and sent replies are distinguishable.
- The Bot's sidebar row shows a wait badge while a hold is open and clears immediately on decide.
Related: #226 (closed, same model for the friend's chat), #254, #271, #278, #306, #371, #349.
Activity
Birfy commentedon Oct 19, 2026
Triage on main
a7f51e8, 2026-10-19 ~14:15 Asia/Shanghai: design conflicts resolved, spec clear; ready to develop. Checked: #226 (same held-bubble model for the friend chat), #306/#168 (face-room held line), #349 (explicit buttons only), #384 (naming), social.md invariant 8 (sender sees nothing until approved; that part stays). Owner-delegated decision on the open detail: one card-placement rule, the decision buttons sit on the held bubble in place (no floating copy in the dock for that chat); docs/subsystems/social.md is made to say the same.Birfy commentedon Oct 19, 2026
Claimed by the maintenance routine (2026-10-19 17:00 Asia/Shanghai). Branch:
Birfy:fix/418-held-reply-in-placeoff main31c4c2a. Scope per the 06:07 UTC triage: held reply visibly held in place with the decision buttons on it (no floating dock copy for that chat), card names the person, Sent / Not sent after-state, wait badge on the Bot's row; social.md gets one card-placement rule.Birfy commentedon Oct 19, 2026
Minor details decided while implementing (PR #430):
Waiting for approval,Chief → SelfB1238, Approve / Reject and the quiet Always approve link (same as the dock card), without repeating the text (the bubble is the text).Sent to SelfB1238; rejected / stale / denied → muted bubble +Not sent; failed →Not sent: delivery failed+ Retry.Chief (SelfB1238)).Held reply card: Auto-send from now on for that pair only, via ThinkF…
Held reply to a friend: held in place, decision on the bubble, Sent /…
Name a friend's Bot one way and approval cards by the receiving person (
fix(social): keep the waiting line only for a room card this chat nev…
social: waiting-for-approval card names no destination and offers no …