You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 7a221ac
Browse filesBrowse the repository at this point in the historyBrowse files
feat(plugin-list): the empty-state "Clear filters" also clears the user-filter chips (#29189)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9Ubb7Lo3k5A85iWVZxQVoCv6Ht)_Fixes#29052
Clause-②: yes (widening)
Metadata-protocol: none
## 维护者速读
- **改了什么**:列表只被用户筛选标签(列表上方的快速筛选
chips)筛空时,空状态现在也给出「清除筛选」主按钮,和被筛选面板、「在队列中」开关筛空时一致;点一下连同面板条件、队列开关、搜索词一起把标签清掉,记录回来,地址栏里的
`uf_*` 参数也一并清掉,刷新后不会再回来。`UserFilters` 新增一个可选的 `ref`(导出类型
`UserFiltersHandle`,只有 `clear()` 一个方法),不传 ref
的宿主行为完全不变。预设标签页(tabs)不算读者的筛选,不清。
- **为什么改**:#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 #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).
<details><summary>Tests</summary>
- New files and the touched #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`.
</details>
<details><summary>Type-check, lint</summary>
- `turbo run type-check --filter=@object-ui/plugin-list`: 50/50 tasks
(it covers the test files through `tsconfig.test.json`).
- eslint on every changed source and test file: 0 errors (warnings only:
two `as any` data-source casts in the new ListView test, the pattern the
#28908 file uses, and pre-existing ones).
- `node scripts/check-sdui-manifest.mjs`: exit 0 (no component config
changed).
- `pnpm check:new-line-citations` under `ui/`: 0 new citations.
</details>
<details><summary>Derived gates</summary>
`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.
</details>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01QsR2jULUUCcok2FGYwt6Gh
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jULUUCcok2FGYwt6Gh)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
feat(plugin-list): the list's empty-state "Clear filters" also clears the user-filter chips, so a list emptied by a chip alone offers it
6
+
7
+
Clause-②: yes (widening)
8
+
Metadata-protocol: none
9
+
10
+
A list emptied by a user-filter chip alone (the quick-filter chips above the grid) said "No records match your current filters or search" and offered only "Add record": the chip kept its selection inside `UserFilters`, out of the list's reach. It now offers "Clear filters" as its primary action, as a list emptied by the filter panel or the "In a queue" toggle does, and one click clears the chips with the rest of the reader's query.
11
+
12
+
-**`UserFilters` takes an optional `ref`** that receives a `UserFiltersHandle` (exported from `@object-ui/plugin-list`) while a dropdown or toggle bar is mounted. Its `clear()` empties every chip, author defaults included, exactly as pressing each chip's × would: `onFilterChange` receives no conditions, and `onSelectionsChange` reports every chip field with an empty selection, so a host that persists selections per field (the console's `uf_*` URL params) drops each one. The selection stays the bar's own state, and the bar is not remounted. A host that passes no ref gets the bar unchanged.
13
+
-**Preset tabs are not cleared.** A tab is one of the view's presets, not a filter the reader added: tabs mode sets no handle, and a list a tab empties still offers no clear action.
0 commit comments