Commit Graph

2 Commits

Author SHA1 Message Date
Claude 98b97edb00 fix(packing,journey,server): address review findings on the three fixes
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
2026-09-02 16:21:50 +02:00
Claude 5e5fe288d8 fix(server): release sockets on shutdown so docker stop no longer ends in SIGKILL
Every `docker stop` of a TREK instance with a browser attached exited 137,
exactly ten seconds after the stop.

`http.Server.close()` stops new connections and then waits for the open ones
to end by themselves — and a WebSocket never does. TREK holds one per open
tab, so the close callback never ran. `closeDb()` and `process.exit(0)` lived
inside that callback, which left the only remaining exit a fallback timer set
to 10000ms: exactly when Docker sends SIGKILL. Docker starts counting when it
sends the signal and Node only once the handler is dispatched, so that timer
started strictly later and could never win. It was unreachable code in every
default deployment.

Waiting for Nest was not an option either: `WsAdapter.close()` is the one
place that terminates clients, and Nest reaches it only after every
onModuleDestroy hook has resolved — including the plugin supervisor's SIGTERM
grace for its forked children. The sockets holding the server open would be
hostage to the slowest hook in the container.

So the sockets are now released deliberately: ws clients get 1001 "going
away", idle keep-alive sockets are dropped at once, and anything still
attached after a 2s drain is destroyed. Nest is closed alongside that, the
database is closed on both exit paths rather than only the unreachable one,
and the fallback fires at 5s — inside Docker's grace, so a forced exit is
still ours.

The sequence moved into src/shutdown.ts behind injected dependencies. It was
untestable by construction before, which is part of why this went unnoticed;
it now has nine tests, including the signal that beats the bootstrap.

Closes #2193

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5VwmiBQ9sPCJwfmpv64r3
2026-09-02 16:21:50 +02:00