Skip to content

Commit 7546570

Browse files
revert(app-shell): re-land Studio's validation presets (objectui#11931) and the Interfaces page-create (objectui#11932) (#11967)
Part of #11861 Re-lands objectui#11823 step 3: objectui#11932, the Studio Interfaces page-create (seat `domain:ui#3`). That card stays open. Clause-②: no Held as a draft until objectui#11949 (the +5,120 B allowance, ruling `6056819248` step 2) lands. When this PR was opened, objectui#11949 was already on `main` as `1c51e973b`, and this branch has merged it. ## What this is A plain `git revert 3c888c6`, followed by merges of `main`. `3c888c6` is the rollback PR objectui#11956, a single-parent squash, so `-m 1` does not apply. It brings back: - objectui#11931: Studio's validation "New" menu opens on common rules, with the rule types under "Advanced". This is the validation-rule entry of objectui#11861. - objectui#11932: objectui#11823 step 3. The Interfaces pillar creates a page and opens it on its source beside a live preview. Claim `6057967120` on objectui#11861 · review `6057399892` on objectui#11937 · ruling `6056819248` on objectui#11942. ## Tree proof - The revert commit `7eeffd29b` has the tree of `f3a0488` (`011fe521e`), so `git diff f3a0488 7eeffd2 --stat` prints nothing. - `.changeset/11937-eager-budget-rollback.md` was added by `3c888c6` and never existed at `f3a0488`. Its removal therefore shows against `3c888c6`, not against `f3a0488`. - Against `main`, the branch changes the 15 paths `3c888c6` changed, and no other path. - Twelve of the 15 hold their `f3a0488` blob byte for byte. The other three were also changed on `main` after `3c888c6`, and they carry both intents (next section). ## Merges of main, and the conflicts The branch merged `main` five times: at `1c51e973b`, `d73d98770`, `70e3d7721`, `ccc2824cf` and `247f50349`. Only two of the PRs these brought in touch a restored file. **objectui#11945** (objectui#11923, the *Runs on* row), merged at `70e3d7721`. - One conflicting hunk, in the import block of `ObjectValidationsPanel.tsx`. This side imports `validationPresets.js`; `main` imports `ScriptValidationSchema` / `ScriptValidationParsed` from `@objectstack/spec/data`. Both lines are kept. - Two files merged without conflict: - `metadata-admin/i18n.ts`: `main` removes the `engine.studio.rules.event.delete` row, en and zh. It stays removed. - the `newRuleWaits-11820` pin: `main` asserts there is no Delete box. That assertion stays. - Check, per file: the changed lines of the merge against `main` equal the re-land's own changed lines (`3c888c6..f3a0488`). The changed lines of the merge against this branch's previous head equal objectui#11945's own diff. Both comparisons were identical for all three files. **objectui#11935** (objectui#11894, typed is-empty operators), merged at `247f50349`. - Two conflicting hunks, both in `ObjectValidationsPanel.tsx`. Each side added the same `FieldOpt` member, `type`. - The interface keeps one `type`, `main`'s `multiple` and the presets' `system`. One doc comment names both readers of `type`. - The field mapping keeps one `type` line, `main`'s `multiple` line with its comment, and the presets' `system` line. - `i18n.ts` merged without conflict, and both sides' rows are kept. - The same check, run on this merge, leaves only the shared `type` member and the merged comments as residual lines. `main` moved on after `247f50349` (objectui#11961, #11962, #11944). None of those touches any of the 15 paths, so they are not merged here. ## Changesets - Restored: `.changeset/11861-validation-presets.md` and `.changeset/11823-page-create.md`, both `@object-ui/app-shell: patch`. - Removed: `.changeset/11937-eager-budget-rollback.md` (`@object-ui/app-shell: patch`). This re-land supersedes it. - `check-changeset-overwrite` is report-only and exits 0. It reports one pre-existing changeset deleted, with `@object-ui/app-shell` "GONE from the declaration" of that file. - The package still bumps, because both restored changesets declare it. This is the gate's case 3: a changeset superseded by a replacement in the same change. ## Gates, on head `fb0c91d45` (every merge above included) Tests use the repo-root `pnpm exec vitest run` and run under the shared verify lock. - **Named set:** 98 files and 631 tests, all passed. It covers: - all eight `ObjectValidationsPanel*` suites, including objectui#11945's `runsOn-11923` and objectui#11935's `typedEmptyOps-11894`; - every test file that imports `interfaceCreate` or `StudioDesignSurface`; - `column-identity.ratchet.test.ts`; - the `main` pins next to the conflicts: `ConditionBuilder.typedEmptyOps-11894`, `PagePreview.pageKind-11933` and `SourcePageEditor.pageKind-11933`. - **Widened set:** 87 files and 1,103 tests, all passed. It covers every other test that imports `validationPresets`, `ObjectValidationsPanel` or the designer string table `metadata-admin/i18n.ts`. - `pnpm --filter @object-ui/app-shell type-check` passes, after a turbo build of the app-shell dependency closure (28 of 28 tasks). - Each of these gates exits 0: - `check:i18n-keys`, `check:i18n-designer-parity`, `check:i18n-drift`; - `check-changeset-presence`, `check-changeset-fixed`, `check-changeset-no-major`, `check:changeset-claims`, `check:pending-changeset-literals`, `check-changeset-overwrite` (report-only, see above); - `check:unreferenced-sources`. - eslint on the 11 touched source and test files reports 0 errors and 22 warnings. The repo sets no `--max-warnings`. The warnings are the ones already in these files at `f3a0488`, and the merge adds none. - `check-governed-queue-guard --test` reports NOT GOVERNED: none of the 15 paths is on a governed surface. - Not run: the console build. CI's `Bundle Analysis` measures the first-load bytes against the ceiling that objectui#11949 raised. `scripts/check-eager-closure-budget.mjs` is not touched. - Reference point: on its push run, `f3a0488` (the tree this re-land restores) had 66 check runs succeed, 6 skipped and 1 fail, `Bundle Analysis`. That is 73 of 73 runs read. --- _Generated by [Claude Code](https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent a835c31 commit 7546570

