- Added transcriptMessageComponents to every InteractiveMode ctx test literal; #6033's reuse cache made the field required and addMessageToChat populates it unconditionally.
- Wired setTodoPhases into the eager-todo ToolSession fixture to mirror sdk.ts; the test relied on the stale message_end todo replay #6148 removed.
UiHelpers.renderInitialMessages() reads the module-level settings
singleton (display.collapseCompacted) added upstream. Tests exercising
it directly must call Settings.init() first or they fail with
"Settings not initialized" whenever no earlier test in the same
process happened to initialize it, which is bucket-order-dependent and
flaky.
- Initialized in-memory settings in the repro issue test by resetting settings before theme setup.
- Reduced the UI/TUI CI test bucket chunk size from 10 to 5 to avoid cumulative Bun GC heap aborts.
Conforms to the repo Bun-over-Node guidance in AGENTS.md: 'NEVER use new Promise(r => setTimeout(r, ms))'. Drains the .then(applyCustomMessageDisplay) microtask chain via 'await Bun.sleep(0)' in the issue #1955 regression test.
ExtensionUiController.#applyCustomMessageDisplay rebuilt the chat from the freshly-persisted session entry as soon as the extension's session_start handler fired sendMessage({display:true}). main.ts then called mode.renderInitialMessages(undefined, {preserveExistingChat:true}), which snapshotted the chat children, cleared, re-rendered from session entries (adding the same custom message again), and re-appended the snapshot — leaving the message visible twice.
Gate the rebuild on a new InteractiveModeContext.initialChatRendered flag that UiHelpers.renderInitialMessages flips on first run. Before the initial transcript render, applyCustomMessageDisplay skips: renderInitialMessages will pick up the new session entry once. After it, the rebuild runs as today, so post-init extension sends (tool_result, agent_end, …) still surface immediately.
Fixes#1955