Problem
MessageRenderingService.convertPartsToDomNodes in src/app/services/message-rendering.service.ts has grown to ~140 lines (~30% of the service) and combines multiple competing responsibilities in a single procedural loop:
- 12-branch
if / else if dispatch ladder: Dispatches across text, reasoning, 5 media variants, toolRequest, resource, custom, a2ui, and fallback data.
- Imperative loop mutation (
i += consumed): Inside the render loop, combineAdjacentStringParts mutates the loop index i mid-flight to coalesce streaming chunks. This couples stream normalization with DOM construction and makes index tracking fragile alongside index-dependent lookups (such as A2UI surface anchoring and leftovers mapping).
- Disproportionate complexity: Complex protocol logic (such as A2UI surface card anchoring, container reshaping, and leftovers fallback) lives inline inside the loop body.
Proposed Solution
Adopt a phased pipeline similar to the strategy pattern used for trace previews in src/app/components/json-viewer/render-types:
- Upstream Normalization Pass:
- Introduce a pure, framework-agnostic
normalizeParts(parts: Part[]): Part[] helper that coalesces adjacent string parts (text/reasoning lacking metadata) upfront.
- Eliminates lookaheads,
consumed counters, and i += consumed index mutations during rendering.
- Modular Part Renderers / Strategies:
- Extract individual branch logic into dedicated renderers/helpers (e.g.,
A2uiPartRenderer, MediaPartRenderer, TextPartRenderer).
- Replace the 12-branch ladder with an ordered registry of handlers (
canRender(part) / render(part, context)).
Benefits
- Testability: Normalization and individual part handlers can be unit-tested in isolation without full Angular component/DOM harnesses.
- Maintainability & OCP: Adding support for future part types will no longer require expanding the central render loop.
- Safer Iteration: Removes manual loop pointer manipulation and cleanly decouples stream parsing from DOM generation.
Problem
MessageRenderingService.convertPartsToDomNodesinsrc/app/services/message-rendering.service.tshas grown to ~140 lines (~30% of the service) and combines multiple competing responsibilities in a single procedural loop:if / else ifdispatch ladder: Dispatches acrosstext,reasoning, 5 media variants,toolRequest,resource,custom,a2ui, and fallbackdata.i += consumed): Inside the render loop,combineAdjacentStringPartsmutates the loop indeximid-flight to coalesce streaming chunks. This couples stream normalization with DOM construction and makes index tracking fragile alongside index-dependent lookups (such as A2UI surface anchoring and leftovers mapping).Proposed Solution
Adopt a phased pipeline similar to the strategy pattern used for trace previews in
src/app/components/json-viewer/render-types:normalizeParts(parts: Part[]): Part[]helper that coalesces adjacent string parts (text/reasoning lacking metadata) upfront.consumedcounters, andi += consumedindex mutations during rendering.A2uiPartRenderer,MediaPartRenderer,TextPartRenderer).canRender(part)/render(part, context)).Benefits