Repository navigation
Commit d1f3ac0
fix(app-shell): a stored list sort or filter on a deleted or unreadable field is dropped when the list opens (#29027)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9Ubb6KQHAgTkT9HJRZ4Ezx4M8E)_
Fixes #28949
Clause-②: no
Metadata-protocol: none
## 维护者速读
-
**改了什么**:列表打开时,用户自己存下的排序和筛选现在也走链接参数已有的字段检查。排序层或筛选条件(任意嵌套层级)指向对象已不声明的字段、或当前用户已不可读的字段,就在打开时丢掉;其余存下的状态照常生效。
- **为什么改**:在 app-crm 实测:存下的排序/筛选指向已不存在的字段时,修复前整张列表显示「This view’s query
was rejected」(服务端 400 `INVALID_FIELD` /
`INVALID_SORT`);字段对销售代表隐藏时,服务端对同一查询答 403
`PERMISSION_DENIED`。链接里的同类字段早已被丢弃,存下的偏好却原样进查询。
-
**风险与回滚**:低。只改一个文件里种子(seed)的取值,没有新键、新选项,也不改存储。打开列表时不回写存储行:被丢的部分仍留在行里,字段读权限恢复后会重新生效;用户下一次改排序或筛选时,那一部分被列表当前状态替换。回滚即回退本
PR。
-
**先看**:`ui/packages/app-shell/src/views/objectViewListUrlState.ts`(`acceptStoredFilters`
的嵌套遍历、`acceptStoredSort` 对空排序的处理、`acceptField`
提到分支外);`ui/packages/app-shell/src/views/ObjectView.listUrlState-11860.test.tsx`
新增的 objectstack#28949 组。
- **两分钟验证**:`pnpm --dir ui exec vitest run
packages/app-shell/src/views/ObjectView.listUrlState-11860.test.tsx`,预期全部通过;新组里「a
field the object no longer declares」断言查询只带 `priority` 条件和 `due_date` 排序。
- **需要你做的**:无,席位落地。
## What changes
`useObjectViewListUrlState` seeds a list from the URL or, when the URL
carries no list state, from the user's stored state (`listPrefs`). The
URL seed already passed every sort level and filter condition through
`acceptField`: a field the object does not declare, or one this user
cannot read once field permissions have loaded, is dropped. The stored
seed passed `filters` and `sort` in unchecked.
- `acceptField` moves above the URL/storage branch, unchanged, so both
seeds use the one gate.
- `acceptStoredSort` drops a level naming a refused field and keeps the
others in order. A stored `sort: []` (the user cleared the sort) is
kept; a stored sort whose every level is dropped becomes no sort, so the
view's own sort applies. This is the rule the URL's `uf__sort` reader
already follows.
- `acceptStoredFilters` walks the stored Filter panel group at any
nesting. A condition naming a refused field is dropped; a nested group
left with no condition is dropped with it; every other entry is kept
exactly as stored, row ids included, because the panel rehydrates them.
A group nothing was dropped from is returned as the same object. A group
left with no condition is no filter.
- No new key, option, or stored shape. `ListPreferencesProvider.tsx` is
not touched; only its exported types are imported.
### What the card asks the PR to state (Target 4)
- **Is the dropped part removed from the stored row?** No. Opening the
list does not write the preference row. Measured on app-crm: after the
fixed list opened, the `ui.list.crm_account` row still held the stale
entries. The address bar's `uf__*` mirror shows the cleaned state. The
user's next sort or filter change replaces that part of the row with
what the list shows. Keeping it means a field whose read access comes
back applies again.
- **While field permissions are still loading:** the same rule as the
URL seed and the list's columns. Until `perms.isLoaded`, only the
declaration check applies; a field the user cannot read is dropped only
once permissions have loaded. The seed is taken once per list identity,
so a stored entry on an unreadable field would reach the query if the
seed were taken before permissions arrive. On app-crm the permissions
had loaded before the seed in 4 of 4 opens as a sales rep. Waiting for
`perms.isLoaded` was not added: on a permissions error `isLoaded` stays
false, so a wait would hold the list closed.
## Tests
`ObjectView.listUrlState-11860.test.tsx`, beside the URL seed's "a
malformed or stale param is dropped" pins, gets a new group `a stored
sort or filter naming a stale field is dropped too (objectstack#28949)`.
It renders the real page and `ListView` and reads the query at the data
source:
- a field the object no longer declares: the stale sort level and
condition are gone; the valid sort and condition stored beside them
reach the query and the address bar;
- a field this user cannot read, once permissions are loaded: dropped; a
readable field is untouched;
- every stored entry stale: no filter, and the view's own declared sort;
- CONTROL: while permissions are still loading, only the declaration
check applies, as for a link;
- the helpers directly: nested groups at any depth, ids kept, an
untouched group returned as the same object, a cleared sort kept, a
fully dropped sort gone.
Ablation (one-time, not kept): with the two stored-seed lines put back
to the unchecked values, the first three cases fail (3 failed, 25
passed); the restore read blob equal to `HEAD` with an empty `git diff
HEAD`.
## Acceptance notes
- **Premise, reproduced on `examples/app-crm`** (`pnpm dev:crm --
--fresh`, view `all` of `crm_account`):
- *Missing field, as admin.* The stored row held a sort `ghost_field
desc, annual_revenue desc` and a filter `ghost_field = x AND industry =
technology`. Seeding a field the object does not declare is what a
deleted field leaves behind; no field was deleted at runtime. Unfixed
Console: the list query (`sort=-ghost_field,-annual_revenue`, filter on
`ghost_field`) answered 400 and the list showed "This view’s query was
rejected". Fixed Console: the query carried `sort=-annual_revenue` and
filter `industry = technology`, and the list showed 2 records.
- *Unreadable field, as a non-admin.* A sales rep holding
`crm_sales_user`, with `crm_account.industry` set unreadable by a
temporary local edit to the example permission set (not committed). As
the rep, a filter or sort on `industry` answers 403 `PERMISSION_DENIED`,
which is the query the unfixed seed sends. Fixed Console: the query
carried `sort=-annual_revenue` and filter `name contains c`, and the
list showed 2 records, in 4 of 4 opens.
- The card expected "an empty or refused list with no hint". On the
missing-field case the refusal panel does carry a hint ("Clearing the
filters usually fixes it"). It does not name the stale field or mention
"Reset view".
- Screenshots are in the project's `issue-28949` folder:
`unfixed-deleted-admin.png`, `fixed-deleted-admin.png`,
`fixed-unreadable-rep.png`.
## Verification
Branch base `fed3a51b`; every reading below is on head `59a34b51`.
`origin/main` has moved 12 commits since, none touching a file this diff
changes.
<details><summary>Tests</summary>
- `pnpm --dir ui exec vitest run
packages/app-shell/src/views/ObjectView.listUrlState-11860.test.tsx
packages/app-shell/src/views/ObjectView.userListState-12418.test.tsx`: 2
files, 54/54 passed.
- All 85 `packages/app-shell/src/views/ObjectView*.test.*` files, in two
batches: 43 files, 346 passed; 42 files, 359 passed; 0 failed.
- `pnpm --dir ui exec vitest run scripts/`: 169 files passed, 2 skipped;
4900 tests passed.
- Ablation, as described under Tests: 3 failed, 25 passed with the fix
removed; restored blob equal to `HEAD`.
</details>
<details><summary>Type-check, lint, gates</summary>
- `pnpm --filter @object-ui/app-shell run type-check`: exit 0.
- `eslint` on the two changed files: 0 errors. The 13 warnings in
`objectViewListUrlState.ts` are the 13 the file has on `main`.
- `pnpm --dir ui run check:new-line-citations`: 0 new citations.
- `node scripts/pm/dispatch-gates.mjs --commands`: 34 families derived;
all 34 run, each exit 0 (`check-changeset-no-major`,
`check-closing-keyword-parity` and `check-adr-0087-registration` with
this body as the event). `dispatch-gates --ran`: 34 run, 0 NOT MEASURED.
`node scripts/check-sdui-manifest.mjs`: exit 0, no registry change.
- Dogfood battery: not run, as no `!` is in the title and `Clause-②` has
no narrowing arm.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jTDLaFovMzMRUHDgXU)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 0e43df2 commit d1f3ac0
3 files changed
Lines changed: 176 additions & 9 deletions
File tree
- .changeset
- ui/packages/app-shell/src/views
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
25 | 28 | | |
26 | 29 | | |
27 | 30 | | |
| |||
93 | 96 | | |
94 | 97 | | |
95 | 98 | | |
| 99 | + | |
| 100 | + | |
96 | 101 | | |
97 | 102 | | |
98 | 103 | | |
| |||
398 | 403 | | |
399 | 404 | | |
400 | 405 | | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
401 | 496 | | |
402 | 497 | | |
403 | 498 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| 21 | + | |
21 | 22 | | |
| 23 | + | |
22 | 24 | | |
23 | 25 | | |
24 | | - | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
25 | 31 | | |
26 | 32 | | |
27 | 33 | | |
28 | 34 | | |
29 | 35 | | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
30 | 84 | | |
31 | 85 | | |
32 | 86 | | |
| |||
68 | 122 | | |
69 | 123 | | |
70 | 124 | | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
71 | 130 | | |
72 | 131 | | |
73 | 132 | | |
| |||
93 | 152 | | |
94 | 153 | | |
95 | 154 | | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
96 | 162 | | |
97 | 163 | | |
98 | | - | |
99 | | - | |
100 | | - | |
101 | | - | |
102 | | - | |
103 | | - | |
104 | 164 | | |
105 | 165 | | |
106 | 166 | | |
| |||
113 | 173 | | |
114 | 174 | | |
115 | 175 | | |
116 | | - | |
| 176 | + | |
117 | 177 | | |
118 | | - | |
| 178 | + | |
119 | 179 | | |
120 | 180 | | |
121 | 181 | | |
| |||
0 commit comments