Skip to content

fix(react-tunnel): order tunnel entries by their position in the tree - #3586

Merged
mfal merged 5 commits into
mainfrom
fix/tunnel-entry-order
Oct 27, 2026
Merged

mfal merged 5 commits into
mainfrom
fix/tunnel-entry-order

Conversation

@mfal

@mfal mfal commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

What & why

A tunnel's exit now lists its entries in tree order. Before, each entry took its index once, from a counter that TunnelProvider reset on each of its renders, so the order depended on how often the provider rendered:

Users see it in every component that collects children through a tunnel: Navigation/NavigationGroup links, TabNavigation (its overflow menu slices the tunnel, so a wrong order shows the wrong links under "more"), Tabs, RadioGroup/CheckboxGroup, Select/ComboBox/Autocomplete options, header actions. It needs an entry that mounts after the others (permission check, Suspense, flag) or a reorder. On next, #3579 makes the provider re-render less often, so the old reset hides fewer of these cases.

How: an entry calls reorderEntries in a layout effect when it mounts next to committed entries of its tunnel, or when it commits after a higher index in the same commit. The order check runs in the commit, not the render: a concurrent render yields between entries, a commit does not. Every entry of that tunnel state renders again through useSyncExternalStore, in tree order, and takes its index anew before paint. The provider no longer resets the counter. Entries render null, so the extra pass is cheap; it runs only on such a mount or reorder.

PasswordCreationField showed the first FieldError in its tunnel and rendered its policy error before children. The consumer's error only came first because the policy error mounted later. The policy error now renders after children (own commit).

Visual change: the Section test shows a secondary header button that appears later. The old reference had it right after the first action (Link Secondary Switch Primary); tree order is Link Switch Secondary Primary. The darwin and linux references are updated (linux via update-screenshots).

Limit: an entry that does not render in the reorder pass (inside a hidden Activity or a suspended boundary) takes its index when it renders and is corrected by the next out-of-order check.

Issue

Closes #3487

Tests

  • react-tunnel: an entry mounting later between others, a parent appending an entry, keyed reorders (ABC → CBA → BDCA). All three fail without the change.
  • Select: reordered keyed options are listed in their new order. Fails without the change.
  • Browser suites (Firefox, WebKit): react-tunnel 60/60, components 2122 passed / 2 skipped, remote-react-components browser 110/110 and e2e 30 passed / 1 skipped, remote-vue-components browser 338 passed and parity 221 + 201 + 29 passed. Visual: 870 passed; the 14 failures are the Section change above and 10 failures that also occur on main without this change (missing or outdated darwin references for CartesianChart, ImageCropper, MessageThread, Modal confirmOnClose, Section growing inside a LayoutCard).
  • The patch applies cleanly to next; react-tunnel there: 60/60.
  • The local numbers above predate the move of the order check into the commit; CI on the final head is green (browser, browser-firefox, visual 1–4, e2e, parity, cross-version, lint, unit).

Checklist

  • PR title is a Conventional Commit and matches the base branch above
  • The issue this PR finishes (if any) is linked with Closes #…
  • pnpm lint is clean and pnpm affected:test passes (browser tests if behavior changed)
  • Generated code is committed (git diff is empty after the relevant build:* targets)
  • User-facing strings added to both de-DE and en-US locale files — none added
  • Docs updated if a public API changed; intentional visual changes get updated snapshots / the update-screenshots label

🤖 Generated with Claude Code

mfal and others added 2 commits October 27, 2026 12:16
The field shows the first `FieldError` in its error tunnel. The policy error
was rendered before `children`, so it only came second because it mounted
later and tunnel entries kept their mount order. Rendered after `children`,
a consumer's error wins by its position in the tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An entry took its index once, from a counter that `TunnelProvider` reset on
each of its renders. The exit sorts by that index, so its order depended on
how often the provider rendered:

- An entry mounting next to committed ones got the count since the provider's
  last render: `A, <Gate>B</Gate>, C` showed `ACB`, and a parent appending `C`
  to `A, B` showed `ACB`.
- A keyed reorder kept the indices, so reordered `Select`/`ComboBox` options
  stayed in mount order.

An entry now reports when it mounts next to committed entries or renders after
a higher index in the same render pass. It then calls `reorderEntries` in a
layout effect: every entry of the tunnel state renders again through
`useSyncExternalStore`, in tree order, and takes its index anew before paint.
The provider no longer resets the counter.

The `Section` visual test showed the old order: a secondary header button shown
later sat right after the first action instead of before `Primary`. Its darwin
references are updated; the linux ones need the `update-screenshots` label.

Closes #3487

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mfal mfal added the update-screenshots Label a PR to update the screenshots used for visual regression testing label Oct 27, 2026
@mfal mfal self-assigned this Oct 27, 2026
@github-actions github-actions Bot removed the update-screenshots Label a PR to update the screenshots used for visual regression testing label Oct 27, 2026
@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Coverage Report for ./packages/components/

Status Category Percentage Covered / Total
🔵 Lines 64.82% 1410 / 2175
🔵 Statements 64.28% 1449 / 2254
🔵 Functions 50.63% 318 / 628
🔵 Branches 58.94% 758 / 1286
File CoverageNo changed files found.
Generated in workflow #7456 for commit 354d739 by the Vitest Coverage Report Action

Co-authored-by: mfal <4696750+mfal@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Preview Deployment

Preview environments are ready:

Type URL
docs pr-3586.docs.review.flow-components.de
storybook pr-3586.storybook.review.flow-components.de

Images:

  • docs: ghcr.io/mittwald/flow/docs:pr-3586
  • storybook: ghcr.io/mittwald/flow/storybook:pr-3586

A concurrent render yields between entries, and the microtask that
clears the recorded indices runs at each yield. An inversion across a
yield went unnoticed. A commit does not yield, so the check now runs in
a layout effect.

reorderEntries re-renders synchronously, before the microtask, so the
reorder commit compared against the previous commit's indices and
reordered again. Entries whose index belongs to an older generation now
skip the check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mfal
mfal marked this pull request as ready for review October 27, 2026 11:54
@mfal
mfal requested a review from a team October 27, 2026 11:54
@mfal mfal added the update-screenshots Label a PR to update the screenshots used for visual regression testing label Oct 27, 2026
@github-actions github-actions Bot removed the update-screenshots Label a PR to update the screenshots used for visual regression testing label Oct 27, 2026
@mfal
mfal enabled auto-merge October 27, 2026 12:06
StrictMode replays the mount render with the ref already set, so an entry
mounting next to committed ones lost `needsReorder` in the render that
commits and kept its count-so-far index (`ACB`). The flag is now stored with
the index it belongs to.

`reorderEntries` takes the generation the entry rendered with and does
nothing while a reorder is pending. Entries mounting in one commit no longer
notify every listener once each.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mfal
mfal merged commit 50e568b into main Oct 27, 2026
25 checks passed
@mfal
mfal deleted the fix/tunnel-entry-order branch October 27, 2026 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

react-tunnel: reordered entries keep their mount order (Select/ComboBox options never reorder)

2 participants