Skip to content

fix(fields, plugin-form): form controls reach a 44 px touch target at phone width - #29209

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

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-bi35xl

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

Requested by Steedos · project thread

Fixes #29072

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:手机宽度(低于 sm,640 px)下,新建 / 编辑表单里的输入框、下拉、查找字段、日期选择器、明细行表格和表单底部按钮,触控区域统一达到 44 px;桌面宽度外观不变。
  • 为什么改:移动端 App 用 WebView 承载 Console(Console at phone width: audit and fix the key flows at 375px (the mobile app now hosts the Console) #29040),实测 375 / 390 px 下表单控件只有 28–40 px,日历格子和下拉选项 32 px,低于 44 px 触控下限,容易点错。
  • 风险与回滚:只加 Tailwind 类(min-h-[44px] sm:min-h-0,与表单内置控件已用的同一个记号),不改逻辑、不改导出、不改元数据;回滚即 revert 本 PR。
  • 先看:ui/packages/fields/src/widgets/date-picker/DatePickerField.tsx(右侧按钮变大后输入框右内边距改为 pr-24 sm:pr-16、日历格子 44 px);ui/packages/fields/src/widgets/LookupField.tsx;ui/packages/fields/src/widgets/touchTarget.ts。
  • 两分钟验证:手机尺寸打开 CRM「商机 → 新建」,点开「预计成交日期」日历:每个日期格子约 44 px,一周七天仍完整放在屏幕内;桌面宽度下同一日历仍是 32 px 格子。
  • 需要你做的:无,席位落地。

What changes

At phone width (below sm), the create / edit form's registered field widgets now carry the same touch floor the form renderer's built-in controls already use, min-h-[44px] sm:min-h-0, through a package-internal token module ui/packages/fields/src/widgets/touchTarget.ts (not re-exported, so no published surface moves). From sm up every control keeps its previous height.

  • Inputs: text, email, phone, URL, number, currency and percent inputs; the percent slider's hit area (its root takes the pointer along its whole height).
  • Select: the trigger and every option.
  • Lookup: the trigger, the clear and browse-all buttons, the dropdown's search box, its "show all results" and quick-create buttons; the record picker dialog's search box and Filters button.
  • Date picker: the input, its clear and calendar buttons, the Today / Tomorrow shortcuts, and a 44 px calendar cell below sm ([--cell-size:2.75rem] sm:[--cell-size:2rem] passed to the Calendar primitive, which cn merges over its default). Seven 44 px days plus the calendar padding measure 332 px, inside a 343 px form column at 375 px. The input's right padding grows to pr-24 below sm so its text stops short of the two 44 px buttons.
  • Line-item grid (opportunity products): cell inputs, cell selects and their options, the compact lookup cell, the three row buttons, and "Add line".
  • Form footers: the modal, drawer and master-detail forms' Cancel / Reset / Submit buttons.
  • File field (added from Console at phone width: Record flow audit and fixes at 375 and 390 px #29071's run, on the seat's order): the "Take photo" camera button gets PHONE_TOUCH_HEIGHT and the per-file remove button PHONE_TOUCH_SQUARE in FileField.tsx. Their before sizes, 36 px (Button size="sm", h-9) and 24×24 (h-6 w-6), were read from the Button token and the class, not measured on a page; no create form in either example drew a file field on this flow.

Fence check: FormPage.tsx is not touched (its refusal path stays with #29019), nor sectionFields.ts (#29021), sheet.tsx (#29078), dialog.tsx (#29107), navigation-overlay.tsx, record-list-face.tsx, ObjectView.tsx, recordOpenPreference.ts, ui/packages/mobile/**, the console's main / App / AppContent, or any shared chrome. No ui/packages/components/src/ui/** primitive is edited: every change is a className handed to it.

Run record

Driven in Chromium (Playwright, isMobile, hasTouch, DPR 2) at 375×812 and 390×844, against the Console served from source (Vite) over examples/app-crm and examples/app-showcase started with objectstack dev --seed-admin --fresh. Flow per object: list → phone create button → create form → select listbox → date picker (where the object has one) → lookup dropdown → browse-all record picker → keyboard check on the last and a middle text control → clear the required field and submit (validation) → open a seeded record → Edit form. Before = merge base a4015ab4 (the widget sources restored from it), after = this branch head.

Check Example · page 375×812 before 375×812 after 390×844 before 390×844 after
1 No horizontal page scroll CRM · lead create + edit pass pass pass pass
1 No horizontal page scroll CRM · opportunity create + edit pass pass pass pass
1 No horizontal page scroll Showcase · task create + edit pass pass pass pass
2 Touch targets ≥ 44 px CRM · lead create + edit fail: 15 controls at 40 px, 6 options at 32, lookup search 32 / quick-create 36, picker search 40 / Filters 36 / Close 16 fail: picker Close 16×16 only (finding, #29107) fail: same as 375 fail: picker Close 16×16 only
2 Touch targets ≥ 44 px CRM · opportunity create + edit fail: controls at 40, date buttons 28, line-item cells 32, Add line 36, slider 8, 5 options at 32, 39 calendar cells at 32, lookup and picker as above fail: picker Close 16×16 only fail: same as 375 fail: picker Close 16×16 only
2 Touch targets ≥ 44 px Showcase · task create + edit fail: controls at 40, date buttons 28, 5 options at 32, 39 calendar cells at 32, lookup and picker as above fail: picker Close 16×16 only fail: same as 375 fail: picker Close 16×16 only
3 Dialogs and popovers fit the viewport all three (form dialog, select listbox, date popover, lookup dropdown, record picker) pass pass pass pass
4 Virtual keyboard leaves the focused field visible all three (last and middle text control) pass pass pass pass
Validation: first error focused and in view all three pass pass pass pass
5 Long lists responsive at 4× CPU CRM · opportunity create, Account lookup over 303 accounts, production bundle pass (open 176 ms, typing 64, scroll no long task) borderline (open 184–256 ms over 3 runs, typing ≤ 88, scroll ≤ 51 ms tasks) pass (open 192, typing 72) pass (open 192–256, typing ≤ 88)
How each check was measured
  • Horizontal scroll: document.documentElement.scrollWidth against innerWidth, and every element in the open dialog whose box crosses the viewport edge without an overflow-x ancestor clipping it (the line-item table and the picker table scroll inside their own containers, which is by design).
  • Touch targets: every visible button, input, select, textarea, [role=combobox], [role=option], [role=checkbox], [role=switch], a[href] and the percent slider root inside the open dialog, listbox or popover, by bounding box, against 44 px on both axes.
  • Fit: the open dialog / listbox / popover box inside the viewport.
  • Keyboard: the WebView adjustResize model: focus the control, shrink the layout viewport by 336 px (a Gboard-height keyboard on an 812 px screen), let the browser scroll the focused editable into view, then assert its box is inside the shortened viewport and elementFromPoint at its centre hits it (no sticky footer or header over it). A real device IME was not exercised.
  • 4× CPU: CDP Emulation.setCPUThrottlingRate 4 on the production Console bundle (pnpm console:build, served at /_console by objectstack dev), with 300 extra accounts seeded through the data API. Pass = every Event Timing entry (click, key) at most 200 ms (the "good" INP line), and no long task over 200 ms while scrolling. The dev-server (unbundled React) numbers the harness also prints are 2–3× slower and are not the reading.
  • Screenshots for every step, before and after, are in the project folder (not attached here).

Tests

  • ui/packages/fields/src/widgets/touchTarget.formControls-29072.test.tsx (14 cases): the token shape, each input widget, a host className still reaching the input beside the token, the slider root, the select trigger and options, the lookup trigger / clear / browse-all, the date picker's input, buttons and calendar cell size, and the file field's camera and remove buttons. Reverse-verified: with the widget sources restored from the merge base, 12 of 13 go red (the token-shape case reads only the new module).
  • ui/e2e/form-controls-phone-touch.spec.ts (3 cases, shape of record-path-phone-row.spec.ts): reads the tokens, the Input / icon-Button base heights and the calendar's cell-size classes out of the sources, compiles real Tailwind for them, and measures a fixture: at 375 and 390 px the input, the date-picker button, the browse-all button and all seven calendar days are at least 44 px and the calendar ends inside the form column with no page scroll; at 1024 px the primitives keep 40 / 28 / 40 / 32 px. Red on the merge base (the date picker carries no cell-size override there).

Acceptance notes

Verification

At head 99a5359b (main merged at dedc3505; main touched none of these files). The file-field commit was re-verified on its own: the touchTarget test 14 / 14 (its new file case red with FileField.tsx from the previous head), the touched test files plus complex-widgets.test.tsx 110 files / 851 tests, the @object-ui/fields type-check 47 tasks exit 0, ESLint on the file 0 errors, check-sdui-manifest 0. The readings below were taken at 6034e7ec:

  • node scripts/pm/dispatch-gates.mjs --ran: 34 derived, 34 run, all exit 0. pnpm --dir ui exec vitest run scripts/ was killed once by memory pressure while the run-record servers were up; rerun alone it passes (169 files, 4900 tests).
  • node scripts/check-sdui-manifest.mjs: exit 0, after the merge.
  • Type-check of @object-ui/fields and @object-ui/plugin-form (turbo, 49 tasks): exit 0. ESLint on the changed files: 0 errors.
  • The new unit file (13 at 6034e7ec, 14 now) and the touched widgets' and form footers' existing test files: 106 files, 768 tests, pass after the merge. The e2e spec: 3 pass.
  • Diff at 99a5359b: 20 files, +405 / −38, well under 3000 lines. No exported type of a published package moves.

The os-dev report on #29072 carries the same readings.


Generated by Claude Code

…low sm

At phone width the create / edit form's registered field widgets kept the
primitives' 40 px (inputs, select trigger, lookup trigger), 32 px (select
options, calendar days, line-item cells, lookup search) and 28 px (date
picker buttons) heights. They now carry the same `min-h-[44px] sm:min-h-0`
token the form renderer's built-in controls already do, and the form
footers' buttons do too. From `sm` up nothing changes.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jUqXiWNJKjMJYThRr1
… touch floor below sm

The take-photo button (Button size sm, 36 px) gets PHONE_TOUCH_HEIGHT and
the per-file remove button (24 px) gets PHONE_TOUCH_SQUARE, from #29071's
run. Pinned in the touchTarget test.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jUqXiWNJKjMJYThRr1
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

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

Head-sha: 99a5359b1657f80e66d6f250e0f315bc79aaca5b


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 2, 2026 10:13
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 2, 2026 10:13
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit 39951d2 Nov 2, 2026
60 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-bi35xl branch November 2, 2026 11:00
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/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant