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
6 changes: 3 additions & 3 deletions apps/console/src/mocks/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ function createHandlers(baseUrl: string, kernel: ObjectKernel, driver: InMemoryD
) : null;

console.log('MSW: getData result', JSON.stringify(record));
return HttpResponse.json(record, { status: record ? 200 : 404 });
return HttpResponse.json({ record }, { status: record ? 200 : 404 });
} catch (e) {
console.error('MSW: getData error', e);
return HttpResponse.json({ error: String(e) }, { status: 500 });
Expand All @@ -205,7 +205,7 @@ function createHandlers(baseUrl: string, kernel: ObjectKernel, driver: InMemoryD
console.log('MSW: createData', params.objectName, JSON.stringify(body));
const response = await driver.create(params.objectName as string, body as any);
console.log('MSW: createData result', JSON.stringify(response));
return HttpResponse.json(response, { status: 201 });
return HttpResponse.json({ record: response }, { status: 201 });
}),

// Data endpoints - Update
Expand All @@ -214,7 +214,7 @@ function createHandlers(baseUrl: string, kernel: ObjectKernel, driver: InMemoryD
console.log('MSW: updateData', params.objectName, params.id, JSON.stringify(body));
const response = await driver.update(params.objectName as string, params.id as string, body as any);
console.log('MSW: updateData result', JSON.stringify(response));
return HttpResponse.json(response, { status: 200 });
return HttpResponse.json({ record: response }, { status: 200 });
}),

// Data endpoints - Delete
Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/__tests__/view-compliance.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ describe('View Component Compliance', () => {
// Check direct registration or via namespace aliasing
// ComponentRegistry.get checks namespaces.
// If registered as { type: 'grid', namespace: 'view' }, fullKey is 'view:grid'.
let hasView = ComponentRegistry.getAllConfigs().some(c => c.type === viewKey);
const hasView = ComponentRegistry.getAllConfigs().some(c => c.type === viewKey);

if (!hasView) {
// Try looking for non-namespaced if it is a view category
Expand Down
10 changes: 7 additions & 3 deletions packages/components/src/renderers/action/action-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* Each menu item triggers the corresponding action via ActionRunner.
*/

import React, { forwardRef, useCallback, useState } from 'react';
import React, { forwardRef, useCallback, useMemo, useState } from 'react';
import { ComponentRegistry } from '@object-ui/core';
import type { ActionSchema } from '@object-ui/types';
import { useAction } from '@object-ui/react';
Expand Down Expand Up @@ -56,7 +56,11 @@ const ActionMenuItem: React.FC<{
const isVisible = useCondition(action.visible ? `\${${action.visible}}` : undefined);
const isEnabled = useCondition(action.enabled ? `\${${action.enabled}}` : undefined);

const Icon = resolveIcon(action.icon);
const iconElement = useMemo(() => {
const Icon = resolveIcon(action.icon);
// eslint-disable-next-line react-hooks/static-components -- Icon is resolved from a stable icon registry
return Icon ? <Icon className="mr-2 h-4 w-4" /> : null;
}, [action.icon]);

if (action.visible && !isVisible) return null;

Expand All @@ -72,7 +76,7 @@ const ActionMenuItem: React.FC<{
action.className,
)}
>
{Icon && <Icon className="mr-2 h-4 w-4" />}
{iconElement}
<span>{action.label || action.name}</span>
</DropdownMenuItem>
);
Expand Down
32 changes: 14 additions & 18 deletions packages/components/src/renderers/layout/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* body/children for backward compatibility.
*/

import React from 'react';
import React, { useMemo } from 'react';
import type { PageSchema, PageRegion, SchemaNode } from '@object-ui/types';
import { SchemaRenderer, PageVariablesProvider } from '@object-ui/react';
import { ComponentRegistry } from '@object-ui/core';
Expand Down Expand Up @@ -357,29 +357,25 @@ export const PageRenderer: React.FC<{
} = props;

// Select the layout variant based on template or page type
const TemplateLayout = resolveTemplate(schema);
let LayoutVariant: React.FC<{ schema: PageSchema }>;

if (TemplateLayout) {
// Template takes priority over page type
LayoutVariant = TemplateLayout;
} else {
const layoutElement = useMemo(() => {
const TemplateLayout = resolveTemplate(schema);
if (TemplateLayout) {
// Template takes priority over page type
// eslint-disable-next-line react-hooks/static-components -- TemplateLayout is resolved from a stable template registry
return <TemplateLayout schema={schema} />;
}
switch (pageType) {
case 'home':
LayoutVariant = HomePageLayout;
break;
return <HomePageLayout schema={schema} />;
case 'app':
LayoutVariant = AppPageLayout;
break;
return <AppPageLayout schema={schema} />;
case 'utility':
LayoutVariant = UtilityPageLayout;
break;
return <UtilityPageLayout schema={schema} />;
case 'record':
default:
LayoutVariant = RecordPageLayout;
break;
return <RecordPageLayout schema={schema} />;
}
}
}, [schema, pageType]);

const pageContent = (
<div
Expand Down Expand Up @@ -409,7 +405,7 @@ export const PageRenderer: React.FC<{
)}

{/* Page body — type-specific layout */}
<LayoutVariant schema={schema} />
{layoutElement}
</div>
</div>
);
Expand Down
6 changes: 3 additions & 3 deletions packages/plugin-detail/src/DetailView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
} else {
window.history.back();
}
}, [onBack, schema.backUrl, schema.onNavigate, schema.objectName]);
}, [onBack, schema]);

