Uh oh!
There was an error while loading. Please reload this page.
feat(components,plugin-detail,app-shell): URL-driven record detail tabs — ?tab= survives remounts (#2257) - #2267
Merged
Merged
Conversation
…bs — ?tab= survives remounts (#2257) Root cause (browser-diagnosed with mount probes, StrictMode A/B): - The active tab lived ONLY in uncontrolled Radix state (PageTabsRenderer defaultValue + DetailView autoTabs), so any subtree remount reset it. - Real production triggers: the refreshKey / actionRefreshKey save-refresh keys remount the whole detail tree. Dev-only trigger: under StrictMode ANY URL search change remounts RecordDetailView (zero remounts with StrictMode off — same sequence verified both ways). Fix — the tab is URL-addressable state (ADR-0054 C3): - buildDefaultTabs emits stable semantic values (details / related:<child> / related / activity / history); index-derived values can't be URL tokens (they shift when the item list changes). - PageTabsRenderer honors item.value + host defaultTab (validated) + onTabChange; index fallback kept for authored schemas. - RecordDetailView restores from ?tab= and writes back with replace (tab switches never stack history — Back must keep closing ?form overlays, not page through tabs), injected via withPageTabsUrlSync (pure, clone-on-write — authored/memoized page schemas are never mutated). - Legacy DetailView autoTabs wired to the same defaultTab/onTabChange contract with value validation. Browser-verified (showcase, StrictMode ON — worst case): Related tab + child overlay open/Esc → tab preserved; child create SAVE → tab preserved AND subtable shows the new record (closes the #2604 acceptance gap); ?tab=related deep link lands on Related; ?tab=nope falls back to Details. Unit: +9 tests (stable values, injector identity/clone-on-write); suites 1365 green; turbo type-check 32/32. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019zKcAtbtxuF9SXYgSzjk9v
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
os-zhuang
marked this pull request as ready for review
July 5, 2026 07:18
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概述
修复 #2257:详情页 tab 条被子树重挂载重置回 Details。按 ADR-0054 C3 把 active tab 变成 URL 可寻址状态(
?tab=<value>),同时补齐 framework#2604 验收中缺的最后一项(子表保存后父详情 tab 保留)。根因(浏览器实测 + StrictMode A/B 对照,结论已更新到 #2257)
active tab 只活在非受控 Radix state 里(
PageTabsRenderer的defaultValue+ legacyDetailViewautoTabs),任何子树重挂载即清零。触发面分两类:key={refreshKey}/key={actionRefreshKey}的"保存/action 后整树重建"模式;RecordDetailView—— 同一操作序列在 StrictMode OFF 下零重挂载、tab 保留(mount 探针 A/B 实证),故?recordId=/?form=引发的 tab 丢失是 dev 伪影,生产不受影响。两类触发 URL 化后全部免疫——状态不在组件里,重挂载无所谓。
变更
buildDefaultTabs:tab item 携带稳定语义 value(details/related:<child>(primary 独占 tab)/related/activity/history)——索引合成值不能当 URL token(related list 增减时会漂移指向别的 tab)。PageTabsRenderer:优先用item.value(无则回退索引值,兼容 authored schema);接受宿主schema.defaultTab(仅当命中真实 tab 才生效)+schema.onTabChange。components 包保持 router 无感——只暴露口子。RecordDetailView:从?tab=恢复、onTabChange用replace写回(切 tab 决不压 history——Back 必须继续承担"关?form=overlay"的语义,不能在 tab 间倒带);经纯函数withPageTabsUrlSync注入 page 树(clone-on-write,决不改动 authored/memoized 的页面 schema,含 6 个单测)。DetailViewautoTabs 同一契约接线(value 校验后才采用)。DetailViewSchema.defaultTab/onTabChange、PageTabsItem.value声明。验证
浏览器端到端(showcase,StrictMode 开启的最坏情况):
?tab=related?tab=related深链直达;?tab=nope回退 Details单测 +9(稳定 value × 布局变体 × 注入器身份/不可变);3 包全量 1365 通过;turbo type-check 32/32。changeset 已附(fixed group minor)。
Closes#2257 · Refs framework#2604 · ADR-0054
🤖 Generated with Claude Code
https://claude.ai/code/session_019zKcAtbtxuF9SXYgSzjk9v
Generated by Claude Code