Skip to content

fix(app-shell): phone drawer closes on navigation; 44 px top-bar targets below md - #29168

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-dnpnnb
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-dnpnnb

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #29075

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:手机宽度(375/390 px)下,侧边抽屉里点任一入口后抽屉会自动关上;顶栏的按钮(侧栏开关、应用图标、搜索、收件箱、头像等)和它们打开的菜单行在 768 px 以下都放大到 44 px 触控尺寸。为放得下,640 px 以下应用内顶栏不再画产品图标和 Studio 按钮,两者仍在应用切换菜单里(「Back to home」和 Studio 一行),抽屉的「All apps」也能回首页。
  • 为什么改:Mobile 2.0 用 WebView 承载 Console(Console at phone width: audit and fix the key flows at 375px (the mobile app now hosts the Console) #29040),手机用户只有响应式布局。实测:点抽屉入口后新页面被仍然打开的抽屉盖住,要再点一次;顶栏控件只有 28–34 px,菜单行 32/38 px,低于 44 px。
  • 风险与回滚:低。只改 @object-ui/app-shell 的 7 个布局文件的 max-md: / max-sm: 类名,加一个按路由关闭手机抽屉的 effect;768 px 以上外观与行为不变(1280 px 实测尺寸与 main 相同)。回滚即 revert 本 PR。
  • 先看:① ui/packages/app-shell/src/layout/UnifiedSidebar.tsx 新增的 routeKey effect;② ui/packages/app-shell/src/layout/AppHeader.tsx 产品图标与 Studio 按钮的 max-sm:hidden;③ ui/packages/app-shell/src/layout/AppSwitcher.tsx 的触发器与菜单行。
  • 两分钟验证:pnpm dev:crm,浏览器 375 px 打开 Accounts:顶栏是 ☰ · 应用图标 · 标题 · 搜索 · 铃铛 · 头像;点 ☰ 再点 Contacts,抽屉自动关上,直接看到联系人列表。
  • 需要你做的:无,席位落地。

What changes

The Navigation flow of the Console at phone width, audited at 375×812 and 390×844 on examples/app-crm and examples/app-showcase (card #29075; parent audit #29040).

  • The phone drawer closes on navigation (UnifiedSidebar.tsx). Below 768 px the sidebar is a drawer over the page. A tapped entry opened its page under a drawer that stayed open, so every navigation cost a second tap. The sidebar now closes the drawer when the route (pathname and search) changes. It is keyed on the route, not on one row's handler, so navigation, favorite and recent rows and any other control that changes the route all close it. Wider windows are unchanged: the effect acts only while the sidebar is in its mobile form.
  • 44 px controls below md. max-md:size-11 on the sidebar trigger (class passed by AppHeader), the product mark, search, the Studio buttons, the AI button and the account avatar (AppHeader.tsx), the inbox bell (InboxPopover.tsx, the trigger's class only), the theme and language buttons (ModeToggle.tsx, LocaleSwitcher.tsx) and the page header's Refresh (PageHeader.tsx). The app pill gets max-md:min-h-11 max-md:min-w-11 (AppSwitcher.tsx).
  • 44 px menu rows below md. The app switcher, account, theme and language menus carry max-md:[&_[role=menuitem]]:min-h-11 on their content, so every row they hold, including rows drawn from metadata (hidden apps in the account menu), is 44 px tall. The dropdown primitive (ui/dropdown-menu.tsx) is not edited.
  • Room in a 375 px bar. Seven 44 px controls left about 30 px for the page title. Below 640 px an app's bar therefore draws neither the product mark (and its / rule) nor the Studio button. Both stay one tap away: the app switcher's menu has "Back to home" and the Studio row (the bar hands it the same target), and the drawer's "All apps" goes home. Home and the other platform surfaces keep the product mark, at 44 px; the empty trigger slot there is not drawn below 640 px. Where a host hides the switcher for a single app, Studio is not offered on a phone bar; Studio is a desktop authoring surface.
  • No prop, export, i18n key or metadata changes.

Run record

Production Console build served at /_console by objectstack dev --fresh for "before" (main 6b3ba82); the Vite Console on the branch source against the same backends for "after". Chromium with isMobile and touch, DPR 2. touch-44 lists every visible interactive element under 44 px on either side in the named scope. The keyboard check focuses the search field and shrinks the viewport by 40 % (Android adjustResize); iOS visualViewport is not simulated.

Check Page crm 375 crm 390 showcase 375 showcase 390 Note
touch-44 Home (/home) FAIL FAIL FAIL FAIL app bar fixed; page body: separate card
no-horizontal-scroll app landing pass pass pass pass
touch-44 app landing, app bar FAIL → pass FAIL → pass FAIL → pass FAIL → pass
touch-44 app switcher menu FAIL → pass FAIL → pass FAIL → pass FAIL → pass
overlay-fits app switcher menu pass pass pass pass
touch-44 drawer pass pass pass pass
overlay-fits drawer pass pass pass pass
toggle drawer nav group pass pass pass pass
no-horizontal-scroll drawer scrolled pass pass pass pass
back-closes-drawer-stays-on-page Back with drawer open FAIL FAIL FAIL FAIL card filed
drawer-closes-and-navigates drawer entry tap FAIL → pass FAIL → pass FAIL → pass FAIL → pass
back-returns Back after a drawer navigation pass pass pass pass
overlay-fits search palette pass pass pass pass
touch-44 search palette FAIL FAIL FAIL FAIL Close 16 px: dialog.tsx, seat 4
focused-field-visible search, 40 % keyboard pass pass pass pass
touch-44 account menu FAIL FAIL FAIL FAIL left: two notification switches, Notifications card
no-horizontal-scroll object list pass pass pass pass
touch-44 object list FAIL FAIL FAIL FAIL left: list toolbar, Lists card
no-horizontal-scroll record page pass pass pass pass

Other checks of the card:

  • No horizontal page scroll: pass in every state measured (home, landing, switcher open, drawer open, group toggled, drawer scrolled, after navigation, search, keyboard, account menu, list, record), both examples, both widths.
  • Dialogs and drawers fit the viewport: pass (drawer 288 px wide, switcher, palette and account menu inside the viewport).
  • Virtual keyboard: pass (search field 69 to 127 px from the top with the keyboard up).
  • Long lists at CPU 4× (production build; click to the first painted frame after the drawer mounts): drawer open on app-crm 107 to 253 ms, on app-showcase (46 entries) 294 to 576 ms; nav-group toggle 173 to 252 ms; 20 frames of drawer scroll 316 to 524 ms. Drawer open on the larger app is over a 200 ms interaction budget and is not changed here: 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.
  • Back: Back after a drawer navigation returns to the previous page (pass). Back with the drawer open leaves the page instead of closing the drawer: Console at phone width: Back with the nav drawer open leaves the page (to /home) instead of closing the drawer #29131. On the record page the phone ‹ is a link, so Back after ‹ reopens the record; routed to the Record flow with seat 4.

Screenshots before and after each fix are in the project folder issue-29075/ (before/, after/), with the run record run-record-before.md and the script audit.cjs.

Tests

  • New UnifiedSidebar.phoneDrawerCloses-29075.test.tsx (4 tests, real SidebarProvider and @object-ui/layout): a tapped drawer entry closes the drawer; a navigation from outside the drawer closes it too; the same route leaves it open; CONTROL, a wide window's sidebar is unchanged by a route change.
  • New AppHeader.phoneTargets-29075.test.tsx (8 tests, harness of AppHeader.sidebarTrigger-12429.test.tsx): the 44 px classes on the bar's controls in an app and on Home; the product mark, its rule and the Studio buttons hidden below sm inside an app only; CONTROL, the trigger, search and avatar are never hidden; the menus' row class and the controls drawn by AppSwitcher, ModeToggle, LocaleSwitcher, InboxPopover and PageHeader, read from their source.
  • Ablation (scripts/ablation-replace.mjs, restored to HEAD byte-identical by blob hash): the drawer close made a no-op (if (isMobile && Date.now() < 0) setOpenMobile(false);) reds 2 of 4 in the drawer test, the two navigation cases.

Run from ui/ under scripts/pm/os-verify-lock.sh, head 0564756:

  • pnpm exec vitest run --maxWorkers=1 on the two new files: exit 0, 2 files, 12 tests.
  • pnpm exec vitest run --maxWorkers=2 on the 79 existing layout/__tests__ files for AppHeader, AppSwitcher, UnifiedSidebar, PageHeader, ConsolePageHeader, InboxPopover and LocaleSwitcher (including AppHeader.homeMark-12605, which pins the product mark's classes): exit 0, 79 files, 512 tests (run at 7a92e5d; 0564756 changes only the two new test files).
  • pnpm exec eslint on the ten changed files: no errors; the warnings are the files' existing any and hook warnings, none on a changed line.

Acceptance notes

Verification

  • node scripts/pm/dispatch-gates.mjs --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN; every recorded exit is 0. The 30 gate commands and pnpm --dir ui exec vitest run scripts/ (169 files passed, 2 skipped) ran at 7a92e5d; 0564756 touches only the two new test files.
  • node scripts/check-changeset-no-major.mjs --base origin/main, node scripts/check-adr-0087-registration.mjs --base origin/main, node scripts/check-closing-keyword-parity.mjs and node scripts/check-partof-closing-keyword.mjs, with this body as the event: exit 0 each.
  • node scripts/check-sdui-manifest.mjs: exit 0.
  • pnpm exec turbo run type-check --filter=@object-ui/app-shell --concurrency=2 at 0564756: exit 0, 65 of 65 tasks.
  • Ablation re-run at 0564756: 2 of 4 red, restored byte-identical to HEAD (blob acf40396).

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes. (CI runs the repo lint)
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (not touched)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.

Generated by Claude Code

…ets below md

Below 768 px a route change closes the sidebar drawer, so a tapped entry
opens its page uncovered. The top bar's controls, the account menu's theme
and language buttons and the page header's Refresh are 44 px below md, and
the bar's menus get 44 px rows. Below 640 px an app's bar leaves the product
mark and the Studio button to the app switcher's menu to fit them.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jRQob8nVK5owwvNtfr
…an unused helper

The drawer test's probe assigned module variables during render, which
react-hooks/globals refuses; it now publishes them after each commit. The
phone-targets test no longer declares an unused DOM-order helper.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jRQob8nVK5owwvNtfr
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 2, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu

席位核对 · ACCEPT · 2026-11-02T09:05Z

Head-sha: 056475681ed40673cd908cee31bec0d950a3e297

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Console at phone width: Navigation flow audit and fixes at 375 and 390 px

1 participant