Skip to content

Conversations timeline refactor: complete Phases 3, 6, and the Phase 1 shell extraction #4615

Description

@senamakel

Summary

Complete the remaining phases of the conversations timeline refactor that were deliberately deferred out of #4612 (which landed Phases 1–2 and 4–5 — the per-turn process history feature end-to-end). Three follow-up bodies of work remain, in priority order:

  1. Phase 3 — streaming/merge consolidation (largest; own PR + manual QA)
  2. Phase 6 — cleanup (mostly coupled to Phase 3)
  3. Phase 1 remainder — shell/context extraction (mechanical)

Design and rationale: docs/plans/conversations-timeline-refactor.md (Phases 3, 6, and the Phase 1 shell-extraction items) and docs/plans/per-turn-tool-timeline-history.md.

Problem / Context

#4612 delivered the user-visible feature (each answer keeps its own tool-timeline trail) but intentionally stopped short of the internal-robustness refactor. ChatRuntimeProvider.tsx (~1,664 lines) still merges ~28 snake_case socket events via store.getState() + find-row + full-array-replace, using two parallel dedup mechanisms with divergent row-id schemes, plus the preserveLiveSubagentProse graft (a documented live-only-state workaround). This is the "buggy in how it stores & streams" dimension from the audit.

Why it was deferred: Phase 3 rewrites the live streaming path. The plan explicitly flags its dedup changes as risk-sensitive ("keep the TTL-equivalent dedup in the reducer until Phase 4 lands; do not delete early") and calls for manual QA after Phases 3 and 5 — live streaming behavior that automated tests alone don't fully cover. It belongs in its own reviewed PR rather than bundled with the feature work.

Scope

Phase 3 — reducer-side merge/dedup consolidation

Move all merge logic from ChatRuntimeProvider into typed chatRuntimeSlice reducers so the provider shrinks to parse-and-dispatch (~300 lines), with no getState() and no full-array rebuilds.

  • One typed action per event family: toolEventReceived, subagentEventReceived, textDeltaReceived, turnLifecycleReceived. Migrate handler bodies (onToolCall/onToolResult at ChatRuntimeProvider.tsx:601/:665, the subagent_* handlers, onTextDelta, lifecycle) into reducers.
  • One dedup mechanism, reducer-level, on a unified row-id scheme ${threadId}:${requestId}:${kind}:${callId|taskId|streamId} — used identically by live handlers, hydrateRuntimeFromSnapshot, and hydrateRuntimeFromRunLedger. Kills the live-vs-ledger id divergence.
  • Replace preserveLiveSubagentProse (chatRuntimeSlice.ts) with an ordinary field-level upsert: persisted fields win for settled rows, live fields win for streaming fields.
  • Move the provider-local seenChatEventsRef (TTL map) and segmentDeliveriesRef into slice state (per-request segment state, cleared on chat_done).
  • Verbose debug logging on every dedup drop / merge decision (repo convention).
  • Deferred within Phase 3 / separate step: the backend seq envelope — progress_bridge.rs stamps a per-request monotonic seq: u64 + request_id on every event; reducers dedup by (requestId, seq); persist seq on PersistedToolTimelineEntry (turn_state/types.rs + app/src/types/turnState.ts). Until it lands, the TTL-map dedup moves verbatim into the reducer as the interim mechanism.

Phase 6 — cleanup

  • Delete the app/src/pages/Conversations.tsx re-export shim; point HumanPage/Accounts and the co-located test suites at features/conversations.
  • Delete dead state now subsumed: parallelStreamsByThread (if subsumed by streamingText items), preserveLiveSubagentProse, the provider refs removed in Phase 3.

Phase 1 remainder — shell/context extraction

  • Extract ThreadList, Composer/MicCloudComposer/useComposerState, and the page/sidebar shells out of features/conversations/Conversations.tsx, sharing thread/selection state via a ConversationsContext to avoid prop-drilling. All files ≤ ~500 lines.

Out of scope

Acceptance criteria

  • Phase 3 reducers — merge/dedup logic lives in typed chatRuntimeSlice reducers; ChatRuntimeProvider is parse-and-dispatch only (no getState(), no full-array rebuilds).
  • Unified dedup + row ids — a single reducer-level dedup on ${threadId}:${requestId}:${kind}:${callId|taskId|streamId}, used identically by live handlers and both hydration paths; preserveLiveSubagentProse replaced by a field-level upsert.
  • seq envelopeprogress_bridge.rs stamps (request_id, seq); reducers dedup by it and drop replayed seq <= lastSeq; seq persisted on PersistedToolTimelineEntry (Rust + TS).
  • Phase 6 cleanuppages/Conversations.tsx shim deleted and consumers repointed; dead state removed.
  • Phase 1 shellsThreadList / Composer / MicCloudComposer / useComposerState / page+sidebar shells extracted behind ConversationsContext.
  • Guardrail testsChatRuntimeProvider.test.tsx (assertions rewritten to dispatched actions), chatRuntimeSlice.test.ts + .toolFailure.test.ts, SubagentDrawer.test.tsx, plus new: duplicate-event replay is a no-op; live subagent prose survives snapshot hydration; reconnect-replay simulation.
  • Manual QA (per plan) — pnpm dev:app: multi-turn thread with tools + subagents; switch tab mid-stream and back; kill/restore socket mid-turn; proactive thread with no user message; hideAgentInsights sidebar variant in HumanPage; app restart on a 5-turn thread — all trails restored, ordered identically to live.
  • Diff coverage ≥ 80% — implementing PR(s) meet the changed-lines coverage gate (Vitest + cargo-llvm-cov, enforced by .github/workflows/ci-lite.yml).

Related

  • Builds on feat(conversations): per-turn process history (timeline refactor Phases 1–2, 4–5) #4612 (Phases 1–2, 4–5 shipped).
  • Plans: docs/plans/conversations-timeline-refactor.md (Phases 3, 6, Phase 1 remainder), docs/plans/per-turn-tool-timeline-history.md.
  • Suggested sequencing: land Phase 3 (reducer consolidation with interim TTL dedup) → the seq envelope → Phase 6 cleanup → Phase 1 shell extraction. Each is an independently shippable, small PR.

Metadata

Metadata

Assignees

Labels

refactorStructural code change without a primary behavior change.

Type

No type

Projects

Status
Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions