fix(packing): a bag weighs what everyone put in it, not just what you can see

A bag's weight was summed in the browser over the item list the viewer is
allowed to see. Personal items belonging to other members are filtered out of
that list by design (#858), so their weight vanished with them: two people
looking at the same shared bag saw two different totals, and neither saw the
real one.

That number is then measured against `weight_limit_grams`, an absolute airline
limit. A per-viewer subtotal there is not merely incomplete but wrong — a
shared bag could sit over its limit and warn nobody.

The sum now happens in SQL over every row and ships as `total_weight_grams` on
the bag, with `unassigned_weight_grams` alongside so the "no bag" pile and the
grand total follow the same rule; a screen mixing true totals with per-viewer
ones would be worse than either. Only integers cross the wire: a member learns
that a bag is heavier than the items they can see, never a name, category,
quantity or owner. That disclosure is deliberate and bounded, and it is what
the issue asks for.

The item lists stay viewer-scoped, so #1767 ("someone shared it with you, but
they are the one bringing it") is unchanged — it just no longer decides what a
bag weighs.

Totals also had to stay live, because the reported repro is two people looking
at the same screen. A private item's event reaches only its owner, which is the
very rule that made the totals wrong, so item writes now also ping the room
with a content-free `packing:bag-totals` and everyone re-reads the numbers.

All three surfaces — the bag sidebar, the bag modal and the mobile sheet — read
the server figure and fall back to the local sum only for a bag cached offline
before this change.

Closes #2191

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5VwmiBQ9sPCJwfmpv64r3
This commit is contained in:
Claude
2026-09-02 12:01:28 +00:00
committed by Maurice
parent b7f9c1e8ae
commit 639c40f8de
14 changed files with 301 additions and 41 deletions
+11
View File
@@ -114,6 +114,17 @@ export const TREK_WS_EVENTS = {
payload: z.object({ category: z.string(), assignees: z.array(z.unknown()) }),
},
'packing:template-applied': { scope: 'trip', payload: z.object({ items: z.array(z.unknown()) }) },
/**
* "A bag's weight may have changed" — deliberately content-free (#2191).
*
* Bag totals are summed server-side across every member's items, so a private
* item nobody else may see still moves the number everybody else reads. The
* item events themselves cannot carry that: a private one is delivered only
* to its owner, which is exactly the privacy rule that made the totals wrong
* in the first place. This ping goes to the whole room and says nothing at
* all; receivers refetch the bags and get numbers, never contents.
*/
'packing:bag-totals': { scope: 'trip', payload: z.object({}) },
// ── Todo ─────────────────────────────────────────────────────────────────
'todo:created': { scope: 'trip', payload: z.object({ item: entity }) },