Skip to content

fix(plugin-list): a filter row with no value is not a filter, a date sort reads Oldest → Newest, one sort row per field, and an empty list says how to start - #28423

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-7lwmsx
Oct 31, 2026
Merged

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

Part of #27520 (rows 7 and 17 built; row 21 measured and not built because it no longer reproduces; the card's other open rows stay on it)

Clause-②: yes (widening: @object-ui/i18n gains list.clearSearch, sortBuilder.oldestFirst and sortBuilder.newestFirst in all ten locales, and SortBuilder's fields entries take an optional type; no accept set moves)
Metadata-protocol: none

维护者速读

  • 改了什么:列表工具栏四处小改。筛选面板里还没填值的行不再算进筛选徽标、不再让列表显示「无匹配记录」、也不再被存进用户的列表状态;日期/日期时间字段排序显示「最早 → 最新」而不是「A → Z」;同一字段不能在排序里加两次;搜索导致的「无匹配记录」空状态多一个「清除搜索」按钮;空列表在用户有新建权限时给出「新建」按钮,不再要求视图写 addRecord.enabled。
  • 为什么改:18.3.0 RC 走查(console: suggestions from the 18.3.0 RC browser walkthrough: mention and discussion feedback, list selection and bulk results, refusal wording, approvals inbox, dashboards and money formatting, and phone layout #27520 第 7、17 行)在 Tasks 列表上看到:空筛选行算进徽标并在刷新后回来;日期排序写着 A → Z;同字段可重复排序;搜索跨导航保留,回来只看到「无匹配记录」却没有出口;新对象的空列表没有新建按钮。
  • 风险与回滚:只动前端列表组件和三个新翻译键,不改服务端、不改 spec。筛选只是少存未完成的行,面板里照样显示,用户可以继续填;空状态的新建仍受新建权限(listNew)约束,作者显式写 addRecord.enabled: false 仍然隐藏。回滚即 revert 本 PR。
  • 先看:ui/packages/plugin-list/src/ListView.tsx(appliedFilterGroup、emptyStateOffersCreate、onClearSearch);ui/packages/components/src/custom/sort-builder.tsx(takenElsewhere、directionLabels);ListView.toolbarRows-27520.test.tsx。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/plugin-list/src/__tests__/ListView.toolbarRows-27520.test.tsx packages/components/src/__tests__/sort-builder-date-direction-duplicates-27520.test.tsx → 2 个文件 19 个用例全绿。
  • 需要你做的:无,席位落地(新增 i18n 键属 Clause-② widening,席位先做 TIER_DEFAULT 复核)。

What changes

Measured on main af6ca001 before any code, by reading the code and then pinning each item red through the real ListView / SortBuilder:

Item On main This PR
Row 7: filter row with no value counted on the badge (shownFilterCount counted every row), made the list "No matching records", and was handed whole to onFilterChange, which the console stores as the user's list state (listPrefs.patchView) not counted, not a user query, not handed to the host; the panel still shows it so it can be finished
Row 7: date sort direction SortBuilder had no field type, so every row read "A → Z" a date / datetime row reads "Oldest → Newest" / "Newest → Oldest"; ListView hands each sort field's type
Row 7: duplicate sort field every row offered every field; "Add sort" seeded the first field even when a row held it a field another row holds is unavailable in this row; "Add sort" seeds the first free field and is disabled once none is left
Row 7: "No matching records" after a search no way out in the empty state "Clear search", which clears the search and tells the host (onSearchChange(''))
Row 17: empty list, no New the empty-state Create was drawn only when the view declared addRecord.enabled: true, which few views do, although the console hands onAddRecord exactly when its header New is offered drawn whenever the host hands a create path and mayCreate allows it; an explicit addRecord: { enabled: false } or userActions.addRecordForm: false still withholds it; a view that declares the inline block keeps it as before
Row 21: "In a queue" tooltip does not reproduce: the toggle carries title={t('list.sittingInQueueHint')} in all ten packs and is drawn only on an object the server serves with its injected queue column (objectHasOwnershipQueue), both since #27264 (2302b92b, an ancestor of the walkthrough's c4c515ad) not built (reported)

Not built here, reported: Studio's list-view sort (ui/packages/app-shell/src/views/studio-design/ObjectListViewInspector.tsx, held by #28148) hands SortBuilder fields without type, so its date label stays "A → Z" until that file passes type: f.type. The duplicate refusal lives in SortBuilder itself, so Studio's sort gets it with no edit there.

The search box's own clear (×) button is still unnamed; it is frozen in iconOnlyButtonCensus-12143's backlog and is left to that card.

Tests

  • New: ui/packages/components/src/__tests__/sort-builder-date-direction-duplicates-27520.test.tsx (8), ui/packages/plugin-list/src/__tests__/ListView.toolbarRows-27520.test.tsx (11). Each negative sits beside a control on the same rig.
  • Red first on unfixed source (tracked changes stashed, new tests kept): 11 failed / 8 passed, each on its target assertion (no option disabled, badge still counts the unfinished row, onFilterChange receives the unfinished row, no "Clear search", no empty-state-add-record).
  • Adapted, not deleted: ListView.sortRemovableOnly-11943.test.tsx, two controls that read the old duplicate behaviour ("Add sort" seeded the field the first row held; a field held by the other row was read from the first row's dropdown).
  • Suite: vitest run packages/plugin-list/ packages/components/ packages/plugin-view/ packages/i18n/ under the verify lock: 816 files, 6 reds in 4 files, all from this change's first cut (the two adapted controls; two emptyStateLink-12270 and one ObjectGallery.emptyState-12217 case that read a declared addRecord.enabled with no host handler, answered by keeping that leg; one iconOnlyButtonCensus-12143 count from an aria-label I then took back out). Those 7 files rerun green: 50/50.
  • Ablations through scripts/ablation-replace.mjs, each restored to the HEAD blob (git diff HEAD empty after every leg): taken-elsewhere returns false → 1 red; date label test → false → 2 red; "Add sort" ignores held fields → 2 red; emit the whole group → 1 red; badge from the whole group → 3 red; sort fields untyped → 1 red; host create leg off → 1 red; clear search off → 1 red; user query counts unfinished rows → 1 red.

Acceptance notes

  • @object-ui/plugin-view ObjectView.tsx was added to the claim's surface (amendment 6543827414) for one line, and is left unchanged: its renderListView branch is entered only when a host passes that prop, and Studio's design surface folds "+ New" into the list through it with showCreate: false, so withholding onAddRecord there would break Studio's New.
  • No live browser run; every case mounts the real ListView with the real FilterBuilder / SortBuilder and Radix Select.

Verification

All local readings at head 81af3395 (the merge of origin/main 380a1078 is in it).

Derived gate families: 34 derived, 34 run, 0 NOT-MEASURED

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 34 commands; each was run with stdin from /dev/null and its exit code captured before any pipe. All 34 exited 0, among them node scripts/check-sdui-manifest.mjs, pnpm check:nul-bytes, pnpm check:type-check-debt and pnpm --dir ui exec vitest run scripts/ (169 files passed, 2 skipped; 4901 tests). --ran on the recorded list: "34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN … a DERIVED zero — all 34 recorded an exit code and none of them is 3".

Lint, type-check, merge-tree

CI check-runs: not started at the time of writing.


Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01L6bhgVW2q9rsHS1vCrQ8Q9

…read Oldest → Newest, one sort row per field, and an empty list says how to start

- A filter-panel row with no value is not counted on the badge, does not
  make the list "No matching records", and is not handed to the host's
  onFilterChange (so it is not stored as the user's list state).
- SortBuilder reads an optional field type: date/datetime rows name the
  direction "Oldest → Newest"; a field another row holds is unavailable,
  "Add sort" seeds the first free field and disables when none is left.
- The search-emptied empty state offers "Clear search".
- The empty state offers Create whenever the host gives a create path and
  the user may create; an explicit addRecord.enabled: false still withholds
  it, and object-view hands no create path when Create is turned off.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6bhgVW2q9rsHS1vCrQ8Q9
…ns to one row per field

- A view that declares addRecord.enabled keeps its empty-state Create with
  or without a host handler, as before; a host create path now adds it.
- The two removable-only controls read each field from its own row and
  expect "Add sort" to start on the first free field.
- object-view's renderListView branch is host-only, and Studio folds its
  New into the list through it with showCreate: false, so its create path
  stays as it was.

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

Copy link
Copy Markdown
Contributor Author

domain:ui#4 · os-sales · session_01L6bhgWqjXeTcS3jtug46Dv

维护者速读

  • 改了什么:列表工具栏改了四处。①还没填值的筛选行不再计入筛选徽标,不当作查询条件,也不会被存下来带到下次。②日期、日期时间字段排序显示「最早 → 最新 / 最新 → 最早」,不再是「A → Z」。③同一个字段不能在排序里加两次。④因搜索而出现的「没有匹配的记录」空状态多了「清除搜索」按钮。另外,空列表在宿主提供了新建入口、且用户有新建权限时显示「新建」(console: suggestions from the 18.3.0 RC browser walkthrough: mention and discussion feedback, list selection and bulk results, refusal wording, approvals inbox, dashboards and money formatting, and phone layout #27520 第 17 行)。
  • 为什么改:这些都是 18.3.0 RC 走查第 7、17 行记下的问题。第 21 行(「In a queue」开关的提示)在 main 上早已具备,不复现,只做报告。
  • 风险与回滚:只改界面。服务端查询只会少掉没填完的行;作者显式写了 addRecord.enabled: false 时依然不显示新建。回滚就是 revert。
  • 先看:ListView.tsx 里的 appliedFilterGroup 和 emptyStateOffersCreate,sort-builder.tsx 里的 takenElsewhere / directionLabels。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/plugin-list/src/__tests__/ListView.toolbarRows-27520.test.tsx packages/components/src/__tests__/sort-builder-date-direction-duplicates-27520.test.tsx,应全绿。
  • 需要你做的:无。不在受管面上,属 Tier S,席位在 CI 全绿后自己落地。

席位意见:ACCEPT。「填完才算筛选」的判定和 convertFilterGroupToAST 用的是同一条,所以徽标、存储、查询三者一致。去重放在 SortBuilder 里,Studio 不用改也能生效。Studio 的日期标签要在 studio-design/** 加 type,留给后续认领。

席位核对 — ACCEPT · domain:ui#4 · Head-sha 81af33952cdf668f304243a59e13aa8306a33644 · 2026-10-31T14:16Z

  1. Shape: 17 files, +546 / −23, all inside the claim's surface (6543755532, amendments 6543817600 / 6543827414):
    • plugin-list ListView.tsx and ListEmptyState.tsx.
    • components sort-builder.tsx.
    • Two new tests plus one edited pin (ListView.sortRemovableOnly-11943).
    • Three rows in each of the ten locales.
    • A root changeset (plugin-list, components, i18n patch).
    • plugin-view/src/ObjectView.tsx is untouched: the amendment that added it went unused (see 4). studio-design/** is untouched.
  2. Premise and change measured (seat's own read):
    • Row 7a, unfinished filter rows. isAppliedFilterRow is the valueless-operator / isFilterValueComplete test that convertFilterGroupToAST keeps a row by.
      • appliedFilterGroup drops unfinished rows. A currency companion is kept only while its amount row survives.
      • When nothing is dropped, the function returns the same group, so a host sees the identity it had before.
      • The badge (shownFilterCount), hasFilters, the "no matches" decision and onFilterChange all read the applied group.
      • The panel's own state (setCurrentFilters) keeps every row, so the user can still finish one.
    • Row 7b, date direction. SortBuilder fields gain an optional type. date / datetime read "Oldest → Newest" / "Newest → Oldest", and every other type, including an absent one, reads "A → Z" as before. ListView passes field.type only on the sortable branch.
    • Row 7c, one row per field.
    • Row 7d, Clear search. onClearSearch is passed only while a trimmed search term is applied, and calls handleSearchChange(''). One action draws bare. Two actions, or the authored link, share one wrapping row.
    • Row 17, empty-state Create. emptyStateOffersCreate = showAddRecord || (emptyStateAddRecord && !!props.onAddRecord).
      • emptyStateAddRecord is false only on an explicit addRecord.enabled: false or userActions.addRecordForm: false.
      • ListEmptyState still gates on mayCreate.
      • The console host hands onAddRecord only when its header New is offered.
    • Row 21. Not reproduced: list.sittingInQueueHint has been in place since feat(console): ownership queues in the Console — two-source queue pickers, Claim on a queued record, an In-a-queue list filter, and the authoring guide (ADR-0142) #27264, and the toggle shows only when objectHasOwnershipQueue is true. Report only.
  3. Gates and check-runs:
    • On this head at this reading: 11 success, 11 skipped, 30 in progress, 0 failed. Landing waits for every check to be green or an expected skip.
    • The thread reported dispatch-gates 34/34 exit 0 with 0 NOT-MEASURED, and 9 ablations red then restored.
    • check-governed-merges --pr 28423: NOT governed, 569 changed lines.
    • Commit trailers are model-free.
  4. Accepted deviations:
    • (1) plugin-view/src/ObjectView.tsx was claimed but not changed. Its renderListView branch is host-only, and Studio folds "+ New" into the list through that onAddRecord with showCreate: false. Withholding it there would break Studio's New.
    • (2) Row 21 and the Studio half of row 7's date label are report only, as the claim directs.
    • (3) The PR body's attribution line links the card thread.
    • (4) No live browser run. Every case mounts the real ListView with the real FilterBuilder / SortBuilder and Radix Select.
    • (5) The search box's own ✕ stays unnamed, which keeps the iconOnlyButtonCensus-12143 backlog unchanged.
  5. Out of scope, with destinations: the Studio list-view sort label (ObjectListViewInspector.tsx L1490 passing type: f.type) stays open on console: suggestions from the 18.3.0 RC browser walkthrough: mention and discussion feedback, list selection and bulk results, refusal wording, approvals inbox, dashboards and money formatting, and phone layout #27520 for a later claim. Rows 9 and 14 stay behind Record page v4: same-shaped related panels with no collapse, a persistent tab row under the header (the enlarged state is the active tab), Activity as the first panel and a tab, one top bar on every face, tiers by container width #27524. Skills-docs: n/a (Metadata-protocol: none).
  6. Landing clause: Tier S. On all-green, the seat lands it by relay pr_ready plus automerge_enable.

Clause-② review of record (TIER_DEFAULT, in-seat)

  • Declared: Clause-②: yes (widening), on the claim and the PR.
  • What widens:
    • @object-ui/i18n (published) gains list.clearSearch, sortBuilder.oldestFirst and sortBuilder.newestFirst in all ten locales. No key is renamed or removed.
    • @object-ui/components (published; SortBuilder is re-exported through custom/index.ts → index.ts) gains an optional type?: string on SortBuilderProps['fields'][number]. A caller that omits it renders exactly as before.
    • ListEmptyStateProps.onClearSearch is optional and module-internal (ListEmptyState is not exported from plugin-list's entry).
  • Does any accept set widen? No.
    • The filter AST sent to the server can only lose unfinished rows, which convertFilterGroupToAST already dropped.
    • The sort the builder emits is still { field, order }. It just cannot hold the same field twice.
    • No schema, route or permission accepts anything new. mayCreate still gates Create.
  • Verdict: PASS. The change adds optional props and translation rows to published packages and leaves every accept set in the contract unchanged.

Generated by Claude Code

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 tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant