Skip to content

feat(plugin-list): the empty-state "Clear filters" also clears the user-filter chips - #29189

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

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-e83mu6

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #29052

Clause-②: yes (widening)
Metadata-protocol: none

维护者速读

  • 改了什么:列表只被用户筛选标签(列表上方的快速筛选 chips)筛空时,空状态现在也给出「清除筛选」主按钮,和被筛选面板、「在队列中」开关筛空时一致;点一下连同面板条件、队列开关、搜索词一起把标签清掉,记录回来,地址栏里的 uf_* 参数也一并清掉,刷新后不会再回来。UserFilters 新增一个可选的 ref(导出类型 UserFiltersHandle,只有 clear() 一个方法),不传 ref 的宿主行为完全不变。预设标签页(tabs)不算读者的筛选,不清。
  • 为什么改:List empty state: when the reader's own filter or search leaves no rows, offer 「清除筛选」 — today the copy says to clear them and the only button is 「添加记录」 #28908 落地后,被标签单独筛空的列表文案仍说「试试调整或清除筛选」,按钮却只有「添加记录」;原因是标签的选择状态藏在组件内部,列表无法从外部清除,又不允许用 key 重挂载。
  • 风险与回滚:低。选择状态仍归 UserFilters 自己,不重挂载;clear() 等同于逐个点每个标签的 ×,走同一条 onFilterChange/onSelectionsChange 路径,宿主按字段删除 URL 参数,宿主代码未改。回滚即回退本 PR。
  • 先看:ui/packages/plugin-list/src/UserFilters.tsx(UserFiltersHandle 与 DropdownFilters 里的 commitSelection/useImperativeHandle);ui/packages/plugin-list/src/ListView.tsx(handleClearReaderQuery 与 userFilterChipsClearable);新测试 ListView.clearFiltersChips-29052.test.tsx。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/plugin-list/src/__tests__/ListView.clearFiltersChips-29052.test.tsx,预期 4 个用例全部通过;第一个用例断言主按钮是「Clear filters」、点击后标签的 × 消失、空状态消失、宿主收到 { status: [], rating: [] }。
  • 需要你做的:无,席位落地。

What changes

UserFilters can be cleared from outside, without a remount. It takes an optional ref (React 19 ref-as-prop) that receives a UserFiltersHandle while a dropdown or toggle bar is mounted; the type is exported from @object-ui/plugin-list beside UserFiltersProps. Its one method, clear(), empties every chip, author defaultValues included, exactly as pressing each chip's × would:

  • dropdown: the selection becomes empty for every chip field, onFilterChange receives no conditions, and onSelectionsChange reports every chip field with an empty selection, held or not. The per-chip × path and clear() now share one commit helper (commitSelection), so typing finality, the emit and the host report cannot drift apart;
  • toggle (stored, spec-deprecated): every toggle turns off and no conditions are emitted;
  • tabs: no handle is set. A preset tab is one of the view's presets, not a filter the reader added.

The selection stays the bar's own state. A host that passes no ref gets today's bar unchanged (pinned below). No key remount (ui/AGENTS.md #8), no new metadata key or option.

ListView's "Clear filters" covers the chips. handleClearReaderQuery (from #28908) now also calls the bar's clear(). The empty state offers the action when the chip bar is on screen, is not in tabs mode, and its conditions are part of the applied query (userFilterChipsClearable), beside the existing panel and queue-toggle arms. So a list emptied by a chip alone now shows "Clear filters" as the primary action and "Add record" as the secondary, and the existing copy ("No records match your current filters or search. Try adjusting or clearing them.") now matches the action offered. No copy changed.

What the host persists. Measured by reading the hosts on origin/main 2af0ae2a: all three hosts (objectViewListUrlState.ts handleUserFilterSelectionsChange, InterfaceListPage, ObjectDataPage) write the report through applyUserFilterParams, which deletes uf_FIELD only for a field the report names with an empty selection; a field the report omits keeps its param. That is why clear() reports every chip field rather than {}: a {} report would leave every uf_* param in place, and the chips would come back on the next seed. With every field named, the existing host path drops them all, so no host file changed.

Tests

  • ui/packages/plugin-list/src/__tests__/ListView.clearFiltersChips-29052.test.tsx (new), 4 cases: a chip restored from the host empties the list, "Clear filters" leads, the click clears the chip, the next fetch carries no miss, the rows come back and the host receives every chip field empty; an author-defaulted chip clears the same way; a chip and a panel condition together clear in one click; a list emptied by a preset tab offers no clear action.
  • ui/packages/plugin-list/src/__tests__/UserFilters.clearHandle-29052.test.tsx (new), 5 cases: dropdown clear() (defaults included, unheld fields reported); a chip picked after a clear applies alone; toggle clear(); tabs set no handle; without a ref the bar restores its selection and the chip × clears in place as before.
  • ListView.emptyStateClearFilters-28908.test.tsx: its last case pinned the chip-only list offering no clear action, the behaviour this card changes; it is removed and the header points at the new file, which pins the replacement.

Acceptance notes

  • Preset tabs stay out of the clear. A list emptied by a preset tab still reads "your current filters or search" with no clear action, as before this PR. Clearing a preset has no empty state of its own (with showAllRecords: false there is no unfiltered tab), so it is left to the view's presets. Seat 1's List personalization: one visible 「已个性化」 marker and one reset that restores the whole list — today two reset entries cover different parts and neither says what changed #28950 (branch claude/project-thread-o05ndx, unmerged at writing) counts the tab's conditions among its "filters" personalization part and resets them through its own uf_* strip and remount; the two paths do not touch each other.
  • Mechanism: a handle, not a controlled selections prop. The card allowed either. A controlled selection would need the host to know the bar's effective selection, which it does not: author defaults, chips derived after the definition loads, and the late type coercion all settle inside the bar and are deliberately not echoed to the host. A host clearing to {} would then miss a default-only chip. The handle clears whatever the bar holds, and the declaration face still widens (one optional prop, one exported type), so the claim's Clause-②: yes (widening) stands.
  • No docs page or README describes UserFilters' host props (git grep of onSelectionsChange, initialSelections and UserFiltersProps over content/ and the package README: zero hits), so none is updated.

Verification

Readings on 809862b9 (the change). 77aa59cd merges origin/main (clean; the incoming commits touch no plugin-list file and none of the uf_* host files, so no re-check was owed by AGENTS.md multi-agent §10).

Tests
  • New files and the touched List empty state: when the reader's own filter or search leaves no rows, offer 「清除筛选」 — today the copy says to clear them and the only button is 「添加记录」 #28908 file plus UserFilters.test.tsx: 4 files, 31/31 passed.
  • packages/plugin-list in full, in two locked batches at --maxWorkers=2: 92 files 659 tests, and 92 files 1098 tests, all passed (184 files).
  • Host consumers (git grep -l of UserFilters, ListEmptyState, empty-state-clear-filters, filter-clear-, userFilterSelections, onUserFilterSelectionsChange outside plugin-list: app-shell ObjectDataPage.relationUserFilters-13310, ObjectView.ufViewSwitch-11992, userFilterUrlState.listState-11860, plugin-grid's two empty-state files, plugin-form, plugin-view, types' two parity files): 9 files, 387 tests passed, after building the app-shell closure (64/64 tasks).
  • pnpm --dir ui exec vitest run scripts/: 169 files passed, 2 skipped; 4900 tests passed.
  • Ablation (one-time, not kept): scripts/ablation-replace.mjs replaced userFiltersRef.current?.clear(); in handleClearReaderQuery with a no-op (void userFiltersRef;). The new ListView file went 3 failed / 1 passed (the three chip-clear cases red; the preset-tab case green, as it pins no clear). The restore leg read blob == HEAD and an empty git diff HEAD.
Type-check, lint
Derived gates

node scripts/pm/dispatch-gates.mjs --ran: 34 derived families accounted for, 34 run, 0 NOT MEASURED, every one exit 0 (check:type-check-debt under the verify lock). The PR-body gates (check-changeset-no-major, check-closing-keyword-parity, check-partof-closing-keyword, check-adr-0087-registration, check-empty-changeset) ran on this body: exit 0 on each.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QsR2jULUUCcok2FGYwt6Gh


Generated by Claude Code

…-filter chips

UserFilters gains an optional ref receiving a UserFiltersHandle while a
dropdown or toggle bar is mounted; clear() empties every chip as each
chip's x would and reports every chip field empty to the host. ListView's
Clear filters clears the chips through it, and a list emptied by a chip
alone now offers the action.

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

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 77aa59cd49488d954830d46c45698c548d2baebb
Local-runs: none

Face: declaration only (Clause-②: yes (widening) in the claim 6572312692, the PR body and the changeset). The diff touches no packages/spec/src/**, no governed path and declares no narrowing, so no ④ is owed. Read at 2026-11-02T09:01Z: the PR's file list (8 files) and the diff of UserFilters.tsx, ListView.tsx, ListEmptyState.tsx, index.tsx and the changeset.

① Derived judgments

  • Public surface added (correct, widening only): @object-ui/plugin-list's entry now re-exports the type UserFiltersHandle (index.tsx:21), and UserFiltersProps gains one optional member ref?: React.Ref<UserFiltersHandle>. UserFiltersHandle has one method, clear(): void. Both are reachable from the entry's declarations, so this is a widening of the published surface, as declared.
  • Nothing narrowed: no member of UserFiltersProps was removed, renamed or made required; onSelectionsChange, onFilterChange and initialSelections keep their types. A host that passes no ref gets the same bar: the handle is set only through useImperativeHandle, and the per-chip × path now goes through commitSelection, which runs the same three steps in the same order as the old handleChange (typing finality, setSelectedValues, emitFilters, onSelectionsChange).
  • Accepted inputs: no metadata key, option, or schema read changed (Metadata-protocol: none holds). The UserFilters config (filterElements) shape is untouched.
  • Behaviour reachable through the new surface: clear() reports every chip field with an empty array (fields from the config plus any held key), so hosts that delete uf_FIELD per named field drop all of them; tabs mode sets no handle, so ref.current stays null there and the ?.clear() call in ListView is a no-op. Both match the PR body's Acceptance notes.
  • ListView gating: userFilterBarShown adds listChromeShown, but the bar already renders only inside the {listChromeShown && ( toolbar block, so where the bar draws is unchanged.

② Semver level

@object-ui/plugin-list: patch. Metadata-protocol: none ⇒ patch under AGENTS.md Post-Task Checklist step 3 ("a non-protocol change, widening or breaking, ships patch"). No BREAKING banner is owed: nothing an author or host could write stops working.

③ Boundary flags

  • The dev raised no open_questions. The one deviation (a handle instead of a controlled selections prop; the card allowed either) is stated with its reason in the PR body; it keeps the widening to one optional prop and one type.
  • PR author is objectstack-fleet[bot] (relay write).

Implemented-by: claude/project-thread-e83mu6
Reviewed-by: session_01QsR2jV4dWohojjbTJTQq5m

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m

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

Head-sha: 77aa59cd49488d954830d46c45698c548d2baebb

  • Shape: draft → main, Fixes #29052 in the body only. 8 files, +443 / −32 (475 lines ≤ 3000). No governed path: check-governed-merges --pr 29189 hit 0 of 8 paths. Clause-②: yes (widening) and Metadata-protocol: none are line-initial in the body and the changeset, as claimed. The changeset is patch for @object-ui/plugin-list.
  • Contract review: the TIER_DEFAULT record is 6573154233 on this head, with a PASS.
  • File surface: inside the claim: UserFilters.tsx, ListView.tsx, ListEmptyState.tsx, index.tsx (the type export), two new tests, the List empty state: when the reader's own filter or search leaves no rows, offer 「清除筛选」 — today the copy says to clear them and the only button is 「添加记录」 #28908 test, and the changeset.
  • Read in the diff:
    1. UserFilters takes an optional ref that receives UserFiltersHandle { clear(): void }. In dropdown mode clear() goes through the same commitSelection helper as a chip's ×, and reports every chip field with an empty selection, so the hosts' applyUserFilterParams drops every uf_* param. Toggle mode turns every toggle off. Tabs mode sets no handle.
    2. ListView.handleClearReaderQuery calls userFiltersRef.current?.clear(), and userFilterChipsClearable adds the chip arm to the empty state's clear action. No key remount, no host file changed.
    3. A host with no ref gets today's bar; the List empty state: when the reader's own filter or search leaves no rows, offer 「清除筛选」 — today the copy says to clear them and the only button is 「添加记录」 #28908 case that pinned "no clear for a chip-only list" is replaced by the new file's pin.
  • Tests (dev): the 4 new ListView cases and 5 new UserFilters cases pass; plugin-list whole suite 184 files; 9 host-consumer files 387 tests; scripts/ 169 files. Ablation (the clear() call no-oped) reds 3 of 4 ListView cases and was restored. Type-check 50 of 50; derived gates 34 of 34.
  • Gates (check-runs on this head): 44 success, 11 skipped, 0 failed or pending. mergeable_state is clean.
  • NOT MEASURED: the seat ran no live browser session; the ListView tests on the real UserFilters stand in.
  • Landing: ordinary queue landing, pr_ready then automerge_enable. Fixes #29052 closes the card on merge.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 2, 2026 09:15
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 2, 2026 09:15
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit 7a221ac Nov 2, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-e83mu6 branch November 2, 2026 10:27
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 tooling

Projects

None yet

1 participant