Repository navigation
feat(console): the list title switches views from a dropdown, a pinned view opens by default, and a starred view joins the sidebar favorites - #27943
Conversation
…tarred view joins the sidebar favorites Claude-Session: https://claude.ai/code/session_01Utp6CanDVECEsojSwtvSs7 Co-authored-by: Claude <noreply@anthropic.com>
…tion shows once, and the docs and changeset describe the switcher The title switcher carries a view's description only on a chart face; a list face keeps the list's own info control at the start of its toolbar row, so the text is shown once. The object-list tests that read the retired tab row, the title's plural or a tab's text read the title switcher instead. The views page documents switching, pinning and starring a view. Claude-Session: https://claude.ai/code/session_01Utp6CanDVECEsojSwtvSs7 Co-authored-by: Claude <noreply@anthropic.com>
…isplay locale Claude-Session: https://claude.ai/code/session_01Utp6CanDVECEsojSwtvSs7 Co-authored-by: Claude <noreply@anthropic.com>
…ocus ring The trigger's negative margin was taken out of the width the title is sized to, so a four-character label was cut by one; its outer focus ring was clipped by the title's own truncation. Both seen on the CRM example. Claude-Session: https://claude.ai/code/session_01Utp6CanDVECEsojSwtvSs7 Co-authored-by: Claude <noreply@anthropic.com>
|
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC Reviewed 2026-10-30T11:06Z under the #26676 ruling B (comment 6520722066). A context-free review subagent with no development context got three inputs: #27594's Target and Acceptance (with the three rulings that change how it is read: proxy preview on Reason. All five Target items are met. The leads title reads 「全部线索 ▾ · 5」 and no flat tab row is drawn on the table or the kanban face; filter / sort / display keep their own row. The dropdown has the always-on search, 固定 · 最近 · 全部, authored-then-saved order, view-type icons, ☆ / 📌 on hover, and 「+ 新建视图」 (plus 「管理视图」 for an admin) at the foot. 📌 on 线索看板 makes the bare address and the sidebar entry open it; unpinning restores 全部线索. ☆ puts 「object icon + view label」 into a 收藏 group at the top of the sidebar; clicking it opens the view, unstarring removes it, and a keyboard (Space + arrows) or mouse reorder holds across a reload. Views that an authored sidebar entry already opens (setup app: 全部用户, sessions, notifications) show ☆ disabled with 「已在菜单中」 and are not duplicated, and the CRM favorites do not show in the setup app. In split mode the list keeps the same switcher, and its dropdown opens and switches views with the list body dragged to ≈464 px and at 1280. No
Findings below the bar (none is a REWORK reason for this card):
Not checked: a chart face and a view with a description (the CRM app has neither); a published reader-saved view (the seed has none); reordering non-view favorites. The v4 boards predate this card (they still draw a tab row), so the reviewer used them only as a reference for the split layout. Screenshots (48) and the reviewer's full report are in the project's files: Generated by Claude Code |
|
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC 席位核对ACCEPT · head
Generated by Claude Code |
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC
Requested by Bill · project thread
Fixes #27594
Clause-②: no
Metadata-protocol: none
维护者速读
view),固定视图存本浏览器的 workspacePreference;回滚即 revert 本 PR。views/objectViewTitleSwitcher.tsx(下拉组件与组装它的 hook)、ObjectView.tsx(📌 默认视图的解析顺序:地址 > 固定 > 作者默认 > 第一个)、layout/listViewFavorites.ts与layout/UnifiedSidebar.tsx(☆ 与收藏组拖序)。pnpm --dir ui exec vitest run packages/app-shell/src/views/ObjectView.titleViewSwitcher-27594.test.tsx packages/app-shell/src/views/objectViewTitleSwitcher.test.tsx,预期两个文件全绿。What changes
Measured first, on
mainatddab8b99: the object list page drew its views as a flat tab row in the list's toolbar (ViewTabBar, handed toListViewastoolbarLeading, objectui#12440), the page header'sh1read the object's plural, and a reader could not pin or star a view.ObjectViewTitleSwitcher(new,views/objectViewTitleSwitcher.tsx) is the page header's title: the open view's label, ▾, and· <count>once the list reports its total (ListView.onTotalChange, kept per view id). It opens a popover with a search box (always), then 📌 Pinned, Recent (at most three, the pinned one excluded) and All (the authored views in their order, then the views saved at runtime), each row with its view-type icon and, on hover or focus, ☆ and 📌. Its foot is + New view (the existing create-view flow) and, for an admin, Manage views (the existing dialog). A search lists the matching views flat, in All's order. The split pane's list (Record drawer v4: a bounded peek over the list — md cap instead of the field-count bucket, no dim overlay, the list stays readable and clickable, ⤢ maximizes in place at the same URL; the full page is reached only from the row ↗ and the header ⋯ #27545) is the same page, so it has the same switcher; nothing is special-cased.objectViewTabBar.tsxis deleted; the list gets notoolbarLeading, and a chart view no longer draws its own tab row above the chart. The filter / sort / display controls keep their own row.LIST_PINNED_VIEW_PREFERENCE(list.view.pinned, scopeobject:NAME) in console: one workspace-preference module (record open mode, drawer width, timeline / related column toggles) read per user × object, with a shared panel-toggle toolbar on the record page — defaults derived from the object, never metadata #27052'sworkspacePreferencemodule. A bare list address opens the pinned view, else the authored default (isDefault), else the first; a view in the address still wins. A pin naming a view the list no longer offers is no pin. Unpinning storesnull.view(idview:OBJECT:VIEW, href/apps/APP/OBJECT/view/VIEW, the object's icon) in the existing favorites store. The sidebar draws a view favorite only in the app its address names, beside the object's icon, and the section's own rows (views, records, objects, dashboards) can now be dragged into order, or moved with Space and the arrow keys, throughreorderFavorites(SortableFavoriteRows, the same gesture as the pinned entries of objectui#12059). A view an app navigation entry already opens (anobjectentry with thatviewName, as the member is shown it) shows its ☆ disabled with 「已在菜单中」 / "Already in the menu".ListView's own info control at the start of its toolbar row (its behaviour for a host with no leading node); a chart face, which has no toolbar row, carries it as the switcher's tooltip.view.switcher.{label,pinned,recent,all,pin,unpin,inMenu}in all ten packs. Docs:content/docs/ui/views.mdxgains "Switching views".Deviations from the card, each agreed with the seat before the code
ui/packages/plugin-list/src/**for the tab row → switcher. The console's tab row is drawn by app-shell (objectViewTabBar.tsx,objectViewPageHeader.tsx,objectViewViewCrud.tsx), so the switcher lives in app-shell (objectViewTitleSwitcher.tsx, new) and plugin-list is untouched. The card'scomponents/UnifiedSidebar.tsxislayout/UnifiedSidebar.tsx.app:NAME". It is stored in the existing favorites store instead (FavoritesProvider, theui.favoritesuser-preference row, typeview), filtered by the app its address names. Reason: the sidebar's 收藏 section already draws that store (objectui#12427), so a second store would split one section into two lists with two orders, and the favorites row follows the reader across devices whereworkspacePreferenceis this browser's.RecentViewItem, objectui#12604).Tests
New:
views/objectViewTitleSwitcher.test.tsx(13): label and count; authored-then-saved order in All; search box always there; Pinned → Recent (≤3, without the pinned) → All; a pinned view the list no longer offers draws no Pinned; case-insensitive search narrows flat; choosing switches and closes; 📌 pin / unpin; ☆ toggles, and a menu view's ☆ is disabled with its reason; no ☆ off an app route; the foot and its CONTROL.views/ObjectView.titleViewSwitcher-27594.test.tsx(realObjectView): the title in theh1with the view label and count; no tab bar, tab row or toolbar slot; the dropdown's order; search and its empty state; pin stores{"view":"v3","v":1}atos:pref:list.view.pinned@object:crm_leadand a bare route opens it on remount; unpin returns to the default; a view in the address beats the pin; the split pane (?recordId=with the split preference) shows the same switcher.views/ObjectView.starredView-27594.test.tsx: ☆ writes theviewfavorite (id, label, href, icon); an authoredviewNameentry's ☆ is disabled with "Already in the menu".layout/__tests__/UnifiedSidebar.starredViews-27594.test.tsx: a starred view of this app is drawn with its icon and address, one of another app is not; a keyboard reorder saves the new order.Updated (they read the retired tab row, a tab's text, or the plural
h1):ObjectView.chartViewTabs-13114,viewRefusal-12112,labelLessViewTab-12774,servedViewLabel-11295,userListState-12418,viewWriteRefusal-11583,ufViewSwitch-11992,createViewRefusal-11578,createKanbanView-11581,createChartView-11576,recordOpenPreference-26677,refreshInPlace-10035,externalRefreshSum-10572,relayRungCensus-7559,listTitlePlural-11696,managedByBadgeAuthoring-12194,setupListBadge-13385,listActionsReadGate-12109,objectBoundActions-7234, and consolerecordOpenFromEveryView-27057.ObjectView.viewTabsInToolbar-12440.test.tsxis replaced byObjectView.titleViewSwitcher-27594.test.tsx(the toolbar slot it pinned is gone).Acceptance notes
examples/app-crm(HotCRM is not in this workspace), a deviation from Acceptance 2, and are kept in the project's files for the seat rather than on an evidence branch; see Verification. Two layout faults they showed were fixed before this PR opened: the trigger's negative margin cut a four-character label by one, and its outer focus ring was clipped by the title's truncation.isPinnedflag of a saved view (the old tab row's pin) is not drawn in the dropdown; 📌 is the reader's own pin.MobileViewSwitcherContext); the page header with the title switcher issmand up, as before.ObjectViewdrawsrecord-count-footerunder it, so 「5 条记录」 shows twice (ObjectView.tsx, the split pane's footer; untouched by this diff).packages/spec/**is untouched. One spec description still names the tab strip:view.list.tabs' removal note says "the tab strip above an object's records is the saved-view switcher (ViewTabBar)". It is prose in the generated reference; noted for the spec lane, not changed here.Verification
Local runs (head 16c2570, after merging origin/main 04740b7 once)
All under
scripts/pm/os-verify-lock.sh:pnpm --dir ui exec vitest runover the 238 test files that nameObjectView, the retired tab bar or toolbar slot,FavoritesProvider/useFavorites,workspacePreference,objectViewListRenderer,objectViewPageHeader,StarredAppsorlist-view-switcher(app-shell views, layout, context, utils, console home andAppContenttests, consolerecordOpenFromEveryView-27057), pluspackages/i18n/→ Test Files 330 passed (330), Tests 3335 passed, 13 skipped;VITEST_EXIT=0.pnpm --filter @object-ui/app-shell run type-check(both tsc programs) →TYPECHECK_EXIT=0.eslinton every changed app-shell file → 0 errors (warnings carried over from the deletedobjectViewTabBar.tsx).pnpm --dir ui run check:i18n-drift→ 0 en values changed, 7 keys added;check:i18n-keysandcheck:i18n-dead-keys→ exit 0;pnpm check:new-line-citations(ui) → 0 new citations, exit 0.@object-ui/app-shellchanged; one console test file changed), and the rest of the lint farm.Ablations (each through scripts/ablation-replace.mjs, each restored to HEAD with an empty diff)
ObjectView.tsx: the pin dropped from the open-view resolution (resolvedViewId || defaultViewId) →titleViewSwitcher-275941 of 7 red (the pin is kept and the bare address opens it).objectViewTitleSwitcher.tsx: authored-first order dropped →objectViewTitleSwitcher.test1 of 13 red.UnifiedSidebar.tsx: view favorites not filtered by app →UnifiedSidebar.starredViews-275942 of 2 red.listViewFavorites.ts:inMenualways false →starredView-275941 of 2 red.UnifiedSidebar.tsx: the sidebar's drop not saved (onReorder={() => {}}) →UnifiedSidebar.starredViews-275941 of 2 red.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat16c257005aderived 66 families (the same list as at8aec374c05); each run and recorded ascommand :: exit codebefore any pipe; all 66 exit 0, includingnode scripts/check-sdui-manifest.mjsandpnpm --dir ui exec vitest run scripts/(run alone, nothing else running).pnpm --filter @objectstack/spec run check:skill-examplesexited 3 (NOT MEASURED: stale@objectstack/client-reactdeclarations) on the first pass at8aec374c05; afterpnpm --filter @objectstack/client-react buildit exited 0, and 0 again at16c257005a.--ranreconciliation: 66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN.Screenshots (examples/app-crm, zh-CN, 1440 × 900)
Backend
examples/app-crmwithobjectstack dev --seed-admin --freshon its own port, Console fromui/apps/console(Vite, src) pointed at it, driven by Playwright; both stopped afterwards. Eight PNGs in the project's files for the seat (ui-seat2/card-27594/shots/):00-list-title.png: 「全部线索 ▾ · 5」 as the title; no tab row; the filter / sort / display controls on their own row.01-dropdown-open.png: the dropdown open, a row hovered with ☆ and 📌, 「新建视图」 and 「管理视图」 at the foot.02-bare-address-opens-pinned.png: after 📌 on 「线索看板」, the bare list address opens the board.03-dropdown-pinned-recent-all.png: 「固定 · 最近 · 全部」.04-dropdown-search.png: searching 「看板」.05-sidebar-starred-view.png: 「收藏」 at the top of the sidebar with the starred 「线索看板」 beside the object's icon.06-split-pane.png,07-split-pane-dropdown.png: a record open in the split pane; the list beside it keeps the same switcher, and it opens there.Generated by Claude Code