5fbf319172
The sidebar showed room.lastTs/lastMessage that busService created as 0/"" on every load, so the time rendered as the epoch-0 "01:00" and there was no preview. busService now owns a room store that keeps these fields live. - busService gains a room store: the room list plus a per-room metadata subscription. Since the wire has no message history (NATS delivers live only), staying subscribed to every room is the only way to know each room's latest message and to count unread for rooms the user is not viewing. Each delivered message updates the room's lastTs, lastMessage (a "name: body" preview, truncated, reusing the directory resolver) and bumps unread when the room is not active. - New surface: watchRooms(listener) to mirror the store into React, loadRooms() to (re)populate and subscribe, setActiveRoom(id) to clear a room's unread. createRoom adds the new room to the store and, because its data-plane refresh() drops all subscriptions, re-subscribes every room. subscribeRoom now shares the same decode core (subscribeRoomInternal) used by the metadata subscription. The store is reset on login/logout. - ChatShell mirrors the store via watchRooms instead of a one-shot listRooms, selects a room through setActiveRoom (clearing its unread), and auto-selects the first room once the list loads. - Sidebar: timeShort renders an em dash for a room with no message yet instead of the epoch-0 "01:00". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>