Repository navigation
Commit c92ae76
fix(plugin-form): drawer, tabbed and wizard edit forms for a record that does not exist show the not-found state, not an empty editable form (#29351)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9Ubb3Z5yLX6trsrar5zLPSuFd8)_
Fixes #29247
Clause-②: no
Metadata-protocol: none
Declarations as set by the claim (card comment 6576081714), which fixes
the file surface: `DrawerForm.tsx`, `TabbedForm.tsx`, `WizardForm.tsx`,
`loadFailure.ts` (a module-private shared helper only), `ModalForm.tsx`
only because its not-found drawing moved into that helper, their tests
and a root changeset.
## 维护者速读
- **改了什么**:抽屉表单、分页签表单和向导表单在编辑一条不存在的记录时,现在显示「Record not found」未找到状态(与记录页和
`?form=` 弹窗相同的文案),不再显示可编辑的空白表单,也没有「更新」按钮。四种记录表单共用 `loadFailure.ts`
里同一个私有组件来画这个状态。新建和编辑已存在的记录不变。
- **为什么改**:用户从已删除记录的旧链接进入这三种表单,会看到空白表单,填完点更新才被告知记录不存在;PR #29246 只修了弹窗表单。
- **风险与回滚**:低。三处记录读取 effect 在 `findOne` 返回 `null` 时记一次
`RecordNotFoundError` 加载失败、不再把 `{}`
填进表单;原有的「加载失败时不画表单和底栏」路径接管其余。表单打开期间后台重读发现记录已删,也显示同一状态。`ModalForm`
只是改为调用共享组件,行为不变。回滚即还原本 PR。
- **先看**:`ui/packages/plugin-form/src/loadFailure.ts` 末尾的
`RecordNotFoundState`;`DrawerForm.tsx` 记录读取 effect 的 `data == null` 分支与
`renderContent` 开头的分支;`WizardForm.tsx` 同一处(它还设置 `persistedRecord`)。
- **两分钟验证**:`pnpm --dir ui exec vitest run
packages/plugin-form/src/recordNotFound-29247.test.tsx
packages/plugin-form/src/ModalForm.recordNotFound-29157.test.tsx`,预期 2
个文件、16 个测试全绿。
- **需要你做的**:无,席位落地。
## What changes
- **A null edit read is a failure, not an empty record, in every record
form.** `DataSource.findOne` is typed `T | null`, and the ObjectStack
adapter's `readOne` answers `null` when the server answers 404.
`DrawerForm`, `TabbedForm` and `WizardForm` ran `setFormData(data ||
{})` (the wizard also `setPersistedRecord(data || {})`) and committed,
so an edit form for a deleted or unknown record drew a blank editable
form with an enabled Update button. Each now fails the read with the
package-private `RecordNotFoundError`, through the per-read failure run
every other load failure uses, and the form is not seeded. This is the
branch PR #29246 gave `ModalForm`.
- **One drawing for the state.** `loadFailure.ts` gains a module-private
`RecordNotFoundState`: `Empty` with `EmptyTitle` and `EmptyDescription`,
reading the existing pack keys `empty.recordNotFound` and
`empty.recordNotFoundDescription` through its own
`createSafeTranslation` table (the `en` values, so a provider-less host
still reads English). Each of the four layouts draws it ahead of the
generic "Error loading form" box when the shown failure is a
`RecordNotFoundError`; the drawer keeps its footer off while a load
failure is shown, as before, and the tabbed and wizard layouts return
the state in place of the form. `ModalForm`'s inline copy of the same
drawing and its two table rows move into the helper; its test id is
kept.
- **Also on a background re-read.** A bus re-read
(`useRecordInvalidation`) that finds the record deleted while the form
is open shows the same state.
- **Unchanged.** Create (or no id) never reads; an edit of a record that
exists draws its form as before. No new string, no new export from the
package entry, no prop or schema change.
## Why
A user who opens an old link to a deleted record in a drawer, tabbed or
wizard form saw a blank form, filled it in, and learned the record did
not exist only on save.
## Tests
- New `recordNotFound-29247.test.tsx`, one `describe.each` over
`DrawerForm`, `TabbedForm` and `WizardForm` (each an `object-form` in
edit mode with one section, the real registered fields): a `null` read
draws "Record not found", its description and the layout's test id, with
no `title` input, no Update button and no "Error loading form"; a found
record that a bus re-read then finds deleted switches to the same state;
CONTROL a found record still draws its form and Update; CONTROL a create
draws an empty form with Create and never calls `findOne`.
- `ModalForm.recordNotFound-29157.test.tsx` (unchanged) holds
`ModalForm` after its drawing moved into the helper.
## Acceptance notes
- **Live console not measured.** The chain is pinned in vitest from the
`findOne` answer down; the adapter's 404 to `null` mapping is read from
`ObjectStackAdapter.readOne`, not re-exercised.
- **The helper is built with `createElement`** because `loadFailure.ts`
stays a `.ts` module as the claim names it; the `data-testid` prop is
typed onto `Empty`'s props instead of cast.
- **`setFormData(data)`** in the three layouts: after the `null` branch
the `|| {}` fallback could no longer fire, so it is dropped.
`ModalForm`'s own line is left as it is.
- **One lint warning in the new test** (`no-explicit-any` on the
parametrised layout type), the same documented shape and reason as
`recordSwapLoading.test.tsx`: the four layouts share no props base.
## Verification
<details><summary>New pin · 12/12 here, the 6 not-found cases red
against main's three layouts</summary>
`pnpm --dir ui exec vitest run --maxWorkers=2
packages/plugin-form/src/recordNotFound-29247.test.tsx
packages/plugin-form/src/ModalForm.recordNotFound-29157.test.tsx` under
`os-verify-lock` at `c4c659f5`: Test Files 2 passed (2), Tests 16 passed
(16).
Reverse check at `35a69884`: `DrawerForm.tsx`, `TabbedForm.tsx` and
`WizardForm.tsx` restored from the base `c347d22d` with `git restore
--source` (on disk: `RecordNotFoundError` count 0 in each; `data || {}`
count 1, 1 and 2), `loadFailure.ts` kept at HEAD, new test file alone:
Tests 6 failed, 6 passed (12). All six failures were the two not-found
cases of each layout, each "Unable to find an element with the text:
Record not found"; all six CONTROLs passed. Restored with `git restore
--source=HEAD --staged --worktree` under an EXIT trap; `git diff HEAD`
empty, `git status --porcelain` empty, and each file's `git hash-object`
equal to its HEAD blob.
At the final head `56a7a05d` (after the second merge of `main`): the two
not-found files plus `ModalForm.untypedRepeater.test.tsx` (new on main)
and `recordSwapLoading.test.tsx`: Test Files 4 passed (4), Tests 33
passed (33).
</details>
<details><summary>plugin-form package, type check and lint</summary>
- `pnpm --dir ui exec vitest run --maxWorkers=2 packages/plugin-form/`
(locked) at `903d4662`: Test Files 220 passed (220), Tests 2435 passed,
1 skipped. Same counts at `35a69884` before the first merge.
- `turbo run type-check --filter=@object-ui/plugin-form` (`tsc --noEmit
&& tsc -p tsconfig.test.json`): exit 0 at `903d4662` and again at
`56a7a05d`. The first run, at `35a69884`, before `815181c6` typed the
test id prop, was red with TS2769 on `createElement(Empty, {
'data-testid' … })`; fixed by naming the attribute in the props type, no
cast.
- `eslint` on the six changed `.ts`/`.tsx` files: 0 errors; the one
warning in a changed hunk is the documented `any` in the new test.
- `pnpm --dir ui run check:new-line-citations`: 0 new citations. `node
scripts/check-i18n-call-site-keys.mjs` (from `ui/`, at `35a69884`): exit
0, the new `createSafeTranslation` table repeats the `en` values.
</details>
<details><summary>Derived gates</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 34 commands (the same 34 against
merge bases `c347d22d` and `d9885f5d`). At `903d4662`, the 33 non-vitest
gates ran in one loop and all exited 0, `check-sdui-manifest.mjs` and
its `--self-test`, `check:nul-bytes`, `check:query-options-erasure`,
`check:type-check-debt` and `check:test-source-alias` among them; `pnpm
--dir ui exec vitest run scripts/`: Test Files 169 passed, 2 skipped;
Tests 4903 passed, 2 skipped. `dispatch-gates --ran`: 34 derived, 34
run, 0 NOT-MEASURED.
`main` then moved 21 commits, none touching the changed files (one new
`ModalForm` test file and one `en` key in plugin-form and i18n). After
merging it, at `56a7a05d`: `check-sdui-manifest`,
`check-changeset-no-major`, `check-adr-0087-registration`,
`check-empty-changeset`, `check:nul-bytes`, `check:type-check-debt`,
`check:type-check-coverage`, `check:test-source-alias`,
`check:cross-package-test-inputs`, `check:undeclared-dep-imports` and
`check:published-files` all exit 0. The other 23 derived gates were last
measured at `903d4662`.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(no spec change)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes. (eslint on the changed
files: 0 errors)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (not touched)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jTeZsth6vzGnL4eoaN)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent ea025d3 commit c92ae76
7 files changed
Lines changed: 237 additions & 18 deletions
File tree
- .changeset
- ui/packages/plugin-form/src
| 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 | |
|---|---|---|---|
| |||
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
| 72 | + | |
| 73 | + | |
72 | 74 | | |
73 | 75 | | |
74 | 76 | | |
| |||
443 | 445 | | |
444 | 446 | | |
445 | 447 | | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
446 | 454 | | |
447 | 455 | | |
448 | | - | |
| 456 | + | |
449 | 457 | | |
450 | 458 | | |
451 | 459 | | |
| |||
775 | 783 | | |
776 | 784 | | |
777 | 785 | | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
778 | 789 | | |
779 | 790 | | |
780 | 791 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | 36 | | |
40 | 37 | | |
41 | 38 | | |
| |||
77 | 74 | | |
78 | 75 | | |
79 | 76 | | |
| 77 | + | |
80 | 78 | | |
81 | 79 | | |
82 | 80 | | |
| |||
113 | 111 | | |
114 | 112 | | |
115 | 113 | | |
116 | | - | |
117 | | - | |
118 | | - | |
119 | | - | |
120 | 114 | | |
121 | 115 | | |
122 | 116 | | |
| |||
919 | 913 | | |
920 | 914 | | |
921 | 915 | | |
922 | | - | |
923 | | - | |
924 | | - | |
925 | | - | |
926 | | - | |
927 | | - | |
| 916 | + | |
928 | 917 | | |
929 | 918 | | |
930 | 919 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| 33 | + | |
| 34 | + | |
33 | 35 | | |
34 | 36 | | |
35 | 37 | | |
| |||
410 | 412 | | |
411 | 413 | | |
412 | 414 | | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
413 | 421 | | |
414 | 422 | | |
415 | | - | |
| 423 | + | |
416 | 424 | | |
417 | 425 | | |
418 | 426 | | |
| |||
581 | 589 | | |
582 | 590 | | |
583 | 591 | | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
584 | 595 | | |
585 | 596 | | |
586 | 597 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
| 42 | + | |
| 43 | + | |
42 | 44 | | |
43 | 45 | | |
44 | 46 | | |
| |||
673 | 675 | | |
674 | 676 | | |
675 | 677 | | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
676 | 684 | | |
677 | 685 | | |
678 | | - | |
679 | | - | |
| 686 | + | |
| 687 | + | |
680 | 688 | | |
681 | 689 | | |
682 | 690 | | |
| |||
1198 | 1206 | | |
1199 | 1207 | | |
1200 | 1208 | | |
| 1209 | + | |
| 1210 | + | |
| 1211 | + | |
1201 | 1212 | | |
1202 | 1213 | | |
1203 | 1214 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
34 | | - | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
35 | 44 | | |
36 | 45 | | |
37 | 46 | | |
| |||
97 | 106 | | |
98 | 107 | | |
99 | 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 | + | |
| 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 | + | |
0 commit comments