Skip to content

Commit 006bde0

Browse files
authored
Merge pull request #873 from objectstack-ai/copilot/fix-254823548-1133319012-be8f04da-b197-46a0-a6e0-288e44824b63
2 parents 4c898f2 + 8e6cf9c commit 006bde0

31 files changed

+517
-2332
lines changed

ROADMAP.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313

1414
ObjectUI is a universal Server-Driven UI (SDUI) engine built on React + Tailwind + Shadcn. It renders JSON metadata from the @objectstack/spec protocol into pixel-perfect, accessible, and interactive enterprise interfaces.
1515

16-
**Where We Are:** Foundation is **solid and shipping** — 35 packages, 99+ components, 6,500+ tests, 80 Storybook stories, 43/43 builds passing, ~85% protocol alignment. SpecBridge, Expression Engine, Action Engine, data binding, all view plugins (Grid/Kanban/Calendar/Gantt/Timeline/Map/Gallery), Record components, Report engine, Dashboard BI features, mobile UX, i18n (11 locales), WCAG AA accessibility, Designer Phase 1 (ViewDesigner drag-to-reorder ✅), Console through Phase 20 (L3), **AppShell Navigation Renderer** (P0.1), **Flow Designer** (P2.4), **Feed/Chatter UI** (P1.5), **App Creation & Editing Flow** (P1.11), **System Settings & App Management** (P1.12), **Page/Dashboard Editor Console Integration** (P1.11), and **Right-Side Visual Editor Drawer** (P1.11) — all ✅ complete.
16+
**Where We Are:** Foundation is **solid and shipping** — 35 packages, 99+ components, 6,500+ tests, 80 Storybook stories, 43/43 builds passing, ~85% protocol alignment. SpecBridge, Expression Engine, Action Engine, data binding, all view plugins (Grid/Kanban/Calendar/Gantt/Timeline/Map/Gallery), Record components, Report engine, Dashboard BI features, mobile UX, i18n (11 locales), WCAG AA accessibility, Console through Phase 20 (L3), **AppShell Navigation Renderer** (P0.1), **Flow Designer** (P2.4), **Feed/Chatter UI** (P1.5), **App Creation & Editing Flow** (P1.11), **System Settings & App Management** (P1.12), **Page/Dashboard Editor Console Integration** (P1.11), and **Right-Side Visual Editor Drawer** (P1.11) — all ✅ complete. **ViewDesigner** has been removed — its capabilities (drag-to-reorder, undo/redo) are now provided by the ViewConfigPanel (right-side config panel).
1717

1818
**What Remains:** The gap to **Airtable-level UX** is primarily in:
1919
1. ~~**AppShell** — No dynamic navigation renderer from spec JSON (last P0 blocker)~~ ✅ Complete
20-
2. **Designer Interaction**ViewDesigner and DataModelDesigner have undo/redo, field type selectors, inline editing, Ctrl+S save, column drag-to-reorder with dnd-kit ✅
20+
2. **Designer Interaction** — DataModelDesigner has undo/redo, field type selectors, inline editing, Ctrl+S save. ViewDesigner has been removed; its capabilities (drag-to-reorder columns via @dnd-kit, undo/redo via useConfigDraft history) are now integrated into ViewConfigPanel (right-side config panel)
2121
3. **View Config Live Preview Sync** — Config panel changes sync in real-time for Grid, but `showSort`/`showSearch`/`showFilters`/`striped`/`bordered` not yet propagated to Kanban/Calendar/Timeline/Gallery/Map/Gantt (see P1.8.1)
2222
4. **Dashboard Config Panel** — Airtable-style right-side configuration panel for dashboards (data source, layout, widget properties, sub-editors, type definitions). Widget config live preview sync and scatter chart type switch ✅ fixed (P1.10 Phase 10). Dashboard save/refresh metadata sync ✅ fixed (P1.10 Phase 11). Data provider field override for live preview ✅ fixed (P1.10 Phase 12).
2323
5. **Console Advanced Polish** — Remaining upgrades for forms, import/export, automation, comments
@@ -89,15 +89,18 @@ ObjectUI is a universal Server-Driven UI (SDUI) engine built on React + Tailwind
8989

