From 7eeffd29bd38fc7ed7b8067c48c22b6c64f31f37 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 10:35:25 +0000 Subject: [PATCH] revert(app-shell): re-land objectui#11931 and objectui#11932 by reverting the rollback 3c888c6 This reverts commit 3c888c6e601bb3bfac1bbc84498bffffe60f37cf (objectui#11956), which rolled back Studio's validation presets (objectui#11931, part of objectui#11861) and the Interfaces page-create (objectui#11932, objectui#11823 step 3) to bring the console's eager closure back under its ceiling. The re-land lands against the +5,120 B allowance of objectui#11949 (ruling 6056819248 step 2). It restores both changes byte for byte to their f3a0488 blobs and removes the rollback's changeset, which this re-land supersedes. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude --- .changeset/11823-page-create.md | 26 ++ .changeset/11861-validation-presets.md | 19 + .changeset/11937-eager-budget-rollback.md | 5 - content/docs/guide/console.md | 2 +- .../src/views/metadata-admin/i18n.ts | 56 ++- ...lidationsPanel.newRuleWaits-11820.test.tsx | 2 + ...ectValidationsPanel.presets-11861.test.tsx | 330 ++++++++++++++++++ .../ObjectValidationsPanel.test.tsx | 3 + .../studio-design/ObjectValidationsPanel.tsx | 163 ++++++++- .../ObjectValidationsPanel.whenKey.test.tsx | 2 + ...urface.interfacesCreatePage-11823.test.tsx | 290 +++++++++++++++ .../studio-design/StudioDesignSurface.tsx | 97 +++-- .../studio-design/interfaceCreate.test.tsx | 140 +++++++- .../views/studio-design/interfaceCreate.tsx | 158 ++++++++- .../views/studio-design/validationPresets.ts | 170 +++++++++ 15 files changed, 1396 insertions(+), 67 deletions(-) create mode 100644 .changeset/11823-page-create.md create mode 100644 .changeset/11861-validation-presets.md delete mode 100644 .changeset/11937-eager-budget-rollback.md create mode 100644 packages/app-shell/src/views/studio-design/ObjectValidationsPanel.presets-11861.test.tsx create mode 100644 packages/app-shell/src/views/studio-design/StudioDesignSurface.interfacesCreatePage-11823.test.tsx create mode 100644 packages/app-shell/src/views/studio-design/validationPresets.ts diff --git a/.changeset/11823-page-create.md b/.changeset/11823-page-create.md new file mode 100644 index 0000000000..a69a44b137 --- /dev/null +++ b/.changeset/11823-page-create.md @@ -0,0 +1,26 @@ +--- +'@object-ui/app-shell': patch +--- + +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). + +The **New** menu beside the navigation rail offers *New page* after *New dashboard* and *New report*. +It opens the shared create dialog (a name, and the identifier derived from it) with one more choice, +the language the page is written in: + +- **HTML** (the default): built from the platform's components and HTML tags, parsed and never run; +- **React**: real React code, run when the page renders. It is offered only where the deployment runs + React pages (a deployment can turn them off). + +On save: + +- the page is saved as a draft in the package: an app page (a page the app's navigation can open) of + the chosen kind, starting from a short source with one line of text; +- an entry linking it, carrying the name typed in the dialog, is added to the app's navigation and + saved with it, so it appears in the rail at once; +- the page opens with its source in the code editor on the right and the live preview, drawn by the + same renderer the running app uses, on the canvas. + +An identifier that another page already holds, published in any package or as a draft, is refused in +the dialog and nothing is saved. As for dashboards and reports, the menu is not offered on a read-only +package, on a package with no app yet, or while the navigation is being edited. diff --git a/.changeset/11861-validation-presets.md b/.changeset/11861-validation-presets.md new file mode 100644 index 0000000000..81466697b7 --- /dev/null +++ b/.changeset/11861-validation-presets.md @@ -0,0 +1,19 @@ +--- +'@object-ui/app-shell': patch +--- + +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). + +"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: + +- **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. +- **Number can't be negative** — a `script` rule on the object's first number, currency or percent field, written at once with its message. +- **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. + +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. + +The per-type list is unchanged, one click away under "Advanced". New rules still start on Create + Update. + +"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. + +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). diff --git a/.changeset/11937-eager-budget-rollback.md b/.changeset/11937-eager-budget-rollback.md deleted file mode 100644 index dfeeba22ff..0000000000 --- a/.changeset/11937-eager-budget-rollback.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@object-ui/app-shell': patch ---- - -The console is back under its first-load budget. Two recent Studio changes are rolled back to get there: the common-rule starting points in an object's Validations "New" menu (objectui#11931), and the Interfaces pillar's step that creates a page and opens it on its source with a live preview (objectui#11932). Their labels sat in a string table every console page loads, which pushed that first load over its limit. Studio behaves as it did before those two changes, and nothing else changes. Both come back once the first-load allowance tracked in objectui#11942 is in place. diff --git a/content/docs/guide/console.md b/content/docs/guide/console.md index da02bedec2..ccf5133a44 100644 --- a/content/docs/guide/console.md +++ b/content/docs/guide/console.md @@ -47,7 +47,7 @@ entries — no code round-trip required: | Tab | Edits | Panel | |-----|-------|-------| -| **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. | +| **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. | | **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. | | **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. | diff --git a/packages/app-shell/src/views/metadata-admin/i18n.ts b/packages/app-shell/src/views/metadata-admin/i18n.ts index 95ede5c57a..9383e3cd0a 100644 --- a/packages/app-shell/src/views/metadata-admin/i18n.ts +++ b/packages/app-shell/src/views/metadata-admin/i18n.ts @@ -3088,6 +3088,19 @@ const ENGINE_STRINGS_EN: Record = { 'engine.studio.rules.event.update': 'Update', 'engine.studio.rules.event.delete': 'Delete', 'engine.studio.rules.priority': 'Priority', + // objectui#11861 — the New menu's starting points, and the type list under Advanced. + 'engine.studio.rules.presets': 'Common rules', + 'engine.studio.rules.advanced': 'Advanced', + 'engine.studio.rules.preset.endAfterStart': 'End date on or after start date', + 'engine.studio.rules.preset.endAfterStartNeeds': 'Needs two date fields on this object.', + 'engine.studio.rules.preset.endAfterStartMessage': '{end} must be on or after {start}.', + 'engine.studio.rules.preset.notNegative': 'Number can’t be negative', + 'engine.studio.rules.preset.notNegativeNeeds': 'Needs a number field on this object.', + 'engine.studio.rules.preset.notNegativeMessage': '{field} can’t be negative.', + 'engine.studio.rules.preset.rejectWhen': 'Reject the save when…', + 'engine.studio.rules.preset.rejectWhenHint': 'Build your own condition, no code needed.', + 'engine.studio.rules.preset.usesOne': 'Uses “{field}”.', + 'engine.studio.rules.preset.usesTwo': 'Uses “{start}” and “{end}”.', 'engine.studio.settings.basics': 'Basics', 'engine.studio.settings.noInspector': 'No default object inspector registered.', 'engine.studio.settings.semanticRoles': 'Semantic roles', @@ -3228,8 +3241,8 @@ const ENGINE_STRINGS_EN: Record = { 'engine.studio.inspector.listView.addSort': '+ Add sort…', 'engine.studio.inspector.listView.sortAsc': '{field} ascending', 'engine.studio.inspector.listView.sortDesc': '{field} descending', - // objectui#11823 (step 2) — the Interfaces pillar's New dashboard / New report. - 'engine.studio.interfaces.create.menuTitle': 'Create a dashboard or a report in this package', + // objectui#11823 (steps 2 and 3) — the Interfaces pillar's New dashboard / New report / New page. + 'engine.studio.interfaces.create.menuTitle': 'Create a dashboard, a report or a page in this package', 'engine.studio.interfaces.create.dashboard': 'New dashboard', 'engine.studio.interfaces.create.report': 'New report', 'engine.studio.interfaces.create.description': @@ -3256,6 +3269,20 @@ const ENGINE_STRINGS_EN: Record = { 'engine.studio.interfaces.create.datasetsFailed': 'The datasets could not be loaded: {error}', 'engine.studio.interfaces.create.noMeasures': 'This dataset declares no measures, so a report cannot show it.', 'engine.studio.interfaces.create.needsBinding': 'Choose the dataset this report reads, and one of its measures.', + 'engine.studio.interfaces.create.page': 'New page', + 'engine.studio.interfaces.create.pageNameLabel': 'Page name', + 'engine.studio.interfaces.create.pageNamePlaceholder': 'Page name (e.g. Team handbook)', + 'engine.studio.interfaces.create.pageIdPlaceholder': 'Identifier (e.g. team_handbook)', + 'engine.studio.interfaces.create.pageTaken': + 'A page with the identifier “{name}” already exists. Choose another identifier.', + 'engine.studio.interfaces.create.pageKind': 'Written in', + 'engine.studio.interfaces.create.pageKindHtml': 'HTML', + 'engine.studio.interfaces.create.pageKindReact': 'React', + 'engine.studio.interfaces.create.pageKindHtmlHint': + 'Built from the platform’s components and HTML tags, parsed and never run. The page opens on its source beside a live preview.', + 'engine.studio.interfaces.create.pageKindReactHint': + 'Real React code, run when the page renders, for state, effects and event handlers. The page opens on its source beside a live preview.', + 'engine.studio.interfaces.create.pageStarter': 'A new page. Write its content in the source editor.', 'engine.studio.inspector.designersMissing': 'No metadata designers are registered in this session, so there is nothing to edit here.', 'engine.studio.inspector.noPageSchema': 'Page settings are unavailable — the page schema could not be loaded.', @@ -6220,6 +6247,18 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.studio.rules.event.update': '更新', 'engine.studio.rules.event.delete': '删除', 'engine.studio.rules.priority': '优先级', + 'engine.studio.rules.presets': '常用规则', + 'engine.studio.rules.advanced': '高级', + 'engine.studio.rules.preset.endAfterStart': '结束日期不早于开始日期', + 'engine.studio.rules.preset.endAfterStartNeeds': '需要该对象有两个日期字段。', + 'engine.studio.rules.preset.endAfterStartMessage': '{end}不能早于{start}。', + 'engine.studio.rules.preset.notNegative': '数值不能为负', + 'engine.studio.rules.preset.notNegativeNeeds': '需要该对象有一个数值字段。', + 'engine.studio.rules.preset.notNegativeMessage': '{field}不能为负数。', + 'engine.studio.rules.preset.rejectWhen': '满足条件时拒绝保存…', + 'engine.studio.rules.preset.rejectWhenHint': '自行搭建条件,无需写代码。', + 'engine.studio.rules.preset.usesOne': '使用「{field}」。', + 'engine.studio.rules.preset.usesTwo': '使用「{start}」和「{end}」。', 'engine.studio.settings.basics': '基础信息', 'engine.studio.settings.noInspector': '未注册对象默认检查器。', 'engine.studio.settings.semanticRoles': '语义角色', @@ -6330,7 +6369,7 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.studio.inspector.listView.sortAsc': '{field} 升序', 'engine.studio.inspector.listView.sortDesc': '{field} 降序', // objectui#11823 (step 2) — the Interfaces pillar's New dashboard / New report. - 'engine.studio.interfaces.create.menuTitle': '在本包中新建仪表盘或报表', + 'engine.studio.interfaces.create.menuTitle': '在本包中新建仪表盘、报表或页面', 'engine.studio.interfaces.create.dashboard': '新建仪表盘', 'engine.studio.interfaces.create.report': '新建报表', 'engine.studio.interfaces.create.description': '存为本包的草稿,加入 {app} 的导航,并在画布上打开。', @@ -6352,6 +6391,17 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.studio.interfaces.create.datasetsFailed': '无法加载数据集:{error}', 'engine.studio.interfaces.create.noMeasures': '这个数据集没有声明度量,报表无法展示它。', 'engine.studio.interfaces.create.needsBinding': '请选择这个报表读取的数据集,以及其中的一个度量。', + 'engine.studio.interfaces.create.page': '新建页面', + 'engine.studio.interfaces.create.pageNameLabel': '页面名称', + 'engine.studio.interfaces.create.pageNamePlaceholder': '页面名称(如:团队手册)', + 'engine.studio.interfaces.create.pageIdPlaceholder': '标识符(如:team_handbook)', + 'engine.studio.interfaces.create.pageTaken': '已有标识为「{name}」的页面,请换一个标识。', + 'engine.studio.interfaces.create.pageKind': '编写方式', + 'engine.studio.interfaces.create.pageKindHtml': 'HTML', + 'engine.studio.interfaces.create.pageKindReact': 'React', + 'engine.studio.interfaces.create.pageKindHtmlHint': '由平台组件与 HTML 标签组成,只解析、不执行。页面打开时,源码与实时预览并排显示。', + 'engine.studio.interfaces.create.pageKindReactHint': '真正的 React 代码,在页面渲染时运行,可使用状态、副作用与事件处理。页面打开时,源码与实时预览并排显示。', + 'engine.studio.interfaces.create.pageStarter': '新页面。请在源码编辑器中编写它的内容。', 'engine.studio.inspector.designersMissing': '本次会话没有注册任何元数据设计器,这里没有可编辑的内容。', 'engine.studio.inspector.noPageSchema': '页面设置不可用——无法加载页面 schema。', 'engine.studio.inspector.sourcePageLine1': '这个页面是 {kind} 源码,不是积木树 ——', diff --git a/packages/app-shell/src/views/studio-design/ObjectValidationsPanel.newRuleWaits-11820.test.tsx b/packages/app-shell/src/views/studio-design/ObjectValidationsPanel.newRuleWaits-11820.test.tsx index e9984ca013..419b955374 100644 --- a/packages/app-shell/src/views/studio-design/ObjectValidationsPanel.newRuleWaits-11820.test.tsx +++ b/packages/app-shell/src/views/studio-design/ObjectValidationsPanel.newRuleWaits-11820.test.tsx @@ -68,6 +68,8 @@ function Harness({ onPatch, initial = baseDraft }: { onPatch: (p: Record