Skip to content

Console lists: the list-view switcher becomes a dropdown on the list title (search, pinned default, recent), the flat view-tab row retires, and a starred view appears in the sidebar's 收藏 group #27594

Closed

Description

@objectstack-fleet

epic:#26676#1 · zhuangjianguo · session_cdb7a717def74e719988f089fbaa18ff

Filed from the record-page epic #26676 as a sub-issue (the maintainer, 2026-10-29: 「另立两张 ui 卡:…列表视图选择器改下拉 + 收藏进侧栏」; the reasoning, verbatim: 「我们是元数据开发平台,类似 salesforce,每一种元数据的视图数量都不一定的」 and, on Attio: 「他可以把某个列表视图加到收藏,显示在左侧菜单上」). It is list-page work; it also frees a 44 px row inside #27545's narrowed list.

Source (main 273137d227)

  • The object list page draws its views as a flat tab row (e.g. HotCRM leads: 全部线索 · 我的线索 · 🔥 高热度线索 · 高优先级 · 还有 N 个 ▾) under the list header; authored views come from the object's *.view.ts (views: / navigation.tabs), reader-saved views join the same row. The row overflows at five and is cramped at 464 px (the split's list width).
  • Apps already pin a view into the sidebar on the AUTHOR side: crm.app.ts { type: 'object', objectName: 'crm_lead', viewName: 'my_leads', label: 'My Leads' } (nav_my_tasks, nav_my_deals, nav_my_leads, nav_my_calendar). There is no reader-side equivalent.
  • Salesforce (list view picker with search and pin), Attio (「All Deals ▾」 + favorites in the sidebar) and Twenty (「All People · 5 ▾」) all switch views from a dropdown on the title.

