Skip to content

Commit 9b95ca5

Browse files
authored
Merge pull request #786 from objectstack-ai/copilot/fix-top-nav-application-visibility
2 parents 78bbfd0 + c0d87d9 commit 9b95ca5

File tree

5 files changed

+102
-7
lines changed

5 files changed

+102
-7
lines changed

ROADMAP.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -494,15 +494,15 @@ ObjectUI is a universal Server-Driven UI (SDUI) engine built on React + Tailwind
494494
- [x] `useConfirmDialog` hook integration for cancel workflow
495495

496496
**Testing:**
497-
- [x] 9 type tests (isValidAppName, wizardDraftToAppSchema, type shapes)
497+
- [x] 11 type tests (isValidAppName, wizardDraftToAppSchema, type shapes)
498498
- [x] 41 AppCreationWizard tests (rendering, steps 1-4, navigation, callbacks, cancel confirm, save draft, i18n, read-only)
499499
- [x] 33 NavigationDesigner tests (rendering, add, remove, groups, badges, i18n, read-only, icon editing, visibility toggle, export/import, responsive)
500500
- [x] 7 EditorModeToggle tests (render, active mode, onChange, accessibility, disabled)
501501
- [x] 22 DashboardEditor tests (rendering, add/remove widgets, property panel, read-only, undo/redo, export/import, preview mode, widget layout)
502502
- [x] 23 PageCanvasEditor tests (rendering, add/remove components, property panel, read-only, mode tabs, undo/redo, export/import, preview mode)
503503
- [x] 12 ObjectViewConfigurator tests (rendering, view type switch, column visibility, toggles, read-only)
504504
- [x] 29 BrandingEditor tests (rendering, editing, light/dark preview, read-only, undo/redo, export/import, keyboard shortcuts, preview content)
505-
- [x] **Total: 235 tests across 10 files, all passing**
505+
- [x] **Total: 238 tests across 10 files, all passing**
506506

507507
**ComponentRegistry:**
508508
- [x] Registered: `app-creation-wizard`, `navigation-designer`, `dashboard-editor`, `page-canvas-editor`, `object-view-configurator`, `branding-editor`
@@ -531,12 +531,13 @@ ObjectUI is a universal Server-Driven UI (SDUI) engine built on React + Tailwind
531531
- [x] AppSidebar "Edit App" button → navigates to `/edit-app/:appName`
532532
- [x] CommandPalette "Create New App" command (⌘+K → Actions group)
533533
- [x] Empty state CTA "Create Your First App" when no apps configured
534-
- [x] `wizardDraftToAppSchema()` conversion on completion
534+
- [x] `wizardDraftToAppSchema()` conversion on completion — includes `icon`, `label`, `branding` fields
535+
- [x] `EditAppPage` merges wizard output with original app config to preserve fields not in wizard (e.g. `active`)
535536
- [x] `client.meta.saveItem('app', name, schema)` — persists app metadata to backend on create/edit
536537
- [x] MSW PUT handler for `/meta/:type/:name` — dev/mock mode metadata persistence
537538
- [x] Draft persistence to localStorage with auto-clear on success
538539
- [x] `createApp` i18n key added to all 10 locales
539-
- [x] 13 console integration tests (routes, wizard callbacks, draft persistence, saveItem, CommandPalette)
540+
- [x] 14 console integration tests (routes, wizard callbacks, draft persistence, saveItem, merge preservation, CommandPalette)
540541

541542
### P1.12 System Settings & App Management Center
542543

apps/console/src/__tests__/app-creation-integration.test.tsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ vi.mock('@object-ui/plugin-designer', () => ({
135135
<div data-testid="app-creation-wizard">
136136
<span data-testid="wizard-objects-count">{availableObjects?.length ?? 0}</span>
137137
{initialDraft?.name && <span data-testid="wizard-initial-name">{initialDraft.name}</span>}
138-
<button data-testid="wizard-complete" onClick={() => onComplete?.({ name: 'my_app', title: 'My App', branding: { logo: '', primaryColor: '#000', favicon: '' }, objects: [], navigation: [], layout: 'sidebar' })}>
138+
<button data-testid="wizard-complete" onClick={() => onComplete?.({ name: 'my_app', title: 'My App', icon: 'LayoutDashboard', branding: { logo: '', primaryColor: '#000', favicon: '' }, objects: [], navigation: [], layout: 'sidebar' })}>
139139
Complete
140140
</button>
141141
<button data-testid="wizard-cancel" onClick={() => onCancel?.()}>
@@ -424,6 +424,27 @@ describe('Console App Creation Integration', () => {
424424
);
425425
});
426426
});
427+
428+
it('preserves original app fields not managed by wizard after merge', async () => {
429+
renderApp('/apps/sales/edit-app/sales');
430+
431+
await waitFor(() => {
432+
expect(screen.getByTestId('wizard-complete')).toBeInTheDocument();
433+
}, { timeout: 10000 });
434+
435+
fireEvent.click(screen.getByTestId('wizard-complete'));
436+
437+
await waitFor(() => {
438+
const client = MockAdapterInstance.getClient();
439+
const savedSchema = client.meta.saveItem.mock.calls[0]?.[2];
440+
// Wizard-generated fields are present
441+
expect(savedSchema.label).toBe('My App');
442+
expect(savedSchema.icon).toBe('LayoutDashboard');
443+
expect(savedSchema.branding).toEqual({ logo: '', primaryColor: '#000', favicon: '' });
444+
// Original app field not in wizard is preserved via merge
445+
expect(savedSchema.active).toBe(true);
446+
});
447+
});
427448
});
428449