9090
> **Priority #1.** All items below directly affect end-user experience. Target: indistinguishable from Airtable for core CRUD workflows.
9191
92-
### P1.1 Designer Interaction (ViewDesigner + DataModelDesigner)
92+
### P1.1 Designer Interaction (DataModelDesigner)
9393

94-
> Source: ROADMAP_DESIGNER Phase 2. These two designers are the core user workflow.
94+
> Source: ROADMAP_DESIGNER Phase 2. ViewDesigner has been removed — its capabilities (column reorder, undo/redo) are now provided by ViewConfigPanel.
9595
96-
**ViewDesigner:**
96+
**ViewDesigner:** _(Removed — replaced by ViewConfigPanel)_
9797
- [x] Column drag-to-reorder via `@dnd-kit/core` (replace up/down buttons with drag handles)
9898
- [x] Add `Ctrl+S`/`Cmd+S` keyboard shortcut to save
9999
- [x] Add field type selector dropdown with icons from `DESIGNER_FIELD_TYPES`
100100
- [x] Column width validation (min/max/pattern check)
101+
- [x] Removed: ViewDesigner replaced by ViewConfigPanel (right-side config panel)
102+
- [x] ViewConfigPanel upgraded: undo/redo integrated into `useConfigDraft` hook
103+
- [x] ViewConfigPanel upgraded: drag-and-drop column sorting via `@dnd-kit/sortable`
101104

102105
**DataModelDesigner:**
103106
- [x] Entity drag-to-move on canvas
@@ -107,7 +110,7 @@ ObjectUI is a universal Server-Driven UI (SDUI) engine built on React + Tailwind
107110

108111
**Shared Infrastructure:**
109112
- [x] Implement `useDesignerHistory` hook (command pattern with undo/redo stacks)
110-
- [x] Wire undo/redo to ViewDesigner and DataModelDesigner
113+
- [x] Wire undo/redo to DataModelDesigner
111114

112115
### P1.2 Console — Forms & Data Collection
113116

@@ -207,7 +210,7 @@ ObjectUI is a universal Server-Driven UI (SDUI) engine built on React + Tailwind
207210
- [x] Type-specific options in config panel (kanban/calendar/map/gallery/timeline/gantt)
208211
- [x] Unified create/edit mode (`mode="create"|"edit"`) — single panel entry point
209212
- [x] Unified data model (`UnifiedViewConfig`) for view configuration
210-
- [x] ViewDesigner retained as "Advanced Editor" with weaker entry point
213+
- [x] ViewDesigner removed — its capabilities replaced by ViewConfigPanel (right-side config panel)
211214
- [x] Panel header breadcrumb navigation (Page > List/Kanban/Gallery)
212215
- [x] Collapsible/expandable sections with chevron toggle
213216
- [x] Data section: Sort by (summary), Group by, Prefix field, Fields (count visible)
@@ -1012,7 +1015,7 @@ The `FlowDesigner` is a canvas-based flow editor that bridges the gap between th
10121015
|--------|---------|-------------|--------------|
10131016
| **Protocol Alignment** | ~85% | 90%+ (UI-facing) | Protocol Consistency Assessment |
10141017
| **AppShell Renderer** | ✅ Complete | Sidebar + nav tree from `AppSchema` JSON | Console renders from spec JSON |
1015-
| **Designer Interaction** | Phase 2 (most complete) | ViewDesigner + DataModelDesigner drag/undo | Manual UX testing |
1018+
| **Designer Interaction** | Phase 2 (most complete) | DataModelDesigner drag/undo; ViewDesigner removed (replaced by ViewConfigPanel) | Manual UX testing |
10161019
| **Build Status** | 42/42 pass | 42/42 pass | `pnpm build` |
10171020
| **Test Count** | 5,070+ | 5,618+ | `pnpm test` summary |
10181021
| **Test Coverage** | 90%+ | 90%+ | `pnpm test:coverage` |
@@ -1046,7 +1049,7 @@ The `FlowDesigner` is a canvas-based flow editor that bridges the gap between th
10461049
| Risk | Mitigation |
10471050
|------|------------|
10481051
| AppShell complexity (7 nav types, areas, mobile) | Start with static nav tree, add mobile modes incrementally |
1049-
| Designer DnD integration time | Use `@dnd-kit/core` (already proven in Kanban/Dashboard); ViewDesigner first |
1052+
| Designer DnD integration time | Use `@dnd-kit/core` (already proven in Kanban/Dashboard) |
10501053
| Airtable UX bar is high | Focus on Grid + Kanban + Form triad first; defer Gallery/Timeline polish |
10511054
| PWA real sync complexity | Keep simulated sync as fallback; real sync behind feature flag |
10521055
| Performance regression | Performance budgets in CI, 10K-record benchmarks |

