Skip to content

fix(components): dropdown-menu rows and tabs reach a 44 px touch target at phone width - #29348

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

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-ogqo3w

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #29215

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:手机宽度(小于 sm 断点)下,所有下拉菜单的每一行(普通项、勾选项、单选项、子菜单入口)和所有 Tabs 标签页按钮的点击高度至少 44 px,Tabs 外框随之长高;桌面宽度下样式一字未改。
  • 为什么改:移动端 App 用 WebView 承载 Console,记录页「更多」菜单、列表行操作菜单和各处标签页都用这两个共享组件,原来菜单行 32 px、标签页 32 到 38 px,手机上容易点错。
  • 风险与回滚:只加了 max-sm: 前缀的样式类,桌面不受影响;手机上带固定高度的标签栏(如 AI 对话页的紧凑切换器)会变高。回滚即 revert 本 PR。
  • 先看:ui/scripts/shadcn-local-patches.mjs(新增的两组补丁声明)、ui/packages/components/src/ui/tabs.tsx(TabsList 与 TabsTrigger 两行)、ui/packages/components/src/__tests__/menu-tabs-touch-target-29215.test.tsx。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/components/src/__tests__/menu-tabs-touch-target-29215.test.tsx scripts/__tests__/shadcn-local-patches.test.ts,预期全绿;或在 375 px 宽的 Console 打开任一记录页点「More actions」,每行高 44 px。
  • 需要你做的:无,席位落地。

What changes

  • Below sm, every DropdownMenu row is at least 44 px tall: DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem and DropdownMenuSubTrigger each gain max-sm:min-h-11 right after their py-1.5. DropdownMenuSubTrigger is included because a phone user taps it to open the sub-menu; DropdownMenuLabel is not a control and is left alone.
  • Below sm, TabsTrigger gains max-sm:min-h-11, and TabsList gains max-sm:h-auto at the end of its class string, so the list drops its fixed h-10 there and grows with its triggers.
  • From sm up, every class list is upstream's, token for token. No export, prop or TabsContent change.
  • dropdown-menu.tsx and tabs.tsx are Shadcn-synced (ui/AGENTS.md commandment 7), so both edits are declared in ui/scripts/shadcn-local-patches.mjs as two new families, dropdownMenuRowTouchTargetPatches and tabsTouchTargetPatches, built by one small phoneTouchTargetPatch helper that inserts the phone class after a named token inside a registry anchor. The shipped files were written by applying those patches, and pnpm shadcn:update re-applies them. The checkbox and radio items share one upstream class string, so their anchor declares two occurrences.
  • Both families are new to LOCAL_PATCHES, so the registry provenance test requires vendored fixtures: ui/scripts/__tests__/fixtures/shadcn-registry/dropdown-menu.registry.txt and tabs.registry.txt, captured verbatim from shadcn-ui/ui 8a7701ec27eb9cb8e0377db769fbe6d744113c52 through raw.githubusercontent.com. The same capture reproduces dialog's recorded sha256 byte for byte. tabs round-trips byte for byte (roundTrip: true). dropdown-menu cannot, because it already ships the undeclared Tailwind v4 origin-(--radix-…) spelling that shadcn-components.json documents, so its entry pins the inequality with that reason, as calendar does.
  • ui/packages/components/shadcn-components.json: the dropdown-menu localEdits note names the new patch beside the Tailwind v4 note, and tabs gains a localEdits note.
  • Changeset .changeset/29215-menu-tabs-phone-touch-target.md: @object-ui/components patch.