15 files changed

Lines changed: 1398 additions & 68 deletions

‎.changeset/11823-page-create.md‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
Studio's Interfaces pillar can now create a page in a writable package, and opens it on its source beside a live preview (objectui#11823, step 3).
6+
7+
The **New** menu beside the navigation rail offers *New page* after *New dashboard* and *New report*.
8+
It opens the shared create dialog (a name, and the identifier derived from it) with one more choice,
9+
the language the page is written in:
10+
11+
- **HTML** (the default): built from the platform's components and HTML tags, parsed and never run;
12+
- **React**: real React code, run when the page renders. It is offered only where the deployment runs
13+
React pages (a deployment can turn them off).
14+
15+
On save:
16+
17+
- the page is saved as a draft in the package: an app page (a page the app's navigation can open) of
18+
the chosen kind, starting from a short source with one line of text;
19+
- an entry linking it, carrying the name typed in the dialog, is added to the app's navigation and
20+
saved with it, so it appears in the rail at once;
21+
- the page opens with its source in the code editor on the right and the live preview, drawn by the
22+
same renderer the running app uses, on the canvas.
23+
24+
An identifier that another page already holds, published in any package or as a draft, is refused in
25+
the dialog and nothing is saved. As for dashboards and reports, the menu is not offered on a read-only
26+
package, on a package with no app yet, or while the navigation is being edited.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
Studio's validation-rule "New" menu opens on common rules in plain words, with the rule types under "Advanced" (part of objectui#11861: the validation-rule entry).
6+
7+
"New" in an object's Validations view used to open on the spec's rule types ("Script — CEL fail condition", "Cross-field — CEL over multiple fields", …). It now opens on three starting points, each a rule of a type the spec already has:
8+
9+
- **End date on or after start date** — a `cross_field` rule on the object's first two date fields (its own fields: never a system or hidden one). It is written at once, with its message, and the error attaches to the end date.
10+
- **Number can't be negative** — a `script` rule on the object's first number, currency or percent field, written at once with its message.
11+
- **Reject the save when…** — a `script` rule whose condition the author builds. As with any new rule since objectui#11820, it is not saved until it has a condition, and it opens with the condition editor focused.
12+
13+
Each menu row names the fields it will use. A preset the object has no fields for is listed disabled, saying what it needs. Every condition a preset fills guards each comparison with `!= null`. The server rejects a write whose rule condition cannot be evaluated, and comparing an empty value is such a condition. The guard keeps the rule from rejecting records that leave the field empty. The filled rules open as editable condition rows, not raw CEL.
14+
15+
The per-type list is unchanged, one click away under "Advanced". New rules still start on Create + Update.
16+
17+
"Unique" and "Required when…" are not offered as validation presets. The spec has no uniqueness rule type; it points to a unique index or a field's `unique` instead. "Required when" is a field's own setting (`requiredWhen`), already in the field inspector.
18+
19+
Nothing is added to the package entry: no export, prop, type member or language-pack key. The new copy lives in the metadata-admin designer's own string tables (en and zh).

‎.changeset/11937-eager-budget-rollback.md‎

Lines changed: 0 additions & 5 deletions
This file was deleted.

‎content/docs/guide/console.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ entries — no code round-trip required:
4747

4848
| Tab | Edits | Panel |
4949
|-----|-------|-------|
50-
| **Validations** | the object's inline `validations[]` (spec `ValidationRuleSchema`) | Master-detail covering **every** rule type — `script`, `cross_field`, `state_machine`, `format`, `json_schema`, `conditional`. The **New** menu adds any type (seeded with a valid, never-firing skeleton); a rule's type can be switched in place. CEL predicates reuse the shared `ConditionBuilder`, fed the object's draft fields. |
50+
| **Validations** | the object's inline `validations[]` (spec `ValidationRuleSchema`) | Master-detail covering **every** rule type — `script`, `cross_field`, `state_machine`, `format`, `json_schema`, `conditional`. The **New** menu opens on common rules in plain words (*End date on or after start date*, *Number can't be negative*, *Reject the save when…*); each writes a working rule, or waits for the condition the author gives. Every rule type is under **Advanced**. A new rule whose type carries a condition (`script`, `cross_field`, `conditional`) is saved only once it has one; a rule's type can still be switched in place. CEL predicates reuse the shared `ConditionBuilder`, fed the object's draft fields. |
5151
| **Hooks** | the separate `hook` metadata type targeting this object | Master-detail whose editor is the platform `SchemaForm` **driven by the live `hook` JSONSchema from `/meta/types`**, so its fields and enums always match the running server's contract. |
5252
| **Actions** | the object's inline `actions[]` (spec `ActionSchema`) | Master-detail using the type-aware `ActionDefaultInspector`; anything not curated falls through to a **"More fields"** form fed the live `action` JSONSchema, so no spec property is un-editable. |
5353