apps/console/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
"test:ui": "vitest --ui"
3434
},
3535
"devDependencies": {
36+
"@dnd-kit/core": "^6.3.1",
37+
"@dnd-kit/sortable": "^10.0.0",
38+
"@dnd-kit/utilities": "^3.2.2",
3639
"@object-ui/auth": "workspace:*",
3740
"@object-ui/collaboration": "workspace:*",
3841
"@object-ui/components": "workspace:*",

apps/console/src/App.tsx

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ const RecordDetailView = lazy(() => import('./components/RecordDetailView').then
2828
const DashboardView = lazy(() => import('./components/DashboardView').then(m => ({ default: m.DashboardView })));
2929
const PageView = lazy(() => import('./components/PageView').then(m => ({ default: m.PageView })));
3030
const ReportView = lazy(() => import('./components/ReportView').then(m => ({ default: m.ReportView })));
31-
const ViewDesignerPage = lazy(() => import('./components/ViewDesignerPage').then(m => ({ default: m.ViewDesignerPage })));
3231
const SearchResultsPage = lazy(() => import('./components/SearchResultsPage').then(m => ({ default: m.SearchResultsPage })));
3332

3433
// App Creation / Edit Pages (lazy — only needed during app management)
@@ -346,14 +345,6 @@ export function AppContent() {
346345
<RecordDetailView key={refreshKey} dataSource={dataSource} objects={allObjects} onEdit={handleEdit} />
347346
} />
348347

349-
{/* View Designer - Create/Edit Views */}
350-
<Route path=":objectName/views/new" element={
351-
<ViewDesignerPage objects={allObjects} />
352-
} />
353-
<Route path=":objectName/views/:viewId" element={
354-
<ViewDesignerPage objects={allObjects} />
355-
} />
356-
357348
<Route path="dashboard/:dashboardName" element={
358349
<DashboardView dataSource={dataSource} />
359350
} />

apps/console/src/__tests__/ObjectView.test.tsx

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -283,21 +283,6 @@ describe('ObjectView Component', () => {
283283
expect(screen.getByTestId('view-config-panel')).toBeInTheDocument();
284284
});
285285

286-
it('navigates to view designer when Advanced Editor is clicked', () => {
287-
mockAuthUser = { id: 'u1', name: 'Admin', role: 'admin' };
288-
mockUseParams.mockReturnValue({ objectName: 'opportunity' });
289-
290-
render(<ObjectView dataSource={mockDataSource} objects={mockObjects} onEdit={vi.fn()} />);
291-
292-
const designBtn = screen.getByTitle('console.objectView.designTools');
293-
fireEvent.click(designBtn);
294-
295-
const advancedBtn = screen.getByText('console.objectView.advancedEditor');
296-
fireEvent.click(advancedBtn);
297-
298-
expect(mockNavigate).toHaveBeenCalledWith('views/new', { relative: 'path' });
299-
});
300-
301286
it('shows breadcrumb with object and view name', () => {
302287
mockUseParams.mockReturnValue({ objectName: 'opportunity' });
303288

apps/console/src/__tests__/ViewConfigPanel.test.tsx

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,64 @@ vi.mock('@object-ui/components', () => {
2727

2828
// useConfigDraft mock — mirrors real implementation
2929
function useConfigDraft(source: any, options?: any) {
30+
const maxHistory = options?.maxHistory ?? 50;
3031
const [draft, setDraft] = React.useState({ ...source });
3132
const [isDirty, setIsDirty] = React.useState(options?.mode === 'create');
33+
const pastRef = React.useRef<any[]>([]);
34+
const futureRef = React.useRef<any[]>([]);
35+
const [, forceRender] = React.useState(0);
3236

3337
React.useEffect(() => {
3438
setDraft({ ...source });
3539
setIsDirty(options?.mode === 'create');
40+
pastRef.current = [];
41+
futureRef.current = [];
3642
}, [source]); // eslint-disable-line react-hooks/exhaustive-deps
3743

3844
const updateField = React.useCallback((field: string, value: any) => {
39-
setDraft((prev: any) => ({ ...prev, [field]: value }));
45+
setDraft((prev: any) => {
46+
pastRef.current = [...pastRef.current.slice(-(maxHistory - 1)), prev];
47+
futureRef.current = [];
48+
return { ...prev, [field]: value };
49+
});
4050
setIsDirty(true);
51+
forceRender((n: number) => n + 1);
4152
options?.onUpdate?.(field, value);
4253
}, [options?.onUpdate]);
4354

55+
const undo = React.useCallback(() => {
56+
if (pastRef.current.length === 0) return;
57+
setDraft((prev: any) => {
58+
const past = [...pastRef.current];
59+
const previous = past.pop()!;
60+
pastRef.current = past;
61+
futureRef.current = [prev, ...futureRef.current];
62+
return previous;
63+
});
64+
forceRender((n: number) => n + 1);
65+
}, []);
66+
67+
const redo = React.useCallback(() => {
68+
if (futureRef.current.length === 0) return;
69+
setDraft((prev: any) => {
70+
const future = [...futureRef.current];
71+
const next = future.shift()!;
72+
futureRef.current = future;
73+
pastRef.current = [...pastRef.current, prev];
74+
return next;
75+
});
76+
forceRender((n: number) => n + 1);
77+
}, []);
78+
4479
const discard = React.useCallback(() => {
4580
setDraft({ ...source });
4681
setIsDirty(false);
82+
pastRef.current = [];
83+
futureRef.current = [];
84+
forceRender((n: number) => n + 1);
4785
}, [source]);
4886

49-
return { draft, isDirty, updateField, discard, setDraft };
87+
return { draft, isDirty, updateField, discard, setDraft, undo, redo, canUndo: pastRef.current.length > 0, canRedo: futureRef.current.length > 0 };
5088
}
5189

5290
// ConfigPanelRenderer mock — renders schema sections with proper collapse/visibility

apps/console/src/__tests__/ViewDesignerSave.test.tsx

Lines changed: 0 additions & 85 deletions
This file was deleted.

apps/console/src/components/ObjectView.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -512,11 +512,6 @@ export function ObjectView({ dataSource, objects, onEdit, onRowClick }: any) {
512512
<Plus className="h-4 w-4 mr-2" />
513513
{t('console.objectView.addView')}
514514
</DropdownMenuItem>
515-
<DropdownMenuSeparator />
516-
<DropdownMenuItem onClick={() => navigate(viewId ? '../../views/new' : 'views/new', { relative: 'path' })}>
517-
<Wrench className="h-4 w-4 mr-2" />
518-
{t('console.objectView.advancedEditor')}
519-
</DropdownMenuItem>
520515
</DropdownMenuContent>
521516
</DropdownMenu>
522517
)}

apps/console/src/components/ViewConfigPanel.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export function ViewConfigPanel({ open, onClose, mode = 'edit', activeView, obje
9090
const effectiveActiveView = mode === 'create' ? defaultNewView : stableActiveView;
9191

9292
// Schema-driven draft state management
93-
const { draft, isDirty, updateField, discard } = useConfigDraft(effectiveActiveView, {
93+
const { draft, isDirty, updateField, discard, undo, redo, canUndo, canRedo } = useConfigDraft(effectiveActiveView, {
9494
mode,
9595
onUpdate: onViewUpdate,
9696
});
@@ -162,6 +162,12 @@ export function ViewConfigPanel({ open, onClose, mode = 'edit', activeView, obje
162162
onFieldChange={updateField}
163163
onSave={handleSave}
164164
onDiscard={handleDiscard}
165+
onUndo={undo}
166+
onRedo={redo}
167+
canUndo={canUndo}
168+
canRedo={canRedo}
169+
undoLabel={t('designer.undo')}
170+
redoLabel={t('designer.redo')}
165171
objectDef={objectDef}
166172
saveLabel={t('console.objectView.save')}
167173
discardLabel={t('console.objectView.discard')}

0 commit comments

Comments
 (0)