Repository navigation
fix(ui): the list flow at phone width: 44 px targets, inset panels, a card pager, the active sort on the bar, and Back that pops cleanly - #29273
Merged
Conversation
…els, a card pager, pull-to-refresh only at the top WIP: toolbar, view switcher, filter, sort and grouping builders, Display panel, the phone card list's pager and the pull-to-refresh guard. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jUwDX3ZR6eBujakKoT
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jUwDX3ZR6eBujakKoT
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jUwDX3ZR6eBujakKoT
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jUwDX3ZR6eBujakKoT
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jUwDX3ZR6eBujakKoT
…he history Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jUwDX3ZR6eBujakKoT
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jUwDX3ZR6eBujakKoT
This was referenced Nov 2, 2026
…a third named pager Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jUwDX3ZR6eBujakKoT
…er commits it in a transition Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jUwDX3ZR6eBujakKoT
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jUwDX3ZR6eBujakKoT
This was referenced Nov 2, 2026
Merged
… draws Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jUwDX3ZR6eBujakKoT
Contributor
Author
|
domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu 席位核对 · ACCEPT · 2026-11-02T12:36ZHead-sha:
|
8 tasks done
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.
domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu
Requested by Steedos · project thread
Fixes #29074
Fixes #29057
Clause-②: no
Metadata-protocol: none
维护者速读
sm以下)下,列表流程的每个控件都至少 44 px:工具栏的视图切换、筛选、排序、显示、搜索(含搜索框和清除),视图切换菜单,筛选面板,排序构建器,分组编辑器,以及显示面板的行、开关和底部按钮。筛选、排序、显示三个面板左右各留 16 px。排序层级和筛选条件的字段选择框占满整行,标签不再被截成「Accou…」。服务端分页时,手机卡片列表末尾多了一个翻页条(首页、上一页、第 N/M 页、下一页、末页),复用表格已有的文案。下拉刷新只在列表滚到顶部时触发。有排序时,工具栏保留排序按钮并显示层级数。手机上从列表打开的记录页,「‹」改为在历史里后退,之后再按返回会离开列表,不会重新打开记录;「‹」仍显示对象复数名。桌面尺寸和行为不变。max-sm:的className,桌面不受影响。三处行为变化:手机列表推入记录页时带上RECORD_PAGE_PUSHED_STATE标记(替换式跳转不带);usePullToRefresh在任何宽度都只在触点与绑定元素之间的滚动框都在顶部时才开始下拉;触屏设备上视图行的 ☆ 和 📌 一直显示(鼠标仍是悬停显示)。不加键、不加 prop、不加导出、不加文案。回滚即回退本 PR。ui/packages/plugin-grid/src/ObjectGrid.tsx卡片分支里的cardPager(只在manualPaginationOn且总数超过一页时绘制,翻页走表格同一个manualOnPageChange);ui/packages/mobile/src/usePullToRefresh.ts的scrolledBetween;ui/packages/plugin-list/src/ListView.tsx排序按钮的隐藏条件(sortInDisplay && (currentSort.length === 0 || phoneSearchOpen));ui/packages/app-shell/src/views/objectViewRecordNavigation.ts的phone && !replace标记,以及RecordDetailView.tsx里「‹」标签条件对列表来源保留复数名。pnpm --dir ui exec vitest run packages/plugin-list/src/__tests__/ListView.phoneWidth-29074.test.tsx packages/plugin-grid/src/__tests__/phoneCardPager-29074.test.tsx packages/mobile/src/__tests__/usePullToRefresh.innerScroller-29074.test.tsx packages/components/src/__tests__/list-builders-phone-width-29074.test.tsx packages/app-shell/src/views/objectViewTitleSwitcher.phoneWidth-29074.test.tsx packages/app-shell/src/views/objectViewRecordNavigation.phonePush-29074.test.tsx,预期 6 个文件全部通过。Part of #29040. Claim: issuecomment-6572134252, surface amended in issuecomment-6572419569, issuecomment-6573926280 and issuecomment-6573994363.
What changes
The mobile app hosts the Console in a WebView, so below
smthe Console is the phone UI. This PR covers the Lists flow: the list toolbar, the view switcher, Filter, Sort, Display, phone search, the card list and its end, and pull-to-refresh. Every change applies belowsm(max-sm:utilities), so wider screens are unchanged, except where a bullet says otherwise.ListView(plugin-list): the view switcher, Filter, Sort, Display and phone search triggers are 44 px, as are the open search field and its Clear. The Filter and Sort popovers keep 16 px from both screen edges (collisionPadding, phone only). Belowsmthe Sort builder lives in the Display panel, so an active sort used to show nowhere on the bar (Phone list: an active sort shows nowhere on the toolbar once Sort moves into the Display panel, and the sort builder truncates field labels at phone width #29057). While a sort is active, the bar now keeps the Sort trigger with its level count. It steps aside while the phone search field is open, so the field keeps its room.ViewSettingsPopover(plugin-list): section headers, switch rows (the whole row is the label), the color select, hide-fields rows, Density, Reset, Refresh and Export are 44 px. The panel keeps 16 px from both edges. It exportsPHONE_TOUCH_TARGET,PHONE_POPOVER_GUTTERanduseBelowSmtoListViewinside the package; the package's public entry does not re-export them. The record-open section is untouched, since feat(ui)!: a record opened from a list opens in the drawer by default, and the split pane retires #29108 rewrote it.objectViewTitleSwitcher(app-shell): trigger, search, rows, ☆, 📌, New view and Manage views are 44 px. On a touch screen (pointer-coarse) a row's ☆ and 📌 always show, because there is no hover to reveal them. This holds at every width on a touch screen; a mouse still reveals them on hover.SortBuilder,FilterBuilder,GroupingEditor(components): every control is 44 px. A sort level is laid out on three lines (its words; the field beside Remove; the direction), and a filter condition gives its field the whole row, so a label such as "Last Modified At" or "Account Name" shows whole. The filter value field uses 16 px text, so iOS Safari does not zoom in on focus.ObjectGrid(plugin-grid): under server pagination the phone's card list held one page (50 of 403) with no way to the rest except search or filter. The card list now ends in a pager inside its scroller: First, Previous, "Page N of M", Next, Last, each 44 px, with the table pager's existingtable.*strings. It turns the same page the table's pager turns, and returns the scroller to the top.from, but without the pushed marker a related row's push carries. The page's 「‹」 therefore navigated forward to a fresh copy of the list, and Back after it opened the record again. 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 (landed first) makes that link replace the record's entry, so Back no longer reopens the record but lands on a second copy of the list.objectViewRecordNavigationnow addsRECORD_PAGE_PUSHED_STATEon a phone push (not on the replace a phone answers a drawer link with, which leaves no list below), andobjectViewPluginSchemadoes so for plugin views. The record page then goes back in the history, so one Back after 「‹」 leaves the list.RecordDetailView's 「‹」 label condition keeps the object's plural for a page whose origin is the object's own list route (Target 3 of console mobile (≤ 640 px): list as card rows with view chips and a FAB, the record page with a compact header, sticky tabs and a bottom action bar, related records pushed as full pages with a path row and back — the record is always a page on a phone #27058); a page pushed from a record, of this object or another, still names that record. Desktop state is unchanged.usePullToRefresh(mobile): the list binds the gesture to its root, which clips its body and never scrolls, so the hook readscrollTop0 even half-way down the list. A pull now starts only when every scroll box between the touched node and the bound element is at its top. This applies at every width, wherever the hook is used.No key, prop, package export, locale string or spec surface is added. The 22 px link of an embedded grid's first column is not touched; it is #29219.
Why
Measured on
main(a4015ab) at 375 and 390 px on the CRM and showcase examples, every list control was 24–36 px; Filter ran flush right and Display flush left; records past the first page could not be reached on a phone; a downward drag mid-list refreshed; an active sort was invisible on the phone bar (#29057); and on a phone, Back after a list-opened record's 「‹」 opened that record again (#29210's run).Tests
ListView.phoneWidth-29074.test.tsx(new): toolbar tokens; the Sort trigger is hidden belowsmwhen unsorted, shown with its count when sorted, and hidden while phone search is open; the Display panel still carries the sort block;useBelowSmfollows amatchMediastub.phoneCardPager-29074.test.tsx(new): 12 records at page size 5 and 390 px: the pager reads Page 1 of 3, Next shows records 6–10, Last shows 11–12 and disables Next; every button ish-11 w-11. Control: 3 records draw no pager.usePullToRefresh.innerScroller-29074.test.tsx(new): control, a drag on a row with the inner box at its top pulls and refreshes; with the inner box scrolled the same drag shows no indicator and no refresh; a touch outside the scrolled box still pulls.list-builders-phone-width-29074.test.tsx(new): SortBuilder, FilterBuilder and GroupingEditor tokens, desktop tokens kept beside them.objectViewTitleSwitcher.phoneWidth-29074.test.tsx(new): trigger and menu tokens; ☆ and 📌 show onpointer-coarseand stay hover-only otherwise.objectViewRecordNavigation.phonePush-29074.test.tsx(new): a phone row click carries the marker; a desktop row click and a phone's drawer-link replace do not; a plugin view's push does. On the realRecordDetailViewat 390 px, home → list → row → 「‹ Leads」 (a button) returns to the list, and Back then lands on home. Control: an unmarked push makes 「‹」 a link, which since 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 replaces the record's entry with a second copy of the list, so Back stays on the list and only a second Back reaches home. A lead pushed from another lead's page still reads 「‹ Mira Costa」. Ablating each of the three source edits on its own fails its cases (the list hook: 2; the plugin hook: 1; the label condition: 1), and the rest pass.origin/main's eight source files copied in (ListView.tsx,ViewSettingsPopover.tsx,objectViewTitleSwitcher.tsx,sort-builder.tsx,filter-builder.tsx,grouping-editor.tsx,usePullToRefresh.ts,ObjectGrid.tsx), the first five new files went 15 failed and 5 passed. The five that passed are the controls (unsorted list hides Sort, Display keeps the sort block, a pull at the top, a touch outside the scrolled box, a one-page list draws no pager). The restore leftgit statusclean.Acceptance notes
The card's run record, measured in a real browser. The Console is a production build of this branch (
pnpm console:build), served at/_consolebyobjectstack dev --seed-admin --fresh. Examples: CRM (crm_account) and showcase (showcase_account), each seeded with 400 extra accounts (403 rows). Chromium withisMobileandhasTouchat 375×812 and 390×844, DPR 2. The before run is onmaina4015ab, the after run on this branch at 9f27c26. Header chrome (AppHeader) is #29075's and is left out of the touch census.Each cell reads before / after. The Back fix came in after this run (from #29210's findings). It was checked live on the rebuilt production console at 375 and 390 (CRM, home → lead list → first card → 「‹」 → browser Back): 「‹」 is a button reading 「Leads」, it lands on the list, and Back then lands on home rather than the record.
main; on this branch it shows nothing and makes no request. At the top it refetches once, as before.Emulation.setCPUThrottlingRate, production build): first card 3.2–4.4 s, 40 scroll frames p95 17–20 ms (max 25) with no long task, search typed to result 352–509 ms. Onmain: 3.2–3.8 s, p95 18–20 ms, 450–680 ms. The CRM and showcase runs of one width shared the box.sys_inbox_message?view=mine, 60 rows, 4× CPU; Console at phone width: Notifications flow audit and fixes at 375 and 390 px #29076 recorded a 284–495 ms first-scroll frame there): the first visit in a fresh browser context reaches network idle (about 2 s) before the card list has mounted, and the next frames carry the mount, 279–341 ms with 245–299 ms long tasks. Once the list is there, every scroll frame is 18–36 ms with no long task, scrolled at once or after 1.5 s (11 runs). The long frame is the page's first render, not scrolling; this PR does not change that render.mainand on this branch alike. It is a render cost of the embedded grid (plugin-detailRelatedListtab activation, then plugin-gridObjectGrid), not a phone-layout fault, and needs a profile to place.Verification
Readings on head
cc0e7090(merged with main ateff2d2a9, which brought #29210's replacing 「‹」 link and #29252's Display footer; the footer conflict kept main's layout plus this PR's phone sizes). Atcc0e7090: the five packages' type-check 69 of 69;plugin-listandmobilewhole plus every new file, the census, the components dialog and builder files and the record-page Back suites, 245 files, 2402 of 2403 tests passed, the one failure being the Back file's stale control, since corrected; the corrected Back file, shuffled with its neighbours under a concurrent load, 20 of 20 runs green, and ablating the list hook's marker still fails its 2 cases. Earlier: The package suites ran at6641c3be(main with #29108 merged), before the Back commit; that commit's app-shell files were then checked with their own suites, and the derived sweep re-ran atea6d9657. Merging main again (5692ba8d, #29189 editsListView's imports; a one-line import resolution) was followed by the five packages' type-check (69 of 69) and a re-run ofplugin-listandmobilewhole plus every new file and the record-page Back suites: 204 files, 1931 tests passed.Tests
plugin-list,plugin-gridandmobilewhole, everycomponentstest file that names the sort, filter or group builder (29 files, the new one among them) and the threeapp-shellfiles that name the view switcher, under the verify lock with--maxWorkers=2: 496 files and 4664 tests passed. This is a narrowing forcomponentsandapp-shell; CI's Test UI runs the rest.objectViewRecordNavigation.phonePush-29074.test.tsxwithRecordDetailView.phoneFace-27058,RecordDetailView.topBar-27524,recordPagePush-27058,objectViewRecordNavigation.listRows-27524andrecordPaneGroupScope-28461: 6 files, 45 tests passed.Type-check and lint
turbo run type-checkfor@object-ui/plugin-list,plugin-grid,components,app-shellandmobile(bothtsclegs, src and tests) on the merged head: 69 of 69 tasks passed;@object-ui/app-shellagain atea6d9657: exit 0.ui/) on the 17 changed source and test files: 0 errors. With--no-inline-configit reports one error,react-hooks/static-componentsatObjectGrid.tsxDataEmptyState, whichmainalready carries with aneslint-disable-next-line; no warning is new apart fromreact-refresh/only-export-componentson the threeViewSettingsPopoverexports.pnpm check:new-line-citationsunderui/: 0 new citations.Derived gates
node scripts/pm/dispatch-gates.mjs --ran: 35 derived families accounted for, 35 run, 0 NOT MEASURED, every one exit 0, the whole sweep re-run atea6d9657. That includesnode scripts/check-sdui-manifest.mjsand its--self-test,pnpm --dir ui exec vitest run scripts/(169 files passed, 2 skipped; 4900 tests passed) andpnpm check:type-check-debtunder the verify lock. The PR-body gates (check-changeset-no-major,check-closing-keyword-parity,check-adr-0087-registration,check-empty-changeset,check-partof-closing-keyword) ran on this body.Human Minimum
packages/specholds no runtime logic. (No spec change.)main, one pull request. (Phone list: an active sort shows nowhere on the toolbar once Sort moves into the Display panel, and the sort builder truncates field labels at phone width #29057 is folded in by the card's claim amendment.)packages/specnot touched.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_01QsR2jUwDX3ZR6eBujakKoT