Skip to content

Commit 7db1332

Browse files
feat(spec): a metadata-form row each for object.access, highlightFields, requiredPermissions, searchableFields and permission.adminScope (#20349) (#20405)
Fixes #20349 Flight G1a of #19332 (ruling 5861442317). Clause-②: no ## What Five live keys that the object and permission-set schemas declare had no form row, so an author could reach them only through the Source tab. Each now has exactly one row. Each row copies a row that a registered form already has for the same node shape. The four-locale catalogue rows are in the same PR. | key | form, section | control | the row it copies | |:--|:--|:--|:--| | `object.highlightFields` | `object.form.ts`, Basics (beside `nameField`) | `widget: 'string-tags'` | `view.form.ts` `searchableFields` | | `object.searchableFields` | `object.form.ts`, Basics | `widget: 'string-tags'` | `view.form.ts` `searchableFields` | | `object.access` | `object.form.ts`, Advanced (beside `sharingModel`) | `type: 'composite'` over one declared `default` select (`public` / `private`) | `object.form.ts` `lifecycle` | | `object.requiredPermissions` | `object.form.ts`, Advanced | `widget: 'json'` | `object.form.ts` `validations` | | `permission.adminScope` | `permission.form.ts`, System Permissions | `widget: 'json'` | `permission.form.ts` `objects` / `tabPermissions` / `rowLevelSecurity` | The ruling's widget rules, as applied here: - `requiredPermissions` is the one union in this flight: `string[]` or a strict `{read, create, update, delete}` map. It takes `json` and never `string-tags`. - The two field-name lists take `string-tags`, as free text. The help text says what the runtime does with each value, including what absence resolves to. Each claim was checked against its reader: - `access` absent means public: `security-plugin.ts` reads `isPrivate` as `access?.default === 'private'`. - An empty `requiredPermissions` skips the capability gate: `security-plugin.ts` normalizes it to empty buckets. - A dangling `highlightFields` entry is refused at `error` on the object publish door: `validateObjectFieldRefs`, `runtimeTypes: ['flow', 'object']`. - A dangling or virtual `searchableFields` entry is refused at `error`: `validateSearchableFields`, including `object`. No schema's accepted input changes, and no export changes. What changes is the form payload that `getMetaTypes()` serves and the translation keys that `os i18n extract` walks. ## The ruling's objectui premise, re-checked at the `.objectui-sha` pin The ruling carries this premise for the first form flight to re-check: "`string-tags` reads a non-array as `[]`; `field-multi` binds nothing on an object draft". I read objectui `f8a9d0fb0596f4521076628e2bbfe27e6ce67d52` (the pin on `main`) from source. No browser was run. **Both halves hold.** - `string-tags` — `packages/app-shell/src/views/metadata-admin/widgets.tsx:1290`: the widget sets `tags` to the value when the value is an array, and to an empty array otherwise. `add` and `remove` then write `next`, which is built from `tags`, back through `onChange`. So a stored map would be replaced by a list on the first edit. **HOLDS.** - `field-multi` — `ResourceEditPage.tsx:889-893`: the field catalogue's source object comes from `interfaceConfig.source`, `data.object`, `object` or `objectName` on the draft. The object type's served JSON schema declares none of those four (probe on this tree: 43 top-level keys, all four absent, lit control `name` present). So the picker would offer an empty list. **HOLDS.** Also read at the pin, because the new rows depend on it: `resolveFieldFace` in `SchemaForm.tsx:667-750`. - `json` is not a registered widget. It is in `KNOWN_PASSTHROUGH_WIDGETS`, so the face comes from the node after `resolveUnionBranch`. - For `requiredPermissions`, the served node is an `anyOf` of array-of-string and an object with four properties. - A stored map resolves to the object branch, which renders as a nested form. - A stored list, or a create with no value, resolves to the array branch, which renders as the scalar list control. - `adminScope` is served inline as an object with properties, so it renders as a nested form. - Nested-form edits merge. `setField` spreads the stored value and writes only the one key, so no key the author did not touch is rewritten. - Schema defaults are placeholders and are never written on mount. So an untouched `adminScope` stays absent. ## Residue of the reconciliation gate The gate's own helper block (`metadata-form-zod-reconciliation.test.ts`, the whole file copied into a scratch probe that was never committed) was run at the root coordinate over every object-rooted type. It counts offerable keys, minus offered keys, minus root `omit` rows. The probe asserted two controls in the same run: - lit control: `field.accept`, a G1b key, is in the residue on both trees; - dark control: the fabricated key `object.zzFabricated20349` is in no residue. | tree | residue | per type | |:--|:--|:--| | merge base `15bf186f5` | **28** | object 9 · field 12 · action 6 · permission 1 | | this branch (same `src/` as head `353e708a`) | **23** | object 5 · field 12 · action 6 · permission 0 | Removed: `object.access`, `object.highlightFields`, `object.requiredPermissions`, `object.searchableFields`, `permission.adminScope`. Added: none. Both probe runs: 1 file, 58 tests passed. ## Verification All test runs went through `scripts/pm/os-verify-lock.sh`, and each reported `VERDICT command-exit 0`. | run | result | |:--|:--| | `pnpm --filter @objectstack/spec test` | Test Files 557 passed (557) · Tests 16497 passed, 1 todo (16498) | | `pnpm --filter @objectstack/spec test:repo` | Test Files 35 passed (35) · Tests 634 passed (634) | | `pnpm --filter @objectstack/platform-objects test` | Test Files 55 passed (55) · Tests 911 passed (911) | | `pnpm --filter @objectstack/spec typecheck` | exit 0 (`tsc --noEmit`, `check:scripts-typecheck`, `check:test-typecheck` OK: 53 files, 255 errors, 142 signatures held by the ledger) | | `pnpm --filter @objectstack/platform-objects typecheck` | exit 0 (`check:test-typecheck` OK: 1 file, 3 errors, 2 signatures held by the ledger) | | lint `src/validate-predicate-path-refs.test.ts` (reads the object form) | 1 file, 54 tests passed | | cli unit `test/i18n-coverage.test.ts` (reads the form registry) | 1 file, 20 tests passed | | `pnpm check:i18n` (after the closure build it names) | all 9 packages in sync; the catalogues regenerate byte-for-byte from the forms | | `pnpm --filter @objectstack/spec check:generated` | all 15 generated artifacts up to date | Gates: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 85 commands. All 85 were run, and each exit code was written to disk before it was read. `--ran` reports: 85 derived, 85 run, 0 NOT-MEASURED, 0 UNRUN. - `check:dual-build-cjs-loads` first exited 3 (PREREQUISITE NOT MET: nine packages had no `dist/`). The missing `dist/` directories then existed, and the re-run exited 0. The re-run's code is the one recorded. - `check:entry-nameability` prints its standing structural NOT MEASURED line for two entries with no callable export. That line is independent of this diff. No ablation was run. This PR adds rows and moves population pins; it adds no guard. The residue before/after above is the measurement of the delta. ## Hand-over account This branch was resumed from `bc1d395d3`, the previous dev run's pushed head. It was re-read hunk by hunk and re-verified in full. - `7ce01726` (the five rows): kept. Every help-text claim was checked against its runtime reader, and every comment claim about the renderer was checked at the pin (above). - `3820f4a7` and `3ae44a78` (catalogue rows and re-extract): kept. After the merge, `pnpm check:i18n` regenerates them byte-for-byte. The source-hash rows the first commit added were removed by the second, net zero. - `abbf9c7f` (echo-decision pins and changeset): kept. The pins were already complete: the platform-objects suite passes on the merged tree with no further pin edit. - `353e708a` (this run): the changeset lacked the `Clause-②: no` line. It now carries that line, and it names the pin that the renderer description is read at. - `e505f724`: merge of `origin/main` via `scripts/pm/os-regen-merge.sh`. No os-regen path conflicted, and nothing needed regenerating (`check:generated` is green). ## Acceptance notes - The ruling says "a misspelling is refused loudly at parse". The refusal is at the publish door and in `os validate` (the two lint rules above, at `error`), not in the Zod parse: `ObjectSchema` does not judge field names. The changeset says so. - `metadata-form-zod-reconciliation.test.ts` has a dark-control comment reading "`object.access` is authorable and no form offers it". After this PR a form offers it. The assertions under that comment still hold, because they test ruled-class admission and not whether a key is offered. That file is the ledger, which is outside this flight's surface. Carrier: the #19333 item 2 wiring, which edits that file. - Nothing pins the ruling's rule that union values never take `string-tags` across the registry. A future row could flip `requiredPermissions` back without any test turning red. This is not a defect today. It is noted for the #19333 wiring, which is where a registry-wide union check would live. - A nested-form face cannot unset an object key. Once an author types into `adminScope`, clearing every sub-field leaves an empty object, and the parse then refuses it loudly because `businessUnit` is required. The failure is loud, it comes from the generic objectui renderer, and it is the same for every nested-form row. Carrier: none. --- _Generated by [Claude Code](https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2c31070 commit 7db1332

9 files changed

Lines changed: 182 additions & 6 deletions
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
"@objectstack/spec": minor
3+
"@objectstack/platform-objects": patch
4+
---
5+
6+
Clause-②: no
7+
8+
Five live structured metadata keys are authorable in the metadata form: `object.access`, `object.highlightFields`, `object.requiredPermissions`, `object.searchableFields` and `permission.adminScope`. Each was **declared** by its schema, graded `live` by the liveness ledger, and offered by **no** form in `METADATA_FORM_REGISTRY`, so an author's only door was the Source tab. Each now has exactly one row, whose control mirrors a row a registered form already carries for the same node shape:
9+
10+
- `highlightFields` and `searchableFields` (Basics, beside `nameField`) — `widget: 'string-tags'`, the view form's `searchableFields` row: a free-text chip list over `string[]`. A field picker is not offered because the object draft carries no source object for one to read its catalog from. A misspelt entry is not dropped quietly: publishing refuses it (`object-field-ref-unknown`, `searchable-field-unknown`, both at `error`), and so does `os validate`. The object schema's own parse does not judge these names.
11+
- `access` (Advanced, beside `sharingModel`) — a `composite` over one declared `default` select (`public` / `private`), the `lifecycle` row's shape. Absent still resolves to `public`.
12+
- `requiredPermissions` (Advanced) — `widget: 'json'`, **never** `string-tags`: the value is a union of `string[]` and a `{read, create, update, delete}` map, and the tag widget reads a non-array as an empty list and writes the list back, which would silently replace a stored per-operation map. With the `json` hint the renderer resolves the face from the stored value's branch, so a stored map is edited as a map.
13+
- `permission.adminScope` (System Permissions) — `widget: 'json'`, the hint every structured row on the permission form carries; the renderer derives a nested form over its six keys, and edits merge into the stored scope.
14+
15+
The help text states what the runtime does with each value, including what absence resolves to. The renderer behaviour described above is objectui's metadata-admin form engine at this repository's `.objectui-sha` pin.
16+
17+
⛔ **No schema accept set moves and no export changes.** `METADATA_FORM_REGISTRY` is declared as an opaque `Readonly<Record<string, FormView>>`, so row contents were never part of the declared surface. What changes is the **form payload** `getMetaTypes()` serves and the translation keys `os i18n extract` walks — hence the regenerated `platform-objects` metadata-form bundles, whose 12 new leaves are authored in `zh-CN`, `ja-JP` and `es-ES` rather than left as extractor fills.
18+
19+
⛔ **The gate that would notice a missing row is NOT landed here.** The reconciliation gate's top-level `zodOnly` direction stays unwired; this change lands offers only.

‎packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ export const enMetadataForms: NonNullable<TranslationData['metadataForms']> = {
6464
label: "Is System",
6565
helpText: "System object (protected from deletion; defaults sharing to public)"
6666
},
67+
highlightFields: {
68+
label: "Highlight Fields",
69+
helpText: "Field names of this object, most important first — the first entry wins where only one fits (ADR-0085). Drives the default list columns, cards, child-record previews and the detail highlight strip. A name that is not a field of this object is refused at publish."
70+
},
71+
searchableFields: {
72+
label: "Searchable Fields",
73+
helpText: "Field names the $search query matches (ADR-0061): the default for the record picker, list quick-search and global search; a view may narrow it. Unset, search uses the name/title field plus short-text fields. Each entry must name a stored field of this object — an unknown name or a virtual formula field is refused at publish."
74+
},
6775
fields: {
6876
label: "Fields",
6977
helpText: "Add the columns this object will store"
@@ -269,6 +277,18 @@ export const enMetadataForms: NonNullable<TranslationData['metadataForms']> = {
269277
label: "Sharing Model",
270278
helpText: "Org-Wide Default record visibility for internal users. A custom object that omits it resolves to private at runtime (ADR-0090 D1)."
271279
},
280+
access: {
281+
label: "Access",
282+
helpText: "Wildcard-grant posture (ADR-0066 D2). Absent resolves to public. It decides whether a permission set's '*' object grant covers this object; record visibility between users is sharingModel."
283+
},
284+
"access.default": {
285+
label: "Default",
286+
helpText: "public: covered by '*' wildcard grants. private: needs an explicit per-object grant, and is exempt from wildcard row-level security."
287+
},
288+
requiredPermissions: {
289+
label: "Required Permissions",
290+
helpText: "Capabilities (permission-set systemPermissions) a caller must hold to reach this object, checked in addition to CRUD grants (ADR-0066 D3). A list gates every operation; a {read, create, update, delete} map gates only the operations it lists. Absent or empty: no capability gate."
291+
},
272292
managedBy: {
273293
label: "Managed By",
274294
helpText: "Lifecycle bucket: platform (user CRUD), config (admin authored), system-data (platform-defined schema with admin/user-writable data), engine-owned (no user writes), append-only (audit), better-auth (identity). UI clients derive their CRUD affordances from it, so it decides what a user is offered on records of this object."
@@ -2079,6 +2099,10 @@ export const enMetadataForms: NonNullable<TranslationData['metadataForms']> = {
20792099
label: "System Permissions",
20802100
helpText: "List of system capability keys"
20812101
},
2102+
adminScope: {
2103+
label: "Admin Scope",
2104+
helpText: "Delegated administration (ADR-0090 D12): lets holders of this set administer one business-unit subtree. businessUnit is the subtree root (sys_business_unit.name) and is required, non-blank; includeSubtree defaults to true; manageAssignments, manageBindings and authorEnvironmentSets default to false; a delegate may hand out only the sets named in assignablePermissionSets. Leave empty for a set that delegates nothing."
2105+
},
20822106
objects: {
20832107
label: "Objects",
20842108
helpText: "{ \"account\": { allowRead: true, allowEdit: true, ... } }"

‎packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
6464
label: "Integrado del sistema",
6565
helpText: "Objeto de sistema (protegido contra eliminación; el uso compartido predeterminado es público)"
6666
},
67+
highlightFields: {
68+
label: "Campos destacados",
69+
helpText: "Nombres de campo de este objeto, del más importante al menos; donde solo cabe uno, gana la primera entrada (ADR-0085). Determinan las columnas de lista predeterminadas, las tarjetas, las vistas previas de registros hijos y la franja destacada del detalle. Un nombre que no sea un campo de este objeto se rechaza al publicar."
70+
},
71+
searchableFields: {
72+
label: "Campos buscables",
73+
helpText: "Nombres de campo con los que coincide la consulta $search (ADR-0061): el valor predeterminado del selector de registros, la búsqueda rápida de listas y la búsqueda global; una vista puede acotarlo. Sin definir, la búsqueda usa el campo de nombre/título más los campos de texto corto. Cada entrada debe nombrar un campo almacenado de este objeto: un nombre desconocido o un campo formula virtual se rechaza al publicar."
74+
},
6775
fields: {
6876
label: "Campos",
6977
helpText: "Añade las columnas que almacenará este objeto"
@@ -269,6 +277,18 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
269277
label: "Modelo de uso compartido",
270278
helpText: "Visibilidad predeterminada de la organización (OWD) para usuarios internos. Un objeto personalizado que lo omita se resuelve como private en tiempo de ejecución (ADR-0090 D1)."
271279
},
280+
access: {
281+
label: "Acceso",
282+
helpText: "Postura ante las concesiones comodín (ADR-0066 D2). Si se omite, se resuelve como public. Decide si la concesión de objeto '*' de un conjunto de permisos cubre este objeto; la visibilidad de registros entre usuarios la decide sharingModel."
283+
},
284+
"access.default": {
285+
label: "Exposición predeterminada",
286+
helpText: "public: cubierto por las concesiones comodín '*'. private: necesita una concesión explícita por objeto y queda exento de la seguridad a nivel de fila comodín."
287+
},
288+
requiredPermissions: {
289+
label: "Permisos requeridos",
290+
helpText: "Capacidades (systemPermissions de conjuntos de permisos) que quien llama debe tener para acceder a este objeto, comprobadas además de las concesiones CRUD (ADR-0066 D3). Una lista restringe todas las operaciones; un mapa {read, create, update, delete} restringe solo las operaciones que enumera. Ausente o vacío: sin restricción por capacidad."
291+
},
272292
managedBy: {
273293
label: "Gestionado por",
274294
helpText: "Categoría de ciclo de vida: platform (CRUD de usuario), config (escrito por el administrador), system-data (esquema definido por la plataforma con datos escribibles por administrador o usuario), engine-owned (sin escrituras de usuario), append-only (auditoría), better-auth (identidad). Los clientes de UI derivan de aquí las acciones CRUD disponibles, así que decide qué se ofrece al usuario en los registros de este objeto."
@@ -2079,6 +2099,10 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
20792099
label: "Permisos del sistema",
20802100
helpText: "Lista de claves de capacidades del sistema"
20812101
},
2102+
adminScope: {
2103+
label: "Ámbito de administración delegada",
2104+
helpText: "Administración delegada (ADR-0090 D12): permite a quienes tienen este conjunto administrar un subárbol de unidades de negocio. businessUnit es la raíz del subárbol (sys_business_unit.name), es obligatorio y no puede estar en blanco; includeSubtree vale true por defecto; manageAssignments, manageBindings y authorEnvironmentSets valen false por defecto; un delegado solo puede asignar los conjuntos nombrados en assignablePermissionSets. Déjalo vacío en un conjunto que no delega nada."
2105+
},
20822106
objects: {
20832107
label: "Permisos de objeto",
20842108
helpText: "Ejemplo: { \"account\": { allowRead: true, allowEdit: true, ... } }"

‎packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
6464
label: "システム組み込み",
6565
helpText: "システムオブジェクト(削除から保護。共有の既定は公開)"
6666
},
67+
highlightFields: {
68+
label: "ハイライト項目",
69+
helpText: "このオブジェクトの項目名を重要な順に並べます。1 つしか表示できない場所では先頭の項目が使われます(ADR-0085)。既定の一覧列、カード、子レコードのプレビュー、詳細画面のハイライト帯を決めます。このオブジェクトの項目でない名前は公開時に拒否されます。"
70+
},
71+
searchableFields: {
72+
label: "検索対象項目",
73+
helpText: "$search クエリが照合する項目名(ADR-0061)。レコードピッカー、一覧のクイック検索、グローバル検索の既定値で、ビューで絞り込めます。未設定の場合は名前/タイトル項目と短いテキスト項目で検索します。各項目はこのオブジェクトの保存済みの項目を指す必要があり、不明な名前や仮想の formula 項目は公開時に拒否されます。"
74+
},
6775
fields: {
6876
label: "フィールド",
6977
helpText: "このオブジェクトが保存する列を追加"
@@ -269,6 +277,18 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
269277
label: "共有モデル",
270278
helpText: "社内ユーザー向けの組織既定のレコード可視性(OWD)。カスタムオブジェクトで省略した場合、実行時は private として解決されます(ADR-0090 D1)。"
271279
},
280+
access: {
281+
label: "アクセス設定",
282+
helpText: "ワイルドカード付与の方針(ADR-0066 D2)。省略時は public として解決されます。権限セットの '*' オブジェクト付与がこのオブジェクトに及ぶかどうかを決めます。ユーザー間のレコード可視性は sharingModel が決めます。"
283+
},
284+
"access.default": {
285+
label: "既定の公開範囲",
286+
helpText: "public: '*' ワイルドカード付与の対象になります。private: オブジェクトごとの明示的な付与が必要で、ワイルドカードの行レベルセキュリティの対象外になります。"
287+
},
288+
requiredPermissions: {
289+
label: "必要な権限",
290+
helpText: "このオブジェクトにアクセスするために呼び出し元が保持すべき機能(権限セットの systemPermissions)。CRUD 付与に加えてチェックされます(ADR-0066 D3)。リストはすべての操作を制限し、{read, create, update, delete} マップは列挙した操作だけを制限します。省略または空の場合、機能による制限はありません。"
291+
},
272292
managedBy: {
273293
label: "ライフサイクル区分",
274294
helpText: "ライフサイクル区分: platform(ユーザーによる CRUD)、config(管理者が記述)、system-data(プラットフォーム定義のスキーマで、データは管理者/ユーザーが書き込み可)、engine-owned(エンジン所有、ユーザー書き込み不可)、append-only(監査)、better-auth(ID)。UI クライアントはこの値から CRUD の可否を導くため、このオブジェクトのレコードでユーザーに何が提供されるかを決めます。"
@@ -2079,6 +2099,10 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
20792099
label: "システム権限",
20802100
helpText: "システム機能キーのリスト"
20812101
},
2102+
adminScope: {
2103+
label: "委任管理スコープ",
2104+
helpText: "委任管理(ADR-0090 D12): この権限セットの保持者が 1 つのビジネスユニットのサブツリーを管理できるようにします。businessUnit はサブツリーのルート(sys_business_unit.name)で、必須かつ空白不可です。includeSubtree の既定値は true、manageAssignments・manageBindings・authorEnvironmentSets の既定値は false です。委任先が付与できるのは assignablePermissionSets に挙げた権限セットだけです。何も委任しない権限セットでは空のままにしてください。"
2105+
},
20822106
objects: {
20832107
label: "オブジェクト権限",
20842108
helpText: "例: { \"account\": { allowRead: true, allowEdit: true, ... } }"

‎packages/platform-objects/src/apps/translations/object-collapsed-sections-echo-decisions.test.ts‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -895,10 +895,15 @@ describe('#19403 round 8 — the population, DERIVED from the form and a shape',
895895
// ratchet further down still reads zero. The population is DERIVED from the
896896
// form, so the arrival needed no edit here; these counts are the measured
897897
// facts that moved with it.
898-
expect(PANEL_LEAVES.length).toBe(55);
898+
// 61 since #20349: that card gave `advanced` the two ADR-0066 access rows —
899+
// `access` (a composite over one declared `default` select) and
900+
// `requiredPermissions` — a label and a help text each, three rows and six
901+
// leaves, all eighteen translated leaves authored by the same card. `advanced`
902+
// reads 46 → 52; `capabilities` is untouched.
903+
expect(PANEL_LEAVES.length).toBe(61);
899904
expect(PANEL_LEAVES.every((l) => l.prop === 'label' || l.prop === 'helpText')).toBe(true);
900905
expect(PANEL_LEAVES.filter((l) => l.section === 'capabilities').length).toBe(9);
901-
expect(PANEL_LEAVES.filter((l) => l.section === 'advanced').length).toBe(46);
906+
expect(PANEL_LEAVES.filter((l) => l.section === 'advanced').length).toBe(52);
902907
});
903908

904909
it('⭐ DARK, OUTWARD — the open sections are excluded, and `fields.placeholder` is the one that proves it', () => {
@@ -908,8 +913,11 @@ describe('#19403 round 8 — the population, DERIVED from the form and a shape',
908913
// 96 since #19331 gave `basics` its `nameField` row — the ADR-0079
909914
// record-title pointer the schema declared and no control offered — whose
910915
// label and help text are authored in all three locales by that same card,
911-
// so the `openEchoes` reading below is unchanged at zero.
912-
expect(OPEN_LEAVES.length).toBe(96);
916+
// so the `openEchoes` reading below is unchanged at zero. 100 since #20349
917+
// gave `basics` the object's two own field-name lists, `highlightFields` and
918+
// `searchableFields`, beside `nameField` — four leaves, their twelve
919+
// translated leaves authored by that card, so `openEchoes` stays at zero.
920+
expect(OPEN_LEAVES.length).toBe(100);
913921
expect(PANEL_LEAVES.some((l) => l.path === 'fields.placeholder')).toBe(false);
914922
expect(OPEN_LEAVES.some((l) => l.path === 'fields.placeholder')).toBe(true);
915923
for (const path of ['name', 'label', 'fields', 'fields.valueDomain', 'fields.deleteBehavior', 'fields.expression']) {

‎packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,8 +1115,11 @@ describe('#19403 round 10 — the verdicts, on the live bundles', () => {
11151115
// form's `columns` / `sort` / `tabs` repeaters, authored in all three
11161116
// locales. 608 since #20161: the report form's joined-block `chart` row
11171117
// left with its key (nothing ever drew a block chart), taking its label
1118-
// — authored in all three locales — out of the catalog.
1119-
expect(translated.length, `${locale} positive control`).toBe(608);
1118+
// — authored in all three locales — out of the catalog. 614 since #20349:
1119+
// six new row labels (the object form's `highlightFields`,
1120+
// `searchableFields`, `access`, `access.default` and `requiredPermissions`,
1121+
// and the permission form's `adminScope`), authored in all three locales.
1122+
expect(translated.length, `${locale} positive control`).toBe(614);
11201123
}
11211124
// ⭐ DARK — the blindness, executable. On a synthetic two-locale catalog the
11221125
// all-three predicate returns 0 while the per-locale one returns 1, so the

0 commit comments

Comments
 (0)