Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .changeset/6318-code-editor-bar-chart-zod.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
---
"@object-ui/types": minor
---

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.
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@
"value": "2"
},
{
"type": "Option 3",
"label": "Option 3",
"value": "3"
}
]
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
{
"type": "tabs",
"defaultValue": "tab1",
"items": [
{
"label": "Tab 1",
"value": "tab1",
"content": [
{
"type": "text",
Expand All@@ -12,6 +14,7 @@
},
{
"label": "Tab 2",
"value": "tab2",
"content": [
{
"type": "text",
Expand All@@ -21,6 +24,7 @@
},
{
"label": "Tab 3",
"value": "tab3",
"content": [
{
"type": "text",
Expand Down
122 changes: 122 additions & 0 deletions examples/schema-catalog/test/safe-validate-corpus-6318.test.ts
Original file line numberDiff line numberDiff line change
@@ -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<Record<string, unknown>> }).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<Record<string, unknown>> }).buttons;
expect(buttons.map((b) => b.label)).toEqual(['Copy', 'Cut', 'Paste']);
expect(buttons.map((b) => b.icon)).toEqual(['copy', 'scissors', 'clipboard']);
});
});
58 changes: 21 additions & 37 deletions packages/plugin-charts/src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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: [
Expand All@@ -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<Record<string, any>>;

/**
* 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';
68 changes: 26 additions & 42 deletions packages/plugin-editor/src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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!");',
Expand All@@ -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';
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .changeset/6318-code-editor-bar-chart-zod.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
---
"@object-ui/types": minor
---

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.
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@
"value": "2"
},
{
"type": "Option 3",
"label": "Option 3",
"value": "3"
}
]
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
{
"type": "tabs",
"defaultValue": "tab1",
"items": [
{
"label": "Tab 1",
"value": "tab1",
"content": [
{
"type": "text",
Expand All@@ -12,6 +14,7 @@
},
{
"label": "Tab 2",
"value": "tab2",
"content": [
{
"type": "text",
Expand All@@ -21,6 +24,7 @@
},
{
"label": "Tab 3",
"value": "tab3",
"content": [
{
"type": "text",
Expand Down
122 changes: 122 additions & 0 deletions examples/schema-catalog/test/safe-validate-corpus-6318.test.ts
Original file line numberDiff line numberDiff line change
@@ -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<Record<string, unknown>> }).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<Record<string, unknown>> }).buttons;
expect(buttons.map((b) => b.label)).toEqual(['Copy', 'Cut', 'Paste']);
expect(buttons.map((b) => b.icon)).toEqual(['copy', 'scissors', 'clipboard']);
});
});
58 changes: 21 additions & 37 deletions packages/plugin-charts/src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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: [
Expand All@@ -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<Record<string, any>>;

/**
* 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';
68 changes: 26 additions & 42 deletions packages/plugin-editor/src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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!");',
Expand All@@ -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';
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .changeset/6318-code-editor-bar-chart-zod.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
---
"@object-ui/types": minor
---

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.
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@
"value": "2"
},
{
"type": "Option 3",
"label": "Option 3",
"value": "3"
}
]
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
{
"type": "tabs",
"defaultValue": "tab1",
"items": [
{
"label": "Tab 1",
"value": "tab1",
"content": [
{
"type": "text",
Expand All@@ -12,6 +14,7 @@
},
{
"label": "Tab 2",
"value": "tab2",
"content": [
{
"type": "text",
Expand All@@ -21,6 +24,7 @@
},
{
"label": "Tab 3",
"value": "tab3",
"content": [
{
"type": "text",
Expand Down
122 changes: 122 additions & 0 deletions examples/schema-catalog/test/safe-validate-corpus-6318.test.ts
Original file line numberDiff line numberDiff line change
@@ -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<Record<string, unknown>> }).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<Record<string, unknown>> }).buttons;
expect(buttons.map((b) => b.label)).toEqual(['Copy', 'Cut', 'Paste']);
expect(buttons.map((b) => b.icon)).toEqual(['copy', 'scissors', 'clipboard']);
});
});
58 changes: 21 additions & 37 deletions packages/plugin-charts/src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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: [
Expand All@@ -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<Record<string, any>>;

/**
* 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';
68 changes: 26 additions & 42 deletions packages/plugin-editor/src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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!");',
Expand All@@ -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';
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .changeset/6318-code-editor-bar-chart-zod.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
---
"@object-ui/types": minor
---

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.
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@
"value": "2"
},
{
"type": "Option 3",
"label": "Option 3",
"value": "3"
}
]
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
{
"type": "tabs",
"defaultValue": "tab1",
"items": [
{
"label": "Tab 1",
"value": "tab1",
"content": [
{
"type": "text",
Expand All@@ -12,6 +14,7 @@
},
{
"label": "Tab 2",
"value": "tab2",
"content": [
{
"type": "text",
Expand All@@ -21,6 +24,7 @@
},
{
"label": "Tab 3",
"value": "tab3",
"content": [
{
"type": "text",
Expand Down
122 changes: 122 additions & 0 deletions examples/schema-catalog/test/safe-validate-corpus-6318.test.ts
Original file line numberDiff line numberDiff line change
@@ -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<Record<string, unknown>> }).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<Record<string, unknown>> }).buttons;
expect(buttons.map((b) => b.label)).toEqual(['Copy', 'Cut', 'Paste']);
expect(buttons.map((b) => b.icon)).toEqual(['copy', 'scissors', 'clipboard']);
});
});
58 changes: 21 additions & 37 deletions packages/plugin-charts/src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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: [
Expand All@@ -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<Record<string, any>>;

/**
* 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';
68 changes: 26 additions & 42 deletions packages/plugin-editor/src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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!");',
Expand All@@ -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';
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .changeset/6318-code-editor-bar-chart-zod.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
---
"@object-ui/types": minor
---

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.
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@
"value": "2"
},
{
"type": "Option 3",
"label": "Option 3",
"value": "3"
}
]
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
{
"type": "tabs",
"defaultValue": "tab1",
"items": [
{
"label": "Tab 1",
"value": "tab1",
"content": [
{
"type": "text",
Expand All@@ -12,6 +14,7 @@
},
{
"label": "Tab 2",
"value": "tab2",
"content": [
{
"type": "text",
Expand All@@ -21,6 +24,7 @@
},
{
"label": "Tab 3",
"value": "tab3",
"content": [
{
"type": "text",
Expand Down
122 changes: 122 additions & 0 deletions examples/schema-catalog/test/safe-validate-corpus-6318.test.ts
Original file line numberDiff line numberDiff line change
@@ -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<Record<string, unknown>> }).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<Record<string, unknown>> }).buttons;
expect(buttons.map((b) => b.label)).toEqual(['Copy', 'Cut', 'Paste']);
expect(buttons.map((b) => b.icon)).toEqual(['copy', 'scissors', 'clipboard']);
});
});
58 changes: 21 additions & 37 deletions packages/plugin-charts/src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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: [
Expand All@@ -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<Record<string, any>>;

/**
* 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';
68 changes: 26 additions & 42 deletions packages/plugin-editor/src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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!");',
Expand All@@ -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';
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .changeset/6318-code-editor-bar-chart-zod.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
---
"@object-ui/types": minor
---

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.
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@
"value": "2"
},
{
"type": "Option 3",
"label": "Option 3",
"value": "3"
}
]
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
{
"type": "tabs",
"defaultValue": "tab1",
"items": [
{
"label": "Tab 1",
"value": "tab1",
"content": [
{
"type": "text",
Expand All@@ -12,6 +14,7 @@
},
{
"label": "Tab 2",
"value": "tab2",
"content": [
{
"type": "text",
Expand All@@ -21,6 +24,7 @@
},
{
"label": "Tab 3",
"value": "tab3",
"content": [
{
"type": "text",
Expand Down
122 changes: 122 additions & 0 deletions examples/schema-catalog/test/safe-validate-corpus-6318.test.ts
Original file line numberDiff line numberDiff line change
@@ -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<Record<string, unknown>> }).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<Record<string, unknown>> }).buttons;
expect(buttons.map((b) => b.label)).toEqual(['Copy', 'Cut', 'Paste']);
expect(buttons.map((b) => b.icon)).toEqual(['copy', 'scissors', 'clipboard']);
});
});
58 changes: 21 additions & 37 deletions packages/plugin-charts/src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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: [
Expand All@@ -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<Record<string, any>>;

/**
* 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';
68 changes: 26 additions & 42 deletions packages/plugin-editor/src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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!");',
Expand All@@ -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';
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .changeset/6318-code-editor-bar-chart-zod.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
---
"@object-ui/types": minor
---

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.
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@
"value": "2"
},
{
"type": "Option 3",
"label": "Option 3",
"value": "3"
}
]
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
{
"type": "tabs",
"defaultValue": "tab1",
"items": [
{
"label": "Tab 1",
"value": "tab1",
"content": [
{
"type": "text",
Expand All@@ -12,6 +14,7 @@
},
{
"label": "Tab 2",
"value": "tab2",
"content": [
{
"type": "text",
Expand All@@ -21,6 +24,7 @@
},
{
"label": "Tab 3",
"value": "tab3",
"content": [
{
"type": "text",
Expand Down
122 changes: 122 additions & 0 deletions examples/schema-catalog/test/safe-validate-corpus-6318.test.ts
Original file line numberDiff line numberDiff line change
@@ -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<Record<string, unknown>> }).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<Record<string, unknown>> }).buttons;
expect(buttons.map((b) => b.label)).toEqual(['Copy', 'Cut', 'Paste']);
expect(buttons.map((b) => b.icon)).toEqual(['copy', 'scissors', 'clipboard']);
});
});
58 changes: 21 additions & 37 deletions packages/plugin-charts/src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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: [
Expand All@@ -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<Record<string, any>>;

/**
* 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';
68 changes: 26 additions & 42 deletions packages/plugin-editor/src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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!");',
Expand All@@ -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';
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .changeset/6318-code-editor-bar-chart-zod.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
---
"@object-ui/types": minor
---

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.
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@
"value": "2"
},
{
"type": "Option 3",
"label": "Option 3",
"value": "3"
}
]
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
{
"type": "tabs",
"defaultValue": "tab1",
"items": [
{
"label": "Tab 1",
"value": "tab1",
"content": [
{
"type": "text",
Expand All@@ -12,6 +14,7 @@
},
{
"label": "Tab 2",
"value": "tab2",
"content": [
{
"type": "text",
Expand All@@ -21,6 +24,7 @@
},
{
"label": "Tab 3",
"value": "tab3",
"content": [
{
"type": "text",
Expand Down
122 changes: 122 additions & 0 deletions examples/schema-catalog/test/safe-validate-corpus-6318.test.ts
Original file line numberDiff line numberDiff line change
@@ -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<Record<string, unknown>> }).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<Record<string, unknown>> }).buttons;
expect(buttons.map((b) => b.label)).toEqual(['Copy', 'Cut', 'Paste']);
expect(buttons.map((b) => b.icon)).toEqual(['copy', 'scissors', 'clipboard']);
});
});
58 changes: 21 additions & 37 deletions packages/plugin-charts/src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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: [
Expand All@@ -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<Record<string, any>>;

/**
* 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';
68 changes: 26 additions & 42 deletions packages/plugin-editor/src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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!");',
Expand All@@ -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';
Loading
Loading