Tests

  • New ui/packages/components/src/__tests__/menu-tabs-touch-target-29215.test.tsx (10 cases). For each of DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuSubTrigger, TabsTrigger and TabsList, the rendered element carries exactly the one phone class, and every other token equals upstream's list in order. A label and TabsContent get no phone class. A caller's own py-2, or h-7 and h-6, still merge through cn, and the phone class survives them.
  • ui/scripts/__tests__/shadcn-local-patches.test.ts: provenance entries for the two fixtures, plus a new describe block. It checks the declared ids. Applied to registry bytes, each family adds only max-sm: classes, leaves every class string's other tokens identical in order, and adds exactly 4 max-sm:min-h-11 for dropdown-menu and 1 max-sm:min-h-11 plus 1 max-sm:h-auto for tabs. Only the four menu rows carry the floor, and TabsList still opens with inline-flex h-10 (the opening e2e/approvals-inbox-phone.spec.ts reads it by). The family refuses when upstream re-pads the item (found 0x), and when the checkbox and radio rows stop sharing a class string (found 1x).
  • ui/packages/components/src/__tests__/__snapshots__/snapshot-critical.test.tsx.snap: the two "Tabs snapshots" regenerated with vitest --update. The diff is the TabsList and TabsTrigger class attributes only, gaining max-sm:h-auto and max-sm:min-h-11. A sweep of every *.snap, *.test.* and *.spec.* file for the upstream item, trigger and list class strings finds no other pin.
  • Ablation, one-shot, at 43e61db, through scripts/ablation-replace.mjs (anchor 1 to 0, restore verified git diff HEAD empty): with max-sm:min-h-11 dropped from DropdownMenuItem in the shipped file, 3 cases go red. They are "DropdownMenuItem: at least 44 px below sm, upstream classes from sm up", "a caller's own padding still merges, and the phone floor survives it" and "dropdown-menu.tsx carries its declared patches".

