From 5641a9c4169312280f50e7cf65e486b91773009d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 14:44:16 +0000 Subject: [PATCH 1/3] feat(types): declare RichtextFieldMetadata for the widget's third registry key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `markdown`, `html` and `richtext` are one widget (objectui#5498). Two of the three carried an exported metadata type; `richtext` carried none, so the runtime served it by structure while an author could not write its metadata under an annotation at all. The only way to write one was `as unknown as MarkdownFieldMetadata` — a deliberate cast in this repo's own pin test that was the gap's sole evidence. `RichtextFieldMetadata` is now exported from `@object-ui/types` and joins the `FieldMetadata` union, so a richtext field's metadata is a typed literal and narrows out of the union on `type`. The cast and its comment are gone, and the surrounding docblock is re-pointed so the gap cannot be rediscovered. The member's shape was derived from what `RichTextField` reads on the `richtext` path, not copied from the two siblings: `type`, `rows`, `placeholder`, `mobile_fullscreen` and `label` (the last three already sit on `BaseFieldMetadata`), with the readonly branch's cell renderers contributing no metadata key. `max_length` is declared on the measurement that `richtext` is symmetric with `markdown`/`html` at the `@objectstack/spec` 17.3.0 authoring boundary, not on the siblings having it. Additive only: nothing is removed or narrowed, `RichTextField` is unchanged, and metadata previously written through a cast keeps compiling. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QtGhnU3WnnWyiWeYQhw2aX --- .changeset/7083-richtext-field-metadata.md | 47 +++++ .../__tests__/RichTextField.rows.test.tsx | 18 +- .../richtext-field-metadata-7083.test.tsx | 176 ++++++++++++++++++ packages/types/src/field-types.ts | 72 +++++++ packages/types/src/index.ts | 1 + 5 files changed, 308 insertions(+), 6 deletions(-) create mode 100644 .changeset/7083-richtext-field-metadata.md create mode 100644 packages/fields/src/widgets/__tests__/richtext-field-metadata-7083.test.tsx diff --git a/.changeset/7083-richtext-field-metadata.md b/.changeset/7083-richtext-field-metadata.md new file mode 100644 index 0000000000..bd4db19fe4 --- /dev/null +++ b/.changeset/7083-richtext-field-metadata.md @@ -0,0 +1,47 @@ +--- +'@object-ui/types': minor +--- + +`RichtextFieldMetadata` — the third registry key of `RichTextField` becomes declarable +(objectui#7083, maintainer ruling 2026-09-07, director decision batch #71). + +`markdown`, `html` and `richtext` are one widget (objectui#5498). Two of the three +already had an exported metadata type; `richtext` had none, so the runtime served it by +structure while an author could not write its metadata under an annotation at all. The +only way to write one was `as unknown as MarkdownFieldMetadata`, and that deliberate +cast — in this repo's own pin test — was the gap's sole evidence. The state was neither +a union member nor a recorded alias, which is why it had to be rediscovered to be seen. + +**New.** `RichtextFieldMetadata` is exported from `@object-ui/types` and joins the +`FieldMetadata` union, so a richtext field's metadata can be written as a typed literal +and narrowed out of the union on `type`: + +```ts +import type { RichtextFieldMetadata } from '@object-ui/types'; + +const doc: RichtextFieldMetadata = { + type: 'richtext', + name: 'doc', + label: 'Release notes', + rows: 10, + placeholder: 'Write the release notes…', +}; +``` + +**Additive only.** Nothing is removed or narrowed: `richtext` field metadata that was +previously written through a cast keeps compiling, and every other member of the union +is untouched. The one behavioural surface — `RichTextField` — is unchanged; it already +served all three keys and this release only gives the third one a face. + +**The member's shape was derived, not copied from its two siblings.** `type`, `rows`, +`placeholder`, `mobile_fullscreen` and `label` are the keys `RichTextField` actually +reads on the `richtext` path (the last three already sit on `BaseFieldMetadata`, so the +member declares `type` and `rows`); the readonly branch hands the metadata to a cell +renderer that reads `value` only and contributes no key. `max_length` is the one +declared key the widget does not read, and it is there because the cross-check measured +`richtext` symmetric with `markdown` and `html` on every available axis — one widget and +one code path, plus `@objectstack/spec` 17.3.0 `FieldSchema` answering identically for +all three (`rows` admitted, the spec's own `maxLength` admitted, the legacy snake_case +`max_length` refused by name alike). Omitting it would have left `richtext` the one key +of the three whose ceiling cannot be authored — a fresh instance of the asymmetry this +member exists to end. diff --git a/packages/fields/src/widgets/__tests__/RichTextField.rows.test.tsx b/packages/fields/src/widgets/__tests__/RichTextField.rows.test.tsx index 031a8050ca..4757897eb1 100644 --- a/packages/fields/src/widgets/__tests__/RichTextField.rows.test.tsx +++ b/packages/fields/src/widgets/__tests__/RichTextField.rows.test.tsx @@ -15,8 +15,11 @@ * 2026-08-25, Option A, aligning the `TextareaFieldMetadata` precedent), so * the field literals below carry it under the excess-property check rather * than through a cast. The `richtext` registry key resolves to the same - * widget (objectui#5498) with no union member of its own, so its case is the - * one deliberate `as` in this file. + * widget (objectui#5498) and now has a union member of its own too + * (`RichtextFieldMetadata`, objectui#7083), so all three literals here are + * annotated and this file holds no `as` at all — the deliberate cast that used + * to sit on the richtext case WAS the only evidence that the third key had no + * declarable face, and it went with the gap it recorded. * * Direction of the DOM assertion: `rows` lands on the HTML `rows` attribute of * the inline `