From 84d3dd97195ecfa562335002c3b44dae52e233b3 Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Fri, 31 Jul 2026 00:05:29 +0800 Subject: [PATCH] refactor(types)!: Page/App/Dashboard renderer nodes stop claiming the spec's document names (objectstack#4115 group B) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Group B is the `type`-semantics collision: the spec's `type` IS the page / dashboard kind (`record|app|utility|list|home`), while objectui's `type` is the component discriminator (`'page'`) and the kind lives on `pageType`. So a spec-authored `{type: 'list'}` page failed objectui's schema and an objectui page's `pageType` was silently dropped by spec's — under names that told every reader the two were the same thing. Renamed to the *ComponentSchema convention group A settled on (node = *ComponentSchema, union = *UnionSchema), with no back-compat aliases: AppSchema -> AppComponentSchema DashboardSchema -> DashboardComponentSchema PageSchema -> PageNodeSchema Page is a deliberate exception to the convention: `PageComponentSchema` is ALREADY a `@objectstack/spec/ui` export — and it means a component *inside* a page region (`{type: PageComponentType, properties, …}`), not the page itself. Taking that name would have rebuilt the exact defect this issue exists to remove, one concept over. The guard caught it: the first pass of this commit used `PageComponentSchema` and failed by name. Ledger 129 -> 126, regenerated with `--ledger` and diffed entry by entry: removals are exactly these three, no other package's debt dropped. Mutation-tested in both directions — re-declaring the node as `PageSchema` *and* as `PageComponentSchema` each fail the guard by name and file. 78/78 type-check; full suite 8701 assertions green. BREAKING CHANGE: `AppSchema`, `DashboardSchema` and `PageSchema` are renamed in `@object-ui/types` (and its `/zod` entry point) with no aliases. Every in-repo consumer is updated here, including apps/console. Co-Authored-By: Claude Fable 5 --- .../src/schemas/objectDetailPageSchema.ts | 4 +-- .../app-shell/src/hooks/useNavigationSync.ts | 20 ++++++------- .../components/src/renderers/layout/page.tsx | 28 +++++++++---------- packages/core/src/utils/dashboard-filters.ts | 4 +-- packages/layout/src/AppSchemaRenderer.tsx | 6 ++-- packages/layout/src/Page.tsx | 4 +-- .../src/__tests__/AppSchemaRenderer.test.tsx | 22 +++++++-------- .../src/DashboardGridLayout.tsx | 12 ++++---- .../src/DashboardRenderer.tsx | 4 +-- .../src/DashboardWithConfig.tsx | 6 ++-- .../DashboardGridLayout.persistence.test.ts | 8 +++--- .../DashboardRenderer.designMode.test.tsx | 4 +-- .../DashboardRenderer.filters.test.tsx | 12 ++++---- .../DashboardRenderer.headerActions.test.tsx | 6 ++-- .../DashboardRenderer.legacyRetired.test.tsx | 8 +++--- .../plugin-designer/src/DashboardEditor.tsx | 18 ++++++------ .../src/pages/DashboardDesignPage.tsx | 14 +++++----- packages/react/src/SchemaRenderer.tsx | 2 +- packages/runner/src/App.tsx | 6 ++-- packages/runner/src/LayoutRenderer.tsx | 4 +-- packages/runner/src/lib/MetadataLoader.ts | 14 +++++----- .../src/__tests__/app-creation-types.test.ts | 6 ++-- .../src/__tests__/navigation-model.test.ts | 16 +++++------ .../src/__tests__/p1-spec-alignment.test.ts | 22 +++++++-------- .../page-app-dashboard-spec-parity.test.ts | 6 ++-- .../src/__tests__/phase2-schemas.test.ts | 14 +++++----- packages/types/src/app.ts | 4 +-- packages/types/src/complex.ts | 4 +-- packages/types/src/index.ts | 18 ++++++------ packages/types/src/layout.ts | 4 +-- packages/types/src/registry.ts | 4 +-- packages/types/src/zod/app.zod.ts | 4 +-- packages/types/src/zod/complex.zod.ts | 4 +-- packages/types/src/zod/index.zod.ts | 10 +++---- packages/types/src/zod/layout.zod.ts | 4 +-- scripts/check-spec-symbol-derivation.mjs | 3 -- 36 files changed, 163 insertions(+), 166 deletions(-) diff --git a/apps/console/src/schemas/objectDetailPageSchema.ts b/apps/console/src/schemas/objectDetailPageSchema.ts index fd8645210e..23efa01673 100644 --- a/apps/console/src/schemas/objectDetailPageSchema.ts +++ b/apps/console/src/schemas/objectDetailPageSchema.ts @@ -13,7 +13,7 @@ * @module schemas/objectDetailPageSchema */ -import type { PageSchema, BaseSchema } from '@object-ui/types'; +import type { PageNodeSchema, BaseSchema } from '@object-ui/types'; /** Widget schema node with `objectName` property. */ interface ObjectWidgetNode extends BaseSchema { @@ -36,7 +36,7 @@ interface ObjectWidgetNode extends BaseSchema { export function buildObjectDetailPageSchema( objectName: string, item?: Record | null, -): PageSchema { +): PageNodeSchema { const label = (item?.label as string) || objectName; const description = (item?.description as string) || objectName; diff --git a/packages/app-shell/src/hooks/useNavigationSync.ts b/packages/app-shell/src/hooks/useNavigationSync.ts index 1377f3ddb1..a4545023a1 100644 --- a/packages/app-shell/src/hooks/useNavigationSync.ts +++ b/packages/app-shell/src/hooks/useNavigationSync.ts @@ -11,7 +11,7 @@ import { useCallback, useEffect, useRef } from 'react'; import { toast } from 'sonner'; -import type { NavigationItem, AppSchema } from '@object-ui/types'; +import type { NavigationItem, AppComponentSchema } from '@object-ui/types'; import { useObjectTranslation } from '@object-ui/i18n'; import { useAdapter } from '../providers/AdapterProvider'; import { useMetadata } from '../providers/MetadataProvider'; @@ -210,7 +210,7 @@ export function useNavigationSync(): UseNavigationSyncReturn { /** Persist an updated app schema and refresh metadata. */ const saveApp = useCallback( - async (appName: string, schema: AppSchema) => { + async (appName: string, schema: AppComponentSchema) => { const client = adapterRef.current?.getClient(); if (client) { await client.meta.saveItem('app', appName, schema); @@ -222,8 +222,8 @@ export function useNavigationSync(): UseNavigationSyncReturn { /** Find the current app schema from metadata by name. */ const findApp = useCallback( - (appName: string): AppSchema | undefined => - matchAppBySegment(apps, appName) as AppSchema | undefined, + (appName: string): AppComponentSchema | undefined => + matchAppBySegment(apps, appName) as AppComponentSchema | undefined, [apps], ); @@ -245,7 +245,7 @@ export function useNavigationSync(): UseNavigationSyncReturn { icon: 'FileText', }; const updated = addNavigationItem(prev, newItem); - const updatedApp: AppSchema = { ...app, navigation: updated }; + const updatedApp: AppComponentSchema = { ...app, navigation: updated }; try { await saveApp(appName, updatedApp); @@ -283,7 +283,7 @@ export function useNavigationSync(): UseNavigationSyncReturn { icon: 'LayoutDashboard', }; const updated = addNavigationItem(prev, newItem); - const updatedApp: AppSchema = { ...app, navigation: updated }; + const updatedApp: AppComponentSchema = { ...app, navigation: updated }; try { await saveApp(appName, updatedApp); @@ -320,7 +320,7 @@ export function useNavigationSync(): UseNavigationSyncReturn { const updated = removeNavigationItems(prev, 'page', pageName); if (navigationEqual(updated, prev)) return; // nothing changed - const updatedApp: AppSchema = { ...app, navigation: updated }; + const updatedApp: AppComponentSchema = { ...app, navigation: updated }; try { await saveApp(appName, updatedApp); @@ -353,7 +353,7 @@ export function useNavigationSync(): UseNavigationSyncReturn { const updated = removeNavigationItems(prev, 'dashboard', dashboardName); if (navigationEqual(updated, prev)) return; - const updatedApp: AppSchema = { ...app, navigation: updated }; + const updatedApp: AppComponentSchema = { ...app, navigation: updated }; try { await saveApp(appName, updatedApp); @@ -390,7 +390,7 @@ export function useNavigationSync(): UseNavigationSyncReturn { const updated = renameNavigationItems(prev, 'page', oldName, newName); if (navigationEqual(updated, prev)) return; - const updatedApp: AppSchema = { ...app, navigation: updated }; + const updatedApp: AppComponentSchema = { ...app, navigation: updated }; try { await saveApp(appName, updatedApp); @@ -423,7 +423,7 @@ export function useNavigationSync(): UseNavigationSyncReturn { const updated = renameNavigationItems(prev, 'dashboard', oldName, newName); if (navigationEqual(updated, prev)) return; - const updatedApp: AppSchema = { ...app, navigation: updated }; + const updatedApp: AppComponentSchema = { ...app, navigation: updated }; try { await saveApp(appName, updatedApp); diff --git a/packages/components/src/renderers/layout/page.tsx b/packages/components/src/renderers/layout/page.tsx index 31738cfa67..036f14aaf4 100644 --- a/packages/components/src/renderers/layout/page.tsx +++ b/packages/components/src/renderers/layout/page.tsx @@ -13,7 +13,7 @@ */ import React, { useMemo } from 'react'; -import type { PageSchema, PageRegion, SchemaNode } from '@object-ui/types'; +import type { PageNodeSchema, PageRegion, SchemaNode } from '@object-ui/types'; import { SchemaRenderer, PageVariablesProvider, PageVariableActionBridge } from '@object-ui/react'; import { ComponentRegistry } from '@object-ui/core'; import { compile, manifestFromConfigs } from '@object-ui/sdui-parser'; @@ -183,7 +183,7 @@ const RegionLayout: React.FC<{ // FlatContent — legacy body/children fallback // --------------------------------------------------------------------------- -const FlatContent: React.FC<{ schema: PageSchema }> = ({ schema }) => { +const FlatContent: React.FC<{ schema: PageNodeSchema }> = ({ schema }) => { const content = schema.body || schema.children; const nodes: SchemaNode[] = Array.isArray(content) ? content @@ -207,7 +207,7 @@ const FlatContent: React.FC<{ schema: PageSchema }> = ({ schema }) => { // --------------------------------------------------------------------------- /** Template: full-width single column */ -const FullWidthTemplate: React.FC<{ schema: PageSchema }> = ({ schema }) => { +const FullWidthTemplate: React.FC<{ schema: PageNodeSchema }> = ({ schema }) => { if (schema.regions && schema.regions.length > 0) { return ; } @@ -215,7 +215,7 @@ const FullWidthTemplate: React.FC<{ schema: PageSchema }> = ({ schema }) => { }; /** Template: header-sidebar-main — header spanning full width, sidebar + main below */ -const HeaderSidebarMainTemplate: React.FC<{ schema: PageSchema }> = ({ schema }) => { +const HeaderSidebarMainTemplate: React.FC<{ schema: PageNodeSchema }> = ({ schema }) => { const regions = schema.regions || []; if (regions.length === 0) return ; @@ -245,7 +245,7 @@ const HeaderSidebarMainTemplate: React.FC<{ schema: PageSchema }> = ({ schema }) }; /** Template: three-column — sidebar + main + aside */ -const ThreeColumnTemplate: React.FC<{ schema: PageSchema }> = ({ schema }) => { +const ThreeColumnTemplate: React.FC<{ schema: PageNodeSchema }> = ({ schema }) => { const regions = schema.regions || []; if (regions.length === 0) return ; @@ -283,7 +283,7 @@ const ThreeColumnTemplate: React.FC<{ schema: PageSchema }> = ({ schema }) => { }; /** Template: dashboard — 2x2 grid of regions */ -const DashboardTemplate: React.FC<{ schema: PageSchema }> = ({ schema }) => { +const DashboardTemplate: React.FC<{ schema: PageNodeSchema }> = ({ schema }) => { const regions = schema.regions || []; if (regions.length === 0) return ; @@ -305,7 +305,7 @@ const DashboardTemplate: React.FC<{ schema: PageSchema }> = ({ schema }) => { }; /** Template registry — maps template names to layout components */ -const TEMPLATE_REGISTRY: Record> = { +const TEMPLATE_REGISTRY: Record> = { 'default': FullWidthTemplate, 'full-width': FullWidthTemplate, 'header-sidebar-main': HeaderSidebarMainTemplate, @@ -314,7 +314,7 @@ const TEMPLATE_REGISTRY: Record> = { }; /** Resolve template: if the schema specifies a template name, use the matching layout */ -function resolveTemplate(schema: PageSchema): React.FC<{ schema: PageSchema }> | null { +function resolveTemplate(schema: PageNodeSchema): React.FC<{ schema: PageNodeSchema }> | null { if (!schema.template) return null; return TEMPLATE_REGISTRY[schema.template] || null; } @@ -324,7 +324,7 @@ function resolveTemplate(schema: PageSchema): React.FC<{ schema: PageSchema }> | // --------------------------------------------------------------------------- /** Record page — detail-oriented, narrower max-width */ -const RecordPageLayout: React.FC<{ schema: PageSchema }> = ({ schema }) => { +const RecordPageLayout: React.FC<{ schema: PageNodeSchema }> = ({ schema }) => { if (schema.regions && schema.regions.length > 0) { return ; } @@ -332,7 +332,7 @@ const RecordPageLayout: React.FC<{ schema: PageSchema }> = ({ schema }) => { }; /** Home page — dashboard-style, wider layout */ -const HomePageLayout: React.FC<{ schema: PageSchema }> = ({ schema }) => { +const HomePageLayout: React.FC<{ schema: PageNodeSchema }> = ({ schema }) => { if (schema.regions && schema.regions.length > 0) { return ; } @@ -340,7 +340,7 @@ const HomePageLayout: React.FC<{ schema: PageSchema }> = ({ schema }) => { }; /** App page — application shell, full-width capable */ -const AppPageLayout: React.FC<{ schema: PageSchema }> = ({ schema }) => { +const AppPageLayout: React.FC<{ schema: PageNodeSchema }> = ({ schema }) => { if (schema.regions && schema.regions.length > 0) { return ; } @@ -348,7 +348,7 @@ const AppPageLayout: React.FC<{ schema: PageSchema }> = ({ schema }) => { }; /** Utility page — compact, focused, narrower */ -const UtilityPageLayout: React.FC<{ schema: PageSchema }> = ({ schema }) => { +const UtilityPageLayout: React.FC<{ schema: PageNodeSchema }> = ({ schema }) => { if (schema.regions && schema.regions.length > 0) { return ; } @@ -394,7 +394,7 @@ function getJsxManifest() { // --------------------------------------------------------------------------- export const PageRenderer: React.FC<{ - schema: PageSchema; + schema: PageNodeSchema; className?: string; [key: string]: any; }> = ({ schema, className, ...props }) => { @@ -528,7 +528,7 @@ export const PageRenderer: React.FC<{
{/* Page header — suppressed on record pages (the page:header component in the header region renders the record-bound title instead). - `title` is the objectui spelling; the spec's PageSchema declares + `title` is the objectui spelling; the spec's PageNodeSchema declares `label` (required), so dual-read it — mirrors the fallback DashboardRenderer already uses (framework#1878 §3 recheck). */} {pageType !== 'record' && (pageTitle || schema.description) && ( diff --git a/packages/core/src/utils/dashboard-filters.ts b/packages/core/src/utils/dashboard-filters.ts index a39e2b88a7..4d64faf5b6 100644 --- a/packages/core/src/utils/dashboard-filters.ts +++ b/packages/core/src/utils/dashboard-filters.ts @@ -20,7 +20,7 @@ * are unit-testable in isolation from React and the data layer. */ -import type { DashboardSchema, DashboardWidgetSchema, PageVariable } from '@object-ui/types'; +import type { DashboardComponentSchema, DashboardWidgetSchema, PageVariable } from '@object-ui/types'; /** Reserved filter name for the dashboard's built-in date range. */ export const DATE_RANGE_FILTER_NAME = 'dateRange'; @@ -125,7 +125,7 @@ function normalizeFilterOptions( * named by its `name` (defaulting to `field`). Later duplicates win. */ export function resolveDashboardFilterDefs( - schema: Pick, + schema: Pick, ): DashboardFilterDef[] { const byName = new Map(); diff --git a/packages/layout/src/AppSchemaRenderer.tsx b/packages/layout/src/AppSchemaRenderer.tsx index 9ae869ac89..79eb8eb94c 100644 --- a/packages/layout/src/AppSchemaRenderer.tsx +++ b/packages/layout/src/AppSchemaRenderer.tsx @@ -36,7 +36,7 @@ import { SidebarInput, useSidebar, } from '@object-ui/components'; -import type { AppSchema, NavigationItem, NavigationArea } from '@object-ui/types'; +import type { AppComponentSchema, NavigationItem, NavigationArea } from '@object-ui/types'; import { menuItemToNavigationItem } from '@object-ui/types'; import { AppShell, type AppShellBranding } from './AppShell'; import { @@ -57,7 +57,7 @@ export type MobileNavMode = 'drawer' | 'bottom_nav' | 'hamburger'; export interface AppSchemaRendererProps { /** The AppSchema JSON to render */ - schema: AppSchema; + schema: AppComponentSchema; /** Base URL prefix for generated hrefs (e.g. "/apps/crm") */ basePath?: string; @@ -279,7 +279,7 @@ function InternalSidebar({ enableReorder, onReorder, }: { - schema: AppSchema; + schema: AppComponentSchema; basePath: string; evalVis: VisibilityEvaluator; checkPerm: PermissionChecker; diff --git a/packages/layout/src/Page.tsx b/packages/layout/src/Page.tsx index 343bd70936..b3005d1cb0 100644 --- a/packages/layout/src/Page.tsx +++ b/packages/layout/src/Page.tsx @@ -1,7 +1,7 @@ // packages/layout/src/Page.tsx import React from 'react'; import { SchemaRenderer } from '@object-ui/react'; -import { PageSchema, SchemaNode } from '@object-ui/types'; +import { PageNodeSchema, SchemaNode } from '@object-ui/types'; import { PageHeader } from './PageHeader'; import { cn } from '@object-ui/components'; @@ -12,7 +12,7 @@ const getChildren = (children?: SchemaNode[] | SchemaNode): SchemaNode[] => { return [children]; }; -export function Page({ schema, className, style, id, ...props }: { schema: PageSchema; className?: string; style?: React.CSSProperties; id?: string } & any) { +export function Page({ schema, className, style, id, ...props }: { schema: PageNodeSchema; className?: string; style?: React.CSSProperties; id?: string } & any) { const children = getChildren(schema.children); return ( diff --git a/packages/layout/src/__tests__/AppSchemaRenderer.test.tsx b/packages/layout/src/__tests__/AppSchemaRenderer.test.tsx index 1f794d9d9c..aed3a73f8c 100644 --- a/packages/layout/src/__tests__/AppSchemaRenderer.test.tsx +++ b/packages/layout/src/__tests__/AppSchemaRenderer.test.tsx @@ -10,12 +10,12 @@ import { describe, it, expect, vi } from 'vitest'; import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; -import type { AppSchema, NavigationItem, NavigationArea } from '@object-ui/types'; +import type { AppComponentSchema, NavigationItem, NavigationArea } from '@object-ui/types'; import { AppSchemaRenderer } from '../AppSchemaRenderer'; /** Wrap component in MemoryRouter */ function renderApp( - schema: AppSchema, + schema: AppComponentSchema, props: Partial> = {}, initialEntries: string[] = ['/'], ) { @@ -32,7 +32,7 @@ function renderApp( // Fixtures // --------------------------------------------------------------------------- -const minimalSchema: AppSchema = { +const minimalSchema: AppComponentSchema = { type: 'app', name: 'crm', title: 'Sales CRM', @@ -44,7 +44,7 @@ const navItems: NavigationItem[] = [ { id: 'n3', type: 'page', label: 'Settings', icon: 'Settings', pageName: 'settings' }, ]; -const schemaWithNav: AppSchema = { +const schemaWithNav: AppComponentSchema = { type: 'app', name: 'crm', title: 'Sales CRM', @@ -70,7 +70,7 @@ const serviceArea: NavigationArea = { ], }; -const schemaWithAreas: AppSchema = { +const schemaWithAreas: AppComponentSchema = { type: 'app', name: 'crm', title: 'Sales CRM', @@ -123,7 +123,7 @@ describe('AppSchemaRenderer', () => { // #2918 — `type: 'component'` is part of the nav vocabulary; the sidebar // renders it as a link to the ComponentRegistry route. it('renders a component navigation item with its /component href', () => { - const schema: AppSchema = { + const schema: AppComponentSchema = { type: 'app', name: 'crm', title: 'Sales CRM', @@ -145,7 +145,7 @@ describe('AppSchemaRenderer', () => { // --- Legacy menu migration --- it('renders legacy menu items converted to NavigationItem', () => { - const legacySchema: AppSchema = { + const legacySchema: AppComponentSchema = { type: 'app', name: 'legacy', title: 'Legacy App', @@ -187,7 +187,7 @@ describe('AppSchemaRenderer', () => { // --- Area visibility and permissions --- it('hides areas that fail visibility check', () => { - const schemaWithHiddenArea: AppSchema = { + const schemaWithHiddenArea: AppComponentSchema = { type: 'app', name: 'crm', title: 'CRM', @@ -207,7 +207,7 @@ describe('AppSchemaRenderer', () => { }); it('hides areas that fail permission check', () => { - const schemaWithPermArea: AppSchema = { + const schemaWithPermArea: AppComponentSchema = { type: 'app', name: 'crm', title: 'CRM', @@ -257,7 +257,7 @@ describe('AppSchemaRenderer', () => { // --- Permission & visibility on nav items --- it('hides navigation items based on visibility', () => { - const schema: AppSchema = { + const schema: AppComponentSchema = { type: 'app', name: 'crm', title: 'CRM', @@ -274,7 +274,7 @@ describe('AppSchemaRenderer', () => { }); it('hides navigation items based on permissions', () => { - const schema: AppSchema = { + const schema: AppComponentSchema = { type: 'app', name: 'crm', title: 'CRM', diff --git a/packages/plugin-dashboard/src/DashboardGridLayout.tsx b/packages/plugin-dashboard/src/DashboardGridLayout.tsx index b7110bff2a..47cbf104cd 100644 --- a/packages/plugin-dashboard/src/DashboardGridLayout.tsx +++ b/packages/plugin-dashboard/src/DashboardGridLayout.tsx @@ -4,7 +4,7 @@ import 'react-grid-layout/css/styles.css'; import { cn, Card, CardHeader, CardTitle, CardContent, Button } from '@object-ui/components'; import { Edit, GripVertical, Save, X, RefreshCw } from 'lucide-react'; import { SchemaRenderer, useHasDndProvider, useDnd } from '@object-ui/react'; -import type { DashboardSchema, DashboardWidgetSchema } from '@object-ui/types'; +import type { DashboardComponentSchema, DashboardWidgetSchema } from '@object-ui/types'; import { isObjectProvider } from './utils'; import { classifyWidgetType } from './widgetDispatch'; @@ -33,7 +33,7 @@ const CHART_COLORS = [ ]; export interface DashboardGridLayoutProps { - schema: DashboardSchema; + schema: DashboardComponentSchema; className?: string; /** * Fires on every drag/resize tick with the raw react-grid-layout payload. @@ -50,16 +50,16 @@ export interface DashboardGridLayoutProps { * persist to localStorage or anywhere else (per Rule #1 Protocol Agnostic: * persistence is the parent's responsibility, not the renderer's). */ - onSchemaChange?: (schema: DashboardSchema) => void; + onSchemaChange?: (schema: DashboardComponentSchema) => void; /** Callback invoked when dashboard refresh is triggered (manual or auto) */ onRefresh?: () => void; } /** Merge react-grid-layout coordinates back into a DashboardSchema's widgets. */ export function mergeLayoutIntoSchema( - schema: DashboardSchema, + schema: DashboardComponentSchema, layout: RGLLayout[], -): DashboardSchema { +): DashboardComponentSchema { if (!schema.widgets?.length) return schema; const byId = new Map(layout.map((l) => [l.i, l])); const widgets = schema.widgets.map((w, index) => { @@ -74,7 +74,7 @@ export function mergeLayoutIntoSchema( return { ...schema, widgets }; } -function buildDefaultLayouts(schema: DashboardSchema): { lg: RGLLayout[] } { +function buildDefaultLayouts(schema: DashboardComponentSchema): { lg: RGLLayout[] } { return { lg: schema.widgets?.map((widget: DashboardWidgetSchema, index: number) => ({ i: widget.id || `widget-${index}`, diff --git a/packages/plugin-dashboard/src/DashboardRenderer.tsx b/packages/plugin-dashboard/src/DashboardRenderer.tsx index 3b4ee2e3b8..70a113db35 100644 --- a/packages/plugin-dashboard/src/DashboardRenderer.tsx +++ b/packages/plugin-dashboard/src/DashboardRenderer.tsx @@ -6,7 +6,7 @@ * LICENSE file in the root directory of this source tree. */ -import type { DashboardSchema, DashboardWidgetSchema } from '@object-ui/types'; +import type { DashboardComponentSchema, DashboardWidgetSchema } from '@object-ui/types'; import { SchemaRenderer, useActionEngine, useObjectLabel, PageVariablesProvider, usePageVariables } from '@object-ui/react'; import { useObjectTranslation } from '@object-ui/i18n'; import type { ActionDef, ActionResult, ActionContext, ModalHandler } from '@object-ui/core'; @@ -160,7 +160,7 @@ const LEGACY_RETIRED_WIDGET_SCHEMA = { } as const; export interface DashboardRendererProps { - schema: DashboardSchema; + schema: DashboardComponentSchema; className?: string; /** Callback invoked when dashboard refresh is triggered (manual or auto) */ onRefresh?: () => void; diff --git a/packages/plugin-dashboard/src/DashboardWithConfig.tsx b/packages/plugin-dashboard/src/DashboardWithConfig.tsx index 555d3d4691..7e5551b2d2 100644 --- a/packages/plugin-dashboard/src/DashboardWithConfig.tsx +++ b/packages/plugin-dashboard/src/DashboardWithConfig.tsx @@ -10,7 +10,7 @@ import * as React from 'react'; import { useState, useCallback, useEffect } from 'react'; import { Settings } from 'lucide-react'; import { cn, Button } from '@object-ui/components'; -import type { DashboardSchema, DashboardWidgetSchema } from '@object-ui/types'; +import type { DashboardComponentSchema, DashboardWidgetSchema } from '@object-ui/types'; import { DashboardRenderer } from './DashboardRenderer'; import { DashboardConfigPanel } from './DashboardConfigPanel'; @@ -23,7 +23,7 @@ import type { WidgetDatasetCatalogEntry } from './dataset-catalog'; export interface DashboardWithConfigProps { /** Dashboard schema for rendering */ - schema: DashboardSchema; + schema: DashboardComponentSchema; /** Current dashboard configuration (for the config panel) */ config: Record; /** Called when config panel saves dashboard-level changes */ @@ -81,7 +81,7 @@ export function DashboardWithConfig({ // Internal schema state for live preview during widget editing. // Updated on every field change; reset when external schema prop changes. - const [liveSchema, setLiveSchema] = useState(schema); + const [liveSchema, setLiveSchema] = useState(schema); const [configVersion, setConfigVersion] = useState(0); useEffect(() => { diff --git a/packages/plugin-dashboard/src/__tests__/DashboardGridLayout.persistence.test.ts b/packages/plugin-dashboard/src/__tests__/DashboardGridLayout.persistence.test.ts index af0b59ac89..7cc82079ed 100644 --- a/packages/plugin-dashboard/src/__tests__/DashboardGridLayout.persistence.test.ts +++ b/packages/plugin-dashboard/src/__tests__/DashboardGridLayout.persistence.test.ts @@ -1,8 +1,8 @@ import { describe, it, expect } from 'vitest'; import { mergeLayoutIntoSchema } from '../DashboardGridLayout'; -import type { DashboardSchema } from '@object-ui/types'; +import type { DashboardComponentSchema } from '@object-ui/types'; -const SCHEMA: DashboardSchema = { +const SCHEMA: DashboardComponentSchema = { type: 'dashboard', name: 'demo', title: 'Demo', @@ -38,12 +38,12 @@ describe('mergeLayoutIntoSchema', () => { }); it('returns the original schema reference when there are no widgets', () => { - const empty: DashboardSchema = { type: 'dashboard', name: 'empty', widgets: [] }; + const empty: DashboardComponentSchema = { type: 'dashboard', name: 'empty', widgets: [] }; expect(mergeLayoutIntoSchema(empty, [{ i: 'x', x: 0, y: 0, w: 1, h: 1 }])).toBe(empty); }); it('falls back to "widget-${index}" id for widgets missing an explicit id', () => { - const schema: DashboardSchema = { + const schema: DashboardComponentSchema = { type: 'dashboard', name: 'unnamed', widgets: [{ title: 'no id', type: 'metric' }], diff --git a/packages/plugin-dashboard/src/__tests__/DashboardRenderer.designMode.test.tsx b/packages/plugin-dashboard/src/__tests__/DashboardRenderer.designMode.test.tsx index 96dbfa8e91..982e90e9dd 100644 --- a/packages/plugin-dashboard/src/__tests__/DashboardRenderer.designMode.test.tsx +++ b/packages/plugin-dashboard/src/__tests__/DashboardRenderer.designMode.test.tsx @@ -1,7 +1,7 @@ import { describe, it, expect, vi } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/react'; import { DashboardRenderer } from '../DashboardRenderer'; -import type { DashboardSchema } from '@object-ui/types'; +import type { DashboardComponentSchema } from '@object-ui/types'; // Mock SchemaRenderer to avoid pulling in the full renderer tree. // Forwards className and includes an interactive child to simulate real chart content. @@ -19,7 +19,7 @@ vi.mock('@object-ui/react', async () => { }; }); -const DASHBOARD_WITH_WIDGETS: DashboardSchema = { +const DASHBOARD_WITH_WIDGETS: DashboardComponentSchema = { type: 'dashboard', title: 'Test Dashboard', columns: 2, diff --git a/packages/plugin-dashboard/src/__tests__/DashboardRenderer.filters.test.tsx b/packages/plugin-dashboard/src/__tests__/DashboardRenderer.filters.test.tsx index b3eb7b2b9f..1ed0422370 100644 --- a/packages/plugin-dashboard/src/__tests__/DashboardRenderer.filters.test.tsx +++ b/packages/plugin-dashboard/src/__tests__/DashboardRenderer.filters.test.tsx @@ -20,7 +20,7 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup, fireEvent, waitFor } from '@testing-library/react'; -import type { DashboardSchema } from '@object-ui/types'; +import type { DashboardComponentSchema } from '@object-ui/types'; import { DashboardRenderer } from '../DashboardRenderer'; afterEach(cleanup); @@ -41,7 +41,7 @@ const lastRuntimeFilter = ( describe('DashboardRenderer dashboard-level filters', () => { it('renders no filter bar when the schema declares no filters', async () => { const queryDataset = makeQueryDataset(); - const schema: DashboardSchema = { + const schema: DashboardComponentSchema = { type: 'dashboard', widgets: [{ id: 'w1', type: 'bar', dataset: 'invoices', values: ['count'] }], }; @@ -53,7 +53,7 @@ describe('DashboardRenderer dashboard-level filters', () => { it('broadcasts the default date range into each widget via its own bound field', async () => { const queryDataset = makeQueryDataset(); - const schema: DashboardSchema = { + const schema: DashboardComponentSchema = { type: 'dashboard', dateRange: { field: 'created_at', defaultRange: 'last_30_days', allowCustomRange: true }, widgets: [ @@ -78,7 +78,7 @@ describe('DashboardRenderer dashboard-level filters', () => { it('merges the broadcast with a widget\'s own filter and honors opt-out', async () => { const queryDataset = makeQueryDataset(); - const schema: DashboardSchema = { + const schema: DashboardComponentSchema = { type: 'dashboard', globalFilters: [ { name: 'region', field: 'region', type: 'select', options: ['EMEA', 'APAC'], defaultValue: 'EMEA' }, @@ -102,7 +102,7 @@ describe('DashboardRenderer dashboard-level filters', () => { it('re-scopes all bound widgets live when a filter value changes', async () => { const queryDataset = makeQueryDataset(); - const schema: DashboardSchema = { + const schema: DashboardComponentSchema = { type: 'dashboard', globalFilters: [{ name: 'q', field: 'name', type: 'text', label: 'Search' }], widgets: [ @@ -135,7 +135,7 @@ describe('DashboardRenderer dashboard-level filters', () => { it('injects the merged filter into a dataset widget\'s runtimeFilter', async () => { const queryDataset = vi.fn(async () => ({ rows: [{ revenue: 1 }] })); - const schema: DashboardSchema = { + const schema: DashboardComponentSchema = { type: 'dashboard', globalFilters: [ { name: 'region', field: 'region', type: 'select', options: ['EMEA'], defaultValue: 'EMEA' }, diff --git a/packages/plugin-dashboard/src/__tests__/DashboardRenderer.headerActions.test.tsx b/packages/plugin-dashboard/src/__tests__/DashboardRenderer.headerActions.test.tsx index b7a3afac4e..c08633759b 100644 --- a/packages/plugin-dashboard/src/__tests__/DashboardRenderer.headerActions.test.tsx +++ b/packages/plugin-dashboard/src/__tests__/DashboardRenderer.headerActions.test.tsx @@ -20,13 +20,13 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import type { DashboardSchema } from '@object-ui/types'; +import type { DashboardComponentSchema } from '@object-ui/types'; import { ActionProvider } from '@object-ui/react'; import { DashboardRenderer } from '../DashboardRenderer'; afterEach(cleanup); -function dashboardWith(actionType: string): DashboardSchema { +function dashboardWith(actionType: string): DashboardComponentSchema { return { type: 'dashboard', title: 'Ops', @@ -34,7 +34,7 @@ function dashboardWith(actionType: string): DashboardSchema { header: { actions: [{ label: 'Convert Lead', actionUrl: 'convert_lead_wizard', actionType }], }, - } as unknown as DashboardSchema; + } as unknown as DashboardComponentSchema; } describe('DashboardRenderer header actions', () => { diff --git a/packages/plugin-dashboard/src/__tests__/DashboardRenderer.legacyRetired.test.tsx b/packages/plugin-dashboard/src/__tests__/DashboardRenderer.legacyRetired.test.tsx index c29e45e65e..06e50619db 100644 --- a/packages/plugin-dashboard/src/__tests__/DashboardRenderer.legacyRetired.test.tsx +++ b/packages/plugin-dashboard/src/__tests__/DashboardRenderer.legacyRetired.test.tsx @@ -17,7 +17,7 @@ import { describe, it, expect, afterEach } from 'vitest'; import { render, screen, cleanup } from '@testing-library/react'; -import type { DashboardSchema } from '@object-ui/types'; +import type { DashboardComponentSchema } from '@object-ui/types'; import { DashboardRenderer } from '../DashboardRenderer'; afterEach(cleanup); @@ -33,13 +33,13 @@ describe('DashboardRenderer retired legacy widgets', () => { ['pivot', { type: 'pivot', object: 'invoices', rowField: 'region', valueField: 'amount' }], ['table', { type: 'table', object: 'invoices' }], ])('renders a visible placeholder for a legacy %s widget', (_kind, widget) => { - const schema: DashboardSchema = { type: 'dashboard', widgets: [legacyWidget(widget)] }; + const schema: DashboardComponentSchema = { type: 'dashboard', widgets: [legacyWidget(widget)] }; render(); expect(screen.getByText(/retired data format/i)).toBeInTheDocument(); }); it('does NOT show the placeholder for a dataset-bound widget', () => { - const schema: DashboardSchema = { + const schema: DashboardComponentSchema = { type: 'dashboard', widgets: [{ id: 'w1', type: 'bar', dataset: 'invoices', values: ['count'] }], }; @@ -48,7 +48,7 @@ describe('DashboardRenderer retired legacy widgets', () => { }); it('does NOT show the placeholder for a static options.data widget', () => { - const schema: DashboardSchema = { + const schema: DashboardComponentSchema = { type: 'dashboard', widgets: [{ id: 'w1', type: 'bar', options: { data: [{ name: 'A', value: 1 }] } }], }; diff --git a/packages/plugin-designer/src/DashboardEditor.tsx b/packages/plugin-designer/src/DashboardEditor.tsx index 6a5d62649d..e83f9b0d93 100644 --- a/packages/plugin-designer/src/DashboardEditor.tsx +++ b/packages/plugin-designer/src/DashboardEditor.tsx @@ -24,7 +24,7 @@ */ import React, { useState, useCallback, useEffect, useRef } from 'react'; -import type { DashboardSchema, DashboardWidgetSchema } from '@object-ui/types'; +import type { DashboardComponentSchema, DashboardWidgetSchema } from '@object-ui/types'; import { Trash2, GripVertical, @@ -59,17 +59,17 @@ function cn(...inputs: (string | undefined | false)[]) { export interface DashboardEditorProps { /** Dashboard schema to edit */ - schema: DashboardSchema; + schema: DashboardComponentSchema; /** Callback when schema changes */ - onChange: (schema: DashboardSchema) => void; + onChange: (schema: DashboardComponentSchema) => void; /** Read-only mode */ readOnly?: boolean; /** CSS class */ className?: string; /** Callback when JSON is exported */ - onExport?: (schema: DashboardSchema) => void; + onExport?: (schema: DashboardComponentSchema) => void; /** Callback when JSON is imported */ - onImport?: (schema: DashboardSchema) => void; + onImport?: (schema: DashboardComponentSchema) => void; /** Externally controlled selected widget ID */ selectedWidgetId?: string | null; /** Callback when widget selection changes (for external sync) */ @@ -319,7 +319,7 @@ function WidgetPropertyPanel({ // Preview Panel // ============================================================================ -function DashboardPreview({ schema }: { schema: DashboardSchema }) { +function DashboardPreview({ schema }: { schema: DashboardComponentSchema }) { const { t } = useDesignerTranslation(); const widgets = schema.widgets || []; return ( @@ -385,10 +385,10 @@ export function DashboardEditor({ push: pushHistory, undo, redo, - } = useUndoRedo(schema); + } = useUndoRedo(schema); const applyChange = useCallback( - (newSchema: DashboardSchema) => { + (newSchema: DashboardComponentSchema) => { pushHistory(newSchema); onChange(newSchema); }, @@ -502,7 +502,7 @@ export function DashboardEditor({ const reader = new FileReader(); reader.onload = () => { try { - const parsed = JSON.parse(reader.result as string) as DashboardSchema; + const parsed = JSON.parse(reader.result as string) as DashboardComponentSchema; if (parsed && parsed.type === 'dashboard') { applyChange(parsed); onImport?.(parsed); diff --git a/packages/plugin-designer/src/pages/DashboardDesignPage.tsx b/packages/plugin-designer/src/pages/DashboardDesignPage.tsx index 604c9ceec4..f8a5d4f448 100644 --- a/packages/plugin-designer/src/pages/DashboardDesignPage.tsx +++ b/packages/plugin-designer/src/pages/DashboardDesignPage.tsx @@ -9,7 +9,7 @@ import { useState, useCallback, useEffect, useRef } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { DashboardEditor } from '../DashboardEditor'; -import type { DashboardSchema } from '@object-ui/types'; +import type { DashboardComponentSchema } from '@object-ui/types'; import { toast } from 'sonner'; import { useAdapter } from '@object-ui/react'; import { useMetadata } from '@object-ui/react'; @@ -23,9 +23,9 @@ export function DashboardDesignPage() { const dashboard = dashboards?.find((d: any) => d.name === dashboardName); - const [schema, setSchema] = useState( + const [schema, setSchema] = useState( () => - (dashboard as DashboardSchema) || { + (dashboard as DashboardComponentSchema) || { type: 'dashboard', name: dashboardName ?? '', title: dashboardName ?? '', @@ -37,7 +37,7 @@ export function DashboardDesignPage() { schemaRef.current = schema; const saveSchema = useCallback( - async (toSave: DashboardSchema) => { + async (toSave: DashboardComponentSchema) => { try { if (dataSource) { await dataSource.update('sys_dashboard', dashboardName!, toSave); @@ -54,7 +54,7 @@ export function DashboardDesignPage() { ); const handleChange = useCallback( - async (updated: DashboardSchema) => { + async (updated: DashboardComponentSchema) => { setSchema(updated); await saveSchema(updated); }, @@ -76,7 +76,7 @@ export function DashboardDesignPage() { }, [saveSchema]); const handleExport = useCallback( - (exported: DashboardSchema) => { + (exported: DashboardComponentSchema) => { const blob = new Blob([JSON.stringify(exported, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); @@ -90,7 +90,7 @@ export function DashboardDesignPage() { ); const handleImport = useCallback( - (imported: DashboardSchema) => { + (imported: DashboardComponentSchema) => { toast.success('Dashboard schema imported'); handleChange(imported); }, diff --git a/packages/react/src/SchemaRenderer.tsx b/packages/react/src/SchemaRenderer.tsx index 86b812b4ba..2e6723bcf5 100644 --- a/packages/react/src/SchemaRenderer.tsx +++ b/packages/react/src/SchemaRenderer.tsx @@ -300,7 +300,7 @@ export const SchemaRenderer = forwardRef(null); - const [pageSchema, setPageSchema] = useState(null); + const [appConfig, setAppConfig] = useState(null); + const [pageSchema, setPageSchema] = useState(null); const [currentPath, setCurrentPath] = useState(window.location.pathname); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); diff --git a/packages/runner/src/LayoutRenderer.tsx b/packages/runner/src/LayoutRenderer.tsx index 24f5f6d83c..e1fe8fb3d9 100644 --- a/packages/runner/src/LayoutRenderer.tsx +++ b/packages/runner/src/LayoutRenderer.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import type { AppSchema } from '@object-ui/types'; +import type { AppComponentSchema } from '@object-ui/types'; import { Bell, Box, @@ -36,7 +36,7 @@ import { } from '@object-ui/components'; interface LayoutRendererProps { - app: AppSchema; + app: AppComponentSchema; children: React.ReactNode; currentPath?: string; onNavigate?: (path: string) => void; diff --git a/packages/runner/src/lib/MetadataLoader.ts b/packages/runner/src/lib/MetadataLoader.ts index 7d196539ac..e61fc32e95 100644 --- a/packages/runner/src/lib/MetadataLoader.ts +++ b/packages/runner/src/lib/MetadataLoader.ts @@ -6,11 +6,11 @@ * LICENSE file in the root directory of this source tree. */ -import { AppSchema, PageSchema } from '@object-ui/types'; +import { AppComponentSchema, PageNodeSchema } from '@object-ui/types'; export interface MetadataLoader { - loadAppConfig(): Promise; - loadPage(path: string): Promise; + loadAppConfig(): Promise; + loadPage(path: string): Promise; } /** @@ -22,7 +22,7 @@ export class LocalBundleLoader implements MetadataLoader { private pagesGlob = import.meta.glob('../app-data/pages/**/*.json'); private rootGlob = import.meta.glob('../app-data/*.json'); - async loadAppConfig(): Promise { + async loadAppConfig(): Promise { const key = '../app-data/app.json'; if (this.appGlob[key]) { const mod: any = await this.appGlob[key](); @@ -31,7 +31,7 @@ export class LocalBundleLoader implements MetadataLoader { return null; } - async loadPage(path: string): Promise { + async loadPage(path: string): Promise { // 1. Normalize Path const normalizedPath = path.replace(/^\//, '') || 'index'; @@ -79,7 +79,7 @@ export class NetworkLoader implements MetadataLoader { this.baseUrl = baseUrl; } - async loadAppConfig(): Promise { + async loadAppConfig(): Promise { try { const res = await fetch(`${this.baseUrl}/app.json`); if (!res.ok) return null; @@ -89,7 +89,7 @@ export class NetworkLoader implements MetadataLoader { } } - async loadPage(path: string): Promise { + async loadPage(path: string): Promise { try { // Maps /customers -> /api/pages/customers.json const jsonPath = path === '/' ? '/index' : path; diff --git a/packages/types/src/__tests__/app-creation-types.test.ts b/packages/types/src/__tests__/app-creation-types.test.ts index 4be21e89a6..7c908e58a0 100644 --- a/packages/types/src/__tests__/app-creation-types.test.ts +++ b/packages/types/src/__tests__/app-creation-types.test.ts @@ -14,7 +14,7 @@ import type { BrandingConfig, ObjectSelection, EditorMode, - AppSchema, + AppComponentSchema, } from '../index'; describe('App Creation Types', () => { @@ -44,7 +44,7 @@ describe('App Creation Types', () => { }); describe('wizardDraftToAppSchema', () => { - it('should convert draft to AppSchema', () => { + it('should convert draft to AppComponentSchema', () => { const draft: AppWizardDraft = { name: 'test_app', title: 'Test Application', @@ -62,7 +62,7 @@ describe('App Creation Types', () => { }, }; - const schema: AppSchema = wizardDraftToAppSchema(draft); + const schema: AppComponentSchema = wizardDraftToAppSchema(draft); expect(schema.type).toBe('app'); expect(schema.name).toBe('test_app'); expect(schema.title).toBe('Test Application'); diff --git a/packages/types/src/__tests__/navigation-model.test.ts b/packages/types/src/__tests__/navigation-model.test.ts index bd3a82f4ea..f3e6518a7b 100644 --- a/packages/types/src/__tests__/navigation-model.test.ts +++ b/packages/types/src/__tests__/navigation-model.test.ts @@ -6,7 +6,7 @@ */ import { describe, it, expect } from 'vitest'; import { - AppSchema, + AppComponentSchema, NavigationItemSchema, NavigationAreaSchema, } from '../zod/index.zod'; @@ -226,8 +226,8 @@ describe('NavigationArea Zod Schema', () => { // AppSchema with navigation and areas // ============================================================================ -describe('AppSchema with unified navigation', () => { - it('should validate AppSchema with navigation field', () => { +describe('AppComponentSchema with unified navigation', () => { + it('should validate AppComponentSchema with navigation field', () => { const app = { type: 'app', name: 'crm', @@ -238,11 +238,11 @@ describe('AppSchema with unified navigation', () => { { id: 'nav_contacts', type: 'object', label: 'Contacts', objectName: 'contact' }, ], }; - const result = AppSchema.safeParse(app); + const result = AppComponentSchema.safeParse(app); expect(result.success).toBe(true); }); - it('should validate AppSchema with areas', () => { + it('should validate AppComponentSchema with areas', () => { const app = { type: 'app', name: 'enterprise_crm', @@ -267,11 +267,11 @@ describe('AppSchema with unified navigation', () => { }, ], }; - const result = AppSchema.safeParse(app); + const result = AppComponentSchema.safeParse(app); expect(result.success).toBe(true); }); - it('should validate AppSchema with both legacy menu and new navigation', () => { + it('should validate AppComponentSchema with both legacy menu and new navigation', () => { const app = { type: 'app', name: 'migration_app', @@ -282,7 +282,7 @@ describe('AppSchema with unified navigation', () => { { id: 'nav_home', type: 'page', label: 'Home', pageName: 'home' }, ], }; - const result = AppSchema.safeParse(app); + const result = AppComponentSchema.safeParse(app); expect(result.success).toBe(true); }); }); diff --git a/packages/types/src/__tests__/p1-spec-alignment.test.ts b/packages/types/src/__tests__/p1-spec-alignment.test.ts index 49bd3b875d..9d4b36551d 100644 --- a/packages/types/src/__tests__/p1-spec-alignment.test.ts +++ b/packages/types/src/__tests__/p1-spec-alignment.test.ts @@ -20,11 +20,11 @@ import type { ObjectFormSection, // P1.3 Dashboard types DashboardWidgetSchema, - DashboardSchema, + DashboardComponentSchema, // P1.4 Page types PageType, PageVariable, - PageSchema, + PageNodeSchema, // P1.5 Record component types RecordDetailsComponentProps, RecordHighlightsComponentProps, @@ -342,7 +342,7 @@ describe('P1.3 Dashboard Spec Alignment', () => { }); it('should accept globalFilters with optionsFrom', () => { - const dashboard: DashboardSchema = { + const dashboard: DashboardComponentSchema = { type: 'dashboard', widgets: [], globalFilters: [ @@ -364,7 +364,7 @@ describe('P1.3 Dashboard Spec Alignment', () => { }); it('should accept date range filter', () => { - const dashboard: DashboardSchema = { + const dashboard: DashboardComponentSchema = { type: 'dashboard', widgets: [], dateRange: { @@ -378,7 +378,7 @@ describe('P1.3 Dashboard Spec Alignment', () => { }); it('should accept DashboardHeader with actions', () => { - const dashboard: DashboardSchema = { + const dashboard: DashboardComponentSchema = { type: 'dashboard', widgets: [], header: { @@ -421,7 +421,7 @@ describe('P1.4 Page Composition Spec Alignment', () => { 'grid', 'list', 'gallery', 'kanban', 'calendar', 'timeline', ]; allTypes.forEach((type) => { - const page: PageSchema = { + const page: PageNodeSchema = { type: 'page', pageType: type, }; @@ -443,7 +443,7 @@ describe('P1.4 Page Composition Spec Alignment', () => { // blankLayout config were dropped: no renderer (framework#2265).) it('should accept page ARIA properties', () => { - const page: PageSchema = { + const page: PageNodeSchema = { type: 'page', aria: { ariaLabel: 'Account Details Page', @@ -580,8 +580,8 @@ describe('P1.6 i18n & ARIA Protocol Alignment', () => { expect(schema.aria?.live).toBe('polite'); }); - it('should accept ARIA props on DashboardSchema', () => { - const schema: DashboardSchema = { + it('should accept ARIA props on DashboardComponentSchema', () => { + const schema: DashboardComponentSchema = { type: 'dashboard', widgets: [], aria: { @@ -592,8 +592,8 @@ describe('P1.6 i18n & ARIA Protocol Alignment', () => { expect(schema.aria?.ariaLabel).toBe('Sales Dashboard'); }); - it('should accept ARIA props on PageSchema', () => { - const schema: PageSchema = { + it('should accept ARIA props on PageNodeSchema', () => { + const schema: PageNodeSchema = { type: 'page', aria: { ariaLabel: 'Home Page', diff --git a/packages/types/src/__tests__/page-app-dashboard-spec-parity.test.ts b/packages/types/src/__tests__/page-app-dashboard-spec-parity.test.ts index 2821597936..2989191600 100644 --- a/packages/types/src/__tests__/page-app-dashboard-spec-parity.test.ts +++ b/packages/types/src/__tests__/page-app-dashboard-spec-parity.test.ts @@ -45,9 +45,9 @@ import { DashboardSchema as SpecDashboardSchema, PageSchema as SpecPageSchema, } from '@objectstack/spec/ui'; -import { AppSchema as OuiAppSchema } from '../zod/app.zod.js'; -import { DashboardSchema as OuiDashboardSchema } from '../zod/complex.zod.js'; -import { PageSchema as OuiPageSchema } from '../zod/layout.zod.js'; +import { AppComponentSchema as OuiAppSchema } from '../zod/app.zod.js'; +import { DashboardComponentSchema as OuiDashboardSchema } from '../zod/complex.zod.js'; +import { PageNodeSchema as OuiPageSchema } from '../zod/layout.zod.js'; import { BaseSchema } from '../zod/base.zod.js'; const shapeOf = (s: unknown) => (s as { shape: Record }).shape; diff --git a/packages/types/src/__tests__/phase2-schemas.test.ts b/packages/types/src/__tests__/phase2-schemas.test.ts index 32d226d152..22aff8617c 100644 --- a/packages/types/src/__tests__/phase2-schemas.test.ts +++ b/packages/types/src/__tests__/phase2-schemas.test.ts @@ -4,7 +4,7 @@ */ import { describe, it, expect } from 'vitest'; import { - AppSchema, + AppComponentSchema, AppActionSchema, AppMenuItemSchema, ThemeComponentSchema, @@ -30,8 +30,8 @@ import { ListViewSchema, } from '../zod/index.zod'; -describe('Phase 2: AppSchema Zod Validation', () => { - it('should validate a complete AppSchema', () => { +describe('Phase 2: AppComponentSchema Zod Validation', () => { + it('should validate a complete AppComponentSchema', () => { const appConfig = { type: 'app', name: 'my-crm', @@ -82,7 +82,7 @@ describe('Phase 2: AppSchema Zod Validation', () => { ], }; - const result = AppSchema.safeParse(appConfig); + const result = AppComponentSchema.safeParse(appConfig); expect(result.success).toBe(true); if (result.success) { expect(result.data.type).toBe('app'); @@ -91,12 +91,12 @@ describe('Phase 2: AppSchema Zod Validation', () => { } }); - it('should validate minimal AppSchema', () => { + it('should validate minimal AppComponentSchema', () => { const minimal = { type: 'app', }; - const result = AppSchema.safeParse(minimal); + const result = AppComponentSchema.safeParse(minimal); expect(result.success).toBe(true); }); @@ -106,7 +106,7 @@ describe('Phase 2: AppSchema Zod Validation', () => { layout: 'invalid-layout', }; - const result = AppSchema.safeParse(invalid); + const result = AppComponentSchema.safeParse(invalid); expect(result.success).toBe(false); }); }); diff --git a/packages/types/src/app.ts b/packages/types/src/app.ts index 7df8eeb40f..dd21364b24 100644 --- a/packages/types/src/app.ts +++ b/packages/types/src/app.ts @@ -221,7 +221,7 @@ export interface NavigationArea { /** * Top-level Application Configuration (app.json) */ -export interface AppSchema extends BaseSchema { +export interface AppComponentSchema extends BaseSchema { type: 'app'; /** @@ -552,7 +552,7 @@ export function isValidAppName(name: string): boolean { /** * Convert an AppWizardDraft to an AppSchema. */ -export function wizardDraftToAppSchema(draft: AppWizardDraft): AppSchema { +export function wizardDraftToAppSchema(draft: AppWizardDraft): AppComponentSchema { return { type: 'app', name: draft.name, diff --git a/packages/types/src/complex.ts b/packages/types/src/complex.ts index 9f3e48ed2b..7db83ceced 100644 --- a/packages/types/src/complex.ts +++ b/packages/types/src/complex.ts @@ -749,7 +749,7 @@ export interface DashboardWidgetSchema { /** * Dashboard Schema */ -export interface DashboardSchema extends BaseSchema { +export interface DashboardComponentSchema extends BaseSchema { type: 'dashboard'; /** Dashboard title displayed in the header */ title?: string; @@ -835,4 +835,4 @@ export type ComplexSchema = | FilterBuilderSchema | CarouselSchema | ChatbotSchema - | DashboardSchema; + | DashboardComponentSchema; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 9da5089e7d..15604c93e7 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -51,7 +51,7 @@ // Application - Global Configuration // ============================================================================ export type { - AppSchema, + AppComponentSchema, AppAction, NavigationItem, NavigationItemType, @@ -107,7 +107,7 @@ export type { ResizablePanel, AspectRatioSchema, LayoutSchema, - PageSchema, + PageNodeSchema, PageSlotMap, PageType, PageRegion, @@ -256,7 +256,7 @@ export type { CarouselSchema, DashboardWidgetLayout, DashboardWidgetSchema, - DashboardSchema, + DashboardComponentSchema, ChatMessage, ChatMessageSource, ChatToolInvocation, @@ -637,17 +637,17 @@ export type { // ============================================================================ import type { BaseSchema, SchemaNode } from './base'; -import type { LayoutSchema, PageSchema } from './layout'; +import type { LayoutSchema, PageNodeSchema } from './layout'; import type { FormComponentSchema } from './form'; import type { DataDisplaySchema } from './data-display'; import type { FeedbackSchema } from './feedback'; import type { DisclosureSchema } from './disclosure'; import type { OverlaySchema } from './overlay'; import type { NavigationSchema } from './navigation'; -import type { ComplexSchema, DashboardSchema } from './complex'; +import type { ComplexSchema, DashboardComponentSchema } from './complex'; import type { CRUDComponentSchema } from './crud'; import type { ObjectQLComponentSchema, ListViewSchema } from './objectql'; -import type { AppSchema } from './app'; +import type { AppComponentSchema } from './app'; // ============================================================================ // Phase 2 Schemas - New Additions @@ -791,10 +791,10 @@ export type { * Use this for generic component rendering where the type is determined at runtime. */ export type AnySchema = - | AppSchema + | AppComponentSchema | BaseSchema | LayoutSchema - | PageSchema + | PageNodeSchema | FormComponentSchema | DataDisplaySchema | FeedbackSchema @@ -802,7 +802,7 @@ export type AnySchema = | OverlaySchema | NavigationSchema | ComplexSchema - | DashboardSchema + | DashboardComponentSchema | CRUDComponentSchema | ObjectQLComponentSchema | ListViewSchema; diff --git a/packages/types/src/layout.ts b/packages/types/src/layout.ts index 82c1738541..ca26f8f38e 100644 --- a/packages/types/src/layout.ts +++ b/packages/types/src/layout.ts @@ -507,7 +507,7 @@ export interface PageRegion { * Top-level container for a page route. * Aligned with @objectstack/spec PageSchema */ -export interface PageSchema extends BaseSchema { +export interface PageNodeSchema extends BaseSchema { type: 'page'; /** * Page title @@ -666,5 +666,5 @@ export type LayoutSchema = | ScrollAreaSchema | ResizableSchema | AspectRatioSchema - | PageSchema; + | PageNodeSchema; diff --git a/packages/types/src/registry.ts b/packages/types/src/registry.ts index b33f2132fa..088736ba6d 100644 --- a/packages/types/src/registry.ts +++ b/packages/types/src/registry.ts @@ -20,7 +20,7 @@ import type { TabsSchema, ScrollAreaSchema, ResizableSchema, - PageSchema, + PageNodeSchema, } from './layout'; import type { @@ -115,7 +115,7 @@ export interface SchemaRegistry { 'tabs': TabsSchema; 'scroll-area': ScrollAreaSchema; 'resizable': ResizableSchema; - 'page': PageSchema; + 'page': PageNodeSchema; // Form 'button': ButtonSchema; diff --git a/packages/types/src/zod/app.zod.ts b/packages/types/src/zod/app.zod.ts index 4552f8429c..2214d6007e 100644 --- a/packages/types/src/zod/app.zod.ts +++ b/packages/types/src/zod/app.zod.ts @@ -187,7 +187,7 @@ const SpecAppFields = specFieldsExcept(SpecAppSchema.shape, [ * `@objectstack/spec/ui` `AppSchema` (see {@link SpecAppFields}). The drift * guard is `__tests__/page-app-dashboard-spec-parity.test.ts`. */ -export const AppSchema = BaseSchema.extend(SpecAppFields.shape).extend({ +export const AppComponentSchema = BaseSchema.extend(SpecAppFields.shape).extend({ type: z.literal('app'), name: z.string().optional().describe('Application name (system ID)'), title: z.string().optional().describe('Display title'), @@ -209,4 +209,4 @@ export type NavigationItemSchemaType = z.infer; export type NavigationAreaSchemaType = z.infer; export type MenuItemSchemaType = z.infer; export type AppActionSchemaType = z.infer; -export type AppSchemaType = z.infer; +export type AppSchemaType = z.infer; diff --git a/packages/types/src/zod/complex.zod.ts b/packages/types/src/zod/complex.zod.ts index 9357abee6f..d3a19eddee 100644 --- a/packages/types/src/zod/complex.zod.ts +++ b/packages/types/src/zod/complex.zod.ts @@ -356,7 +356,7 @@ const SpecDashboardFields = specFieldsExcept(SpecDashboardSchema.shape, [ * `@objectstack/spec/ui` `DashboardSchema` (see {@link SpecDashboardFields}). * The drift guard is `__tests__/page-app-dashboard-spec-parity.test.ts`. */ -export const DashboardSchema = BaseSchema.extend(SpecDashboardFields.shape).extend({ +export const DashboardComponentSchema = BaseSchema.extend(SpecDashboardFields.shape).extend({ type: z.literal('dashboard'), columns: z.number().optional().describe('Number of columns'), gap: z.number().optional().describe('Grid gap'), @@ -436,5 +436,5 @@ export const ComplexSchema = z.discriminatedUnion('type', [ FilterBuilderSchema, CarouselSchema, ChatbotSchema, - DashboardSchema, + DashboardComponentSchema, ]); diff --git a/packages/types/src/zod/index.zod.ts b/packages/types/src/zod/index.zod.ts index cfa9295266..39897552d3 100644 --- a/packages/types/src/zod/index.zod.ts +++ b/packages/types/src/zod/index.zod.ts @@ -38,7 +38,7 @@ // Application - Global Configuration // ============================================================================ export { - AppSchema, + AppComponentSchema, AppActionSchema, NavigationItemSchema, NavigationItemTypeSchema, @@ -85,7 +85,7 @@ export { PageRegionSchema, PageVariableSchema, PageTypeSchema, - PageSchema, + PageNodeSchema, LayoutSchema, } from './layout.zod.js'; @@ -231,7 +231,7 @@ export { ChatbotSchema, DashboardWidgetLayoutSchema, DashboardWidgetSchema, - DashboardSchema, + DashboardComponentSchema, DashboardWidgetConfigSchema, DashboardConfigSchema, ComplexSchema, @@ -342,7 +342,7 @@ export { // ============================================================================ import { z } from 'zod'; -import { AppSchema } from './app.zod.js'; +import { AppComponentSchema } from './app.zod.js'; import { LayoutSchema } from './layout.zod.js'; import { FormComponentSchema } from './form.zod.js'; import { DataDisplaySchema } from './data-display.zod.js'; @@ -363,7 +363,7 @@ import { ViewComponentSchema } from './views.zod.js'; * Use this for generic component rendering where the type is determined at runtime. */ export const AnyComponentSchema = z.union([ - AppSchema, + AppComponentSchema, LayoutSchema, FormComponentSchema, DataDisplaySchema, diff --git a/packages/types/src/zod/layout.zod.ts b/packages/types/src/zod/layout.zod.ts index dbe535a4e9..94e3a6f26a 100644 --- a/packages/types/src/zod/layout.zod.ts +++ b/packages/types/src/zod/layout.zod.ts @@ -308,7 +308,7 @@ const SpecPageFields = specFieldsExcept(SpecPageSchema.shape, [ * `PageSchema` (see {@link SpecPageFields}). The drift guard is * `__tests__/page-app-dashboard-spec-parity.test.ts`. */ -export const PageSchema = BaseSchema.extend(SpecPageFields.shape).extend({ +export const PageNodeSchema = BaseSchema.extend(SpecPageFields.shape).extend({ type: z.literal('page'), title: z.string().optional().describe('Page title'), icon: z.string().optional().describe('Page icon (Lucide icon name)'), @@ -343,5 +343,5 @@ export const LayoutSchema = z.discriminatedUnion('type', [ ScrollAreaSchema, ResizableSchema, AspectRatioSchema, - PageSchema, + PageNodeSchema, ]); diff --git a/scripts/check-spec-symbol-derivation.mjs b/scripts/check-spec-symbol-derivation.mjs index 578c2e942b..72103f13c0 100644 --- a/scripts/check-spec-symbol-derivation.mjs +++ b/scripts/check-spec-symbol-derivation.mjs @@ -133,14 +133,12 @@ const DEBT = { "@object-ui/types": [ "ActionParam", "AppContextSelectorSchema", - "AppSchema", "ChartSeries", "ChartSeriesSchema", "ConditionalValidation", "CreateExportJobRequest", "CreateExportJobResult", "CrossFieldValidation", - "DashboardSchema", "DashboardWidgetSchema", "DatasourceSchema", "DriverInterface", @@ -163,7 +161,6 @@ const DEBT = { "OfflineConfig", "PageRegion", "PageRegionSchema", - "PageSchema", "QueryAST", "QuerySchema", "ResponsiveConfig",