Deleting a bag over MCP moves every figure, the items fall into the unassigned pile, yet it sent no totals ping; it pings the room now like REST and the plugin RPC do. The client refetches totals after a room re-join and when coming back online, through the same coalescing window. The plugin SDK types learn the create fields the routes accept since #2154, on both hand-kept declarations.
An adversarial review of the diff found sixteen defects that survived
verification. The substantive ones:
- The new `packing:bag-totals` event was never classified in wsEventPolicy,
so the registry-parity ratchet was red. It belongs in
HANDLED_OUTSIDE_TRIP_STORE: it carries no state to apply, which is the
point of it.
- The Journey Settings tracks switch called onSaved(), which closes the
dialog. That destroyed the switch the moment it was flipped AND skipped
the unsaved-changes guard, so a title typed but not saved was silently
lost. It refreshes instead.
- Deleting a bag sends no totals ping, yet packing_items.bag_id is
ON DELETE SET NULL: everything in the bag lands in the unassigned pile and
both figures move. The comment claiming otherwise was wrong.
- Only the REST handlers pinged. The MCP tools and the plugin RPC are two
more live write paths, and since the displayed number now comes from the
server rather than from the item events, weights simply stopped updating
for anything written through them.
- Every item write pinged, including checking an item off — the most
frequent packing write there is — and clearing twelve checked items sent
twelve. The ping now fires only when a weight-relevant field changed, and
the clients coalesce bursts and never overlap two refetches.
- Bags have no repo and no Dexie table, so offline the server totals froze
at the last online read and went blind to the mutation queue. A stale
absolute number measured against an airline limit is worse than an honest
partial one, so offline the surfaces sum what they can see.
- SIGTERM arriving mid-bootstrap called server.close() on undefined, and the
fire-and-forget promise turned that into an unhandled rejection: on Node 22
a crash, which is the opposite of the fix.
- Promise.allSettled swallowed a rejected nestApp.close(), reporting a clean
exit 0 while the crons and the plugin children were never torn down. This
bug class is diagnosed from container logs.
Also: the track fetch gate read a stale `current` during journey-to-journey
navigation and fetched the previous journey's tracks; listBags ran the weight
aggregate twice per request, on a route the ping now fires constantly; the
grand total counted unassigned weight whose row was not rendered; the client
declared show_trip_tracks boolean while the wire carries SQLite 0|1; and the
six broadcast calls had no test, so removing all of them left the suite green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5VwmiBQ9sPCJwfmpv64r3