Summary
<Tabs> from @openuidev/react-ui selects the last tab on initial mount instead of the first, even for a fully static (non-streaming) response.
Reproduction
Standalone repro (published packages only, npm install && npm run dev):
https://github.com/Shinyaigeek/openui-tabs-repro
root = Tabs([t1, t2, t3])
t1 = TabItem("a", "Apple", [c1])
t2 = TabItem("b", "Banana", [c2])
t3 = TabItem("c", "Cherry", [c3])
c1 = TextContent("Apple tab content")
c2 = TextContent("Banana tab content")
c3 = TextContent("Cherry tab content")
- Expected: the first tab (
Apple) is active on mount.
- Actual: the last tab (
Cherry) is active on mount.

Versions: @openuidev/react-ui@0.13.0, @openuidev/react-lang@0.2.8, @openuidev/react-headless@0.9.2, React 19.
Root cause
In packages/react-ui/src/genui-lib/Tabs/index.tsx, two effects race on mount:
- An effect that defaults
activeTab to the first tab.
- A dependency-less effect that auto-advances to any tab whose serialized content grew since the last render (streaming follow-along):
for (const item of items) {
const size = JSON.stringify(item.props.content).length;
const prevSize = prevContentSizes.current[item.props.value] ?? 0; // {} on first run
nextSizes[item.props.value] = size;
if (size > prevSize) {
candidate = item.props.value; // every tab "grew" -> candidate ends as the LAST tab
}
}
On the very first pass prevContentSizes.current is empty, so every tab satisfies size > prevSize, and candidate ends the loop pointing at the last item. Both effects run in the same commit; the auto-advance effect runs after the default-to-first effect, so the last tab wins.
(SectionBlock guards its equivalent auto-advance with an isStreaming check; Tabs has no such guard.)
Suggested fix
Treat the first pass as a baseline-recording pass only: record nextSizes but skip candidate selection when prevContentSizes.current is empty. Streaming follow-along still works — subsequent growth (or a newly appearing tab) still advances the active tab.
I have a PR ready for this.
Summary
<Tabs>from@openuidev/react-uiselects the last tab on initial mount instead of the first, even for a fully static (non-streaming) response.Reproduction
Standalone repro (published packages only,
npm install && npm run dev):https://github.com/Shinyaigeek/openui-tabs-repro
Apple) is active on mount.Cherry) is active on mount.Versions:
@openuidev/react-ui@0.13.0,@openuidev/react-lang@0.2.8,@openuidev/react-headless@0.9.2, React 19.Root cause
In
packages/react-ui/src/genui-lib/Tabs/index.tsx, two effects race on mount:activeTabto the first tab.On the very first pass
prevContentSizes.currentis empty, so every tab satisfiessize > prevSize, andcandidateends the loop pointing at the last item. Both effects run in the same commit; the auto-advance effect runs after the default-to-first effect, so the last tab wins.(
SectionBlockguards its equivalent auto-advance with anisStreamingcheck;Tabshas no such guard.)Suggested fix
Treat the first pass as a baseline-recording pass only: record
nextSizesbut skip candidate selection whenprevContentSizes.currentis empty. Streaming follow-along still works — subsequent growth (or a newly appearing tab) still advances the active tab.I have a PR ready for this.