Repository navigation
Commit f296334
<!-- claude-projects-attribution: {"github_login":"os-bill"} -->
_Requested by **Bill** · [project
thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wgVFfRuMa522AiLcTZSChFrD)_
Fixes #27644
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:Studio
数据页的「校验规则」标签保留草稿读回的服务端判定,每条规则的条件带上自己的判定路径(`validations.N.condition`),发布会拒收的条件就挡住自动保存。
- 为什么改:之前判定被丢掉,空值比较这类发布拒收的规则照样显示没问题并自动保存(PUT 200),直到点发布才被拒。
- 风险与回滚:只动控制台三个文件;每次自动保存后多一次草稿读取。回滚即还原本 PR,不涉及数据或元数据形状。
- 先看:`StudioDesignSurface.tsx` 的 DataPillar 加载和 `doSave`
两处、`ConditionBuilder.tsx` 行模式的 hold 计数、`ObjectValidationsPanel.tsx` 的
`ruleIndex`。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/app-shell/src/views/studio-design/DataPillar.validationVerdictHold-27644.test.tsx`,预期
6 个用例全绿。
- 需要你做的:无,席位落地。
## What changes
- **`DataPillar`** keeps the object draft read's served `_diagnostics`
and the body they judged (`editedItemDiagnostics`, the layered read's
verdict and body without a pending draft), drops both on each object
load, and re-reads the draft after each save, so the next verdict is the
stored body's. A sequence counter keeps a stale re-read from landing
over a later load. Both are provided to the Validations tab only,
through the existing `DraftVerdictContext` / `DraftVerdictBodyContext`.
- **`ObjectValidationsPanel`** hands each rule's guard its verdict path:
`validations.N.GUARD`, where N is the rule's index in the draft's raw
`validations` list (the index the lint locates by, since #27643) and
GUARD is `condition`, or `when` on a conditional rule. A rule not yet in
the draft gets none.
- **`ConditionBuilder`** takes `verdictPath`. In raw mode it forwards it
to `CelPredicateField`, whose #27612 `useServerHoldAt` already reports
the served error through `onLintChange` while the text on screen is the
judged text. In row mode no CEL editor is mounted, so the builder reads
the same `useServerHoldAt` itself: it shows the served message under the
rows and counts it in `onBlockingIssuesChange`. No second hold mechanism
and no client copy of the rule: the server's errors are taken whole, at
their exact path, through the #27612 helpers.
The panel's verdict count already rides #27629's per-rule kept verdicts,
so the hold also survives leaving the tab, and the held line names the
rule.
## Why
A guard the publish refuses (an ordering compare on a nullable field,
`record.amount > 100`) read clean in the Validations tab and autosaved
until Publish refused it. Measured on this branch before the fix: the
stored refused guard round-trips as a ROW, so the raw-editor half alone
would not hold the card's own shape (ablation 2 below).
## Tests
- New `DataPillar.validationVerdictHold-27644.test.tsx`, pinned the way
`PermissionMatrixEditor.serverVerdictHold-27482.test.tsx` is: a fake
door decides the refusal and serves it in the draft read's
`_diagnostics`; the CEL engine is stubbed to pass every guard, so every
hold is the served verdict's. (a) stored refused draft: another edit to
the rule is held, no PUT, the message stays; (b) the card flow: the
refused guard saves once, its re-read holds the next edit; (c) editing
the guard away releases, the save goes out and its clean re-read clears
the message; (d) typed back to the judged text before the autosave
fires: held again; (e) leaving the tab keeps the hold; control: the rule
beside it shows none of it.
- New `ConditionBuilder.serverHold-27644.test.tsx`: the row builder
shows and reports the served error, editing the row away reports 0, and
without `verdictPath` nothing is read.
## Acceptance notes
- **Not reproduced live.** The pre-fix behaviour (reads "Valid CEL", PUT
200 until Publish) was not reproduced against a running stack; the
ablations below are the before-state evidence.
- **An envelope-form guard is not held.** `serverHoldAt` compares the
judged body's value at the path with the text on screen, and a guard
stored as an expression envelope (`dialect` + `source`) reads as an
object there, so it never matches. No producer of envelope-form
validation guards was found (none in `examples/`); the Studio writes a
plain string unless the prior value was an envelope. Left as is, not
filed.
- **One extra draft read per autosave** in the Data pillar, as the
Permission matrix already does after each package-door save.
- `StudioDesignSurface.tsx` hunks: the shared import block (one name
added) and `DataPillar` only; disjoint from #27988's `InterfacesPillar`
hunks.
## Verification
All readings on this branch, against `objectstack-ai/objectstack`. The
final head is `479a7575`: this card's six files over `origin/main`
`066ff51e`, which already carries #27988.
<details><summary>New tests and ablations</summary>
- `pnpm --dir ui exec vitest run
packages/app-shell/src/views/metadata-admin/inspectors/ConditionBuilder.serverHold-27644.test.tsx
packages/app-shell/src/views/studio-design/DataPillar.validationVerdictHold-27644.test.tsx`:
Test Files 2 passed, Tests 9 passed.
- Ablation 1, the served verdict dropped (`DraftVerdictContext.Provider
value={objVerdict}` to `value={undefined}`, through
`scripts/ablation-replace.mjs`, anchor 1 to 0): `DataPillar` (a) to (e)
fail, the control passes (5 failed, 1 passed). Restored: blob equals
HEAD, `git diff HEAD` empty.
- Ablation 2, the row builder's count dropped (`raw ? celErrors :
held.length` to `raw ? celErrors : 0`): both row-builder cases fail, and
so do `DataPillar` (a) and (e), the cases that open the stored refused
guard (4 failed, 5 passed). This is the measurement that the stored
guard opens as a row. Restored the same way.
</details>
<details><summary>Package checks on the final head</summary>
- `pnpm --filter @object-ui/app-shell type-check` (both `tsc` projects;
`tsconfig.test.json` compiles the tests): exit 0.
- 169 test files around the change (`ObjectValidationsPanel*`,
`DataPillar*`, `StudioDesignSurface*`, `ConditionBuilder*`,
`CelPredicateField*`, the `serverVerdict*` suites), including both new
ones: Test Files 169 passed, Tests 1234 passed.
- `eslint` on the five changed `ui/` files: 0 errors (the warnings are
on lines this PR does not touch).
- `pnpm check:new-line-citations`: 0 new citations.
- Narrowed, declared: the whole `packages/app-shell/` suite (about 1950
files) was not completed locally; a run passed an hour on the shared box
before the second `origin/main` merge, and was stopped. `Test UI` in CI
runs it.
</details>
<details><summary>Derived gates</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`: 34 commands, the same list at every head.
At `23227677` all 34 exit 0, including `node
scripts/check-sdui-manifest.mjs`. The re-run on the final head is
reported in the os-dev report on the card.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Utp6CZjUkAAB57j1TQa8sE)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 13d85bb commit f296334
6 files changed
Lines changed: 465 additions & 11 deletions
File tree
- .changeset
- ui/packages/app-shell/src/views
- metadata-admin/inspectors
- studio-design
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
29 | | - | |
| 29 | + | |
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| 34 | + | |
34 | 35 | | |
35 | 36 | | |
36 | 37 | | |
| |||
820 | 821 | | |
821 | 822 | | |
822 | 823 | | |
823 | | - | |
| 824 | + | |
824 | 825 | | |
825 | 826 | | |
826 | 827 | | |
| |||
911 | 912 | | |
912 | 913 | | |
913 | 914 | | |
| 915 | + | |
| 916 | + | |
| 917 | + | |
| 918 | + | |
| 919 | + | |
| 920 | + | |
| 921 | + | |
| 922 | + | |
| 923 | + | |
914 | 924 | | |
915 | 925 | | |
916 | 926 | | |
| |||
1063 | 1073 | | |
1064 | 1074 | | |
1065 | 1075 | | |
1066 | | - | |
| 1076 | + | |
| 1077 | + | |
| 1078 | + | |
| 1079 | + | |
| 1080 | + | |
1067 | 1081 | | |
1068 | 1082 | | |
1069 | 1083 | | |
| |||
1114 | 1128 | | |
1115 | 1129 | | |
1116 | 1130 | | |
| 1131 | + | |
1117 | 1132 | | |
1118 | 1133 | | |
1119 | 1134 | | |
| |||
1205 | 1220 | | |
1206 | 1221 | | |
1207 | 1222 | | |
| 1223 | + | |
| 1224 | + | |
| 1225 | + | |
| 1226 | + | |
| 1227 | + | |
| 1228 | + | |
| 1229 | + | |
| 1230 | + | |
| 1231 | + | |
| 1232 | + | |
| 1233 | + | |
| 1234 | + | |
1208 | 1235 | | |
1209 | 1236 | | |
1210 | 1237 | | |
0 commit comments