‎packages/app-shell/src/views/metadata-admin/i18n.ts‎

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3096,6 +3096,19 @@ const ENGINE_STRINGS_EN: Record<string, string> = {
30963096
'engine.studio.rules.event.insert': 'Create',
30973097
'engine.studio.rules.event.update': 'Update',
30983098
'engine.studio.rules.priority': 'Priority',
3099+
// objectui#11861 — the New menu's starting points, and the type list under Advanced.
3100+
'engine.studio.rules.presets': 'Common rules',
3101+
'engine.studio.rules.advanced': 'Advanced',
3102+
'engine.studio.rules.preset.endAfterStart': 'End date on or after start date',
3103+
'engine.studio.rules.preset.endAfterStartNeeds': 'Needs two date fields on this object.',
3104+
'engine.studio.rules.preset.endAfterStartMessage': '{end} must be on or after {start}.',
3105+
'engine.studio.rules.preset.notNegative': 'Number can’t be negative',
3106+
'engine.studio.rules.preset.notNegativeNeeds': 'Needs a number field on this object.',
3107+
'engine.studio.rules.preset.notNegativeMessage': '{field} can’t be negative.',
3108+
'engine.studio.rules.preset.rejectWhen': 'Reject the save when…',
3109+
'engine.studio.rules.preset.rejectWhenHint': 'Build your own condition, no code needed.',
3110+
'engine.studio.rules.preset.usesOne': 'Uses “{field}”.',
3111+
'engine.studio.rules.preset.usesTwo': 'Uses “{start}” and “{end}”.',
30993112
'engine.studio.settings.basics': 'Basics',
31003113
'engine.studio.settings.noInspector': 'No default object inspector registered.',
31013114
'engine.studio.settings.semanticRoles': 'Semantic roles',
@@ -3236,8 +3249,8 @@ const ENGINE_STRINGS_EN: Record<string, string> = {
32363249
'engine.studio.inspector.listView.addSort': '+ Add sort…',
32373250
'engine.studio.inspector.listView.sortAsc': '{field} ascending',
32383251
'engine.studio.inspector.listView.sortDesc': '{field} descending',
3239-
// objectui#11823 (step 2) — the Interfaces pillar's New dashboard / New report.
3240-
'engine.studio.interfaces.create.menuTitle': 'Create a dashboard or a report in this package',
3252+
// objectui#11823 (steps 2 and 3) — the Interfaces pillar's New dashboard / New report / New page.
3253+
'engine.studio.interfaces.create.menuTitle': 'Create a dashboard, a report or a page in this package',
32413254
'engine.studio.interfaces.create.dashboard': 'New dashboard',
32423255
'engine.studio.interfaces.create.report': 'New report',
32433256
'engine.studio.interfaces.create.description':
@@ -3264,6 +3277,20 @@ const ENGINE_STRINGS_EN: Record<string, string> = {
32643277
'engine.studio.interfaces.create.datasetsFailed': 'The datasets could not be loaded: {error}',
32653278
'engine.studio.interfaces.create.noMeasures': 'This dataset declares no measures, so a report cannot show it.',
32663279
'engine.studio.interfaces.create.needsBinding': 'Choose the dataset this report reads, and one of its measures.',
3280+
'engine.studio.interfaces.create.page': 'New page',
3281+
'engine.studio.interfaces.create.pageNameLabel': 'Page name',
3282+
'engine.studio.interfaces.create.pageNamePlaceholder': 'Page name (e.g. Team handbook)',
3283+
'engine.studio.interfaces.create.pageIdPlaceholder': 'Identifier (e.g. team_handbook)',
3284+
'engine.studio.interfaces.create.pageTaken':
3285+
'A page with the identifier “{name}” already exists. Choose another identifier.',
3286+
'engine.studio.interfaces.create.pageKind': 'Written in',
3287+
'engine.studio.interfaces.create.pageKindHtml': 'HTML',
3288+
'engine.studio.interfaces.create.pageKindReact': 'React',
3289+
'engine.studio.interfaces.create.pageKindHtmlHint':
3290+
'Built from the platform’s components and HTML tags, parsed and never run. The page opens on its source beside a live preview.',
3291+
'engine.studio.interfaces.create.pageKindReactHint':
3292+
'Real React code, run when the page renders, for state, effects and event handlers. The page opens on its source beside a live preview.',
3293+
'engine.studio.interfaces.create.pageStarter': 'A new page. Write its content in the source editor.',
32673294
'engine.studio.inspector.designersMissing':
32683295
'No metadata designers are registered in this session, so there is nothing to edit here.',
32693296
'engine.studio.inspector.noPageSchema': 'Page settings are unavailable — the page schema could not be loaded.',
@@ -6232,6 +6259,18 @@ const ENGINE_STRINGS_ZH: Record<string, string> = {
62326259
'engine.studio.rules.event.insert': '新建',
62336260
'engine.studio.rules.event.update': '更新',
62346261
'engine.studio.rules.priority': '优先级',
6262+
'engine.studio.rules.presets': '常用规则',
6263+
'engine.studio.rules.advanced': '高级',
6264+
'engine.studio.rules.preset.endAfterStart': '结束日期不早于开始日期',
6265+
'engine.studio.rules.preset.endAfterStartNeeds': '需要该对象有两个日期字段。',
6266+
'engine.studio.rules.preset.endAfterStartMessage': '{end}不能早于{start}。',
6267+
'engine.studio.rules.preset.notNegative': '数值不能为负',
6268+
'engine.studio.rules.preset.notNegativeNeeds': '需要该对象有一个数值字段。',
6269+
'engine.studio.rules.preset.notNegativeMessage': '{field}不能为负数。',
6270+
'engine.studio.rules.preset.rejectWhen': '满足条件时拒绝保存…',
6271+
'engine.studio.rules.preset.rejectWhenHint': '自行搭建条件,无需写代码。',
6272+
'engine.studio.rules.preset.usesOne': '使用「{field}」。',
6273+
'engine.studio.rules.preset.usesTwo': '使用「{start}」和「{end}」。',
62356274
'engine.studio.settings.basics': '基础信息',
62366275
'engine.studio.settings.noInspector': '未注册对象默认检查器。',
62376276
'engine.studio.settings.semanticRoles': '语义角色',
@@ -6342,7 +6381,7 @@ const ENGINE_STRINGS_ZH: Record<string, string> = {
63426381
'engine.studio.inspector.listView.sortAsc': '{field} 升序',
63436382
'engine.studio.inspector.listView.sortDesc': '{field} 降序',
63446383
// objectui#11823 (step 2) — the Interfaces pillar's New dashboard / New report.
6345-
'engine.studio.interfaces.create.menuTitle': '在本包中新建仪表盘或报表',
6384+
'engine.studio.interfaces.create.menuTitle': '在本包中新建仪表盘、报表或页面',
63466385
'engine.studio.interfaces.create.dashboard': '新建仪表盘',
63476386
'engine.studio.interfaces.create.report': '新建报表',
63486387
'engine.studio.interfaces.create.description': '存为本包的草稿,加入 {app} 的导航,并在画布上打开。',
@@ -6364,6 +6403,17 @@ const ENGINE_STRINGS_ZH: Record<string, string> = {
63646403
'engine.studio.interfaces.create.datasetsFailed': '无法加载数据集:{error}',
63656404
'engine.studio.interfaces.create.noMeasures': '这个数据集没有声明度量,报表无法展示它。',
63666405
'engine.studio.interfaces.create.needsBinding': '请选择这个报表读取的数据集,以及其中的一个度量。',
6406+
'engine.studio.interfaces.create.page': '新建页面',
6407+
'engine.studio.interfaces.create.pageNameLabel': '页面名称',
6408+
'engine.studio.interfaces.create.pageNamePlaceholder': '页面名称(如:团队手册)',
6409+
'engine.studio.interfaces.create.pageIdPlaceholder': '标识符(如:team_handbook)',
6410+
'engine.studio.interfaces.create.pageTaken': '已有标识为「{name}」的页面,请换一个标识。',
6411+
'engine.studio.interfaces.create.pageKind': '编写方式',
6412+
'engine.studio.interfaces.create.pageKindHtml': 'HTML',
6413+
'engine.studio.interfaces.create.pageKindReact': 'React',
6414+
'engine.studio.interfaces.create.pageKindHtmlHint': '由平台组件与 HTML 标签组成,只解析、不执行。页面打开时,源码与实时预览并排显示。',
6415+
'engine.studio.interfaces.create.pageKindReactHint': '真正的 React 代码,在页面渲染时运行,可使用状态、副作用与事件处理。页面打开时,源码与实时预览并排显示。',
6416+
'engine.studio.interfaces.create.pageStarter': '新页面。请在源码编辑器中编写它的内容。',
63676417
'engine.studio.inspector.designersMissing': '本次会话没有注册任何元数据设计器,这里没有可编辑的内容。',
63686418
'engine.studio.inspector.noPageSchema': '页面设置不可用——无法加载页面 schema。',
63696419
'engine.studio.inspector.sourcePageLine1': '这个页面是 {kind} 源码,不是积木树 ——',

‎packages/app-shell/src/views/studio-design/ObjectValidationsPanel.newRuleWaits-11820.test.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ function Harness({ onPatch, initial = baseDraft }: { onPatch: (p: Record<string,
6868

6969
function addFromMenu(label: string) {
7070
fireEvent.click(screen.getByText('New'));
71+
// The per-type list sits under Advanced since objectui#11861.
72+
fireEvent.click(screen.getByRole('button', { name: 'Advanced' }));
7173
fireEvent.click(screen.getByRole('button', { name: label }));
7274
}
7375

0 commit comments

Comments
 (0)