Skip to content

Commit 12e1c1d

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-8524-ai-usage-single-pool
2 parents c1e867d + 8522396 commit 12e1c1d

11 files changed

Lines changed: 368 additions & 225 deletions
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@object-ui/app-shell': minor
3+
---
4+
5+
The metadata-admin `SchemaForm` no longer crashes on a form section that references a field group, and the form-section authoring type accepts that shape (objectui#8725).
6+
7+
**Clause-②: yes** — two exported types change shape. `FormSectionSpec.fields` becomes optional, which widens what an author may write to match `@objectstack/spec`. `RichMetadataTypeEntry.form` narrows from `Record<string, unknown>` to `FormViewSpec`.
8+
9+
**BREAKING (types).** A consumer that reads `section.fields` unguarded, or indexes `FormSectionSpec['fields'][number]`, no longer compiles (`TS18048` / `TS2537`), because a `{ group }` section carries no `fields`. Read the element type through `NonNullable`. Do not default the list with `?? []`: a `{ group }` section then renders as a silently empty one. A consumer that treated `RichMetadataTypeEntry.form` as an open record now gets the `FormViewSpec` keys only. Outside `SchemaForm`, whose three reads are what this change fixes, no published source in this repository had to change. The other readers that changed were tests: two type-level pins, one in this package and one in `apps/console` (`FormPage.fieldSpec.test.ts`), and four `mergeServerFields` assertions.
10+
11+
**What was wrong.** `@objectstack/spec` lets a form section declare its members one of two ways: enumerate `fields`, or point `group` at a declared field group. The `/meta/types` registry hands its form document to `SchemaForm` without validation, and a `{ group }` section reached the renderer's first `fields` read. That read threw `TypeError: s.fields is not iterable` out of the component body and blanked the whole form, well-formed sections included, on both the simple and the tabbed layout. The required `fields` on `FormSectionSpec` only stopped a TypeScript author from writing the shape; the server channel carried it anyway, through an `as any` cast.
12+
13+
**What changed, in observable terms.**
14+
15+
- Every read of a section's `fields` in `SchemaForm` now sees a resolved section. A `{ group }` section goes through `@object-ui/plugin-form`'s `resolveSectionGroupReferences`, the resolver `ObjectForm` and the console `FormPage` already use, so `SchemaForm` gives their answer.
16+
- No `SchemaForm` host carries an object definition, and `/meta/types` declares no field groups for a metadata type. So a `{ group }` section in a metadata-type editor names no declared field group. For that case the resolver renders the section empty and reports the dangling key once on `console.error`, naming the group and the form's `schemaId`. The other sections render normally.
17+
- A section that declares neither `fields` nor `group` is still refused out loud. The render throws a `TypeError` that names the section; it does not draw an empty section.
18+
- `RichMetadataTypeEntry.form` is typed `FormViewSpec`, and the `as any` casts where `ResourceEditPage` and `EmbeddedItemEditor` hand it to `SchemaForm` are gone.
19+
20+
No change for any form none of whose sections carries a `group` key: the resolver returns such a list by identity. A section carrying both `fields` and `group`, a shape `@objectstack/spec` refuses at parse, now takes the resolver's answer for its `group` (in a metadata-type editor, an empty section and the same `console.error`) instead of rendering its enumerated `fields`.

‎.changeset/quiet-pans-repeat.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22
---
33

44
Test-only change: pins the objectui#8725 reachability measurement for a `{ group }` form section reaching `SchemaForm`. No published behaviour, type or API changes.
5+
6+
⚠️ **Dated note, 2026-09-28 — those pins have since been rewritten onto the ruled behaviour — objectui#8725.** The measurement they recorded (a `{ group }` section making `SchemaForm` throw `s.fields is not iterable`) went red when the fix landed, and the same file now pins that `SchemaForm` resolves such a section through `resolveSectionGroupReferences` and still refuses a section with no member source. The rest of this entry is kept as the reading of this change.

‎apps/console/src/components/FormPage.fieldSpec.test.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ?
6464
*/
6565
type ConsoleFormViewSpec = Parameters<typeof buildSections>[0];
6666
type ConsoleSectionSpec = NonNullable<ConsoleFormViewSpec['sections']>[number];
67-
type ConsoleFieldSpec = Exclude<ConsoleSectionSpec['fields'][number], string>;
67+
// `fields` is OPTIONAL on the section since objectui#8725 (a `{ group }` section
68+
// carries none), so the element type is read through `NonNullable`.
69+
type ConsoleFieldSpec = Exclude<NonNullable<ConsoleSectionSpec['fields']>[number], string>;
6870

6971
/**
7072
* These never run. They are the half of this card a runtime test cannot express

‎packages/app-shell/src/views/metadata-admin/EmbeddedItemEditor.tsx‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
} from './SchemaForm.js';
3636
import { getMetadataPreview } from './preview-registry.js';
3737
import { useMetadataClient, useMetadataTypes } from './useMetadata.js';
38+
import type { FormViewSpec } from './form-spec.js';
3839
import { useMetadataLocale, t, tFormat, translateValidationMessage } from './i18n.js';
3940
import { errorCodeIsAnyOf } from '@object-ui/types';
4041

@@ -73,7 +74,7 @@ export function EmbeddedItemEditor({
7374
// than promised, by `EmbeddedItemEditor.indexFallback.test.tsx`.
7475
const fallback = !subEntry?.schema && editAs ? FALLBACK_SCHEMAS[editAs] : undefined;
7576
const schema = (subEntry?.schema as Record<string, unknown> | undefined) ?? fallback?.schema;
76-
const form = (subEntry?.form as any) ?? fallback?.form;
77+
const form = subEntry?.form ?? fallback?.form;
7778
// Opt-in per sub-type, exactly like the Preview tab on the full page: a type
7879
// with no registered renderer gets no surface at all (never a "preview not
7980
// available" placeholder).
@@ -336,7 +337,7 @@ function spliceEmbedded(
336337
*/
337338
export const FALLBACK_SCHEMAS: Record<
338339
string,
339-
{ schema: Record<string, unknown>; form?: Record<string, unknown> }
340+
{ schema: Record<string, unknown>; form?: FormViewSpec }
340341
> = {
341342
index: {
342343
schema: {

‎packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,7 +1421,7 @@ function MetadataResourceEditPageImpl({
14211421
if (!key) return path;
14221422
// Resolve the human label for the HEAD segment from the form/schema.
14231423
const headLabel = ((): string => {
1424-
const formForLabels = (createMode && config.createSchema ? undefined : (entry?.form as any));
1424+
const formForLabels = (createMode && config.createSchema ? undefined : entry?.form);
14251425
const sections = Array.isArray(formForLabels?.sections) ? formForLabels.sections : [];
14261426
for (const section of sections) {
14271427
const fields = Array.isArray(section?.fields) ? section.fields : [];
@@ -2826,7 +2826,7 @@ function MetadataResourceEditPageImpl({
28262826
<SchemaForm
28272827
schema={schema}
28282828
idPath={formIdPath}
2829-
form={createMode && config.createSchema ? undefined : (entry?.form as any)}
2829+
form={createMode && config.createSchema ? undefined : entry?.form}
28302830
value={draft}
28312831
onChange={handleCreateAwareChange}
28322832
issues={displayIssues}
@@ -2850,7 +2850,7 @@ function MetadataResourceEditPageImpl({
28502850
<SchemaForm
28512851
schema={schema}
28522852
idPath={formIdPath}
2853-
form={createMode && config.createSchema ? undefined : (entry?.form as any)}
2853+
form={createMode && config.createSchema ? undefined : entry?.form}
28542854
value={draft}
28552855
onChange={handleCreateAwareChange}
28562856
issues={displayIssues}

0 commit comments

Comments
 (0)