const handleEdit = React.useCallback(() => {
if (onEdit) {
Expand All @@ -89,7 +89,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
} else if (schema.editUrl) {
window.location.href = schema.editUrl;
}
}, [onEdit, schema.editUrl, schema.onNavigate, schema.objectName, schema.resourceId]);
}, [onEdit, schema]);

const handleDelete = React.useCallback(() => {
const confirmMessage = schema.deleteConfirmation || 'Are you sure you want to delete this record?';
Expand All @@ -102,7 +102,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
schema.onNavigate(`/${schema.objectName}`, { replace: true });
}
}
}, [onDelete, schema.deleteConfirmation, schema.onNavigate, schema.objectName]);
}, [onDelete, schema]);

if (loading || schema.loading) {
return (
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-form/src/ObjectForm.msw.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ const handlers = [
http.get(`${BASE_URL}/api/v1/data/:object/:id`, ({ params }) => {
const { object, id } = params;
if (object === 'contact' && id === '1') {
return HttpResponse.json(mockRecord);
return HttpResponse.json({ record: mockRecord });
}
return new HttpResponse(null, { status: 404 });
})
Expand Down
12 changes: 11 additions & 1 deletion packages/plugin-form/src/ObjectForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,17 @@ export const ObjectForm: React.FC<ObjectFormProps> = ({
);
}

// Default: simple form (original implementation below)
// Default: simple form
return <SimpleObjectForm schema={schema} dataSource={dataSource} />;
};

/**
* SimpleObjectForm — default form variant with auto-generated fields from ObjectQL schema.
*/
const SimpleObjectForm: React.FC<ObjectFormProps> = ({
schema,
dataSource,
}) => {

const [objectSchema, setObjectSchema] = useState<any>(null);
const [formFields, setFormFields] = useState<FormField[]>([]);
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-report/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"build": "vite build",
"clean": "rm -rf dist",
"test": "vitest run",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
"lint": "eslint ."
},
"peerDependencies": {
"react": "^18.0.0",
Expand Down
10 changes: 7 additions & 3 deletions packages/plugin-view/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,25 @@ export type { SortUIProps } from './SortUI';
* SchemaRendererContext is created by @object-ui/react.
* We import it dynamically to avoid a circular dependency.
* The context value provides { dataSource }.
* A fallback context is created so hooks are never called conditionally.
*/
let SchemaRendererContext: React.Context<any> | null = null;
const FallbackContext = React.createContext<any>(null);
let SchemaRendererContext: React.Context<any> = FallbackContext;
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const mod = require('@object-ui/react');
// The context is re-exported from @object-ui/react
SchemaRendererContext = mod.SchemaRendererContext || null;
if (mod.SchemaRendererContext) {
SchemaRendererContext = mod.SchemaRendererContext;
}
} catch {
// @object-ui/react not available — registry-based dataSource only
}

// Register object-view component
const ObjectViewRenderer: React.FC<{ schema: any }> = ({ schema }) => {
// Resolve dataSource from SchemaRendererProvider context
const ctx = SchemaRendererContext ? useContext(SchemaRendererContext) : null;
const ctx = useContext(SchemaRendererContext);
const dataSource = ctx?.dataSource ?? null;

return <ObjectView schema={schema} dataSource={dataSource} />;
Expand Down
Loading