Repository navigation
fix(app-shell): on a phone, Back with the nav drawer open closes the drawer and stays on the page - #29323
Conversation
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jTvKAh25Doey2NM9Qw
…live e2e Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jTvKAh25Doey2NM9Qw
…l closes the drawer Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jTvKAh25Doey2NM9Qw
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jTvKAh25Doey2NM9Qw
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jTvKAh25Doey2NM9Qw
|
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y 席位核对 · ACCEPT · 2026-11-02T12:44ZHead-sha:
Generated by Claude Code |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 41448041289 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
|
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y Landed · 2026-11-02T13:42Z · PR #29323 merged through the merge queue as
The card closed through Generated by Claude Code |
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y
Requested by Steedos · project thread
Fixes #29131
Clause-②: no
Metadata-protocol: none
维护者速读
@object-ui/app-shell新增一个 hook,在ConsoleLayout挂一行;不改UnifiedSidebar.tsx和任何components/src/ui/**原件。风险点是历史栈:手机上打开抽屉多一条同 URL 的记录,抽屉内链接点击被改为 replace 导航。回滚即 revert 本 PR。ui/packages/app-shell/src/layout/usePhoneNavDrawerHistory.ts的三个 effect,尤其抽屉内链接的捕获监听与 popstate 关闭;②ConsoleLayout.tsx的挂载行;③urlParams.ts新常量NAV_DRAWER_OPEN_STATE。pnpm dev:crm,浏览器 375 px 打开 Leads 列表,点 ☰ 打开抽屉,按浏览器返回:抽屉关上,地址仍是 Leads 列表;再按一次返回才离开。What changes
At phone width (below 768 px) the Console's sidebar is a drawer over the page. Back with the drawer open used to leave the page; now it closes the drawer and the URL stays the page it was on. Implemented as the seat ruled on the card: one router-owned history entry while the drawer is open, no popstate guard.
usePhoneNavDrawerHistory(ui/packages/app-shell/src/layout/), mounted once inConsoleLayoutInnerbesideuseResponsiveSidebar, inside the sidebar provider. It acts only whileuseSidebar().isMobileis true:NAV_DRAWER_OPEN_STATE: truein its state.popstatelistener closes it too:BrowserRoutercommits the push and a Back pressed before the push committed as one transition, so the router reports no location change at all in that case (found by the live spec, see Tests).navigate(-1), only when the current entry carries the flag.navigate(to, { replace: true })and prevents the default, soLinkskips its own push. A link to the page already shown pops the entry instead. This covers the navigation, favorite and recent rows and the drawer's "All apps" link without editingUnifiedSidebar.tsxor@object-ui/layout.history.state) is replaced by one without the flag, and the drawer stays closed. Forward to an entry this document pushed reopens the drawer.NAV_DRAWER_OPEN_STATEsits inurlParams.tsbesideRECORD_RELATED_ENLARGE_PUSHED_STATE. Neither the hook nor the constant is exported from the package entry.isMobilefalse the hook writes no history.UnifiedSidebar.tsx,sidebar.tsxor any othercomponents/src/ui/**primitive. The native shell's Android Back goes through the WebView's history and needs no bridge code.Tests
usePhoneNavDrawerHistory-29131.test.tsx, memory router, realSidebarProvider, realUnifiedSidebarandNavigationRendererlinks, the sidebar's own route-change close running beside the hook): Back closes the drawer on the same URL; Escape pops the entry and Back goes to the previous page; a nav entry replaces the drawer entry and Back returns to the page the drawer was opened on; the "All apps" link (which closes the drawer itself) does not pop as well; a reload on a flagged entry leaves the drawer closed with the flag gone; Forward reopens the drawer; CONTROL at 1280 px: toggling the sidebar commits no location.ui/e2e/live/phone-nav-drawer-back-29131.spec.ts, Chromium with touch at 375 and 390 px, real history): Back closes the drawer and stays on the list; an outside tap pops the entry and Back goes to the previous page; a nav entry then Back returns to the list; a reload leaves the drawer closed with no flagged entry and the same history length; CONTROL at 1280 px: no history entry. Runs intest:e2e:live:nightly, not the:ciallowlist (see Acceptance notes).Reverse checks and ablations
One-shot, nothing left in the tree (each restore checked: blob equals HEAD and
git diff HEADempty).ConsoleLayoutmount line taken from the base commit, Console rebuilt and served byvite previewagainst a showcase backend): 8 of 8 phone tests red, desktop CONTROL green.popstateclose ablated (scripts/ablation-replace.mjs, anchor 1 to 0): exactly the two "Back with the drawer open" tests red (375 and 390), the other 7 green.Acceptance notes
Three deviations from the ruling's text, accepted by the seat:
popstateclose. The ruling's location-driven close misses a Back pressed before the push commits, becauseBrowserRouterthen reports no location change. Measured red in the live spec; the ablation above reds exactly that pair.BrowserRoutercommits locations insidestartTransition, so the push and the pop collapsed into one commit with an unchanged location and the drawer stayed open. Thepopstateclose fixes it; the ablation above shows it is the load-bearing part.ConsoleLayout.tsx, notUnifiedSidebar.tsx, so this PR does not touch the file Console nav drawer takes 300–580 ms to open on the showcase at CPU 4x (46 entries); profile and bring it under 200 ms #29141 edits.actionthat navigates in code pushes as before (only links are intercepted). Back from that page returns to the drawer entry, and the drawer reopens there (the Forward rule), then the next Back closes it.test:e2e:live:nightly(claim amendment 6576569654 added that oneui/package.jsonscript line). It stays out of:cifor that job's two-minute budget; promotion is a later call once nightly has a run population for it.Verification
Step 07 of the card on the real Console: production build served at
/_consolebyobjectstack dev --fresh(examples/app-crmon its lead list,examples/app-showcaseon its task list), Chromium withisMobileand touch, DPR 2. "Before" is the Console built without the mount line (equivalent tomain07eaec2); "after" is the final hook. Script and every screenshot: project filesissue-29131/(step07.cjs,before/,after/).Before, 07a:
{"urlBefore":".../_console/apps/com.example.crm/crm_lead","urlAfter":".../_console/home","drawerStill":false}, the card's reading, in all four runs. After:urlAfterequalsurlBefore,drawerStillfalse, flag false.Local gates at head 1789809 (merged main 7788eed)
The 34 families derived for the six source paths ran at 1789809. Head a76bb1a merged ten more
maincommits (to 504ba51) touching none of those paths. Head 9626c78 adds the nightly script line inui/package.json; it derives 12 more families, run there: 12 of 12 exit 0.--ranover all 46: 46 derived, 46 run, 0 NOT-MEASURED (a derived zero).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 34 families for the 6 changed paths. Each ran with its exit code captured before any pipe: 34 of 34 exit 0,check:type-check-debtandpnpm --dir ui exec vitest run scripts/(169 files, 4900 tests) underos-verify-lock.--ranreconciliation: 34 derived, 34 run, 0 NOT-MEASURED (a derived zero).node scripts/check-sdui-manifest.mjs: exit 0 (and--self-testexit 0).pnpm --dir ui exec vitest runover the new file plus everyConsoleLayout.*andUnifiedSidebar.*test inapp-shell/src/layout/__tests__: 31 files, 152 tests passed.pnpm --filter @object-ui/app-shell run type-check(source and test configs): exit 0.pnpm --dir ui run type-check:e2e: exit 0. ESLint on the changed files: 0 errors (4 warnings, all on existingConsoleLayout.tsxlines).vite preview, showcase backend): 9 of 9 passed.Generated by Claude Code