Repository navigation
Commit 35ee2ad
fix(app-shell): "Add filter" in the Studio list-view inspector adds a row that can be edited (#26821)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbU2U5PMGb7vt7eH4fiuXwNj)_
Fixes #26738
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么**:Studio 列表视图检查器(以及标签页预设)里的筛选器控件,现在自己保存「还没填完」的条件行。点「Add
filter」会出现一行可编辑的条件;填完之后才把这条规则写进视图。
-
**为什么改**:控件以前每次渲染都从已保存的规则重新生成行,而保存端的折叠(`foldFilterGroupToSpecRules`)会正确地丢掉未填完的行,于是新加的空行在下一次渲染就消失了,Studio
里根本无法给列表视图加筛选。
- **风险与回滚**:低。只改 app-shell
一个控件的状态持有方式;保存规则不变(未填完的条件仍然不会写进视图),不新增任何元数据键,spec 不动。回滚即 revert 本 PR。
- **先看**:`ui/packages/app-shell/src/views/metadata-admin/widgets.tsx` 里
`FilterBuilderField` 的 `draft` / `seededFrom` 两个状态和 `handle`
里新增的「与已存规则相同则不写」判断;新测试 `widgets.filterBuilderHeldRow-26738.test.tsx`。
- **两分钟验证**:`cd ui && pnpm exec vitest run
packages/app-shell/src/views/metadata-admin/widgets.filterBuilderHeldRow-26738.test.tsx`,预期
4 个用例全绿;或在 Studio Interfaces 打开某对象的列表视图检查器,Filter →「Add
filter」,出现一行,填入值后视图草稿里出现该 filter。
- **需要你做的**:无,席位落地。
## What changes
`FilterBuilderField` (the `filter-builder` widget the Studio list-view
inspector's Filter row uses, and the per-tab filter of `filter-mode` tab
presets) built the `FilterBuilder`'s row group from the stored rules on
every render. The builder's "Add filter" inserts a blank row; the
save-side fold drops a row that is not complete yet, as it must; the
host stored the unchanged rules and re-rendered, and the re-derived
group replaced the builder's rows, so the new row disappeared before it
could be edited.
The widget now holds the builder's row group in its own state:
- every builder change is folded; a refused fold (OR over several rows,
a nested group) still toasts and writes nothing;
- an accepted fold updates the held rows, and writes only when the
folded rules differ from what is stored. An incomplete row therefore
stays on screen and writes nothing; completing it writes exactly the
complete rule;
- when the stored rules change and are not the echo of this builder's
own write (another editor, a reload), the held rows are re-seeded from
what is stored. The comparison keys on the rules' JSON, never on an
array's identity.
`viewFilterFold.ts` is unchanged: what is persisted is decided by the
same fold as before.
## Tests
New `widgets.filterBuilderHeldRow-26738.test.tsx` mounts the real
`filter-builder` widget in a host that writes the way
`ObjectListViewInspector` does (a new body object on every `onChange`,
`filter` dropped when empty):
- "Add filter" adds an editable row, writes nothing while it is
incomplete, and completing it writes `[{ field: 'title', operator:
'equals', value: 'open' }]` with the row still on screen;
- a second, incomplete row is held beside a stored rule without a write;
- a stored filter shows as its row and summary, opening it writes
nothing, and editing the value away and back writes the stored rule
unchanged (round-trip);
- CONTROL: a stored filter changed from outside re-seeds the rows.
Reverse verification (one-off, not kept): with the fix committed,
`widgets.tsx` restored to base `c8583f80` in the working tree only
(`groupOfStoredRules` occurrences 3 → 0) made the new file red, Tests 3
failed | 1 passed (4); each red is the vanished row ("expected [] to
have a length of 1 but got +0"). The round-trip case is green on base
too, as a preservation pin should be. Restored with `git checkout HEAD
-- …` (occurrences back to 3, `git diff HEAD` empty).
## Acceptance notes
- The card suspected a regression from the filter-fold work of #26306 /
#26348. Those changed the builder's value types, not how this widget
feeds the builder; the re-derived group predates them in the code read
here. History was not walked (shallow checkout), so when it started is
not measured.
- The inspector's write callback rebuilds the view body on every
`onChange`, so before this change an incomplete row would also have
written an unchanged body back to the draft. The widget now writes only
when the folded rules change.
- The popover's held rows live as long as the widget is mounted, so
closing and reopening the popover keeps an incomplete row ("held until
complete").
## Verification
All readings at head `5221c34d` (base `c8583f80`), under
`scripts/pm/os-verify-lock.sh`.
<details><summary>Touched package: @object-ui/app-shell</summary>
- Dependency closure: `pnpm exec turbo run build --concurrency=2
--filter='@object-ui/app-shell^...'`, 63 tasks, VERDICT command-exit 0.
- `pnpm --filter @object-ui/app-shell run type-check` (`tsc --noEmit`,
then `tsc -p tsconfig.test.json`): VERDICT command-exit 0, zero TS
errors.
- New test file: Test Files 1 passed (1), Tests 4 passed (4).
- Narrowed app-shell run: every app-shell test file naming the
metadata-admin `widgets` module, `filter-builder-trigger`,
`FilterBuilderWidget`, `viewFilterFold`, `ObjectListViewInspector`,
`FilterModeWidget` or `filter-mode` (52 files): Test Files 52 passed
(52), Tests 754 passed (754). The rest of app-shell is left to CI's Test
UI shards.
</details>
<details><summary>Derived gates</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 34 commands at `5221c34d`; each ran
with its exit code captured before any pipe, plus `pnpm
check:changeset-fixed` (its roster sits under `.changeset/`). `--ran`
reconciliation: "34 derived famil(ies) accounted for — 34 run, 0
NOT-MEASURED".
- `node scripts/check-adr-0087-registration.mjs --base origin/main` →
exit 0
- `node scripts/check-adr-0087-registration.mjs --self-test` → exit 0
- `node scripts/check-changeset-no-major.mjs --base origin/main` → exit
0
- `node scripts/check-closing-keyword-parity.mjs` → exit 0
- `node scripts/check-closing-keyword-parity.mjs --self-test` → exit 0
- `node scripts/check-comment-mask-corpus.mjs` → exit 0
- `node scripts/check-empty-changeset.mjs --base origin/main` → exit 0
- `node scripts/check-empty-changeset.mjs --self-test` → exit 0
- `node scripts/check-sdui-manifest.mjs` → exit 0
- `node scripts/check-sdui-manifest.mjs --self-test` → exit 0
- `node scripts/pm/release-rehearsal-clone.mjs --self-test` → exit 0
- `node scripts/release-pending-publish.mjs --self-test` → exit 0
- `pnpm --filter @objectstack/spec run check:duration-unit-keys` → exit
0
- `pnpm check:changeset-gate-self-tests` → exit 0
- `pnpm check:closed-ceremony-text` → exit 0
- `pnpm check:cross-package-test-inputs` → exit 0
- `pnpm check:dispatcher-error-vocabulary` → exit 0
- `pnpm check:driver-memory-census` → exit 0
- `pnpm check:engine-double-contract` → exit 0
- `pnpm check:gitlink-declared` → exit 0
- `pnpm check:nul-bytes` → exit 0
- `pnpm check:pm-changeset-deadline-census` → exit 0
- `pnpm check:published-files` → exit 0
- `pnpm check:query-options-erasure` → exit 0
- `pnpm check:refd-timer-probe` → exit 0
- `pnpm check:skill-identifier-liveness` → exit 0
- `pnpm check:test-source-alias` → exit 0
- `pnpm check:tier-file-adoption` → exit 0
- `pnpm check:type-check-coverage` → exit 0
- `pnpm check:type-check-debt` → exit 0
- `pnpm check:undeclared-dep-imports` → exit 0
- `pnpm check:watch-hint-literal` → exit 0
- `pnpm check:where-matcher` → exit 0
- `pnpm check:changeset-fixed` → exit 0
- `pnpm --dir ui exec vitest run scripts/` → exit 0
`pnpm --dir ui exec vitest run scripts/`: Test Files 178 passed | 2
skipped (180), Tests 5065 passed | 2 skipped (5067). `pnpm
check:new-line-citations` (from `ui/`): 0 new citation(s), exit 0.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jV9H4DGK4phGzhzdZ4)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01QsR2jV9H4DGK4phGzhzdZ4
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent cdd9f0a commit 35ee2ad
3 files changed
Lines changed: 201 additions & 14 deletions
File tree
- .changeset
- ui/packages/app-shell/src/views/metadata-admin
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 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 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2730 | 2730 | | |
2731 | 2731 | | |
2732 | 2732 | | |
| 2733 | + | |
| 2734 | + | |
| 2735 | + | |
| 2736 | + | |
| 2737 | + | |
| 2738 | + | |
| 2739 | + | |
| 2740 | + | |
| 2741 | + | |
| 2742 | + | |
| 2743 | + | |
| 2744 | + | |
| 2745 | + | |
| 2746 | + | |
| 2747 | + | |
| 2748 | + | |
| 2749 | + | |
2733 | 2750 | | |
2734 | 2751 | | |
2735 | 2752 | | |
| |||
2762 | 2779 | | |
2763 | 2780 | | |
2764 | 2781 | | |
2765 | | - | |
2766 | | - | |
2767 | | - | |
2768 | | - | |
2769 | | - | |
2770 | | - | |
2771 | | - | |
2772 | | - | |
2773 | | - | |
2774 | | - | |
2775 | | - | |
2776 | | - | |
2777 | | - | |
| 2782 | + | |
| 2783 | + | |
| 2784 | + | |
| 2785 | + | |
| 2786 | + | |
| 2787 | + | |
| 2788 | + | |
| 2789 | + | |
| 2790 | + | |
| 2791 | + | |
| 2792 | + | |
| 2793 | + | |
| 2794 | + | |
| 2795 | + | |
| 2796 | + | |
| 2797 | + | |
| 2798 | + | |
| 2799 | + | |
| 2800 | + | |
| 2801 | + | |
2778 | 2802 | | |
2779 | 2803 | | |
2780 | 2804 | | |
| |||
2789 | 2813 | | |
2790 | 2814 | | |
2791 | 2815 | | |
| 2816 | + | |
| 2817 | + | |
| 2818 | + | |
| 2819 | + | |
2792 | 2820 | | |
2793 | 2821 | | |
2794 | 2822 | | |
| |||
2810 | 2838 | | |
2811 | 2839 | | |
2812 | 2840 | | |
2813 | | - | |
| 2841 | + | |
2814 | 2842 | | |
2815 | 2843 | | |
2816 | 2844 | | |
| |||
0 commit comments