From 08571fe5052267f7cc9150fd7a027838e0892283 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 02:14:15 +0000 Subject: [PATCH 1/2] fix(types): model `code-editor` and `bar-chart`, repair three catalog fixtures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of #6318. Bucket B — two REGISTERED component types that `AnyComponentSchema` did not model, so `safeValidateSchema` refused every document naming them regardless of content. Both render today: `@object-ui/plugin-editor` registers `code-editor` and `@object-ui/plugin-charts` registers `bar-chart`. Every key on the new `CodeEditorSchema` / `BarChartSchema` (and their Zod mirrors) is taken from a read site — `plugin-editor/src/index.tsx:43-49` and `plugin-charts/src/ChartRenderer.tsx:28-38` — not from a view of what either component's authorable surface ought to be. Bucket A — three fixtures that were wrong about their own renderer, each confirmed by rendering it: `basic-select`'s third option spelled its label `type` (the open list showed two options, now three); `icon-toolbar`'s buttons carried only `icon`/`value`, neither of which `button-group` reads (three blank buttons, now labelled — the sibling `with-icons.json` already carried all three keys); `basic-tabs` gave its items no `value` and no `defaultValue`, so no panel could be selected (the tab content now paints). The remaining 28 entries in the bucket are NOT fixture bugs and are left untouched: each is a Zod declaration that contradicts what its renderer reads, reported on the card rather than swept. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB --- .changeset/6318-code-editor-bar-chart-zod.md | 20 +++ .../icon-toolbar.json | 3 + .../components-form-select/basic-select.json | 2 +- .../components-layout-tabs/basic-tabs.json | 4 + .../test/safe-validate-corpus-6318.test.ts | 122 ++++++++++++++++++ .../src/__tests__/zod-mirror-parity.test.ts | 12 +- packages/types/src/data-display.ts | 60 ++++++++- packages/types/src/form.ts | 70 +++++++++- packages/types/src/index.ts | 2 + packages/types/src/registry.ts | 4 + packages/types/src/zod/data-display.zod.ts | 27 ++++ packages/types/src/zod/form.zod.ts | 26 ++++ packages/types/src/zod/index.zod.ts | 2 + 13 files changed, 347 insertions(+), 7 deletions(-) create mode 100644 .changeset/6318-code-editor-bar-chart-zod.md create mode 100644 examples/schema-catalog/test/safe-validate-corpus-6318.test.ts diff --git a/.changeset/6318-code-editor-bar-chart-zod.md b/.changeset/6318-code-editor-bar-chart-zod.md new file mode 100644 index 0000000000..d0cda6ed1f --- /dev/null +++ b/.changeset/6318-code-editor-bar-chart-zod.md @@ -0,0 +1,20 @@ +--- +"@object-ui/types": patch +--- + +Model `code-editor` and `bar-chart` in `AnyComponentSchema`, and repair three catalog fixtures + +Both types render — `@object-ui/plugin-editor` registers `code-editor`, +`@object-ui/plugin-charts` registers `bar-chart` — and neither had a Zod member, +so `safeValidateSchema` (and therefore `objectui validate`) refused every +document that named them, whatever the document said. `CodeEditorSchema` and +`BarChartSchema` are now declared in `@object-ui/types` and mirrored in +`@object-ui/types/zod`, derived key-for-key from what the two renderers +demonstrably read rather than from a view of what either component ought to +accept. + +Alongside them, three `examples/schema-catalog` entries that were wrong about +their own renderer: `basic-select`'s third option spelled its label `type`, so +the option rendered blank; `icon-toolbar`'s buttons carried only `icon`/`value`, +which `button-group` never reads, so all three rendered blank; and `basic-tabs` +gave its items no `value` and no `defaultValue`, so no panel could be selected. diff --git a/examples/schema-catalog/src/schemas/components-basic-button-group/icon-toolbar.json b/examples/schema-catalog/src/schemas/components-basic-button-group/icon-toolbar.json index 84517f80f0..9606b86351 100644 --- a/examples/schema-catalog/src/schemas/components-basic-button-group/icon-toolbar.json +++ b/examples/schema-catalog/src/schemas/components-basic-button-group/icon-toolbar.json @@ -4,14 +4,17 @@ "size": "sm", "buttons": [ { + "label": "Copy", "icon": "copy", "value": "copy" }, { + "label": "Cut", "icon": "scissors", "value": "cut" }, { + "label": "Paste", "icon": "clipboard", "value": "paste" } diff --git a/examples/schema-catalog/src/schemas/components-form-select/basic-select.json b/examples/schema-catalog/src/schemas/components-form-select/basic-select.json index 652a0863cc..70e7fc8f45 100644 --- a/examples/schema-catalog/src/schemas/components-form-select/basic-select.json +++ b/examples/schema-catalog/src/schemas/components-form-select/basic-select.json @@ -11,7 +11,7 @@ "value": "2" }, { - "type": "Option 3", + "label": "Option 3", "value": "3" } ] diff --git a/examples/schema-catalog/src/schemas/components-layout-tabs/basic-tabs.json b/examples/schema-catalog/src/schemas/components-layout-tabs/basic-tabs.json index e0a648e2fd..8debfe9239 100644 --- a/examples/schema-catalog/src/schemas/components-layout-tabs/basic-tabs.json +++ b/examples/schema-catalog/src/schemas/components-layout-tabs/basic-tabs.json @@ -1,8 +1,10 @@ { "type": "tabs", + "defaultValue": "tab1", "items": [ { "label": "Tab 1", + "value": "tab1", "content": [ { "type": "text", @@ -12,6 +14,7 @@ }, { "label": "Tab 2", + "value": "tab2", "content": [ { "type": "text", @@ -21,6 +24,7 @@ }, { "label": "Tab 3", + "value": "tab3", "content": [ { "type": "text", diff --git a/examples/schema-catalog/test/safe-validate-corpus-6318.test.ts b/examples/schema-catalog/test/safe-validate-corpus-6318.test.ts new file mode 100644 index 0000000000..ec484fb341 --- /dev/null +++ b/examples/schema-catalog/test/safe-validate-corpus-6318.test.ts @@ -0,0 +1,122 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * objectui#6318 — the seven catalog entries this card moved out of + * `objectui check`'s "carries a registered ObjectUI component type but did not + * validate" bucket stay out of it. + * + * ## Why a pin at all + * + * The bucket is reported by a COUNT and a file list on stdout. Nothing fails + * when a file rejoins it: `objectui check` exits 0 whether the list is empty or + * 53 entries long (`packages/cli/src/commands/check.ts` increments `errors` + * only on a parse failure). So a corpus repair that is not pinned is a repair + * that regresses silently — which is how the two shapes below got here. + * + * ## The two shapes are NOT the same repair, and must not be pinned alike + * + * 1. **The validator was short four files** (`code-editor` ×3, `bar-chart`). + * Both types RENDER — `@object-ui/plugin-editor` and + * `@object-ui/plugin-charts` register them — and `AnyComponentSchema` + * modelled neither, so every document naming them failed + * `safeValidateSchema` no matter what it said. The fix was in + * `@object-ui/types`; the fixtures were never wrong and are UNCHANGED by + * this card. Their assertion is therefore about the union, and it is paired + * with a counter-probe below: a mirror that accepted everything would + * satisfy `.success` while declaring nothing. + * + * 2. **Three fixtures were wrong** — measured against what their own renderer + * reads, and confirmed the way objectui#6318's triage asks: the corrected + * file must RENDER DIFFERENTLY, because a "correction" that changes no + * pixel is evidence the schema was at fault instead. All three changed: + * the select grew a third option in its list, the button group's three + * buttons went from blank to labelled, and the tab panel began painting its + * content. Each pin below names the KEY that moved, not just `.success` — + * `.success` alone would go green again if a later sweep deleted the key + * and the enclosing object with it. + * + * ⛔ This file deliberately does NOT pin the size of the remaining bucket. The + * 28 entries still in it are open findings on the Zod union (`tooltip` and + * `context-menu` demand a `children` their renderers never read; `tree-view` + * demands `data` where the renderer reads `nodes` first; `kanban` declares + * `columns[].items` where the board reads `columns[].cards`; and so on), and a + * number pinned here would turn red on the card that repairs any one of them. + */ +import { describe, it, expect } from 'vitest'; +import { safeValidateSchema } from '@object-ui/types/zod'; + +import javascriptEditor from '../src/schemas/plugin-editor/javascript-editor.json' with { type: 'json' }; +import pythonEditor from '../src/schemas/plugin-editor/python-editor.json' with { type: 'json' }; +import readOnlyJsonViewer from '../src/schemas/plugin-editor/read-only-json-viewer.json' with { type: 'json' }; +import simpleBarChart from '../src/schemas/plugin-charts/simple-bar-chart.json' with { type: 'json' }; +import basicSelect from '../src/schemas/components-form-select/basic-select.json' with { type: 'json' }; +import basicTabs from '../src/schemas/components-layout-tabs/basic-tabs.json' with { type: 'json' }; +import iconToolbar from '../src/schemas/components-basic-button-group/icon-toolbar.json' with { type: 'json' }; + +/** Report the first issue rather than `false`, so a red run says what broke. */ +function reasons(schema: unknown): string[] { + const r = safeValidateSchema(schema); + return r.success ? [] : r.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`); +} + +describe('objectui#6318 — the union now models the two plugin types it rendered but could not validate', () => { + it.each([ + ['plugin-editor/javascript-editor', javascriptEditor], + ['plugin-editor/python-editor', pythonEditor], + ['plugin-editor/read-only-json-viewer', readOnlyJsonViewer], + ['plugin-charts/simple-bar-chart', simpleBarChart], + ])('%s validates unchanged', (_id, fixture) => { + expect(reasons(fixture)).toEqual([]); + }); + + it('the members are real declarations, not passthrough holes', () => { + // Counter-probes. Every key probed is one the mirror DECLARES — an unknown + // key proves nothing here, because `BaseSchema` is `.passthrough()`. + expect(safeValidateSchema({ type: 'code-editor', theme: 'solarized' }).success).toBe(false); + expect(safeValidateSchema({ type: 'code-editor', height: 300 }).success).toBe(false); + expect(safeValidateSchema({ type: 'bar-chart', height: '300px' }).success).toBe(false); + expect(safeValidateSchema({ type: 'bar-chart', dataKey: 5 }).success).toBe(false); + // …and the good shapes still pass, so the four above are not failing for + // some unrelated reason. + expect(safeValidateSchema({ type: 'code-editor', theme: 'light', height: '300px' }).success).toBe(true); + expect(safeValidateSchema({ type: 'bar-chart', height: 300, dataKey: 'value' }).success).toBe(true); + }); +}); + +describe('objectui#6318 — three fixtures were wrong about what their renderer reads', () => { + it('basic-select: the third option carries `label`, which is the only child SelectItem renders', () => { + expect(reasons(basicSelect)).toEqual([]); + // `select.tsx` renders `{opt.label}` and nothing else, so an option without + // it is a blank row in the open list — measured before the repair. + const options = (basicSelect as { options: Array> }).options; + expect(options.map((o) => o.label)).toEqual(['Option 1', 'Option 2', 'Option 3']); + expect(options.some((o) => 'type' in o)).toBe(false); + }); + + it('basic-tabs: every item carries `value`, and `defaultValue` names one of them', () => { + expect(reasons(basicTabs)).toEqual([]); + // `tabs.tsx` passes `item.value` to both `TabsTrigger` and `TabsContent`; + // with all three undefined no panel can be selected, and `defaultValue` is + // what makes one paint at all (the registration marks it `required: true`). + const tabs = basicTabs as { defaultValue?: string; items: Array<{ value?: string }> }; + const values = tabs.items.map((i) => i.value); + expect(values).toEqual(['tab1', 'tab2', 'tab3']); + expect(values).toContain(tabs.defaultValue); + }); + + it('icon-toolbar: every button carries `label`, the only key the group renders', () => { + expect(reasons(iconToolbar)).toEqual([]); + // `button-group.tsx` renders `{button.label}` and reads neither `icon` nor + // `value`; the sibling `with-icons.json` — which already validated — is the + // corpus's own precedent for carrying all three. + const buttons = (iconToolbar as { buttons: Array> }).buttons; + expect(buttons.map((b) => b.label)).toEqual(['Copy', 'Cut', 'Paste']); + expect(buttons.map((b) => b.icon)).toEqual(['copy', 'scissors', 'clipboard']); + }); +}); diff --git a/packages/types/src/__tests__/zod-mirror-parity.test.ts b/packages/types/src/__tests__/zod-mirror-parity.test.ts index 1fe01a8cb0..eb466d3ae6 100644 --- a/packages/types/src/__tests__/zod-mirror-parity.test.ts +++ b/packages/types/src/__tests__/zod-mirror-parity.test.ts @@ -118,10 +118,10 @@ import { BaseSchema, ComponentConfigSchema, ComponentInputSchema, ComponentMetaS import { BlockEditorSchema, BlockInstanceSchema, BlockLibraryItemSchema, BlockLibrarySchema, BlockMetadataSchema, BlockSchema, BlockSlotSchema, BlockVariableSchema, ComponentSchema } from '../zod/blocks.zod.js'; import { CalendarEventSchema, CalendarViewSchema, CarouselItemSchema, CarouselSchema, ChatbotSchema, ChatMessageSchema, ChatMessageSourceSchema, ChatToolInvocationSchema, DashboardComponentSchema, DashboardConfigSchema, DashboardWidgetConfigSchema, DashboardWidgetLayoutSchema, DashboardWidgetSchema, FilterBuilderSchema, FilterFieldSchema, KanbanCardSchema, KanbanColumnSchema, KanbanSchema } from '../zod/complex.zod.js'; import { ActionCallbackSchema, CRUDDialogSchema, DetailSchema } from '../zod/crud.zod.js'; -import { AlertSchema, AvatarSchema, BadgeSchema, ChartDataSeriesSchema, ChartSchema, DataTableSchema, HtmlSchema, KbdSchema, ListItemSchema, ListSchema, MarkdownSchema, StaticTableColumnSchema, StatisticSchema, TableColumnSchema, TableSchema, TimelineEventSchema, TimelineSchema, TreeViewSchema } from '../zod/data-display.zod.js'; +import { AlertSchema, AvatarSchema, BadgeSchema, BarChartSchema, ChartDataSeriesSchema, ChartSchema, DataTableSchema, HtmlSchema, KbdSchema, ListItemSchema, ListSchema, MarkdownSchema, StaticTableColumnSchema, StatisticSchema, TableColumnSchema, TableSchema, TimelineEventSchema, TimelineSchema, TreeViewSchema } from '../zod/data-display.zod.js'; import { AccordionItemSchema, AccordionSchema, CollapsibleSchema, ToggleGroupItemSchema, ToggleGroupSchema } from '../zod/disclosure.zod.js'; import { EmptySchema, LoadingSchema, ProgressSchema, SkeletonSchema, SonnerSchema, SpinnerSchema, ToasterSchema, ToastSchema } from '../zod/feedback.zod.js'; -import { ButtonSchema, CalendarSchema, CheckboxSchema, ComboboxOptionSchema, ComboboxSchema, CommandGroupSchema, CommandItemSchema, CommandSchema, DatePickerSchema, FieldConditionSchema, FieldConstraintsSchema, FileUploadSchema, FormFieldSchema, FormSchema, InputOTPSchema, InputSchema, LabelSchema, RadioGroupSchema, RadioOptionSchema, SelectOptionSchema, SelectSchema, SliderSchema, SwitchSchema, TextareaSchema, ToggleSchema } from '../zod/form.zod.js'; +import { ButtonSchema, CalendarSchema, CheckboxSchema, CodeEditorSchema, ComboboxOptionSchema, ComboboxSchema, CommandGroupSchema, CommandItemSchema, CommandSchema, DatePickerSchema, FieldConditionSchema, FieldConstraintsSchema, FileUploadSchema, FormFieldSchema, FormSchema, InputOTPSchema, InputSchema, LabelSchema, RadioGroupSchema, RadioOptionSchema, SelectOptionSchema, SelectSchema, SliderSchema, SwitchSchema, TextareaSchema, ToggleSchema } from '../zod/form.zod.js'; import { AspectRatioSchema, BoxSchema, CardSchema, ContainerSchema, DivSchema, FlexSchema, GridSchema, IconSchema, ImageSchema, PageNodeRegionSchema, PageNodeSchema, ResizablePanelSchema, ResizableSchema, ScrollAreaSchema, SeparatorSchema, StackSchema, TabItemSchema, TabsSchema, TextSchema, TextSpanSchema } from '../zod/layout.zod.js'; import { BreadcrumbItemSchema, BreadcrumbSchema, ButtonGroupButtonSchema, ButtonGroupSchema, HeaderBarSchema, NavigationMenuSchema, PaginationSchema, SidebarSchema } from '../zod/navigation.zod.js'; import { ObjectCalendarSchema, ObjectChartSchema, ObjectFormSchema, ObjectGanttSchema, ObjectGridSchema, ObjectKanbanSchema, ObjectMapConfigSchema, ObjectMapSchema, ObjectTreeSchema, ObjectViewSchema, SortConfigSchema } from '../zod/objectql.zod.js'; @@ -135,10 +135,10 @@ import type { BlockEditorSchema as Ts_BlockEditorSchema, BlockInstanceSchema as import type { CalendarEvent as Ts_CalendarEvent, CalendarViewSchema as Ts_CalendarViewSchema, CarouselItem as Ts_CarouselItem, CarouselSchema as Ts_CarouselSchema, ChatbotSchema as Ts_ChatbotSchema, ChatMessage as Ts_ChatMessage, ChatMessageSource as Ts_ChatMessageSource, ChatToolInvocation as Ts_ChatToolInvocation, DashboardComponentSchema as Ts_DashboardComponentSchema, DashboardWidgetLayout as Ts_DashboardWidgetLayout, DashboardWidgetSchema as Ts_DashboardWidgetSchema, FilterBuilderSchema as Ts_FilterBuilderSchema, FilterField as Ts_FilterField, KanbanCard as Ts_KanbanCard, KanbanColumn as Ts_KanbanColumn, KanbanSchema as Ts_KanbanSchema } from '../complex'; import type { DashboardConfig as Ts_DashboardConfig, DashboardWidgetConfig as Ts_DashboardWidgetConfig } from '../designer'; import type { ActionCallback as Ts_ActionCallback, CRUDDialogSchema as Ts_CRUDDialogSchema, DetailSchema as Ts_DetailSchema } from '../crud'; -import type { AlertSchema as Ts_AlertSchema, AvatarSchema as Ts_AvatarSchema, BadgeSchema as Ts_BadgeSchema, ChartDataSeries as Ts_ChartDataSeries, ChartSchema as Ts_ChartSchema, DataTableSchema as Ts_DataTableSchema, HtmlSchema as Ts_HtmlSchema, KbdSchema as Ts_KbdSchema, ListItem as Ts_ListItem, ListSchema as Ts_ListSchema, MarkdownSchema as Ts_MarkdownSchema, StaticTableColumn as Ts_StaticTableColumn, StatisticSchema as Ts_StatisticSchema, TableColumn as Ts_TableColumn, TableSchema as Ts_TableSchema, TimelineEvent as Ts_TimelineEvent, TimelineSchema as Ts_TimelineSchema, TreeViewSchema as Ts_TreeViewSchema, BreadcrumbItem as Ts_BreadcrumbItem, BreadcrumbSchema as Ts_BreadcrumbSchema } from '../data-display'; +import type { AlertSchema as Ts_AlertSchema, AvatarSchema as Ts_AvatarSchema, BadgeSchema as Ts_BadgeSchema, BarChartSchema as Ts_BarChartSchema, ChartDataSeries as Ts_ChartDataSeries, ChartSchema as Ts_ChartSchema, DataTableSchema as Ts_DataTableSchema, HtmlSchema as Ts_HtmlSchema, KbdSchema as Ts_KbdSchema, ListItem as Ts_ListItem, ListSchema as Ts_ListSchema, MarkdownSchema as Ts_MarkdownSchema, StaticTableColumn as Ts_StaticTableColumn, StatisticSchema as Ts_StatisticSchema, TableColumn as Ts_TableColumn, TableSchema as Ts_TableSchema, TimelineEvent as Ts_TimelineEvent, TimelineSchema as Ts_TimelineSchema, TreeViewSchema as Ts_TreeViewSchema, BreadcrumbItem as Ts_BreadcrumbItem, BreadcrumbSchema as Ts_BreadcrumbSchema } from '../data-display'; import type { AccordionItem as Ts_AccordionItem, AccordionSchema as Ts_AccordionSchema, CollapsibleSchema as Ts_CollapsibleSchema, ToggleGroupItem as Ts_ToggleGroupItem, ToggleGroupSchema as Ts_ToggleGroupSchema } from '../disclosure'; import type { EmptySchema as Ts_EmptySchema, LoadingSchema as Ts_LoadingSchema, ProgressSchema as Ts_ProgressSchema, SkeletonSchema as Ts_SkeletonSchema, SonnerSchema as Ts_SonnerSchema, SpinnerSchema as Ts_SpinnerSchema, ToasterSchema as Ts_ToasterSchema, ToastSchema as Ts_ToastSchema } from '../feedback'; -import type { ButtonSchema as Ts_ButtonSchema, CalendarSchema as Ts_CalendarSchema, CheckboxSchema as Ts_CheckboxSchema, ComboboxOption as Ts_ComboboxOption, ComboboxSchema as Ts_ComboboxSchema, CommandGroup as Ts_CommandGroup, CommandItem as Ts_CommandItem, CommandSchema as Ts_CommandSchema, DatePickerSchema as Ts_DatePickerSchema, FieldCondition as Ts_FieldCondition, FieldValidationRules as Ts_FieldValidationRules, FileUploadSchema as Ts_FileUploadSchema, FormField as Ts_FormField, FormSchema as Ts_FormSchema, InputOTPSchema as Ts_InputOTPSchema, InputSchema as Ts_InputSchema, LabelSchema as Ts_LabelSchema, RadioGroupSchema as Ts_RadioGroupSchema, RadioOption as Ts_RadioOption, SelectOption as Ts_SelectOption, SelectSchema as Ts_SelectSchema, SliderSchema as Ts_SliderSchema, SwitchSchema as Ts_SwitchSchema, TextareaSchema as Ts_TextareaSchema, ToggleSchema as Ts_ToggleSchema } from '../form'; +import type { ButtonSchema as Ts_ButtonSchema, CalendarSchema as Ts_CalendarSchema, CheckboxSchema as Ts_CheckboxSchema, CodeEditorSchema as Ts_CodeEditorSchema, ComboboxOption as Ts_ComboboxOption, ComboboxSchema as Ts_ComboboxSchema, CommandGroup as Ts_CommandGroup, CommandItem as Ts_CommandItem, CommandSchema as Ts_CommandSchema, DatePickerSchema as Ts_DatePickerSchema, FieldCondition as Ts_FieldCondition, FieldValidationRules as Ts_FieldValidationRules, FileUploadSchema as Ts_FileUploadSchema, FormField as Ts_FormField, FormSchema as Ts_FormSchema, InputOTPSchema as Ts_InputOTPSchema, InputSchema as Ts_InputSchema, LabelSchema as Ts_LabelSchema, RadioGroupSchema as Ts_RadioGroupSchema, RadioOption as Ts_RadioOption, SelectOption as Ts_SelectOption, SelectSchema as Ts_SelectSchema, SliderSchema as Ts_SliderSchema, SwitchSchema as Ts_SwitchSchema, TextareaSchema as Ts_TextareaSchema, ToggleSchema as Ts_ToggleSchema } from '../form'; import type { AspectRatioSchema as Ts_AspectRatioSchema, BoxSchema as Ts_BoxSchema, CardSchema as Ts_CardSchema, ContainerSchema as Ts_ContainerSchema, DivSchema as Ts_DivSchema, FlexSchema as Ts_FlexSchema, GridSchema as Ts_GridSchema, IconSchema as Ts_IconSchema, ImageSchema as Ts_ImageSchema, PageNodeRegion as Ts_PageNodeRegion, PageNodeSchema as Ts_PageNodeSchema, ResizablePanel as Ts_ResizablePanel, ResizableSchema as Ts_ResizableSchema, ScrollAreaSchema as Ts_ScrollAreaSchema, SeparatorSchema as Ts_SeparatorSchema, StackSchema as Ts_StackSchema, TabItem as Ts_TabItem, TabsSchema as Ts_TabsSchema, TextSchema as Ts_TextSchema, TextSpanSchema as Ts_TextSpanSchema } from '../layout'; import type { ButtonGroupButton as Ts_ButtonGroupButton, ButtonGroupSchema as Ts_ButtonGroupSchema, HeaderBarSchema as Ts_HeaderBarSchema, NavigationMenuSchema as Ts_NavigationMenuSchema, PaginationSchema as Ts_PaginationSchema, SidebarSchema as Ts_SidebarSchema } from '../navigation'; import type { ObjectCalendarSchema as Ts_ObjectCalendarSchema, ObjectChartSchema as Ts_ObjectChartSchema, ObjectFormSchema as Ts_ObjectFormSchema, ObjectGanttSchema as Ts_ObjectGanttSchema, ObjectGridSchema as Ts_ObjectGridSchema, ObjectKanbanSchema as Ts_ObjectKanbanSchema, ObjectMapConfig as Ts_ObjectMapConfig, ObjectMapSchema as Ts_ObjectMapSchema, ObjectTreeSchema as Ts_ObjectTreeSchema, ObjectViewSchema as Ts_ObjectViewSchema, SortConfig as Ts_SortConfig } from '../objectql'; @@ -376,6 +376,7 @@ const MIRRORS = { 'data-display.zod.ts#DataTableSchema': DataTableSchema, 'data-display.zod.ts#HtmlSchema': HtmlSchema, 'data-display.zod.ts#KbdSchema': KbdSchema, + 'data-display.zod.ts#BarChartSchema': BarChartSchema, 'data-display.zod.ts#ListItemSchema': ListItemSchema, 'data-display.zod.ts#ListSchema': ListSchema, 'data-display.zod.ts#MarkdownSchema': MarkdownSchema, @@ -407,6 +408,7 @@ const MIRRORS = { 'form.zod.ts#CommandGroupSchema': CommandGroupSchema, 'form.zod.ts#CommandItemSchema': CommandItemSchema, 'form.zod.ts#CommandSchema': CommandSchema, + 'form.zod.ts#CodeEditorSchema': CodeEditorSchema, 'form.zod.ts#DatePickerSchema': DatePickerSchema, 'form.zod.ts#FieldConditionSchema': FieldConditionSchema, 'form.zod.ts#FieldConstraintsSchema': FieldConstraintsSchema, @@ -539,6 +541,7 @@ interface Declared { 'data-display.zod.ts#DataTableSchema': Ts_DataTableSchema; 'data-display.zod.ts#HtmlSchema': Ts_HtmlSchema; 'data-display.zod.ts#KbdSchema': Ts_KbdSchema; + 'data-display.zod.ts#BarChartSchema': Ts_BarChartSchema; 'data-display.zod.ts#ListItemSchema': Ts_ListItem; 'data-display.zod.ts#ListSchema': Ts_ListSchema; 'data-display.zod.ts#MarkdownSchema': Ts_MarkdownSchema; @@ -570,6 +573,7 @@ interface Declared { 'form.zod.ts#CommandGroupSchema': Ts_CommandGroup; 'form.zod.ts#CommandItemSchema': Ts_CommandItem; 'form.zod.ts#CommandSchema': Ts_CommandSchema; + 'form.zod.ts#CodeEditorSchema': Ts_CodeEditorSchema; 'form.zod.ts#DatePickerSchema': Ts_DatePickerSchema; 'form.zod.ts#FieldConditionSchema': Ts_FieldCondition; 'form.zod.ts#FieldConstraintsSchema': Ts_FieldValidationRules; diff --git a/packages/types/src/data-display.ts b/packages/types/src/data-display.ts index b648c58bf0..f0b3285d7e 100644 --- a/packages/types/src/data-display.ts +++ b/packages/types/src/data-display.ts @@ -1688,6 +1688,63 @@ export interface KbdSchema extends BaseSchema { keys?: string | string[]; } +/** + * Bar chart component (`bar-chart`), rendered by `@object-ui/plugin-charts` + * over Recharts. + * + * Declared here for the same reason `MarkdownSchema` above is: a registered + * component type that `AnyComponentSchema` (`./zod/index.zod.ts`) does not + * model cannot be validated at all — `objectui validate` refuses every document + * that names it, and `objectui check` reports it as unrecognised + * (objectui#6318). `@object-ui/types` has zero dependencies and cannot import + * the plugin's own `BarChartSchema`, so this is the twin-declaration shape the + * markdown and kanban components already carry. + * + * ⚠️ Derived from READ SITES, not from a view of what a bar chart should + * accept: `packages/plugin-charts/src/ChartRenderer.tsx:28-38` + * (`ChartBarRenderer`) forwards exactly `data`, `dataKey`, `xAxisKey`, + * `height`, `className` and `color` into the lazy chart implementation, and the + * registration's `inputs`/`defaultProps` (`plugin-charts/src/index.tsx:45-64`) + * name the same five authorable keys. + * + * ⛔ Not to be confused with {@link ChartSchema} (`type: 'chart'`), the + * multi-series component with its own `series`/`chartType` vocabulary. This one + * plots a single `dataKey` and is reached only under the `bar-chart` keyword. + */ +export interface BarChartSchema extends BaseSchema { + type: 'bar-chart'; + /** + * Rows to plot. Each row supplies one bar: its category comes from + * {@link xAxisKey} and its magnitude from {@link dataKey}. + */ + data?: Array>; + /** + * Row key holding the bar's value (the y axis). + * + * @default 'value' + */ + dataKey?: string; + /** + * Row key holding the bar's category label (the x axis). + * + * @default 'name' + */ + xAxisKey?: string; + /** + * Chart height in pixels. A number, not a CSS length — the registration + * declares `type: 'number'` and defaults it to 400. + * + * @default 400 + */ + height?: number; + /** + * Bar fill colour, forwarded to Recharts verbatim. + * + * @default '#8884d8' + */ + color?: string; +} + /** * Union type of all data display schemas */ @@ -1706,7 +1763,8 @@ export type DataDisplaySchema = | HtmlSchema | StatisticSchema | BreadcrumbSchema - | KbdSchema; + | KbdSchema + | BarChartSchema; /** * Raw HTML component diff --git a/packages/types/src/form.ts b/packages/types/src/form.ts index 01ceb78962..6d68f52177 100644 --- a/packages/types/src/form.ts +++ b/packages/types/src/form.ts @@ -1427,6 +1427,73 @@ export interface CommandSchema extends BaseSchema { onChange?: (value: string) => void; } +/** + * Code editor component (`code-editor`), rendered by `@object-ui/plugin-editor` + * over Monaco. + * + * Declared here — rather than only in the plugin — for the reason `markdown` + * and `kanban` already are: `AnyComponentSchema` in `./zod/index.zod.ts` is the + * validator `objectui validate` and `objectui check` read, and a registered + * component type absent from it validates as nothing at all (objectui#6318). + * `@object-ui/types` has zero dependencies, so it cannot import the plugin's + * own `CodeEditorSchema`; this is the same twin-declaration shape `markdown` + * carries, and the zod mirror below it is pinned to THIS declaration. + * + * ⚠️ Every key is taken from a READ SITE, not from a view of what a code editor + * ought to accept — `packages/plugin-editor/src/index.tsx:43-49` forwards + * exactly `value`, `language`, `theme`, `height`, `onChange`, `readOnly` and + * `className` into the lazy Monaco implementation, and the registration's own + * `inputs`/`defaultProps` (same file, 64-77) name the same set. + */ +export interface CodeEditorSchema extends BaseSchema { + type: 'code-editor'; + /** + * The code content shown in the editor. + * + * Read at `plugin-editor/src/index.tsx:43` as `value ?? schema.value` — the + * host prop wins, so this is the authored default rather than a controlled + * value. + */ + value?: string; + /** + * Monaco language id for syntax highlighting. + * + * Deliberately a bare `string` and not an enum: the renderer forwards it + * verbatim to Monaco, which resolves any registered language id, and the + * plugin's own declaration already widens its six-name list with `| string`. + * The registration's `inputs` offer `javascript`, `typescript`, `python`, + * `json`, `html` and `css` as the authoring shortlist — that is a picker + * hint, not the accepted set. + * + * @default 'javascript' + */ + language?: string; + /** + * Editor colour theme. Closed, unlike `language`: these are the two spellings + * the plugin's `CodeEditorSchema` declares and the registration offers. + * + * @default 'vs-dark' + */ + theme?: 'vs-dark' | 'light'; + /** + * Editor height, forwarded to Monaco as a CSS length. + * + * @default '400px' + */ + height?: string; + /** + * Whether the editor refuses edits. + * + * @default false + */ + readOnly?: boolean; + /** + * Change handler. A runtime slot, not an authorable key — no JSON document + * can carry a function. + */ + onChange?: (value: string | undefined) => void; +} + /** * Union type of all form schemas */ @@ -1447,5 +1514,6 @@ export type FormComponentSchema = | FormSchema | LabelSchema | ComboboxSchema - | CommandSchema; + | CommandSchema + | CodeEditorSchema; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 6ebe1505e1..852dc49fad 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -174,6 +174,7 @@ export type { ToggleSchema, FormSchema, LabelSchema, + CodeEditorSchema, FormComponentSchema, } from './form.js'; @@ -206,6 +207,7 @@ export type { KbdSchema, HtmlSchema, StatisticSchema, + BarChartSchema, DataDisplaySchema, TableColumnType, } from './data-display.js'; diff --git a/packages/types/src/registry.ts b/packages/types/src/registry.ts index 3e71815e1a..d354e0281c 100644 --- a/packages/types/src/registry.ts +++ b/packages/types/src/registry.ts @@ -39,6 +39,7 @@ import type { CalendarSchema as FormCalendarSchema, InputOTPSchema, FormSchema, + CodeEditorSchema, } from './form.js'; import type { @@ -54,6 +55,7 @@ import type { TimelineSchema, HtmlSchema, StatisticSchema, + BarChartSchema, } from './data-display.js'; import type { @@ -134,6 +136,7 @@ export interface SchemaRegistry { 'calendar': FormCalendarSchema; 'input-otp': InputOTPSchema; 'form': FormSchema; + 'code-editor': CodeEditorSchema; // Data Display 'alert': AlertSchema; @@ -148,6 +151,7 @@ export interface SchemaRegistry { 'timeline': TimelineSchema; 'html': HtmlSchema; 'statistic': StatisticSchema; + 'bar-chart': BarChartSchema; // Feedback 'loading': LoadingSchema; diff --git a/packages/types/src/zod/data-display.zod.ts b/packages/types/src/zod/data-display.zod.ts index 74c7bfa60f..b566a22c4e 100644 --- a/packages/types/src/zod/data-display.zod.ts +++ b/packages/types/src/zod/data-display.zod.ts @@ -443,6 +443,32 @@ export const HtmlSchema = BaseSchema.extend({ /** * Data Display Schema Union - All data display component schemas */ +/** + * Bar Chart Schema — mirrors `BarChartSchema` in `../data-display.ts`. + * + * Closes half of objectui#6318's bucket B: `bar-chart` is a REGISTERED + * component that no union member modelled, so `safeValidateSchema` refused + * every document naming it and `objectui check` could only report it. Every key + * here is one `ChartBarRenderer` demonstrably reads + * (`plugin-charts/src/ChartRenderer.tsx:28-38`); nothing is admitted on the + * strength of what a bar chart "should" accept. + * + * All five are OPTIONAL, deliberately. `data` is the one the registration marks + * `required: true` in its authoring `inputs`, but the renderer reads it as + * `schema.data` with no guard and the implementation defaults an absent array — + * so requiring it here would refuse a document the renderer draws. An authoring + * hint and a validation floor are different claims; only the second belongs in + * a schema. + */ +export const BarChartSchema = BaseSchema.extend({ + type: z.literal('bar-chart'), + data: z.array(z.record(z.string(), z.any())).optional().describe('Rows to plot; one bar per row'), + dataKey: z.string().optional().describe('Row key holding the bar value (y axis)'), + xAxisKey: z.string().optional().describe('Row key holding the bar category (x axis)'), + height: z.number().optional().describe('Chart height in pixels'), + color: z.string().optional().describe('Bar fill colour'), +}); + export const DataDisplaySchema = z.discriminatedUnion('type', [ AlertSchema, StatisticSchema, @@ -457,4 +483,5 @@ export const DataDisplaySchema = z.discriminatedUnion('type', [ TimelineSchema, KbdSchema, HtmlSchema, + BarChartSchema, ]); diff --git a/packages/types/src/zod/form.zod.ts b/packages/types/src/zod/form.zod.ts index 1ba4d41409..d453543a28 100644 --- a/packages/types/src/zod/form.zod.ts +++ b/packages/types/src/zod/form.zod.ts @@ -652,6 +652,31 @@ export const FormSchema = BaseSchema.extend({ /** * Form Component Schema Union - All form component schemas */ +/** + * Code Editor Schema — mirrors `CodeEditorSchema` in `../form.ts`. + * + * Closes the other half of objectui#6318's bucket B: `code-editor` is a + * REGISTERED component (`@object-ui/plugin-editor`) that no union member + * modelled, so every document naming it failed `safeValidateSchema` and three + * schema-catalog entries were reported as unrecognised content. + * + * Derived from the renderer's forwards + * (`plugin-editor/src/index.tsx:43-49`), not from a view of what a code editor + * ought to accept. `language` stays `z.string()` rather than the registration's + * six-name picker list, because the renderer hands the value straight to Monaco + * and the plugin's own declaration already widens that list with `| string`; + * `theme` stays closed because both declarations agree it is two spellings. + */ +export const CodeEditorSchema = BaseSchema.extend({ + type: z.literal('code-editor'), + value: z.string().optional().describe('Code content'), + language: z.string().optional().describe('Monaco language id for syntax highlighting'), + theme: z.enum(['vs-dark', 'light']).optional().describe('Editor colour theme'), + height: z.string().optional().describe('Editor height as a CSS length'), + readOnly: z.boolean().optional().describe('Whether the editor refuses edits'), + onChange: z.function().optional().describe('Change handler'), +}); + export const FormComponentSchema = z.discriminatedUnion('type', [ ButtonSchema, InputSchema, @@ -670,4 +695,5 @@ export const FormComponentSchema = z.discriminatedUnion('type', [ LabelSchema, CommandSchema, FormSchema, + CodeEditorSchema, ]); diff --git a/packages/types/src/zod/index.zod.ts b/packages/types/src/zod/index.zod.ts index 199d56ef5c..42273fa3a4 100644 --- a/packages/types/src/zod/index.zod.ts +++ b/packages/types/src/zod/index.zod.ts @@ -120,6 +120,7 @@ export { CommandSchema, FormFieldSchema, FormSchema, + CodeEditorSchema, FormComponentSchema, } from './form.zod.js'; @@ -147,6 +148,7 @@ export { TimelineSchema, KbdSchema, HtmlSchema, + BarChartSchema, DataDisplaySchema, } from './data-display.zod.js'; From 0e123f16cf227e2b8d1c2986e2c2bd5d4fdbb43d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 03:16:31 +0000 Subject: [PATCH 2/2] fix(types): one authority per schema name; regrade the changeset to minor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two fixes to the review of #6318's PR. 1. objectui#6273 (the 2026-08-25 family ruling objectui#6172 / 甲-A1) went red: the two schemas this branch added to `@object-ui/types` collided by name with the pre-existing declarations in their owning plugins. The two shapes were compared STRUCTURALLY before choosing, not by reading: a tsc probe asserting mutual assignability in both directions, per pair, plus a per-member probe for `language` — the one member the two spelled differently (`'javascript' | … | string`, which TypeScript collapses to exactly `string`). Every relation held, and a deliberate counter-probe against an unrelated shape failed as required, so the green was a measurement rather than a vacuous assertion. Same heritage, same member set, same per-member types and optionality ⇒ these are re-points, not renames. Direction is forced: `@object-ui/types` is the lower layer and cannot import from a plugin without a cycle, so the plugins re-point at types'. `export type { X } from '@object-ui/types'` is a re-export, which the gate does not count as a second declaration. ⛔ `KNOWN_COLLISIONS` is untouched (shrink-only), and so is the gate's own test file — #6936 and #6937 both have unlanded edits to it. The published import paths are unchanged: `check:readme-exports` still resolves both README self-imports through the re-export (386 judged, 0 wrong-path, 0 fabricated), and `objectui check` reports a byte-identical file list, so the accept set of the validator did not move. 2. Regrade the changeset `patch` → `minor` for `@object-ui/types`, per contract review: the package's own precedent (the `visible` widen, and `ViewNavigationConfig`) grades a change of this size `minor` on the stated grounds that it is more than patch describes. Purely additive, so no migration note is owed beyond the regrade. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB --- .changeset/6318-code-editor-bar-chart-zod.md | 2 +- packages/plugin-charts/src/types.ts | 58 ++++++----------- packages/plugin-editor/src/types.ts | 68 ++++++++------------ 3 files changed, 48 insertions(+), 80 deletions(-) diff --git a/.changeset/6318-code-editor-bar-chart-zod.md b/.changeset/6318-code-editor-bar-chart-zod.md index d0cda6ed1f..96b8ed1c72 100644 --- a/.changeset/6318-code-editor-bar-chart-zod.md +++ b/.changeset/6318-code-editor-bar-chart-zod.md @@ -1,5 +1,5 @@ --- -"@object-ui/types": patch +"@object-ui/types": minor --- Model `code-editor` and `bar-chart` in `AnyComponentSchema`, and repair three catalog fixtures diff --git a/packages/plugin-charts/src/types.ts b/packages/plugin-charts/src/types.ts index 7fd7e32249..be0839f0d1 100644 --- a/packages/plugin-charts/src/types.ts +++ b/packages/plugin-charts/src/types.ts @@ -8,21 +8,36 @@ /** * TypeScript type definitions for @object-ui/plugin-charts - * + * * These types can be imported by applications using this plugin * to get full TypeScript support for chart schemas. */ -import type { BaseSchema } from '@object-ui/types'; - /** * Bar Chart component schema. * Renders a bar chart using Recharts library. - * + * + * ⚠️ RE-EXPORTED, not declared here. The authority is + * `BarChartSchema` in `@object-ui/types` (`packages/types/src/data-display.ts`), + * which is also where the zod mirror `AnyComponentSchema` validates against + * lives — so the type an author reads and the schema that accepts their + * document cannot drift apart. + * + * Why that direction and not the other (objectui#6273, the 2026-08-25 family + * ruling objectui#6172 / 甲-A1): `@object-ui/types` is the lower layer and + * cannot import from a plugin without creating a cycle, so of the two possible + * authorities only this one is legal. + * + * The two declarations were measured structurally before this re-point rather + * than assumed equivalent — same heritage (`BaseSchema`), same six members, + * same per-member types and optionality, mutually assignable in both + * directions — so nothing about the published shape changes here. The import + * path `@object-ui/plugin-charts` keeps working exactly as before. + * * @example * ```typescript * import type { BarChartSchema } from '@object-ui/plugin-charts'; - * + * * const chartSchema: BarChartSchema = { * type: 'bar-chart', * data: [ @@ -34,35 +49,4 @@ import type { BaseSchema } from '@object-ui/types'; * } * ``` */ -export interface BarChartSchema extends BaseSchema { - type: 'bar-chart'; - - /** - * Array of data points to display in the chart. - */ - data?: Array>; - - /** - * Key in the data object for the Y-axis values. - * @default 'value' - */ - dataKey?: string; - - /** - * Key in the data object for the X-axis labels. - * @default 'name' - */ - xAxisKey?: string; - - /** - * Height of the chart in pixels. - * @default 400 - */ - height?: number; - - /** - * Color of the bars. - * @default '#8884d8' - */ - color?: string; -} +export type { BarChartSchema } from '@object-ui/types'; diff --git a/packages/plugin-editor/src/types.ts b/packages/plugin-editor/src/types.ts index 6f926e92df..fc97476255 100644 --- a/packages/plugin-editor/src/types.ts +++ b/packages/plugin-editor/src/types.ts @@ -8,21 +8,41 @@ /** * TypeScript type definitions for @object-ui/plugin-editor - * + * * These types can be imported by applications using this plugin * to get full TypeScript support for code-editor schemas. */ -import type { BaseSchema } from '@object-ui/types'; - /** * Code Editor component schema. * Renders a Monaco-based code editor with syntax highlighting. - * + * + * ⚠️ RE-EXPORTED, not declared here. The authority is + * `CodeEditorSchema` in `@object-ui/types` (`packages/types/src/form.ts`), + * alongside the zod mirror that `AnyComponentSchema` validates `code-editor` + * documents against — so the type an author reads and the schema that accepts + * their document cannot drift apart. + * + * Why that direction and not the other (objectui#6273, the 2026-08-25 family + * ruling objectui#6172 / 甲-A1): `@object-ui/types` is the lower layer and + * cannot import from a plugin without creating a cycle, so of the two possible + * authorities only this one is legal. + * + * The two declarations were measured structurally before this re-point rather + * than assumed equivalent — same heritage (`BaseSchema`), same seven members, + * same per-member types and optionality, mutually assignable in both + * directions. That includes `language`, the one member the two spelled + * differently: this file wrote + * `'javascript' | 'typescript' | … | string`, which TypeScript collapses to + * exactly `string`, and the authority declares `string` outright with the + * six-name authoring shortlist recorded in its own doc comment. No published + * shape changes here, and the import path `@object-ui/plugin-editor` keeps + * working exactly as before. + * * @example * ```typescript * import type { CodeEditorSchema } from '@object-ui/plugin-editor'; - * + * * const editorSchema: CodeEditorSchema = { * type: 'code-editor', * value: 'console.log("Hello, World!");', @@ -32,40 +52,4 @@ import type { BaseSchema } from '@object-ui/types'; * } * ``` */ -export interface CodeEditorSchema extends BaseSchema { - type: 'code-editor'; - - /** - * The code content to display in the editor. - */ - value?: string; - - /** - * Programming language for syntax highlighting. - * @default 'javascript' - */ - language?: 'javascript' | 'typescript' | 'python' | 'json' | 'html' | 'css' | 'markdown' | string; - - /** - * Color theme for the editor. - * @default 'vs-dark' - */ - theme?: 'vs-dark' | 'light'; - - /** - * Height of the editor. - * @default '400px' - */ - height?: string; - - /** - * Whether the editor is read-only. - * @default false - */ - readOnly?: boolean; - - /** - * Callback when the code content changes. - */ - onChange?: (value: string | undefined) => void; -} +export type { CodeEditorSchema } from '@object-ui/types';