InfoStandWidgetUserView previously subscribed to three room-session
events (RSUBE_BADGES, USER_FIGURE, FAVOURITE_GROUP_UPDATE) and pushed
the result back to its parent via a setAvatarInfo prop, with each
handler running CloneObject(prev) before patching one field. Three
issues with that shape:
- CloneObject was deep-cloning the whole AvatarInfoUser shape blindly
with no class-prototype awareness;
- the three listeners raced on shallow merges across the same prev
reference in StrictMode dev;
- the subscriptions lived outside the state owner, forcing a prop
callback barrier per event.
The subscriptions are now in useAvatarInfoWidget — the actual owner of
avatarInfo — and call three pure reducers extracted to
src/hooks/rooms/widgets/avatarInfo.reducers.ts (applyUserBadgesUpdate,
applyUserFigureUpdate, applyFavouriteGroupUpdate). Each reducer returns
the same reference when the event doesn't apply so React bail-outs work.
The clone now constructs a fresh AvatarInfoUser preserving prototype.
dedupeBadges is extracted to its own pure module under src/api/avatar/
so Vitest can cover it without pulling in the renderer.
InfoStandWidgetUserView loses the setAvatarInfo prop (parent updated)
and the CloneObject import.