fix(app-shell): /home 的 Administration 组改由 NavigationRenderer 渲染,9 个系统管理入口恢复可达 (#3609) - #3630
Merged
Merged
Conversation
…ionRenderer (#3609) `UnifiedSidebar` chose its renderer with one ternary on `context === 'app' && activeApp`. Only the app arm rendered `NavigationRenderer` — the component that descends into `type: 'group'` children. The home arm hand-rolled a non-recursive `homeNavigation.map(item => <Link to={item.url || '/home'}>)`, and home navigation is the only navigation that groups: the nine-entry Administration cluster collapsed into a single row, and because a group carries no `url` of its own that row fell back to `/home` — the page the admin was already standing on. None of the nine children reached the DOM. `resolveLandingPath([])` lands a fresh-deployment admin on exactly this screen, and `HomePage` had dropped its own System card on the grounds that the sidebar carried those entries, so the net effect was an admin with no route into system administration at all. Route the home arm through the same `NavigationRenderer` rather than teach a second renderer to recurse. `NavigationRenderer` takes `basePath` as a prop and reads only `useLocation()` — no `activeApp` coupling — so the reuse needed no loosening. `basePath=""` is what the home arm already computed, and every home entry is `type: 'url'` (verbatim href resolution), so surviving links keep byte-identical hrefs. The group states `expanded: true`: the renderer auto-collapses groups of >= 8 children when unauthored, a heuristic for one long section among many, whereas on `/home` this group IS the navigation — nine entries behind a closed disclosure would be the same defect respelled. Pinning/reorder are deliberately not forwarded (their persistence key resolves to the first app, not to home) nor are the `activeApp`-keyed label resolvers; both omissions are documented at the call site. Tests: the #3590 MEASUREMENT pin asserted the broken shape (`Administration` as a link to `/home`, children absent) and was designed to go red here. It is REPLACED, not duplicated — the same test now asserts the group is a disclosure rather than a link, is open by default, and that all nine entries render at their declared URLs. A second test pins the gate in the negative direction (a non-admin sees none of them while the ungated entries still render). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
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 free
to 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.
Fixes #3609
病灶
UnifiedSidebar用一个三元挑渲染器:context === 'app' && activeApp ? … : …。:504,现:512)渲染NavigationRenderer—— 那才是会递归进type: 'group'子项、把组渲染成 Collapsible 的组件。:606)是手写的:homeNavigation.map(...),每项渲染成一条Link,to={item.url || '/home'}。不递归。而 home 导航偏偏是全仓唯一用
type: 'group'结构化的那份。于是 9 项 Administration 簇被压平成一条;组自身没有url,|| '/home'把它送回用户已经站着的那一页;9 个子项一个也没进过 DOM。resolveLandingPath([])返回/home,零应用部署的管理员第一屏就是这里;而HomePage.tsx:289的注释写着「(系统入口)已经在 nav 里了」,主动撤掉了自己的 System 卡片。净效果:管理员没有任何通往系统管理的入口。修法选择:复用
NavigationRenderer(不是补丁化 home 分支)按 PM 裁决先测量差异面,再决定。测量结果如下:
NavigationRenderer(app 分支)item.url || '/home'SidebarGroup / SidebarGroupContent / SidebarMenu / SidebarMenuItem / SidebarMenuButton asChild / LinkgetIcon(app-shell 自己的)resolveIcon→getLazyIcon(components 包)—— 同一个 lucideDynamicIcon实现、同一个Database兜底、桌面端同为h-4 w-4location.pathname === item.urlresolveActiveNavItem),basePath感知visible/requiredPermissions/requiresObject/requiresServiceuseLocationuseLocation而已 ——basePath是 prop,对activeApp零耦合最后一行是决定性的:分诊担心的「
NavigationRenderer强依赖 activeApp context」不成立,它只吃 props +useLocation()。所以复用不需要放松任何东西,退到手写递归的条件没有触发。两处不变量也被测量确认:
basePath=""正是 home 分支basePath本来就算出的值(context === 'app' && activeApp ? … : '');type: 'url',resolveHref对它原样返回item.url,不拼 basePath。因此两条幸存的顶层链接(Home
/home、Documentation/docs)href 逐字节不变,9 个子项拿到各自声明的绝对 URL。expanded: true:为什么必须显式声明NavigationRenderer在作者未声明时对 ≥ 8 个子项的组自动折叠(AUTO_COLLAPSE_THRESHOLD = 8),Radix 的CollapsibleContent关闭时会 unmount。这个启发式是为「app 导航里众多分区中的某一条长分区」设计的。本组正好 9 项,且在
/home上它不是众多分区之一,它就是管理员的导航本身。若交给启发式,9 项又会退出 DOM —— 那只是同一个 bug 换个拼法。所以在组声明上显式写 spec 字段expanded: true。测试直接钉住aria-expanded="true"。刻意不从 app 分支转发的 props(附理由,非遗漏)
enablePinning/enableReorder:两者都持久化在useNavOrder(activeApp?.name || 'home')下,而 home 上下文里activeApp解析成的是第一个 app(activeApps[0]),不是 home —— 打开就等于让 home 导航吃那个 app 存下的根排序。另外 pinned 区会正好落在本分支自己的 "Starred" 组上方。两者都是独立的产品决定,不属于「把组展开」这一单。resolveGroupLabel/resolveItemLabel:以activeApp.name为键,在 home 上下文里没有意义(会去某个任意 app 的 i18n 命名空间下查sys-administration)。home 的 label 在构造处已经过t()解析。以上两条都写进了调用点的注释。
MEASUREMENT 钉:翻转前后对照
#3590 在
systemNavSettingsTarget.test.tsx留下的钉断言的是坏状态,并写明「一旦修好就会转红,届时应换成真实断言」。本 PR 替换它,不是并存 —— 仓库不能同时钉着 bug 和修复。修前(已删除)
修后(新断言)
两半正好互逆:
Administration由链接变成折叠开关(组无url,任何把它渲染成链接的写法都只能落回|| '/home'死链,所以按 role 断言「不再是链接」比断言「href 变了」更严);子项由缺席变成全部在场。ADMINISTRATION_ENTRIES整簇 9 条全断言(不是抽样 3 条):本 bug 的性质是「子项根本没被访问过」,所以值得钉的性质是「九项俱全、各自 URL 正确」,而非「有些渲染出来了」。另加:System Settings不得退回裸/apps/setup(#3608 刚改对的那条);Home / Documentation 两条扁平项 href 不变。新增第二条测试反向钉权限门:非管理员在
/home看不到组、看不到 9 项中任何一项,同时 Home / Documentation 仍在(否则这条测试在「侧栏什么都没渲染」时也会绿)。逆向验证(先预测后执行)
预测:保留新测试、把
UnifiedSidebar.tsx单独回退到origin/main→ 新测试转红,且首个失败断言是queryByRole('link', {name:'Administration'})).not.toBeInTheDocument()(方向:Administration 仍是链接、子项找不到);非管理员那条保持绿(它从不依赖组渲染)。实测(
git checkout origin/main -- packages/app-shell/src/layout/UnifiedSidebar.tsx):方向与预测一致(红,且红在预测的那一行)。随后已还原实现。
测试证据
UnifiedSidebar.derivedAreaVisibility.test.tsx在上面那 19 个文件里,全绿 —— app 分支(context === 'app')行为零变化,符合预测。影响面 sweep(按规则的消费半径,不按改动包)
枚举了
UnifiedSidebar的全部引用(app-shell/index.ts、layout/index.ts、ConsoleLayout.tsx、FavoritesProvider.tsx、HomePage.tsx、AppSidebar.tsx、apps/console/src/pages/system/SystemHubPage.tsx)与全仓提到homeNavigation/sys-administration/Administration的文件。命中的packages/types/src/__tests__/navigation-model.test.ts:204只是一个恰好叫Administration的 Zod area fixture,与本改动无关。e2e/下无相关断言。i18n locale 包未改动。测试基建的一处必要补充
home 分支现在走
NavigationRenderer,它的图标经@object-ui/components的getLazyIcon(而AppSidebar的手写簇经 app-shell 自己的getIcon)。lucide 的DynamicIcon在useEffect里发异步import()再setState,所以对 components 包做了局部 mock(importOriginal展开后只替换getLazyIcon),Sidebar 原语保持真实。这与该文件原有的getIconmock 是同一目的:让每条链接的可访问名等于它的 label 文本,并保持渲染同步。文件面
packages/app-shell/src/layout/UnifiedSidebar.tsxpackages/app-shell/src/layout/__tests__/systemNavSettingsTarget.test.tsx.changeset/home-administration-group-3609.md(patch)未触碰
AppSidebar.tsx/AppContent.tsx/ConsoleShell.tsx(#3610 / #3611 另有单)、locale 包、content/docs/releases/。🤖 Generated with Claude Code
https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
Generated by Claude Code