B1 · Collapsible digest
A typed, capped "Updates" digest forms the top of the activity card: one line per message, tap to read in full, and it folds to a single line of type counts with the Ask and Blocked rows pinned.
session_switched path.#1● Finding00:48The sidebar store already imports subscribeSessionDetail. Only its switch handler still goes through session_switched.
#2● Finding01:42session_switched has three consumers, not one: MessageList history, the sidebar unread counter and the push deep link. The plan only lists the first.
#3± Scope03:10That makes this bigger than planned: the unread counter and the deep-link handler both have to move to subscribeSessionDetail too, or they break when the legacy path goes.
#4● Finding04:25ws-rpc.ts has typed handlers for detail and history, but none for the summaries the sidebar lists.
#5◆ Decision06:55I'm keeping session_switched behind CONDUIT_LEGACY_SWITCH=1 for one release instead of deleting it, so a bad rollout can be reverted without a deploy.
#6✓ Milestone08:02CONDUIT_LEGACY_SWITCH is wired in runtime.ts, off by default, with unit tests for both paths.
#7✕ Blocked09:20pnpm check fails at ws-rpc.ts:1368: the ViewSession schema lives on feat/ni8-transport, which isn't merged. I can't finish the sidebar without it.
#8↻ Replan09:48Changing order: I'll rebase onto feat/ni8-transport locally and build on top, rather than stubbing the schema and redoing it later.
#9● Finding11:15feat/ni8-transport rebases cleanly apart from one conflict in contracts/session.ts.
#10✓ Milestone12:40Rebased onto feat/ni8-transport. pnpm check is clean again.
#11↶ Correction14:30Correction: the deep link doesn't read session_switched directly. It goes through history-logic.ts:178, so there's one consumer fewer than I said at 01:42.
#12● Finding15:55MessageList replays history from the event store, so it needs no ordering guarantee from the new stream.
#13◆ Decision17:20Moving the unread counter into its own store rather than patching sidebar.svelte.ts, so tests can drive it without a socket.
#14! Risk19:05The unread counter now subscribes per session. With 293 sessions that's 293 open streams per tab on reconnect, which could exhaust the daemon's socket budget.
#15● Finding20:30The daemon caps each tab at 256 streams, so past that the extra subscriptions would fail without an error.
#16◆ Decision22:40I'm batching unread subscriptions into one subscribeSessionSummaries stream instead of adding a cap, because a cap would silently drop badges.
#17✕ Blocked24:10subscribeSessionSummaries needs a projector the event store doesn't have yet. Adding one before I go on.
#18● Finding25:30Summaries can be projected from the existing session_status rows, so no migration is needed.
#19◆ Decision26:05Keeping per-session subscriptions as the fallback when a daemon predates subscribeSessionSummaries.
#20✓ Milestone27:15Sidebar and MessageList are on the typed stream. 523/523 unit files pass, 6,832 tests green.
#21↶ Correction28:20Correction: the first run was 522/523 files, because session-list-triage.test.ts had been skipped. It passes now, so 523/523 stands.
#22● Finding29:40phone-sidebar is the only visual baseline that changed. The other 41 match exactly.
#23✓ Milestone30:10pnpm check and pnpm lint are clean across the branch.
#24? Ask31:02The phone sidebar baseline shifts 2px because the unread badge now paints before the first frame. Recapture it, or keep the old paint order? I'll carry on with the deep link meanwhile.needs reply
#25● Finding32:15iOS restores the page from the back-forward cache, so the deep-link handler runs before the socket reconnects.
#26● Finding33:40The push deep link opens the session before its history loads. That's why it flashed empty on iOS before this change too.
#27! Risk34:50Waiting for history before opening the deep link adds about 180ms on a cold start. Acceptable, but visible.
#28✓ Milestone35:30The deep link now waits for historyReady. The empty flash is gone in the replay test.
#29● Finding36:10sidebar.page.ts still selects rows by the legacy data-switch-id. Updating the page object.
#30✓ Milestone36:55Deep link migrated. pnpm acceptance:visual is green except phone-sidebar, which is waiting on your answer.
session_switched path.#1● Finding00:48The sidebar store already imports subscribeSessionDetail. Only its switch handler still goes through session_switched.
#2● Finding01:42session_switched has three consumers, not one: MessageList history, the sidebar unread counter and the push deep link. The plan only lists the first.
#3± Scope03:10That makes this bigger than planned: the unread counter and the deep-link handler both have to move to subscribeSessionDetail too, or they break when the legacy path goes.
#4● Finding04:25ws-rpc.ts has typed handlers for detail and history, but none for the summaries the sidebar lists.
#5◆ Decision06:55I'm keeping session_switched behind CONDUIT_LEGACY_SWITCH=1 for one release instead of deleting it, so a bad rollout can be reverted without a deploy.
#6✓ Milestone08:02CONDUIT_LEGACY_SWITCH is wired in runtime.ts, off by default, with unit tests for both paths.
#7✕ Blocked09:20pnpm check fails at ws-rpc.ts:1368: the ViewSession schema lives on feat/ni8-transport, which isn't merged. I can't finish the sidebar without it.
#8↻ Replan09:48Changing order: I'll rebase onto feat/ni8-transport locally and build on top, rather than stubbing the schema and redoing it later.
#9● Finding11:15feat/ni8-transport rebases cleanly apart from one conflict in contracts/session.ts.
#10✓ Milestone12:40Rebased onto feat/ni8-transport. pnpm check is clean again.
#11↶ Correction14:30Correction: the deep link doesn't read session_switched directly. It goes through history-logic.ts:178, so there's one consumer fewer than I said at 01:42.
#12● Finding15:55MessageList replays history from the event store, so it needs no ordering guarantee from the new stream.
#13◆ Decision17:20Moving the unread counter into its own store rather than patching sidebar.svelte.ts, so tests can drive it without a socket.
#14! Risk19:05The unread counter now subscribes per session. With 293 sessions that's 293 open streams per tab on reconnect, which could exhaust the daemon's socket budget.
#15● Finding20:30The daemon caps each tab at 256 streams, so past that the extra subscriptions would fail without an error.
#16◆ Decision22:40I'm batching unread subscriptions into one subscribeSessionSummaries stream instead of adding a cap, because a cap would silently drop badges.
#17✕ Blocked24:10subscribeSessionSummaries needs a projector the event store doesn't have yet. Adding one before I go on.
#18● Finding25:30Summaries can be projected from the existing session_status rows, so no migration is needed.
#19◆ Decision26:05Keeping per-session subscriptions as the fallback when a daemon predates subscribeSessionSummaries.
#20✓ Milestone27:15Sidebar and MessageList are on the typed stream. 523/523 unit files pass, 6,832 tests green.
#21↶ Correction28:20Correction: the first run was 522/523 files, because session-list-triage.test.ts had been skipped. It passes now, so 523/523 stands.
#22● Finding29:40phone-sidebar is the only visual baseline that changed. The other 41 match exactly.
#23✓ Milestone30:10pnpm check and pnpm lint are clean across the branch.
#24? Ask31:02The phone sidebar baseline shifts 2px because the unread badge now paints before the first frame. Recapture it, or keep the old paint order? I'll carry on with the deep link meanwhile.needs reply
#25● Finding32:15iOS restores the page from the back-forward cache, so the deep-link handler runs before the socket reconnects.
#26● Finding33:40The push deep link opens the session before its history loads. That's why it flashed empty on iOS before this change too.
#27! Risk34:50Waiting for history before opening the deep link adds about 180ms on a cold start. Acceptable, but visible.
#28✓ Milestone35:30The deep link now waits for historyReady. The empty flash is gone in the replay test.
#29● Finding36:10sidebar.page.ts still selects rows by the legacy data-switch-id. Updating the page object.
#30✓ Milestone36:55Deep link migrated. pnpm acceptance:visual is green except phone-sidebar, which is waiting on your answer.
#31● Finding39:05pnpm acceptance:visual finished: 41 of 42 baselines match, and phone-sidebar is the one.
#32◆ Decision40:20Leaving phone-sidebar unrecaptured until you answer. Everything else is committed on feat/typed-session-list.
#33◆ Decision42:10The flag check stays in runtime.ts, not the store, so removing it next release is a one-file change.
#34✓ Milestone43:50Branch pushed. The legacy path is off by default and can be removed next release.
›Worked44m 30s · $6.80
Done. The session list, unread badges and push deep links now run on the typed stream. session_switched survives behind CONDUIT_LEGACY_SWITCH=1 for one release. One open question: recapture phone-sidebar (a 2px shift), or keep the old paint order?
session_switched path.#1● Finding00:48The sidebar store already imports subscribeSessionDetail. Only its switch handler still goes through session_switched.
#2● Finding01:42session_switched has three consumers, not one: MessageList history, the sidebar unread counter and the push deep link. The plan only lists the first.
#3± Scope03:10That makes this bigger than planned: the unread counter and the deep-link handler both have to move to subscribeSessionDetail too, or they break when the legacy path goes.
#4● Finding04:25ws-rpc.ts has typed handlers for detail and history, but none for the summaries the sidebar lists.
#5◆ Decision06:55I'm keeping session_switched behind CONDUIT_LEGACY_SWITCH=1 for one release instead of deleting it, so a bad rollout can be reverted without a deploy.
#6✓ Milestone08:02CONDUIT_LEGACY_SWITCH is wired in runtime.ts, off by default, with unit tests for both paths.
#7✕ Blocked09:20pnpm check fails at ws-rpc.ts:1368: the ViewSession schema lives on feat/ni8-transport, which isn't merged. I can't finish the sidebar without it.
#8↻ Replan09:48Changing order: I'll rebase onto feat/ni8-transport locally and build on top, rather than stubbing the schema and redoing it later.
#9● Finding11:15feat/ni8-transport rebases cleanly apart from one conflict in contracts/session.ts.
#10✓ Milestone12:40Rebased onto feat/ni8-transport. pnpm check is clean again.
#11↶ Correction14:30Correction: the deep link doesn't read session_switched directly. It goes through history-logic.ts:178, so there's one consumer fewer than I said at 01:42.
#12● Finding15:55MessageList replays history from the event store, so it needs no ordering guarantee from the new stream.
#13◆ Decision17:20Moving the unread counter into its own store rather than patching sidebar.svelte.ts, so tests can drive it without a socket.
#14! Risk19:05The unread counter now subscribes per session. With 293 sessions that's 293 open streams per tab on reconnect, which could exhaust the daemon's socket budget.
#15● Finding20:30The daemon caps each tab at 256 streams, so past that the extra subscriptions would fail without an error.
#16◆ Decision22:40I'm batching unread subscriptions into one subscribeSessionSummaries stream instead of adding a cap, because a cap would silently drop badges.
#17✕ Blocked24:10subscribeSessionSummaries needs a projector the event store doesn't have yet. Adding one before I go on.
#18● Finding25:30Summaries can be projected from the existing session_status rows, so no migration is needed.
#19◆ Decision26:05Keeping per-session subscriptions as the fallback when a daemon predates subscribeSessionSummaries.
#20✓ Milestone27:15Sidebar and MessageList are on the typed stream. 523/523 unit files pass, 6,832 tests green.
#21↶ Correction28:20Correction: the first run was 522/523 files, because session-list-triage.test.ts had been skipped. It passes now, so 523/523 stands.
#22● Finding29:40phone-sidebar is the only visual baseline that changed. The other 41 match exactly.
#23✓ Milestone30:10pnpm check and pnpm lint are clean across the branch.
#24? Ask31:02The phone sidebar baseline shifts 2px because the unread badge now paints before the first frame. Recapture it, or keep the old paint order? I'll carry on with the deep link meanwhile.needs reply
#25● Finding32:15iOS restores the page from the back-forward cache, so the deep-link handler runs before the socket reconnects.
#26● Finding33:40The push deep link opens the session before its history loads. That's why it flashed empty on iOS before this change too.
#27! Risk34:50Waiting for history before opening the deep link adds about 180ms on a cold start. Acceptable, but visible.
#28✓ Milestone35:30The deep link now waits for historyReady. The empty flash is gone in the replay test.
#29● Finding36:10sidebar.page.ts still selects rows by the legacy data-switch-id. Updating the page object.
#30✓ Milestone36:55Deep link migrated. pnpm acceptance:visual is green except phone-sidebar, which is waiting on your answer.
session_switched path.#1● Finding00:48The sidebar store already imports subscribeSessionDetail. Only its switch handler still goes through session_switched.
#2● Finding01:42session_switched has three consumers, not one: MessageList history, the sidebar unread counter and the push deep link. The plan only lists the first.
#3± Scope03:10That makes this bigger than planned: the unread counter and the deep-link handler both have to move to subscribeSessionDetail too, or they break when the legacy path goes.
#4● Finding04:25ws-rpc.ts has typed handlers for detail and history, but none for the summaries the sidebar lists.
#5◆ Decision06:55I'm keeping session_switched behind CONDUIT_LEGACY_SWITCH=1 for one release instead of deleting it, so a bad rollout can be reverted without a deploy.
#6✓ Milestone08:02CONDUIT_LEGACY_SWITCH is wired in runtime.ts, off by default, with unit tests for both paths.
#7✕ Blocked09:20pnpm check fails at ws-rpc.ts:1368: the ViewSession schema lives on feat/ni8-transport, which isn't merged. I can't finish the sidebar without it.
#8↻ Replan09:48Changing order: I'll rebase onto feat/ni8-transport locally and build on top, rather than stubbing the schema and redoing it later.
#9● Finding11:15feat/ni8-transport rebases cleanly apart from one conflict in contracts/session.ts.
#10✓ Milestone12:40Rebased onto feat/ni8-transport. pnpm check is clean again.
#11↶ Correction14:30Correction: the deep link doesn't read session_switched directly. It goes through history-logic.ts:178, so there's one consumer fewer than I said at 01:42.
#12● Finding15:55MessageList replays history from the event store, so it needs no ordering guarantee from the new stream.
#13◆ Decision17:20Moving the unread counter into its own store rather than patching sidebar.svelte.ts, so tests can drive it without a socket.
#14! Risk19:05The unread counter now subscribes per session. With 293 sessions that's 293 open streams per tab on reconnect, which could exhaust the daemon's socket budget.
#15● Finding20:30The daemon caps each tab at 256 streams, so past that the extra subscriptions would fail without an error.
#16◆ Decision22:40I'm batching unread subscriptions into one subscribeSessionSummaries stream instead of adding a cap, because a cap would silently drop badges.
#17✕ Blocked24:10subscribeSessionSummaries needs a projector the event store doesn't have yet. Adding one before I go on.
#18● Finding25:30Summaries can be projected from the existing session_status rows, so no migration is needed.
#19◆ Decision26:05Keeping per-session subscriptions as the fallback when a daemon predates subscribeSessionSummaries.
#20✓ Milestone27:15Sidebar and MessageList are on the typed stream. 523/523 unit files pass, 6,832 tests green.
#21↶ Correction28:20Correction: the first run was 522/523 files, because session-list-triage.test.ts had been skipped. It passes now, so 523/523 stands.
#22● Finding29:40phone-sidebar is the only visual baseline that changed. The other 41 match exactly.
#23✓ Milestone30:10pnpm check and pnpm lint are clean across the branch.
#24? Ask31:02The phone sidebar baseline shifts 2px because the unread badge now paints before the first frame. Recapture it, or keep the old paint order? I'll carry on with the deep link meanwhile.needs reply
#25● Finding32:15iOS restores the page from the back-forward cache, so the deep-link handler runs before the socket reconnects.
#26● Finding33:40The push deep link opens the session before its history loads. That's why it flashed empty on iOS before this change too.
#27! Risk34:50Waiting for history before opening the deep link adds about 180ms on a cold start. Acceptable, but visible.
#28✓ Milestone35:30The deep link now waits for historyReady. The empty flash is gone in the replay test.
#29● Finding36:10sidebar.page.ts still selects rows by the legacy data-switch-id. Updating the page object.
#30✓ Milestone36:55Deep link migrated. pnpm acceptance:visual is green except phone-sidebar, which is waiting on your answer.
#31● Finding39:05pnpm acceptance:visual finished: 41 of 42 baselines match, and phone-sidebar is the one.
#32◆ Decision40:20Leaving phone-sidebar unrecaptured until you answer. Everything else is committed on feat/typed-session-list.
#33◆ Decision42:10The flag check stays in runtime.ts, not the store, so removing it next release is a one-file change.
#34✓ Milestone43:50Branch pushed. The legacy path is off by default and can be removed next release.
›Worked44m 30s · $6.80
Done. The session list, unread badges and push deep links now run on the typed stream. session_switched survives behind CONDUIT_LEGACY_SWITCH=1 for one release. One open question: recapture phone-sidebar (a 2px shift), or keep the old paint order?
- Builds on
- Round-1 B4, the digest above the log, now foldable and capped so it cannot grow with the turn. It keeps the typed labels, now nine of them, and adds M6's four-stop level as a switch in the digest header.
- Placement
- The digest is the top section of the activity block's own card. The activity head and strip sit under it in the same card, so the turn still has exactly one block. The final reply follows the card, and the composer is unchanged.
- Live
- Rows append at the bottom of the digest, right above the Working head, and the newest row gets a brief green tint. At Balanced it shows the latest 8 rows, plus pinned Ask and Blocked rows, and an "▴ 4 earlier" row. The header reads "13 of 30" and the type counts tick up.
- Settled
- Same digest: Balanced shows 15 of 34, as the last 8 rows plus the pinned rows. The final reply sits directly under the card, never folded.
- Collapsible
- The header caret folds the digest to one line: Updates, count, per-type counts (?1 ✕2 !2 ±1 ↻1 ↶2 ◆6 ●12 ✓7). The Ask and both Blocked rows stay pinned under that line. The latest turn opens and older turns fold. On the phone the latest turn also folds once the turn settles, because the final reply repeats the Ask.
- Short-session-threads
- Desktop, open: a header plus up to 11 rows (8 recent, pins, "earlier") ≈ 12 lines. Folded: 4 lines. Phone: rows are 2 lines each with a cap of 4 plus the pins, so about 15 lines open. Folded is 2 header lines plus 3 pinned rows ≈ 9 lines. Everything sits inside the single activity card.
- Easy-to-read
- 13px text. Labelled chips sit in a fixed column, so the type reads before the text, and times and
#nline up. Tap a row to unclamp its full text in place. The Ask row always shows 2 lines. - Mobile-friendly
- Rows become two lines (number, label and time, then about 46 characters of text) instead of squeezing roughly 20 characters beside the chip. The cap drops to 4, and the counts wrap under the header. Settled phones fold the digest so the reply and composer own the bottom of the screen.
- Dev-Navigation-Ease-And-Speed
- Every row carries its
#n. j/k move and Enter expands. a jumps to the open Ask, and unfolds it, from anywhere in the digest. f folds. #7 is a pinned Blocked, so it is one glance away. An arbitrary Finding like #12 means switching to All, then "earlier", then scanning by number. There is no jump-to-number. - Needs-reply
- The Ask row is tinted, has an accent bar and a "needs reply" pill, is pinned when folded and always shows 2 lines. Blocked rows carry a red bar and are pinned too. Settled, the Ask is also the last question in the final reply.
- Density
- A four-stop switch in the digest header, mirrored by a session-wide default. Final: rows and pins hide, and the header line reads "34 hidden · Final only". Key: 6. Balanced (default): 13 live, 15 settled. All: 30 live, 34 settled.
- At-150
- Balanced is about 65 rows. The digest still shows 8 rows plus pins and "59 earlier", and the counts stay one line. Expanding "earlier" produces a long list: that is the honest cost of a list.
- Build-cost
- S. Needs type, time and a stable number on each surfaced message, plus an "answered" flag for Asks. Everything else is list rendering and a per-turn fold state.
- Trade-off
- It is still a list, so the full history scales with the count. The cap hides the older history behind a tap, and pinned Blocked rows stay pinned after the agent has moved past them.