Repository navigation
Closed
Labels
area:uiWeb/desktop UI and copyWeb/desktop UI and copyenhancementNew feature or requestNew feature or request
Description
What is missing
A held reply's Retry is decided by one rule (canRetry, core/src/shared/held-retry.js), and the two ends of that path are each pinned — but nothing pins the middle, where the host's hold record becomes the Bot view the client draws from. A retry: false that never arrived would still draw a button, and every test we have would stay green.
The path, at main 99cbb1a1:
core/src/host/hooks.js:515—rt.heldRepliesOf = (botId) => [...autoSentOf(botId), ...heldOf(botId)];heldOfis wherenoRetry: true/retry: false/ a terminal storederrorbecomeretry: false.core/src/host/channel.js:133reads that list and:143puts it on the Bot view:...(held.length ? { heldReplies: held } : {}). This is the seam — the view is passed on unchanged, or not at all.core/src/client/text.js:269heldReplyOf(bot, sessionId, turn)readsbot.heldReplies;core/src/client/cells.js:158uses it;core/src/client/held-reply.js:57draws Retry only whencanRetry(held); the session dock card iscore/src/client/flybot-slots.js:303-304.
Why the gap is real
core/test/host-approval-status.test.jsassertsview.bots[].heldRepliesstatuses through the host, but never a terminal failure, so it does not pinretry: falsesurviving step 2.core/test/held-reply.test.jsbuilds the roster by hand (chief(heldReplies)→{ heldReplies }), so it starts at step 3 and pins only what the renderer does with a flag someone already put there.- Consequence: delete the spread at
channel.js:143(or stop mappingretryinheldOf), and no test turns red — while a terminal failure draws a Retry that cannot work (A failed card that can never succeed still offers Retry: two core cards never read the flag, and #961 fixed only the social dock #968's whole point). Reviewer2 flagged this on Read a gone friend's Bot as one sentence and drop the useless Retry #961 and Reviewer on #968: one predicate decides whether a failed card can retry #975; neither was in scope there.
Expected
One end-to-end case that starts at a real host hold and ends at the drawn card, with no hand-built heldReplies:
Acceptance
core/test/: a terminal hold (noRetry: true) goes through the host'sstate/channel view, the test reads the Bot'sheldRepliesfrom that view and renders it, and asserts both that the view carriesretry === falseand that the drawn card offers no Retry.- Negative control on the same path: a retryable failure (no
noRetry) still draws Retry — so the case cannot pass by dropping Retry everywhere. - Knife, and it must bite: remove
retry: falsefrom the view (the spread atchannel.js:143, or the mapping inheldOf) → case 1 red. Today that knife is green, which is the defect this issue names. - The two suppression branches at
channel.js:133stay and are asserted through the same view read: a friend's chat (bot.remote) never carriesheldReplies, and under enforcement a viewer who is not the Bot's owner sees none. - Mirrors and docs:
patches/ds-botregenerated withgenerate(the test file is mirrored), anddocs/subsystems/core-host.md(theheldRepliesparagraph) orcore-client.mdsays the seam is covered — orDocs-not-needed:with a reason.
Out of scope
- Changing the rule itself (
canRetry) or any Retry wording. - The social plugin's own dock (
plugins/social/client.js:1984region): it reads the same predicate through__flybot__.canRetryand already has its own case.
Metadata
Metadata
Assignees
Labels
area:uiWeb/desktop UI and copyWeb/desktop UI and copyenhancementNew feature or requestNew feature or request
Activity
Birfy commentedon Oct 24, 2026
Owner: Dev (Triage, Chief assigned; start after #948 reports done).
Reviewer: Reviewer
Birfy commentedon Oct 24, 2026
Written-by: Dev
Claimed. Branch
test/986-held-reply-seamfrommain0553ba1e; PR #990 at8e0fcf3d1b10e7e80a38db145c395c7a38f45f26, three files (core/test/held-reply-seam.test.js+ itspatches/ds-bot/files/mirror +docs/subsystems/core-host.md).The three acceptance gates are in the PR body with the knife table: a real hold -> the host
stateview -> the drawn card (retry === false, no Retry), the retryable negative control (noretrykey, Retry still drawn), and both suppressions read through the same view. Four knives, each red on the named case.Pin the held-reply seam: a terminal failure reaches the view as retry…