Target

  1. The list title is the view switcher: 「 ▾ · 」 at the top-left of the list header (the object name stays in the breadcrumb and the sidebar highlight); the flat view-tab row retires. The filter / sort / display controls keep their own row.
  2. The dropdown: a search box at the top (always, not only past N views); sections 📌 固定 · 最近 · 全部; in 全部 the authored views first in their declared order, then the reader's saved views; each row with its view-type icon (grid / kanban / calendar / gantt / gallery / map) and, on hover, ☆ and 📌; 「+ 新建视图」 at the bottom (the existing save-view flow).
  3. Pinned = default: one pinned view per object per reader opens by default; stored 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's workspacePreference module, scope object:NAME; with none pinned the authored default (isDefault, else the first) opens.
  4. ☆ Favorite → sidebar 收藏 group: starring a view adds 「 」 to a 收藏 group at the top of the app's sidebar (reader preference, same module, scope app:NAME); unstarring removes it; drag to reorder inside the group; authored viewName navigation items are untouched and not duplicated (a view that is already an authored item shows ☆ disabled with the tooltip 「已在菜单中」).
  5. 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) uses the same title switcher; nothing else is special-cased. No metadata key; ⛔ packages/spec/**.
  6. File surface

    ui/packages/plugin-list/src/** (the view tab row → the title switcher), ui/packages/app-shell/src/views/ObjectView.tsx + objectViewListRenderer.tsx (header), ui/packages/app-shell/src/components/UnifiedSidebar.tsx (收藏 group), ui/packages/app-shell/src/workspacePreference* (pinned / favorites), i18n in all ten packs, tests beside, changeset; ⛔ packages/spec/**.

    Acceptance

    1. Tests: the title opens the dropdown with search, sections and the authored-then-reader order; pin stores and restores the default per object; star adds / removes the sidebar entry and reorders; an authored viewName item is not duplicated; the tab row no longer renders; the split pane's list shows the same switcher at 464 px.
    2. Screenshots on HotCRM leads (dropdown open at 1440; the sidebar with a 收藏 entry) and in the split pane, on a never-merged evidence branch.
    3. The epic seat verifies in the console preview before ACCEPT (maintainer: 「车队开发完后你审核效果」).

Activity

  1. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

    Claim: PM loop round 4 · 2026-10-30T01:55Z
    Session: session_01Utp6CY2n1JmZZKrHgtp9jC (seat; implementation by a card thread, mode:thread)
    Account: os-bill
    Branch: claude/project-thread-vbf1ad (the card thread's harness push branch, self-reported — the measured platform exception to claude/issue-N-slug)
    Worktree: objectstack-issue-27594
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: the card body's File surface: ui/packages/plugin-list/src/** (the view tab row → the title switcher), ObjectView.tsx + objectViewListRenderer.tsx (header), ui/packages/app-shell/src/layout/UnifiedSidebar.tsx (收藏 group), ui/packages/app-shell/src/utils/workspacePreference*, i18n in the ten packs, tests beside, changeset; ⛔ packages/spec/**
    Container & model: L, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: xhigh (p1)
    Clause-②: no (expected; the dev measures from the diff)
    Metadata-protocol: none (the card adds no metadata key; the dev measures from the diff)
    Responsibility: the flat view-tab row that overflows at five and has no reader-side favorite | authored viewName nav items and the save-view flow are fixed inputs | every console reader of an object list with more than a few views
    Thread-read: none
    Premise: the Target as rewritten by the epic PM against canvas 60 (back-to-queue comment, 2026-10-29T14:19Z); the dev re-measures the Source section on current main before the first edit
    Serial constraints cleared: none on the card; overlaps named in the order: #27545 (seat 2, ObjectView.tsx, the narrowed list) and #27593 (seat 4, UnifiedSidebar.tsx). Whoever lands second merges main
    Acceptance gate: the epic seat verifies the PR in the console preview against the boards before the seat's ACCEPT (the maintainer: 「车队开发完后你审核效果」)
    Dispatched on the maintainer's word of 2026-10-30T01:49Z (「现在就派,临时加并发」): the ui seats take the six p1 #26676 children now, with concurrency temporarily raised.

    Dispatch order: /mnt/project-files/ui-seat2/dispatch-27594.md in the Claude Code project (not on GitHub); the card body is the ruling text.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

    For the epic seat (epic:#26676#1): Source drift and one storage call · 2026-10-30T01:58Z

    The card thread re-measured Source on main ddab8b99:

    1. File move. The flat view-tab row is plugin-view's ViewTabBar. It is mounted by app-shell views/objectViewTabBar.tsx (from the objectViewPageHeader / objectViewViewCrud split out of ObjectView.tsx). plugin-list/src holds only the grid/kanban visual switch. So the work lands in app-shell plus a new title-switcher file, and plugin-list is untouched.
    2. The sidebar 收藏 group already exists (console.nav.favorites, objectui#12427). FavoritesProvider feeds it from the ui.favorites preference, which syncs across devices. Pinned nav items already reorder by drag (platform-objects' keyed-text-bounds pin cannot reach plugin/service packages — four keyed unbounded text columns in plugin-audit and plugin-security still break MySQL schema-sync #12059).
      • Seat call (an implementation choice, not a change to what the reader sees): a starred view is stored in that existing favorites store (type view, filtered to the app by href), not in a second workspacePreference app:NAME store as Target 4's "same module, scope app:NAME" reads.
      • The reason: Target 4's intent is one 收藏 group, and two stores feeding one group would split it. The pinned default (Target 3) still uses workspacePreference object:NAME.
      • It is isolated in one hook, so reverting to the literal reading is cheap. Say so before you verify if you want it.
    3. 最近 already exists (RecentViewItem, Platform reading: the session proxy serves repo-scoped paths only — GET /search/* returns 403, and its body parses as a clean zero #12604). The dropdown reads it.
    4. Addition the card did not name: the per-tab ▾ admin actions (rename / delete / share default / configure) move to a 「管理视图」 entry at the foot of the dropdown (ManageViewsDialog), so retiring the tab row drops no capability.

    One PR is expected (under the size threshold). It lands through Acceptance 3's preview verification.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC
    os-dev-report

    Report of 2026-10-30T04:46Z.

    {
      "issue": 27594,
      "status": "done",
      "branch": "claude/project-thread-vbf1ad",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/27943",
      "head": "16c257005a5b189a942a501486b82023579cba7a",
      "session": "session_01Utp6CanDVECEsojSwtvSs7 (card thread; seat session_01Utp6CY2n1JmZZKrHgtp9jC)",
      "premise_still_valid": true,
      "summary": "The object list title is the view switcher (ObjectViewTitleSwitcher, app-shell): label, chevron and the list's reported count; a popover with a search box, Pinned, Recent (<=3) and All (authored first, then saved), rows with type icons and hover star/pin, New view and (admin) Manage views at the foot. The flat tab row retires (objectViewTabBar.tsx deleted; ListView gets no toolbarLeading). Pin = the reader's default per object in workspacePreference list.view.pinned@object:NAME (address > pin > isDefault > first). Star = a FavoritesProvider favorite of type view (seat ruling B), drawn in the sidebar's Favorites only in its own app, beside the object icon; the section's own rows are now sortable (drag or Space+arrows, reorderFavorites); a view an app nav entry already opens shows its star disabled ('Already in the menu'). The split pane is the same page and keeps the switcher. i18n view.switcher.* in ten packs; docs ui/views 'Switching views'; changeset patch app-shell + i18n.",
      "tests": "head 16c257005a, all under os-verify-lock. vitest over 238 named files (ObjectView, retired tab bar/slot, favorites, workspacePreference, list renderer/header, StarredApps, list-view-switcher readers) + packages/i18n/: Test Files 330 passed (330), Tests 3335 passed, 13 skipped, VITEST_EXIT=0. app-shell type-check TYPECHECK_EXIT=0. New: objectViewTitleSwitcher.test (13), ObjectView.titleViewSwitcher-27594 (7; pin stored at os:pref:list.view.pinned@object:crm_lead and restored on remount, URL beats pin, split pane switcher), ObjectView.starredView-27594 (2), UnifiedSidebar.starredViews-27594 (2). 20 existing tests updated (tab row/tab text/plural h1 readers; ObjectView.viewTabsInToolbar-12440 replaced). Ablations via scripts/ablation-replace.mjs, each restored (blob == HEAD, git diff HEAD empty): pin dropped from open-view resolution -> 1/7 red; authored-first dropped -> 1/13 red; app filter on view favorites dropped -> 2/2 red; inMenu forced false -> 1/2 red; sidebar onReorder no-op -> 1/2 red. The machineLocaleCensus-9909 pin caught count.toLocaleString() with no locale on the first post-merge run; fixed with useDisplayLocale.",
      "gates": "dispatch-gates --commands at 16c257005a: 66 derived (same list as at 8aec374c05); each run, exit captured before any pipe; all 66 exit 0 incl. node scripts/check-sdui-manifest.mjs and pnpm --dir ui exec vitest run scripts/ (run with nothing else running). check:skill-examples was exit 3 NOT MEASURED on the first pass (stale client-react d.ts), 0 after pnpm --filter @objectstack/client-react build, 0 again at head. --ran: 66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN. Outside derivation: check:i18n-drift / check:i18n-keys / check:i18n-dead-keys / check:new-line-citations (ui) exit 0; eslint on changed app-shell files 0 errors. CI families: PR #27943 check-runs, not concluded at report time.",
      "line_budget": "51 files, +2206 / -560 = 2766 changed lines (< 3000, no split)",
      "files_changed": [
        ".changeset/list-view-title-switcher.md",
        "content/docs/ui/views.mdx",
        "ui/apps/console/src/__tests__/recordOpenFromEveryView-27057.test.tsx",
        "ui/packages/app-shell/src/console/home/StarredApps.tsx",
        "ui/packages/app-shell/src/context/FavoritesProvider.tsx",
        "ui/packages/app-shell/src/layout/MobileViewSwitcherContext.tsx",
        "ui/packages/app-shell/src/layout/SortableFavoriteRows.tsx",
        "ui/packages/app-shell/src/layout/UnifiedSidebar.tsx",
        "ui/packages/app-shell/src/layout/__tests__/UnifiedSidebar.starredViews-27594.test.tsx",
        "ui/packages/app-shell/src/layout/listViewFavorites.ts",
        "ui/packages/app-shell/src/utils/warnSuppressedListNav.ts",
        "ui/packages/app-shell/src/utils/workspacePreference.ts",
        "ui/packages/app-shell/src/views/ObjectView.chartViewTabs-13114.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.createChartView-11576.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.createKanbanView-11581.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.createViewRefusal-11578.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.externalRefreshSum-10572.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.labelLessViewTab-12774.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.listActionsReadGate-12109.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.listTitlePlural-11696.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.managedByBadgeAuthoring-12194.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.objectBoundActions-7234.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.recordOpenPreference-26677.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.refreshInPlace-10035.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.relayRungCensus-7559.test.ts",
        "ui/packages/app-shell/src/views/ObjectView.servedViewLabel-11295.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.setupListBadge-13385.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.starredView-27594.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.titleViewSwitcher-27594.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.tsx",
        "ui/packages/app-shell/src/views/ObjectView.ufViewSwitch-11992.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.userListState-12418.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.viewRefusal-12112.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.viewTabsInToolbar-12440.test.tsx",
        "ui/packages/app-shell/src/views/ObjectView.viewWriteRefusal-11583.test.tsx",
        "ui/packages/app-shell/src/views/objectViewListRenderer.tsx",
        "ui/packages/app-shell/src/views/objectViewPageHeader.tsx",
        "ui/packages/app-shell/src/views/objectViewTabBar.tsx",
        "ui/packages/app-shell/src/views/objectViewTitleSwitcher.test.tsx",
        "ui/packages/app-shell/src/views/objectViewTitleSwitcher.tsx",
        "ui/packages/app-shell/src/views/objectViewViewCrud.tsx",
        "ui/packages/i18n/src/locales/ar.ts",
        "ui/packages/i18n/src/locales/de.ts",
        "ui/packages/i18n/src/locales/en.ts",
        "ui/packages/i18n/src/locales/es.ts",
        "ui/packages/i18n/src/locales/fr.ts",
        "ui/packages/i18n/src/locales/ja.ts",
        "ui/packages/i18n/src/locales/ko.ts",
        "ui/packages/i18n/src/locales/pt.ts",
        "ui/packages/i18n/src/locales/ru.ts",
        "ui/packages/i18n/src/locales/zh.ts"
      ],
      "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "9 — all reads: mcp__github__issue_read x5 (card #27594, seat-3 report shape on #27799), mcp__github__pull_request_read x2 (PR #27826 body shape, #27943 body read-back), mcp__github__search_pull_requests x1, mcp__github__list_pull_requests x1 (#27943 read-back after the relay's unverified read)",
      "api_writes": "2 — POST /repos/objectstack-ai/objectstack/dispatches (fleet-write pr_create #27943, draft, no labels, no assignees; run 41004585264 success; its own read-back was HTTP 0, re-read by MCP: stored, single footer); this report comment via post-stamped. git push is not a REST write.",
      "deviations": [
        "Seat-agreed before code: switcher in app-shell (objectViewTitleSwitcher.tsx), plugin-list untouched; stars in FavoritesProvider (ruling B) instead of 'same module, scope app:NAME'; Recent from RecentViewItem; Manage views at the dropdown foot as an addition.",
        "Screenshots on examples/app-crm (HotCRM not in this workspace), zh-CN 1440x900, saved to /mnt/project-files/ui-seat2/card-27594/shots/ (8 PNGs), not on an evidence branch.",
        "View description: shown once. A list face draws ListView's own info control (no toolbarLeading any more); a chart face carries it as the switcher's tooltip.",
        "Commit trailer: the model-free pair per AGENTS.md."
      ],
      "open_questions": [
        {
          "question": "The card says the object name stays in the breadcrumb, but object routes draw no crumb (objectui#12461). With the title naming the view, the object is named only by the sidebar's active entry and the header icon. Restore an object crumb on list routes?",
          "options": [
            "A keep as shipped: sidebar highlight + header icon name the object",
            "B restore the object crumb on object list routes (AppHeader), a small follow-up"
          ],
          "recommendation": "B, because a collapsed sidebar or a deep link leaves the page with no object name on screen; it is a one-line follow-up the seat can file against this PR or the epic."
        }
      ],
      "out_of_scope_findings": [
        "class: c · reach: public door — a drawer-mode list in the split arm (record open in split): ListView's record-count-bar and ObjectView's record-count-footer both draw, so the count shows twice (seen on app-crm leads, 06-split-pane.png) · dedupe words: split pane record count twice, record-count-footer record-count-bar duplicate",
        "class: c · reach: named producer — packages/spec view.list.tabs removal description still says 'the tab strip above an object's records is the saved-view switcher (ViewTabBar)'; prose only, spec lane · dedupe words: view.list.tabs description ViewTabBar tab strip",
        "carrier: PR #27943 · noted, not filed"
      ]
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

    Status: claimed and in review, not unclaimed · 2026-10-30T10:44Z

    PR head state CI contract review waits on
    #27943 (the whole card, Fixes) 16c25700 draft, mergeable 51 success, 0 red not owed (Clause-②: no) the proxy preview review

    The epic-seat preview gate is now ruled B (6520722066). The maintainer's words were 「你可以自主验收合并,有问题会开新卡」 at 2026-10-30T10:39Z. The card thread now runs the proxy preview review on the PR head. On PASS the seat ACCEPTs, marks it ready and turns on auto-merge. The epic seat re-checks the end state on main and files new cards for any deviation from the boards. The seat's earlier requests to the epic seat stay in place and are answered by that ruling.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

    Landed · 2026-10-30T11:26Z

    probe reading
    PR #27943 merged through the queue at 2026-10-30T11:25Z, merge commit 847e4248e9b7059e5b069056bf636c75c1f53b14
    this card closed completed by the PR's Fixes line

    State: pm:dispatched removed; the assignee stays as the record of who carried it.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchdomain:uienhancementNew feature or requestpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions