From d355809b3d86959451ad36f3d77438c8d0e38df9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 12:08:16 +0000 Subject: [PATCH 1/4] Initial plan From 4639e788ea13f950cc34adff4193f0d6254bac58 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 12:32:38 +0000 Subject: [PATCH 2/4] fix: remove key={refreshKey} from PluginObjectView and fix navigation priority for live preview - Remove key={refreshKey} from PluginObjectView to allow config changes to propagate without requiring component remount (save). Props changes now flow naturally. - Fix navigation overlay to use activeView.navigation with priority over objectDef.navigation, enabling view-level navigation mode changes to take effect immediately. - Mock ListView in tests to isolate data flow verification from async rendering effects. - Add tests for live preview of showRecordCount, allowPrinting, and label changes. Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- .../console/src/__tests__/ObjectView.test.tsx | 145 ++++++++++++++++++ apps/console/src/components/ObjectView.tsx | 4 +- 2 files changed, 147 insertions(+), 2 deletions(-) diff --git a/apps/console/src/__tests__/ObjectView.test.tsx b/apps/console/src/__tests__/ObjectView.test.tsx index 6321dc9a4..ee2bcca98 100644 --- a/apps/console/src/__tests__/ObjectView.test.tsx +++ b/apps/console/src/__tests__/ObjectView.test.tsx @@ -17,6 +17,24 @@ vi.mock('@object-ui/plugin-calendar', () => ({ ObjectCalendar: (props: any) =>
Calendar View: {props.schema.dateField}
})); +// Mock ListView to a simple component that renders schema properties as test IDs +// This isolates the config panel → view data flow from ListView's internal async effects +vi.mock('@object-ui/plugin-list', () => ({ + ListView: (props: any) => { + const viewType = props.schema?.viewType || 'grid'; + return ( +
+ {viewType === 'grid' &&
Grid View: {props.schema?.objectName}
} + {viewType === 'kanban' &&
Kanban View: {props.schema?.options?.kanban?.groupField || props.schema?.groupBy}
} + {viewType === 'calendar' &&
Calendar View: {props.schema?.options?.calendar?.startDateField || props.schema?.startDateField}
} + {props.schema?.showRecordCount &&
showRecordCount
} + {props.schema?.allowPrinting &&
allowPrinting
} + {props.schema?.navigation?.mode &&
{props.schema.navigation.mode}
} +
+ ); + }, +})); + vi.mock('@object-ui/components', async (importOriginal) => { const React = await import('react'); const MockTabsContext = React.createContext({ onValueChange: ( _v: any) => {} }); @@ -543,4 +561,131 @@ describe('ObjectView Component', () => { expect(breadcrumbItems.length).toBeGreaterThanOrEqual(1); }); }); + + it('does not remount PluginObjectView on config panel changes (no key={refreshKey})', async () => { + mockAuthUser = { id: 'u1', name: 'Admin', role: 'admin' }; + mockUseParams.mockReturnValue({ objectName: 'opportunity' }); + + render(); + + // The grid should be rendered initially + expect(screen.getByTestId('object-grid')).toBeInTheDocument(); + + // Open config panel + fireEvent.click(screen.getByTitle('console.objectView.designTools')); + fireEvent.click(screen.getByText('console.objectView.editView')); + + // Make a change + const titleInput = await screen.findByDisplayValue('All Opportunities'); + fireEvent.change(titleInput, { target: { value: 'Changed Live' } }); + + // The breadcrumb updates immediately (live preview) — this verifies that + // viewDraft → activeView data flow propagates config changes without save. + await vi.waitFor(() => { + expect(screen.getByText('Changed Live')).toBeInTheDocument(); + }); + + // Grid persists after config change (no remount) + expect(screen.getByTestId('object-grid')).toBeInTheDocument(); + }); + + it('propagates showRecordCount toggle to ListView schema in real-time', async () => { + mockAuthUser = { id: 'u1', name: 'Admin', role: 'admin' }; + mockUseParams.mockReturnValue({ objectName: 'opportunity' }); + + render(); + + // showRecordCount should not be set initially (default is not explicitly true) + expect(screen.queryByTestId('schema-showRecordCount')).not.toBeInTheDocument(); + + // Open config panel + fireEvent.click(screen.getByTitle('console.objectView.designTools')); + fireEvent.click(screen.getByText('console.objectView.editView')); + + // Toggle showRecordCount on + const recordCountSwitch = screen.getByTestId('toggle-showRecordCount'); + fireEvent.click(recordCountSwitch); + + // Verify the schema property propagated to ListView immediately (live preview) + await vi.waitFor(() => { + expect(screen.getByTestId('schema-showRecordCount')).toBeInTheDocument(); + }); + }); + + it('propagates allowPrinting toggle to ListView schema in real-time', async () => { + mockAuthUser = { id: 'u1', name: 'Admin', role: 'admin' }; + mockUseParams.mockReturnValue({ objectName: 'opportunity' }); + + render(); + + // allowPrinting should not be set initially + expect(screen.queryByTestId('schema-allowPrinting')).not.toBeInTheDocument(); + + // Open config panel + fireEvent.click(screen.getByTitle('console.objectView.designTools')); + fireEvent.click(screen.getByText('console.objectView.editView')); + + // Toggle allowPrinting on + const printSwitch = screen.getByTestId('toggle-allowPrinting'); + fireEvent.click(printSwitch); + + // Verify the schema property propagated to ListView immediately (live preview) + await vi.waitFor(() => { + expect(screen.getByTestId('schema-allowPrinting')).toBeInTheDocument(); + }); + }); + + it('propagates multiple config changes without requiring save', async () => { + mockAuthUser = { id: 'u1', name: 'Admin', role: 'admin' }; + mockUseParams.mockReturnValue({ objectName: 'opportunity' }); + + render(); + + // Open config panel + fireEvent.click(screen.getByTitle('console.objectView.designTools')); + fireEvent.click(screen.getByText('console.objectView.editView')); + + // Toggle showSearch off + const searchSwitch = screen.getByTestId('toggle-showSearch'); + fireEvent.click(searchSwitch); + + // Toggle showSort off + const sortSwitch = screen.getByTestId('toggle-showSort'); + fireEvent.click(sortSwitch); + + // Both should reflect changes immediately without save + await vi.waitFor(() => { + expect(screen.getByTestId('toggle-showSearch').getAttribute('aria-checked')).toBe('false'); + expect(screen.getByTestId('toggle-showSort').getAttribute('aria-checked')).toBe('false'); + }); + + // The grid should still be rendered (live preview, no remount) + expect(screen.getByTestId('object-grid')).toBeInTheDocument(); + }); + + it('uses activeView.navigation for detail overlay with priority over objectDef', () => { + mockAuthUser = { id: 'u1', name: 'Admin', role: 'admin' }; + const objectsWithNav = [ + { + ...mockObjects[0], + navigation: { mode: 'drawer' as const }, + listViews: { + all: { + label: 'All Opportunities', + type: 'grid', + columns: ['name', 'stage'], + navigation: { mode: 'modal' as const }, + }, + pipeline: { label: 'Pipeline', type: 'kanban', kanban: { groupField: 'stage' }, columns: ['name'] } + } + } + ]; + mockUseParams.mockReturnValue({ objectName: 'opportunity' }); + + // Render the component — activeView.navigation should override objectDef.navigation + render(); + + // The component should render without errors + expect(screen.getByTestId('object-grid')).toBeInTheDocument(); + }); }); diff --git a/apps/console/src/components/ObjectView.tsx b/apps/console/src/components/ObjectView.tsx index 88d71275a..4f8e3bc46 100644 --- a/apps/console/src/components/ObjectView.tsx +++ b/apps/console/src/components/ObjectView.tsx @@ -230,7 +230,8 @@ export function ObjectView({ dataSource, objects, onEdit, onRowClick }: any) { }, [dataSource, objectDef.name, refreshKey]); // Navigation overlay for record detail (supports drawer/modal/split/popover via config) - const detailNavigation: ViewNavigationConfig = objectDef.navigation ?? { mode: 'drawer' }; + // Priority: activeView.navigation > objectDef.navigation > default drawer + const detailNavigation: ViewNavigationConfig = activeView?.navigation ?? objectDef.navigation ?? { mode: 'drawer' }; const drawerRecordId = searchParams.get('recordId'); const navOverlay = useNavigationOverlay({ navigation: detailNavigation, @@ -573,7 +574,6 @@ export function ObjectView({ dataSource, objects, onEdit, onRowClick }: any) {
Date: Tue, 24 Feb 2026 12:45:52 +0000 Subject: [PATCH 3/4] docs: update ROADMAP with live preview fix root causes #10 and #11 Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- ROADMAP.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ROADMAP.md b/ROADMAP.md index 771ba20e1..8474cb72c 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -259,6 +259,8 @@ ObjectUI is a universal Server-Driven UI (SDUI) engine built on React + Tailwind 7. ~~**Export toggle broken:** ViewConfigPanel writes `allowExport: boolean` but ListView checks `exportOptions` object~~ → Export now checks both `exportOptions && allowExport !== false`; Console clears `exportOptions` when `allowExport === false` (Issue #719) 8. ~~**`hasExport` logic bug:** `draft.allowExport !== false` was always true when undefined~~ → Fixed to `draft.allowExport === true || draft.exportOptions != null` (Issue #719) 9. **No per-view-type integration tests:** Pending — tests verify config reaches `fullSchema`, but per-renderer integration tests still needed +10. ~~**`key={refreshKey}` on PluginObjectView:** Console wrapped PluginObjectView with `key={refreshKey}`, which only changed on save/create, preventing live preview of config changes~~ → Removed `key={refreshKey}`; props changes now flow naturally without remounting (Issue #784) +11. ~~**Navigation overlay not consuming `activeView.navigation`:** Detail overlay only read `objectDef.navigation`, ignoring view-level navigation config~~ → Navigation now uses priority: `activeView.navigation > objectDef.navigation > default drawer` (Issue #784) **Phase 1 — Grid/Table View (baseline, already complete):** - [x] `gridSchema` includes `striped`/`bordered` from `activeView` From aa7b14b2a99385d012c96b468b3e50e8771aa63e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 12:59:01 +0000 Subject: [PATCH 4/4] test: add comprehensive live preview tests for selection, addRecord, and navigation mode - Enhance ListView mock to render selection.type, addRecord.enabled, and addRecordViaForm - Add test: selection mode change propagates to ListView schema in real-time - Add test: addRecord toggle propagates both addRecord and addRecordViaForm to ListView - Add test: navigation mode change from config panel propagates to ListView schema live - Strengthen navigation priority test to verify ListView receives view-level navigation Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- .../console/src/__tests__/ObjectView.test.tsx | 74 ++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/apps/console/src/__tests__/ObjectView.test.tsx b/apps/console/src/__tests__/ObjectView.test.tsx index ee2bcca98..80edce5bf 100644 --- a/apps/console/src/__tests__/ObjectView.test.tsx +++ b/apps/console/src/__tests__/ObjectView.test.tsx @@ -30,6 +30,9 @@ vi.mock('@object-ui/plugin-list', () => ({ {props.schema?.showRecordCount &&
showRecordCount
} {props.schema?.allowPrinting &&
allowPrinting
} {props.schema?.navigation?.mode &&
{props.schema.navigation.mode}
} + {props.schema?.selection?.type &&
{props.schema.selection.type}
} + {props.schema?.addRecord?.enabled &&
addRecord
} + {props.schema?.addRecordViaForm &&
addRecordViaForm
}
); }, @@ -685,7 +688,76 @@ describe('ObjectView Component', () => { // Render the component — activeView.navigation should override objectDef.navigation render(); - // The component should render without errors + // The component should render without errors and ListView should receive + // the view-level navigation config (modal) instead of object-level (drawer) expect(screen.getByTestId('object-grid')).toBeInTheDocument(); + expect(screen.getByTestId('schema-navigation-mode')).toHaveTextContent('modal'); + }); + + it('propagates selection mode change to ListView schema in real-time', async () => { + mockAuthUser = { id: 'u1', name: 'Admin', role: 'admin' }; + mockUseParams.mockReturnValue({ objectName: 'opportunity' }); + + render(); + + // Open config panel + fireEvent.click(screen.getByTitle('console.objectView.designTools')); + fireEvent.click(screen.getByText('console.objectView.editView')); + + // Change selection mode to 'single' + const selectionSelect = screen.getByTestId('select-selection-type'); + fireEvent.change(selectionSelect, { target: { value: 'single' } }); + + // Verify the selection type propagated to ListView immediately (live preview) + await vi.waitFor(() => { + expect(screen.getByTestId('schema-selection-type')).toHaveTextContent('single'); + }); + }); + + it('propagates addRecord toggle to ListView schema in real-time', async () => { + mockAuthUser = { id: 'u1', name: 'Admin', role: 'admin' }; + mockUseParams.mockReturnValue({ objectName: 'opportunity' }); + + render(); + + // addRecord should not be enabled initially + expect(screen.queryByTestId('schema-addRecord-enabled')).not.toBeInTheDocument(); + + // Open config panel + fireEvent.click(screen.getByTitle('console.objectView.designTools')); + fireEvent.click(screen.getByText('console.objectView.editView')); + + // Toggle addRecord on + const addRecordSwitch = screen.getByTestId('toggle-addRecord-enabled'); + fireEvent.click(addRecordSwitch); + + // Verify addRecord and addRecordViaForm propagated to ListView immediately + await vi.waitFor(() => { + expect(screen.getByTestId('schema-addRecord-enabled')).toBeInTheDocument(); + expect(screen.getByTestId('schema-addRecordViaForm')).toBeInTheDocument(); + }); + }); + + it('propagates navigation mode change from config panel to ListView schema in real-time', async () => { + mockAuthUser = { id: 'u1', name: 'Admin', role: 'admin' }; + mockUseParams.mockReturnValue({ objectName: 'opportunity' }); + + render(); + + // navigation mode should not be set initially (no explicit mode on default view) + expect(screen.queryByTestId('schema-navigation-mode')).not.toBeInTheDocument(); + + // Open config panel + fireEvent.click(screen.getByTitle('console.objectView.designTools')); + fireEvent.click(screen.getByText('console.objectView.editView')); + + // Change navigation mode to 'modal' + const navSelect = screen.getByTestId('select-navigation-mode'); + fireEvent.change(navSelect, { target: { value: 'modal' } }); + + // Verify navigation mode propagated to ListView schema immediately (live preview) + await vi.waitFor(() => { + expect(screen.getByTestId('schema-navigation-mode')).toHaveTextContent('modal'); + }); }); });