diff --git a/apps/console/src/mocks/server.ts b/apps/console/src/mocks/server.ts
index a7894663b..1fbb35773 100644
--- a/apps/console/src/mocks/server.ts
+++ b/apps/console/src/mocks/server.ts
@@ -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 });
@@ -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
@@ -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
diff --git a/packages/components/src/__tests__/view-compliance.test.tsx b/packages/components/src/__tests__/view-compliance.test.tsx
index d193cb264..66ebe8e76 100644
--- a/packages/components/src/__tests__/view-compliance.test.tsx
+++ b/packages/components/src/__tests__/view-compliance.test.tsx
@@ -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
diff --git a/packages/components/src/renderers/action/action-menu.tsx b/packages/components/src/renderers/action/action-menu.tsx
index b59053edd..b03d903cd 100644
--- a/packages/components/src/renderers/action/action-menu.tsx
+++ b/packages/components/src/renderers/action/action-menu.tsx
@@ -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';
@@ -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 ? : null;
+ }, [action.icon]);
if (action.visible && !isVisible) return null;
@@ -72,7 +76,7 @@ const ActionMenuItem: React.FC<{
action.className,
)}
>
- {Icon && }
+ {iconElement}
{action.label || action.name}
);
diff --git a/packages/components/src/renderers/layout/page.tsx b/packages/components/src/renderers/layout/page.tsx
index ee28600cb..14f45e93d 100644
--- a/packages/components/src/renderers/layout/page.tsx
+++ b/packages/components/src/renderers/layout/page.tsx
@@ -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';
@@ -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 ;
+ }
switch (pageType) {
case 'home':
- LayoutVariant = HomePageLayout;
- break;
+ return ;
case 'app':
- LayoutVariant = AppPageLayout;
- break;
+ return ;
case 'utility':
- LayoutVariant = UtilityPageLayout;
- break;
+ return ;
case 'record':
default:
- LayoutVariant = RecordPageLayout;
- break;
+ return ;
}
- }
+ }, [schema, pageType]);
const pageContent = (
+ {layoutElement}
);
diff --git a/packages/plugin-detail/src/DetailView.tsx b/packages/plugin-detail/src/DetailView.tsx
index bfc52316e..d81986051 100644
--- a/packages/plugin-detail/src/DetailView.tsx
+++ b/packages/plugin-detail/src/DetailView.tsx
@@ -75,7 +75,7 @@ export const DetailView: React.FC = ({
} else {
window.history.back();
}
- }, [onBack, schema.backUrl, schema.onNavigate, schema.objectName]);
+ }, [onBack, schema]);
const handleEdit = React.useCallback(() => {
if (onEdit) {
@@ -89,7 +89,7 @@ export const DetailView: React.FC = ({
} 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?';
@@ -102,7 +102,7 @@ export const DetailView: React.FC = ({
schema.onNavigate(`/${schema.objectName}`, { replace: true });
}
}
- }, [onDelete, schema.deleteConfirmation, schema.onNavigate, schema.objectName]);
+ }, [onDelete, schema]);
if (loading || schema.loading) {
return (
diff --git a/packages/plugin-form/src/ObjectForm.msw.test.tsx b/packages/plugin-form/src/ObjectForm.msw.test.tsx
index 3b8f35ff7..9ad21bf8f 100644
--- a/packages/plugin-form/src/ObjectForm.msw.test.tsx
+++ b/packages/plugin-form/src/ObjectForm.msw.test.tsx
@@ -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 });
})
diff --git a/packages/plugin-form/src/ObjectForm.tsx b/packages/plugin-form/src/ObjectForm.tsx
index b9a0d661c..62a0b3460 100644
--- a/packages/plugin-form/src/ObjectForm.tsx
+++ b/packages/plugin-form/src/ObjectForm.tsx
@@ -190,7 +190,17 @@ export const ObjectForm: React.FC = ({
);
}
- // Default: simple form (original implementation below)
+ // Default: simple form
+ return ;
+};
+
+/**
+ * SimpleObjectForm — default form variant with auto-generated fields from ObjectQL schema.
+ */
+const SimpleObjectForm: React.FC = ({
+ schema,
+ dataSource,
+}) => {
const [objectSchema, setObjectSchema] = useState(null);
const [formFields, setFormFields] = useState([]);
diff --git a/packages/plugin-report/package.json b/packages/plugin-report/package.json
index d453b6a5d..8ab0c19fa 100644
--- a/packages/plugin-report/package.json
+++ b/packages/plugin-report/package.json
@@ -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",
diff --git a/packages/plugin-view/src/index.tsx b/packages/plugin-view/src/index.tsx
index 9e6a20076..54567c1c5 100644
--- a/packages/plugin-view/src/index.tsx
+++ b/packages/plugin-view/src/index.tsx
@@ -23,13 +23,17 @@ 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 | null = null;
+const FallbackContext = React.createContext(null);
+let SchemaRendererContext: React.Context = 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
}
@@ -37,7 +41,7 @@ try {
// 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 ;