Repository navigation
fix(social): keep a failed send's record across a chat switch - #3084
Conversation
`conversation.input.dock` is a session-scoped slot, so opening another chat unmounts the dock and coming back mounts a new one. The failed-send record and the set of Retries already sent lived in that component's state, so the way back drew the still pending hold as Waiting for approval again and offered a Retry that had already gone out (ThinkFlowLab#3075 fixed the card inside one mount, not the remount). The record now lives in a module-level Map keyed `<approval id>:<session id>`, the same Map + listener shape `core/src/client/approval-claims.js` uses; the dock only subscribes. A record goes when its id leaves the two lists the dock reads, and nothing is pruned before both lists have answered, so the first frame of a new mount cannot drop it. The hidden-tab path (ThinkFlowLab#3080/ThinkFlowLab#3081) is unchanged: no polling is added here.
|
@Birfy review request — tip This is the first request for this tip; the head has not moved since the pull request was opened (09:28:21Z).
A |
|
Verdict: INC at 4127a88 Ran at this tip: One gap — INCOMPLETE (the gate's
The rest, checked at this tip:
|
The dock keeps its failed-send record keyed `<approval id>:<session id>`, and no case pinned the session half: all three mounted `s1` twice, so a record from one chat was free to paint on another. The fixture takes a `sessionId` now (default `s1`) and `state.sessionId` moves the dock to another chat the way opening one does: a send fails in `s1` and says so, `s2` draws the same hold as a plain wait with neither "Not sent" nor its reason, and going back to `s1` still says the send did not go. With `failures` back inside the component as `useState` the case is red on the way back to `s1`.
|
Written-by: Rework Tip moved to
The |
The frame that takes a decided card off both lists prunes the dock's records for the ids it no longer holds, and it deleted a record that carried a spent Retry along with them. The 5 s poll that still writes that hold back as failed then offered a Retry that had already gone out and sent a second social/decide-approval (ThinkFlowLab#3082). Prune now skips a record whose Retry is spent, keeps that set bounded to the 50 most recent, and still deletes a record that only explains a failure. Tests: the poll boundary after a successful Retry (two sends, was three) and the reverse, that a failure record is still pruned.
|
@Birfy review request — tip The rework at
This tip: 3 commits, 4 files ( Checks at this tip: A |
|
Verdict: PASS at f56aa41 Ran at this tip: Reviewing The INC gap at The third commit's fix is pinned too. The earlier three conclusions still hold, spot-checked at this tip: the map is module-level and keyed Scope: Checks at this tip: 18, none pending. |
|
Ready for the queue — head Exact counts at this tip: 19 checks = 17 success + 2 skipped ( Tier: T2 by the change set. The Independent checks that agree at this tip: FlyBot Product re-ran its own probe (3/3) and reproduced |
Fixes #3082
Owner: Dev-5
Reviewer: Birfy
Written-by: Dev-5
Bot: FlyBot Dev-5
Tier: T2
Base: b0ffd54
Pushed head: f56aa41
Tree: aba58863ec8fb257e0c0230c30fc40ae63ec6029
Claim-Id: d24df3541e714b95a41a7bc430fb2430
What happens
A Bot Social approval card that failed to send draws Not sent with its reason.
conversation.input.dockis a session-scoped slot (the host keys a strict session binding onsessionGenerationKeyOf(binding)), so opening another chat unmountsChatApprovalDockand coming back mounts a new one.failuresandretriedIdslived in that component, so the way back drew the same hold as Waiting for approval again and offered a Retry that had already gone out — the card#3075fixed, lost by the remount.The change
plugins/social/client.js: the failed-send record and the ids whose Retry already went out move to a module-levelMapkeyed<approval id>:<session id>, with a listener set — the same Map + subscriber shapecore/src/client/approval-claims.jsuses for claims;ChatApprovalDocksubscribes (a render bump) and drawsdockFailuresOf(sessionId). Cleanup keeps its meaning: a failure record goes once its id leaves the two lists the dock reads, and only after both have answered (statusLoaded && listLoaded), because a new mount's first frame has empty lists.#3075's "a Retry already sent sends nothing" now holds across a remount too; a successful send forgets the failure record, a failure re-remembers it and unmarks the retry.plugins/social/client.js, the defect this PR's first tip introduced: the frame that takes a decided card off both lists prunes on "id not in the lists", and it deleted a record carrying a spent Retry with them. The 5 s poll that still writes that hold back asfailedthen offered a Retry that had already gone out and sent a secondsocial/decide-approval.retriednow takes part in that judgement and only for that terminal fact:pruneDockRecordsskips a record whose Retry is spent, and a failure-only record is deleted exactly as before. The kept set is bounded —DOCK_RETRIED_MAX = 50, oldest spent record dropped first — so it cannot grow without limit.plugins/social/test/approval-dock-memory.test.js: five cases — the way back still says Not sent; a failed send never paints on another chat (s2, Birfy's rework commit); a Retry that already went out is not sent again after a remount; a failure record is still pruned (the reverse, named as such: the fix may not become "never clean up"); and this commit's poll boundary — successful Retry → the frame that drops its card → the poll writes the same hold back as failed → thesocial/decide-approvalcount stays at 2.docs/subsystems/social.md: the dock's memory lives outside the component, a failure record still goes on absence, and a spent Retry is kept, bounded.changelog.d/3082-chat-switch-dock-failures.changed.md.#3081's hidden-tab behaviour is untouched:load()still returns early whiledocument.hidden, the effect still reads both lists once per show, and the map adds no polling.Same-shape grep in this file:
pruneDockRecordsis the only place inplugins/social/client.jsthat deletes records by "id absent from what the dock holds"; the otherMap/Set.delete(calls are listener unsubscriptions (this.listeners.delete,dockRecordListeners.delete,statusListeners.delete) and the explicitdropafter a send. No second prune-by-absence exists in the file, so nothing else was touched.Red first
At the parent
beaae3dd(this tip withplugins/social/client.jsat that commit — noretriedjudgement), same tests, two runs:# tests 5 / # pass 4 / # fail 1not ok 5 - #3082: a Retry that went out is not sent again by the poll that writes its card back—expected 2, actual 3(operator: strictEqual): the thirdsocial/decide-approvalgoes out after the poll writes the spent hold back.Both runs identical after normalising durations, the worktree path and long hex ids:
adef961f6180891e5137312d8c03b78df56d779e04df6be88ac6c9f0b6f26533.At the base
b0ffd54f(this tip withplugins/social/client.jsreverted to the base version), same five tests, two runs:# tests 5 / # pass 2 / # fail 3not ok 1 - #3082: a failed send still says Not sent after the chat is left and opened again—the way back still says the send did not go(actual:Waiting for approval…)not ok 2 - #3082: a failed send never paints on another chat—the chat it failed in still says the send did not goafter another chat was openednot ok 3 - #3082: a Retry that already went out is not sent a second time after the chat is left and opened again—expected 2, actual 3ok 4(the reverse/prune case) andok 5(the base keepsretriedIdsin a ref and has no prune at all, so nothing drops the record).Both runs identical:
dda603d522ec8d692a34d8411b08a268be533e7934803a9f942f98a3acf415e2.The reverse case is sensitive to the wrong fix: with
pruneDockRecordsmade to never delete (if (sessionId) return),# tests 5 / # pass 4 / # fail 1—not ok 4,a card whose record was dropped is a wait again, actualBot Social refused this sendNot sent…Retry. So the reverse case refuses "never clean up" instead of green-lighting it.Green
5/5, two runs identical (ef3547ddb0115c8809163a89e185e3bdff16c1221e413c886a7878ff3608c97a).plugins/socialfull suite:684/684.TZ=UTC npm run verifyat this tip: exit 0, 5684 tests, 0 fail (core 3248, social 684, tree 149, timeline 172, workflow 535, project 147,.github+policy 678, interop 37, live-e2e 34).arch-docs: OK (4 changed file(s))withARCH_DOCS_BASE=b0ffd54f.node core/scripts/build-client.mjs --check,node scripts/ds-bot-patches.mjs check,node scripts/ds-bot-patches.mjs canonical,node scripts/lint-plugins.mjs: green at the tip.Review history
Verdict: INC at 4127a88941d97447f4cf24854914977ecca8bbfe(comment6875368045) asked for the cross-session coverage;beaae3dd(Birfy's rework commit, tests only) added it. A Product probe on4127a88found theretried-prune defect; it is fixed in this commit, on top ofbeaae3dd, and the earlier conclusion is superseded by this tip.No client bundle regeneration
No
core/file and nopatches/**file changes.plugins/social/client.jsis a hand-written classic script (the plugin'sdsh.cliententry), nothing generates it, and a plugin cannot import core modules, so the module-level map is repeated inside the plugin rather than shared.build-client --checkandds-bot-patches check/canonicalare green at the tip, and no generated file appears in the diff.Not run: the smoke test (no
core/index.jsorcore/src/host/change); a live Bot Social server (this change touches no network path).Checkpoint
{
"pr": 3084,
"claim": "Dev-5",
"issue": 3082,
"summary": "A chat's dock is session-scoped, so a failed send's record and the ids whose Retry already went out died with the component; they now live in a module-level Map keyed : that the dock subscribes to. The frame that takes a decided card off both lists also pruned a record carrying a spent Retry, so the poll that writes that hold back as failed sent a second social/decide-approval; prune now skips a spent Retry, bounded to the 50 most recent, and still deletes a failure-only record. Tests cover that poll boundary, the reverse, and a second session.",
"files": [
"plugins/social/client.js",
"plugins/social/test/approval-dock-memory.test.js",
"docs/subsystems/social.md",
"changelog.d/3082-chat-switch-dock-failures.changed.md"
],
"checks": {
"ran": "TZ=UTC npm run verify; node core/scripts/build-client.mjs --check; node scripts/ds-bot-patches.mjs check; node scripts/ds-bot-patches.mjs canonical",
"result": "green",
"at": "f56aa4163d2b05f0f04efdf681989095f871d243"
},
"next": "none — ready for review"
}