Skip to content

Commit 2f947e4

Browse files
os-zhuangclaude
andauthored
fix(page,field): consume the spec's type/label/maxLength keys (framework#1878 §3 recheck) (#2867)
Three forward-drifts where objectui read a different key than the spec declares, so authoring the documented key silently no-oped: - page `type` -> `pageType`: PageSchema declares the page KIND as `type`; PageRenderer reads `schema.pageType` with a 'record' fallback and NOTHING mapped between them, so every non-record page (home/app/list/utility) rendered with the record max-width, a wrong data-page-type and a suppressed header. PageView now passes pageType alongside the SchemaNode discriminator `type` (which must stay -- ComponentRegistry dispatches on it). - page `label` -> `title`: PageSchema.label is required but the region renderer read only `title`. Dual-reads now, mirroring DashboardRenderer. Coupled with the above: the header is gated on pageType !== 'record', so fixing label alone would still render nothing. - field maxLength/minLength: validation already dual-read them, but ObjectForm's HTML-attribute pass and TextAreaField read max_length only -- no browser cap, no character counter for spec-authored metadata. Browser-verified against the showcase: capability_map (type:'home') now renders data-page-type="home", max-w-screen-2xl and its title; record pages unchanged. 625 tests green across the touched packages. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 20df08c commit 2f947e4

5 files changed

Lines changed: 60 additions & 7 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
"@object-ui/app-shell": patch
3+
"@object-ui/components": patch
4+
"@object-ui/plugin-form": patch
5+
"@object-ui/fields": patch
6+
---
7+
8+
fix(page,field): consume the spec's `type`/`label`/`maxLength` keys (framework#1878 §3 naming-drift recheck)
9+
10+
Three forward-drifts where objectui read a different key than the spec
11+
declares, so authoring the documented key silently no-oped:
12+
13+
- **page `type``pageType`** (app-shell + components): `PageSchema` declares
14+
the page KIND as `type`, but `PageRenderer` reads `schema.pageType` and fell
15+
back to `'record'` — and nothing mapped between them. Every non-record page
16+
(`home`/`app`/`list`/`utility`) rendered with the record max-width, a wrong
17+
`data-page-type` attribute, and a suppressed header. `PageView` now passes
18+
`pageType` alongside the SchemaNode discriminator `type`.
19+
- **page `label``title`** (components): `PageSchema.label` is required but the
20+
region renderer read only `title`. Now dual-reads `title ?? label`, mirroring
21+
the fallback `DashboardRenderer` already uses. Coupled with the above — the
22+
header is gated on `pageType !== 'record'`, so both were needed for a title to
23+
appear.
24+
- **field `maxLength`/`minLength`** (plugin-form + fields): validation already
25+
dual-read these, but `ObjectForm`'s HTML-attribute pass and `TextAreaField`
26+
read `max_length` only, so a spec-authored `maxLength` gave no browser cap and
27+
no character counter. Both now dual-read, matching `buildValidationRules`.
28+
29+
Verified in the browser against the showcase: `capability_map` (`type: 'home'`)
30+
now renders `data-page-type="home"`, the `home` max-width and its page title;
31+
record pages are unchanged.

packages/app-shell/src/views/PageView.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,15 @@ export function PageView() {
120120
key={refreshKey}
121121
schema={{
122122
...page,
123+
// `type` stays the SchemaNode discriminator ComponentRegistry
124+
// dispatches on. The spec's page KIND (`record|home|app|utility|
125+
// list`) rides `pageType`, which PageRenderer reads — without
126+
// this mapping every page fell back to `pageType: 'record'`,
127+
// so non-record pages got the record max-width, a wrong
128+
// `data-page-type` and a suppressed header (framework#1878 §3
129+
// naming-drift recheck).
123130
type: (page as any).type || 'page',
131+
pageType: (page as any).type,
124132
context: { ...(page as any).context, params, refreshKey },
125133
}}
126134
/>

packages/components/src/renderers/layout/page.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,10 @@ export const PageRenderer: React.FC<{
389389
[key: string]: any;
390390
}> = ({ schema, className, ...props }) => {
391391
const pageType = schema.pageType || 'record';
392+
// Spec PageSchema declares `label` (required); `title` is the objectui
393+
// spelling. Dual-read so a spec-authored page still renders its header
394+
// (framework#1878 §3 naming-drift recheck).
395+
const pageTitle = schema.title ?? (schema as any).label;
392396

393397
// Extract designer-related props and strip schema-only metadata that
394398
// would otherwise leak onto the wrapper <div> as invalid HTML attributes
@@ -499,12 +503,15 @@ export const PageRenderer: React.FC<{
499503
>
500504
<div className={cn(fullBleed ? 'space-y-6' : 'mx-auto space-y-6', maxWidthClass)}>
501505
{/* Page header — suppressed on record pages (the page:header component
502-
in the header region renders the record-bound title instead). */}
503-
{pageType !== 'record' && (schema.title || schema.description) && (
506+
in the header region renders the record-bound title instead).
507+
`title` is the objectui spelling; the spec's PageSchema declares
508+
`label` (required), so dual-read it — mirrors the fallback
509+
DashboardRenderer already uses (framework#1878 §3 recheck). */}
510+
{pageType !== 'record' && (pageTitle || schema.description) && (
504511
<div className="space-y-2">
505-
{schema.title && (
512+
{pageTitle && (
506513
<h1 className="text-3xl font-bold tracking-tight text-foreground">
507-
{schema.title}
514+
{pageTitle}
508515
</h1>
509516
)}
510517
{schema.description && (

packages/fields/src/widgets/TextAreaField.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ export function TextAreaField({ value, onChange, field, readonly, errorMessage,
3636

3737
const textareaField = (field || (props as any).schema) as any;
3838
const rows = textareaField?.rows || 4;
39-
const maxLength = textareaField?.max_length;
39+
// Spec FieldSchema declares camelCase `maxLength`; `max_length` is the legacy
40+
// objectui spelling. Dual-read (framework#1878 §3 recheck) — without this a
41+
// spec-authored maxLength gave neither the textarea cap nor the counter.
42+
const maxLength = textareaField?.maxLength ?? textareaField?.max_length;
4043
// Mobile fullscreen flag may arrive on the field metadata, on the form-field
4144
// schema (when called via the form renderer where `field` is the ObjectQL
4245
// metadata sub-object), or as an explicit widget prop.

packages/plugin-form/src/ObjectForm.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -604,8 +604,12 @@ const SimpleObjectForm: React.FC<ObjectFormProps> = ({
604604
}
605605

606606
if (field.type === 'text' || field.type === 'textarea' || field.type === 'markdown' || field.type === 'html') {
607-
formField.maxLength = field.max_length;
608-
formField.minLength = field.min_length;
607+
// Spec FieldSchema declares camelCase; `max_length`/`min_length` is
608+
// the legacy objectui spelling. Dual-read like buildValidationRules
609+
// already does (framework#1878 §3 recheck) — without this a
610+
// spec-authored `maxLength` never reached the HTML maxlength cap.
611+
formField.maxLength = (field as any).maxLength ?? field.max_length;
612+
formField.minLength = (field as any).minLength ?? field.min_length;
609613
}
610614

611615
if (field.type === 'file' || field.type === 'image') {

0 commit comments

Comments
 (0)