diff --git a/work-items/open/20260601-013132-tui-new-session-first-message-missing/artifacts/investigation-20260601.md b/work-items/open/20260601-013132-tui-new-session-first-message-missing/artifacts/investigation-20260601.md new file mode 100644 index 00000000..64437339 --- /dev/null +++ b/work-items/open/20260601-013132-tui-new-session-first-message-missing/artifacts/investigation-20260601.md @@ -0,0 +1,63 @@ +## Investigation summary + +Read-only investigation found that the most likely cause is live-event ordering between `Event::UserMessage` and the first `Event::SegmentRotated` in a fresh session. + +Observed flow: + +1. TUI submit path calls `App::submit_input()` / `method_for_run()` and sends `Method::Run { input }`. +2. `crates/pod/src/controller.rs` handles `Method::Run` and currently broadcasts `Event::UserMessage { segments }` before calling `pod.run(input)`. +3. TUI receives `Event::UserMessage` and appends a `TurnHeader` / `UserMessage` block. +4. `Pod::run()` calls `prepare_for_run()` / `ensure_segment_head()`. +5. In a fresh session, `entries_written == 0`, so `LogEntry::SegmentStart` is committed only at this point. +6. `crates/pod/src/segment_log_sink.rs` converts `SegmentStart` into `Event::SegmentRotated`. +7. TUI handles `SegmentRotated` by clearing blocks through `reset_for_rotation()` and replaying `SegmentStart.history`. +8. The current `UserInput` has not been committed yet, so `SegmentStart.history` does not contain the first user message. +9. Later `LogEntry::UserInput` is committed, but the live path does not emit `Event::UserMessage` from that commit, so the cleared user block is not restored in the live TUI view. + +Likely affected files / functions: + +- `crates/tui/src/app.rs` + - `App::submit_input()` + - `method_for_run()` + - `App::handle_pod_event(Event::UserMessage)` + - `App::handle_pod_event(Event::SegmentRotated)` + - `reset_for_rotation()` +- `crates/pod/src/controller.rs` + - `Method::Run` branch +- `crates/pod/src/pod.rs` + - `Pod::run()` + - `prepare_for_run()` + - `ensure_segment_head()` +- `crates/pod/src/segment_log_sink.rs` + - session-log-derived live event conversion + +## Implementation intent + +Move the live `Event::UserMessage` authority away from the controller's pre-`pod.run()` optimistic broadcast and toward the persisted `LogEntry::UserInput` commit path. + +Preferred shape: + +- Remove the controller-side `Event::UserMessage` broadcast that happens before `pod.run(input)`. +- Emit `Event::UserMessage` when `LogEntry::UserInput` is committed. +- Prefer doing this in the session-log-derived event lane, such as `SegmentLogSink`, so `SegmentStart`, `UserInput`, and other replayable entries share a single ordering source. + +This should make a fresh session produce `SegmentRotated` first and then `UserMessage` for the committed input, preserving the first user message in the TUI after rotation. + +## Requirements / invariants for implementation + +- Do not fix this by adding a TUI-only fake/pending user block after rotation. +- Do not reintroduce local optimistic user blocks in `App::method_for_run()`. +- The displayed message should correspond to `LogEntry::UserInput` / persisted history. +- Existing session attach/restore must continue to replay `UserInput` from `Snapshot.entries`. +- 2nd and later sends must continue to display normally. +- Running-state queued input should display when the queued run is actually accepted/committed, not merely when typed. +- Composer input history should remain TUI-local and unaffected. + +## Suggested validation + +- Add or update a Pod/session-log event ordering test for a fresh session's first `Method::Run`, asserting the live event stream makes the committed user message visible after the initial segment rotation. +- Add or update TUI app/view-model tests if available: + - Current problematic ordering `UserMessage` then `SegmentRotated` clears the block. + - Corrected ordering `SegmentRotated` then `UserMessage` leaves the block visible. +- Confirm snapshot restore still creates user blocks from `LogEntry::UserInput`. +- Run focused tests for `pod` / `tui` crates as appropriate, plus formatting/checks that are reasonably scoped to the touched code. diff --git a/work-items/open/20260601-013132-tui-new-session-first-message-missing/item.md b/work-items/open/20260601-013132-tui-new-session-first-message-missing/item.md index 6599d246..bb81bf2a 100644 --- a/work-items/open/20260601-013132-tui-new-session-first-message-missing/item.md +++ b/work-items/open/20260601-013132-tui-new-session-first-message-missing/item.md @@ -7,7 +7,7 @@ kind: bug priority: P1 labels: [tui, session, display] created_at: 2026-06-01T01:31:32Z -updated_at: 2026-06-01T01:31:32Z +updated_at: 2026-06-01T02:00:33Z assignee: null legacy_ticket: null --- diff --git a/work-items/open/20260601-013132-tui-new-session-first-message-missing/thread.md b/work-items/open/20260601-013132-tui-new-session-first-message-missing/thread.md index e23c4684..7394664e 100644 --- a/work-items/open/20260601-013132-tui-new-session-first-message-missing/thread.md +++ b/work-items/open/20260601-013132-tui-new-session-first-message-missing/thread.md @@ -4,4 +4,75 @@ Created by tickets.sh create. +--- + + + +## Plan + +## Investigation summary + +Read-only investigation found that the most likely cause is live-event ordering between `Event::UserMessage` and the first `Event::SegmentRotated` in a fresh session. + +Observed flow: + +1. TUI submit path calls `App::submit_input()` / `method_for_run()` and sends `Method::Run { input }`. +2. `crates/pod/src/controller.rs` handles `Method::Run` and currently broadcasts `Event::UserMessage { segments }` before calling `pod.run(input)`. +3. TUI receives `Event::UserMessage` and appends a `TurnHeader` / `UserMessage` block. +4. `Pod::run()` calls `prepare_for_run()` / `ensure_segment_head()`. +5. In a fresh session, `entries_written == 0`, so `LogEntry::SegmentStart` is committed only at this point. +6. `crates/pod/src/segment_log_sink.rs` converts `SegmentStart` into `Event::SegmentRotated`. +7. TUI handles `SegmentRotated` by clearing blocks through `reset_for_rotation()` and replaying `SegmentStart.history`. +8. The current `UserInput` has not been committed yet, so `SegmentStart.history` does not contain the first user message. +9. Later `LogEntry::UserInput` is committed, but the live path does not emit `Event::UserMessage` from that commit, so the cleared user block is not restored in the live TUI view. + +Likely affected files / functions: + +- `crates/tui/src/app.rs` + - `App::submit_input()` + - `method_for_run()` + - `App::handle_pod_event(Event::UserMessage)` + - `App::handle_pod_event(Event::SegmentRotated)` + - `reset_for_rotation()` +- `crates/pod/src/controller.rs` + - `Method::Run` branch +- `crates/pod/src/pod.rs` + - `Pod::run()` + - `prepare_for_run()` + - `ensure_segment_head()` +- `crates/pod/src/segment_log_sink.rs` + - session-log-derived live event conversion + +## Implementation intent + +Move the live `Event::UserMessage` authority away from the controller's pre-`pod.run()` optimistic broadcast and toward the persisted `LogEntry::UserInput` commit path. + +Preferred shape: + +- Remove the controller-side `Event::UserMessage` broadcast that happens before `pod.run(input)`. +- Emit `Event::UserMessage` when `LogEntry::UserInput` is committed. +- Prefer doing this in the session-log-derived event lane, such as `SegmentLogSink`, so `SegmentStart`, `UserInput`, and other replayable entries share a single ordering source. + +This should make a fresh session produce `SegmentRotated` first and then `UserMessage` for the committed input, preserving the first user message in the TUI after rotation. + +## Requirements / invariants for implementation + +- Do not fix this by adding a TUI-only fake/pending user block after rotation. +- Do not reintroduce local optimistic user blocks in `App::method_for_run()`. +- The displayed message should correspond to `LogEntry::UserInput` / persisted history. +- Existing session attach/restore must continue to replay `UserInput` from `Snapshot.entries`. +- 2nd and later sends must continue to display normally. +- Running-state queued input should display when the queued run is actually accepted/committed, not merely when typed. +- Composer input history should remain TUI-local and unaffected. + +## Suggested validation + +- Add or update a Pod/session-log event ordering test for a fresh session's first `Method::Run`, asserting the live event stream makes the committed user message visible after the initial segment rotation. +- Add or update TUI app/view-model tests if available: + - Current problematic ordering `UserMessage` then `SegmentRotated` clears the block. + - Corrected ordering `SegmentRotated` then `UserMessage` leaves the block visible. +- Confirm snapshot restore still creates user blocks from `LogEntry::UserInput`. +- Run focused tests for `pod` / `tui` crates as appropriate, plus formatting/checks that are reasonably scoped to the touched code. + + ---