429450
// --- CommandPalette: Create App Command ---

apps/console/src/pages/EditAppPage.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,12 @@ export function EditAppPage() {
5959
async (draft: AppWizardDraft) => {
6060
try {
6161
const appSchema = wizardDraftToAppSchema(draft);
62+
// Merge with original app config to preserve fields not maintained in wizard
63+
const merged = { ...appToEdit, ...appSchema };
6264
// Persist app metadata to backend
6365
const client = adapter?.getClient();
6466
if (client) {
65-
await client.meta.saveItem('app', draft.name, appSchema);
67+
await client.meta.saveItem('app', draft.name, merged);
6668
}
6769
toast.success(`Application "${draft.title}" updated successfully`);
6870
await refresh?.();
@@ -71,7 +73,7 @@ export function EditAppPage() {
7173
toast.error(err?.message || 'Failed to update application');
7274
}
7375
},
74-
[navigate, refresh, adapter],
76+
[navigate, refresh, adapter, appToEdit],
7577
);
7678

7779
const handleCancel = useCallback(() => {

packages/types/src/__tests__/app-creation-types.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,16 @@ describe('App Creation Types', () => {
6666
expect(schema.type).toBe('app');
6767
expect(schema.name).toBe('test_app');
6868
expect(schema.title).toBe('Test Application');
69+
expect(schema.label).toBe('Test Application');
6970
expect(schema.description).toBe('A test app');
71+
expect(schema.icon).toBe('LayoutDashboard');
7072
expect(schema.logo).toBe('https://example.com/logo.svg');
7173
expect(schema.favicon).toBe('https://example.com/favicon.ico');
74+
expect(schema.branding).toEqual({
75+
logo: 'https://example.com/logo.svg',
76+
primaryColor: '#3b82f6',
77+
favicon: 'https://example.com/favicon.ico',
78+
});
7279
expect(schema.layout).toBe('sidebar');
7380
expect(schema.navigation).toHaveLength(1);
7481
expect(schema.navigation![0].id).toBe('nav_1');
@@ -87,6 +94,46 @@ describe('App Creation Types', () => {
8794
const schema = wizardDraftToAppSchema(draft);
8895
expect(schema.navigation).toEqual([]);
8996
expect(schema.layout).toBe('header');
97+
expect(schema.label).toBe('Empty');
98+
});
99+
100+
it('should preserve icon and label for sidebar/app-switcher display', () => {
101+
const draft: AppWizardDraft = {
102+
name: 'sales_crm',
103+
title: 'Sales CRM',
104+
icon: 'TrendingUp',
105+
layout: 'sidebar',
106+
objects: [],
107+
navigation: [],
108+
branding: {
109+
logo: 'https://example.com/sales-logo.png',
110+
primaryColor: '#10b981',
111+
},
112+
};
113+
114+
const schema = wizardDraftToAppSchema(draft);
115+
// These fields are critical for AppSidebar and app switcher
116+
expect(schema.icon).toBe('TrendingUp');
117+
expect(schema.label).toBe('Sales CRM');
118+
expect(schema.branding).toEqual({
119+
logo: 'https://example.com/sales-logo.png',
120+
primaryColor: '#10b981',
121+
});
122+
});
123+
124+
it('should handle draft without icon gracefully', () => {
125+
const draft: AppWizardDraft = {
126+
name: 'no_icon_app',
127+
title: 'No Icon',
128+
layout: 'sidebar',
129+
objects: [],
130+
navigation: [],
131+
branding: {},
132+
};
133+
134+
const schema = wizardDraftToAppSchema(draft);
135+
expect(schema.icon).toBeUndefined();
136+
expect(schema.label).toBe('No Icon');
90137
});
91138
});
92139

packages/types/src/app.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,21 @@ export interface AppSchema extends BaseSchema {
162162
*/
163163
title?: string;
164164

165+
/**
166+
* Display Label (used in navigation and app switcher)
167+
*/
168+
label?: string;
169+
165170
/**
166171
* Application Description
167172
*/
168173
description?: string;
169174

175+
/**
176+
* Icon name (Lucide) for app switcher and navigation
177+
*/
178+
icon?: string;
179+
170180
/**
171181
* Logo URL or Icon name
172182
*/
@@ -177,6 +187,17 @@ export interface AppSchema extends BaseSchema {
177187
*/
178188
favicon?: string;
179189

190+
/**
191+
* Branding configuration
192+
*/
193+
branding?: BrandingConfig;
194+
195+
/**
196+
* Whether the application is active (visible in app switcher)
197+
* @default true
198+
*/
199+
active?: boolean;
200+
180201
/**
181202
* Global Layout Strategy
182203
* - sidebar: Standard admin layout with left sidebar
@@ -461,9 +482,12 @@ export function wizardDraftToAppSchema(draft: AppWizardDraft): AppSchema {
461482
type: 'app',
462483
name: draft.name,
463484
title: draft.title,
485+
label: draft.title,
464486
description: draft.description,
487+
icon: draft.icon,
465488
logo: draft.branding.logo,
466489
favicon: draft.branding.favicon,
490+
branding: draft.branding,
467491
layout: draft.layout,
468492
navigation: draft.navigation,
469493
};

0 commit comments

Comments
 (0)