Acceptance notes

  • Token choice. The card asked for min-h-[44px] sm:min-h-0. This PR uses max-sm:min-h-11 and max-sm:h-auto instead, accepted by the seat before the build. It follows the shadcn-patch precedent on main (the Sheet close and Dialog close families are max-sm: only). It leaves every desktop token byte-identical to upstream, which the tests assert token for token. sm:min-h-0 would also change desktop min-height from auto to 0, which matters in a flex-col tab list (the vertical tabs renderer, ViewSwitcher's vertical orientation). The helper Console at phone width: Forms flow audit and fixes at 375 and 390 px #29072 landed (touchTarget.ts) is internal to @object-ui/fields, so @object-ui/components cannot import it either way.
  • Fixed-height callers grow on phones. A TabsList or TabsTrigger given a fixed height by its caller still gets the phone floor, because max-sm: classes do not conflict with unprefixed ones in tailwind-merge. The AI chat page's compact assistant switcher (h-7 list, h-6 triggers) is therefore 45 px tall below sm. That is the card's intent: every tab strip.
  • Per-page phone arms now overlap the primitive floor. ApprovalsInboxPage (tabTouch), InboxPopover (TAB_TRIGGER_CLASS) and AppHeader (max-md:[&_[role=menuitem]]:min-h-11) already lift their own tabs or menu rows below md. They still own 640 to 767 px, so nothing is removed here.
  • Not covered, same shape, not measured. ContextMenu, Menubar, Select and Command rows have the same py-1.5 shape upstream. This card names only DropdownMenu and Tabs.

Verification

  • Real browser: app-crm backend (objectstack dev --seed-admin --fresh) plus the Console Vite dev server on source, Playwright, CRM account record page (/apps/crm_app/crm_account/record/:id). Before means main's dropdown-menu.tsx and tabs.tsx, with Vite restarted with --force before the leg; the files were restored from HEAD afterwards and git status was clean.
    • "More actions" menu rows (Refresh, Add to favorites, Share, Delete): 375 x 812, 32 px before, 44 px after. 1280 x 800, 32 px before and after.
    • The tab row's "+3" overflow menu rows (Contacts, Opportunities, Leads), 375 x 812: 32 px before, 44 px after.
    • TabsTrigger in real Chromium with real Tailwind (the installed tailwindcss, compiled for the class strings read out of each tabs.tsx): 375 px, trigger 32 to 44 px and list 40 to 52 px. 1280 px, trigger 32 px and list 40 px in both legs.
    • The record page's own tab row (Overview, Activity, …) is not this primitive. It is RecordTabRow in @object-ui/plugin-detail, an h-9 button strip, so it measures 36 px in both legs and both widths. It is reported to the seat as a separate finding and is not changed here.
  • Local gates at d67694b (main merged after fix(components): give the Dialog close button a 44 px touch target at phone width #29204 landed): the 36 families dispatch-gates --commands derives for this diff all exit 0, and the --ran reconciliation reads 36 derived, 36 run, 0 NOT-MEASURED. They include node scripts/check-sdui-manifest.mjs and pnpm --dir ui exec vitest run scripts/ (169 files, 4921 tests passed). @object-ui/components type-check (with tsconfig.test.json), pnpm --dir ui run type-check:scripts, eslint on the five touched source and test files and pnpm check:new-line-citations (0 new) all exit 0, measured at 43e61db.
  • At c25c35b (main merged again, no incoming commit touches these 10 paths): the four touch-target component tests, snapshot-critical, the patch registry test and main's new phoneCardRowActions-29150 re-run green (6 files, 111 tests), and check-sdui-manifest exits 0. The whole components package is left to CI Test UI.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic.
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes.
  • 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.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QsR2jSMMkBJxrdeZwTifs8


Generated by Claude Code

Below sm, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem,
DropdownMenuSubTrigger and TabsTrigger get max-sm:min-h-11, and TabsList
max-sm:h-auto so it grows with its triggers. From sm up every class list is
upstream's. Declared as shadcn local patches with vendored registry fixtures.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jSMMkBJxrdeZwTifs8
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Nov 2, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m

席位核对 · ACCEPT · 2026-11-02T12:53Z

Head-sha: c25c35bcc8bdfc6053b637bbe9327f75942f161d

  • Shape: draft → main, Fixes #29215 in the body only. 10 files, +719 / −15 (734 lines ≤ 3000; 255 of them are the two verbatim registry fixtures). No governed path: check-governed-merges --pr 29348 reports NOT governed (0 of 10 paths). Clause-②: no and Metadata-protocol: none are line-initial in the body and the changeset, as claimed (6575153610, amended 6575228962). The changeset is patch for @object-ui/components.
  • Contract review: none owed (Clause-②: no; class strings only, no export, prop or key changes).
  • File surface: inside the amended claim: dropdown-menu.tsx (Item, CheckboxItem, RadioItem, SubTrigger) and tabs.tsx (TabsList, TabsTrigger), the Shadcn local-patch declaration ui/scripts/shadcn-local-patches.mjs and shadcn-components.json so pnpm shadcn:update re-applies them, its test and the two registry fixtures, the critical snapshot, the new component test, and the changeset.
  • Read in the diff: each change adds max-sm:min-h-11 (and max-sm:h-auto on TabsList so it grows with its triggers); from sm up the class strings are byte-identical to upstream.
  • Serial: the hard constraint held: seat 2's PR fix(components): give the Dialog close button a 44 px touch target at phone width #29204 landed at 11:33Z first, and the os-dev merged main twice (d67694b, c25c35b) with no conflicts.
  • Tests (dev): 6 targeted files 111 / 111 at this head; ablation (drop the Item class) reds 3, restored. Derived gates 36 of 36 at d67694b, including ui/scripts (4921 passed) and check-sdui-manifest; type-check, eslint and line-citations green.
  • Measured (dev): on the CRM record page at 375 px, "More actions" rows go 32 → 44 px and the tab-overflow rows 32 → 44; TabsTrigger in Chromium with the real Tailwind build 32 → 44 (list 40 → 52); all unchanged at 1280.
  • Withdrawn: the body's Verification bullet calling the record page's RecordTabRow (36 px) a separate finding. That reading ran on a base without eed73a3 (fix(plugin-detail, app-shell, components): the record page's controls are 44 px touch targets at phone width, and Back after 「‹」 leaves the record #29210); origin/main already has h-11 sm:h-9 at RecordTabRow.tsx:81, so nothing is owed and no card is filed.
  • Gates (check-runs on this head): 44 success, 11 skipped, 0 failed or pending. mergeable_state is clean.
  • Landing: ordinary queue landing, pr_ready then automerge_enable. Fixes #29215 closes the card on merge.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 2, 2026 12:54
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit 2f380dd Nov 2, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-ogqo3w branch November 2, 2026 13:31
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 tests tooling

Projects

None yet

1 participant