3139a30e52
* fix(desktop): evict settled session states nothing on screen references Closing a tile never removed its runtime's entry from $sessionStates, so every tile ever closed parked its full transcript in the map for the life of the process. Each leftover entry taxes every subsequent stream flush — the map is spread-copied per delta and the busy/attention/draft projections walk every entry per publish — so the app got slower the longer it ran, which users read as "I need to clean my sessions/dbs". Publish now evicts a settling state when no tile and not the primary view holds its runtime (transition side effects still fire, so the settle keeps its unread dot), and closing a tile drops an already-settled state on the spot. Busy and needs-input states stay: background turns feed the sidebar dots, and a first publish always lands because a resume can publish a beat before the surface binds the runtime. 16 tiles streaming in a 2x2 grid with a day's worth of closed-tile residue: worst-second 34 -> 58 fps, p99 frame 90 -> 28 ms, longtasks 37 -> 0. * perf(desktop): index lineage aliases per sessions-list reference lineageAliases scanned the whole recents list per call, and it is called per cached session state per status projection per message delta — with a populated sessions DB and a few busy sessions that multiplied out to millions of row checks a second during streaming. Build the alias index once per list reference (the list is replaced wholesale, never mutated) and look aliases up in O(1). * perf(desktop): journal each in-flight turn under its own storage key The v1 journal kept every session's tail in one localStorage key, so each throttled write re-parsed and re-stringified EVERY busy session's snapshot — a grid of concurrent streams turned that into a whole-store JSON round trip dozens of times a second, all on the main thread. Per-session keys make a write O(own tail) no matter how many other sessions are streaming. A v1 store migrates on first touch; expired/overflow crash residue is pruned once per renderer. * perf(desktop): stress the multitab scenario across grid/streaming/DB axes The one-stack multitab run hid every cost this round of fixes removed: it drove hook.publish (store only — no journal, no wiring cache), with an empty recents list and no closed-tile residue. Streaming now routes through hook.update (the real gateway write path), and the scenario grows axes for the workloads users actually hit: --zones splits tiles across visible grid zones, --streaming caps how many sessions are mid-turn (zone leaders first), --sessions seeds a lived-in recents list, --dead models settled sessions no surface references. launch.mjs pins HERMES_DESKTOP_CDP_PORT so a non-default --port survives the app's own dev-CDP flag.