Gate the selected-route row at its render site and compare display labels

The dedup landed in isRenderableStreamEvent, but that predicate only
feeds the has-any-events gate; the model_switch Match rendered on its
own condition, so live turns still showed the duplicate row. Extract
shouldRenderModelRouteEvent, use it at the Match itself, and compare
display labels instead of raw ids — the done event rewrites
message.model to a provider-reported id that can differ in raw form
while reading identically to the user.
This commit is contained in:
rcourtman
2026-06-11 15:37:36 +01:00
parent b1114f4986
commit 5e4e0ab960
2 changed files with 51 additions and 18 deletions
@@ -257,6 +257,26 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
);
const isSelectedModelRouteEvent = (evt: StreamDisplayEvent) =>
evt.type === 'model_switch' && evt.modelEvent === 'selected' && !evt.failedModel?.trim();
// A plain selected-route row repeats the route badge already shown in the
// turn header. Keep it only when it adds information: the header has no
// route, the route reads differently from the turn's final model, or a
// later switch makes the starting route part of the turn's story. Compare
// display labels, not raw ids — the done event can rewrite message.model to
// a provider-reported id that differs in raw form while reading identically
// to the user.
const shouldRenderModelRouteEvent = (evt: StreamDisplayEvent) => {
if (evt.type !== 'model_switch' || !evt.model?.trim()) return false;
if (!isSelectedModelRouteEvent(evt)) return true;
const hasRouteSwitch = (props.message.streamEvents || []).some(
(event) =>
event.type === 'model_switch' &&
!!event.model?.trim() &&
!isSelectedModelRouteEvent(event),
);
return (
hasRouteSwitch || !messageModelLabel() || modelRouteLabel(evt.model) !== messageModelLabel()
);
};
const isConcreteStreamActivity = (evt: StreamDisplayEvent) => {
switch (evt.type) {
case 'workflow_status':
@@ -304,21 +324,8 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
return !!evt.pendingTool;
case 'tool_cancel':
return !!evt.toolCancel;
case 'model_switch': {
if (!evt.model?.trim()) return false;
if (!isSelectedModelRouteEvent(evt)) return true;
// A plain selected-route row repeats the route badge already shown in
// the turn header. Keep it only when it adds information: the header
// has no route, the route differs from the turn's final model, or a
// later switch makes the starting route part of the turn's story.
const hasRouteSwitch = (props.message.streamEvents || []).some(
(event) =>
event.type === 'model_switch' &&
!!event.model?.trim() &&
!isSelectedModelRouteEvent(event),
);
return hasRouteSwitch || !props.message.model || evt.model !== props.message.model;
}
case 'model_switch':
return shouldRenderModelRouteEvent(evt);
case 'approval':
return !!evt.approval;
case 'question':
@@ -770,9 +777,7 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
</Match>
<Match
when={
evt()?.type === 'model_switch' && evt().model?.trim() ? evt() : undefined
}
when={evt() && shouldRenderModelRouteEvent(evt()) ? evt() : undefined}
>
{(modelEvent) => {
const event = modelEvent();
@@ -1200,6 +1200,34 @@ describe('MessageItem', () => {
expect(screen.getByText('Qwen 3.7 Plus via OpenRouter')).toBeInTheDocument();
});
it('hides the selected-route row when raw ids differ but the display label matches the badge', () => {
const events: StreamDisplayEvent[] = [
{
type: 'model_switch',
model: 'openrouter:qwen/qwen3.7-plus',
modelEvent: 'selected',
},
];
render(() => (
<MessageItem
message={makeMessage({
role: 'assistant',
// The done event can rewrite message.model to a provider-reported
// id that differs in raw form while reading identically.
model: 'openrouter:qwen/qwen3.7-plus-20260101',
streamEvents: events,
})}
getModelRouteLabel={() => 'Qwen 3.7 Plus via OpenRouter'}
{...makeHandlers()}
/>
));
expect(
screen.queryByRole('status', { name: 'Assistant model route selected' }),
).not.toBeInTheDocument();
});
it('keeps the selected-route row when a later switch makes it part of the turn story', () => {
const events: StreamDisplayEvent[] = [
{