diff --git a/ROADMAP.md b/ROADMAP.md index db107704..fdc5d361 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,11 +1,11 @@ # ObjectUI Development Roadmap -> **Last Updated:** March 23, 2026 +> **Last Updated:** April 1, 2026 > **Current Version:** v0.5.x > **Spec Version:** @objectstack/spec v3.3.0 > **Client Version:** @objectstack/client v3.3.0 > **Target UX Benchmark:** ๐ŸŽฏ Airtable parity -> **Current Priority:** AppShell Navigation ยท Designer Interaction ยท **View Config Live Preview Sync โœ…** ยท Dashboard Config Panel ยท Airtable UX Polish ยท **Flow Designer โœ…** ยท **App Creation & Editing Flow โœ…** ยท **System Settings & App Management โœ…** ยท **Right-Side Visual Editor Drawer โœ…** ยท **Object Manager & Field Designer โœ…** ยท **AI SDUI Chatbot (service-ai + vercel/ai) โœ…** +> **Current Priority:** AppShell Navigation ยท Designer Interaction ยท **View Config Live Preview Sync โœ…** ยท Dashboard Config Panel ยท Airtable UX Polish ยท **Flow Designer โœ…** ยท **App Creation & Editing Flow โœ…** ยท **System Settings & App Management โœ…** ยท **Right-Side Visual Editor Drawer โœ…** ยท **Object Manager & Field Designer โœ…** ยท **AI SDUI Chatbot (service-ai + vercel/ai) โœ…** ยท **Unified Home Dashboard โœ…** --- @@ -13,7 +13,7 @@ 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. -**Where We Are:** Foundation is **solid and shipping** โ€” 35 packages, 99+ components, 6,700+ 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), **Right-Side Visual Editor Drawer** (P1.11), and **Console Engine Schema Integration** (P1.14) โ€” all โœ… complete. **ViewDesigner** has been removed โ€” its capabilities (drag-to-reorder, undo/redo) are now provided by the ViewConfigPanel (right-side config panel). +**Where We Are:** Foundation is **solid and shipping** โ€” 35 packages, 99+ components, 6,700+ 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), **Right-Side Visual Editor Drawer** (P1.11), **Console Engine Schema Integration** (P1.14), and **Unified Home Dashboard** (P1.7.1) โ€” all โœ… complete. **ViewDesigner** has been removed โ€” its capabilities (drag-to-reorder, undo/redo) are now provided by the ViewConfigPanel (right-side config panel). **What Remains:** The gap to **Airtable-level UX** is primarily in: 1. ~~**AppShell** โ€” No dynamic navigation renderer from spec JSON (last P0 blocker)~~ โœ… Complete @@ -222,6 +222,22 @@ ObjectUI is a universal Server-Driven UI (SDUI) engine built on React + Tailwind - โœ… `useNavigationOverlay` hook delegates `new_window` to `onNavigate` when available for app-specific URL control - โœ… plugin-view `handleRowClick` supports `split` and `popover` branches +### P1.7.1 Console โ€” Unified Home Dashboard (Workspace) โœ… + +- [x] **HomePage component** โ€” Unified landing page displaying all available applications +- [x] **Route integration** โ€” `/home` route added with proper authentication guards +- [x] **App cards grid** โ€” Responsive grid layout showing all active apps with icons, descriptions, and branding colors +- [x] **QuickActions section** โ€” Quick access cards for creating apps, managing objects, and system settings +- [x] **Recent items** โ€” Display recently accessed objects, dashboards, and pages using `useRecentItems` hook +- [x] **Starred items** โ€” Display user-favorited items using `useFavorites` hook with star/unstar toggle +- [x] **Empty state** โ€” Helpful guidance for new users with "Create First App" and "System Settings" CTAs +- [x] **i18n support** โ€” All labels support internationalization via `useObjectTranslation` +- [x] **RootRedirect update** โ€” Root path (`/`) now redirects to `/home` instead of first app +- [x] **Responsive design** โ€” Mobile-friendly grid layouts that adapt to screen size +- [x] **Airtable/Notion UX pattern** โ€” Inspired by industry-leading workspace home pages + +**Impact:** Users now have a unified workspace dashboard that provides overview of all applications, quick actions, and recent activity. This eliminates the previous behavior of auto-redirecting to the first app, giving users better control and visibility. + ### P1.8 Console โ€” View Config Panel (Phase 20) - [x] Inline ViewConfigPanel for all view types (Airtable-style right sidebar) diff --git a/apps/console/src/App.tsx b/apps/console/src/App.tsx index e317f38d..88c6396c 100644 --- a/apps/console/src/App.tsx +++ b/apps/console/src/App.tsx @@ -55,6 +55,9 @@ const PermissionManagementPage = lazy(() => import('./pages/system/PermissionMan const AuditLogPage = lazy(() => import('./pages/system/AuditLogPage').then(m => ({ default: m.AuditLogPage }))); const ProfilePage = lazy(() => import('./pages/system/ProfilePage').then(m => ({ default: m.ProfilePage }))); +// Home Page (lazy โ€” landing page) +const HomePage = lazy(() => import('./pages/home/HomePage').then(m => ({ default: m.HomePage }))); + import { useParams } from 'react-router-dom'; import { ThemeProvider } from './components/theme-provider'; import { ConsoleToaster } from './components/ConsoleToaster'; @@ -456,44 +459,14 @@ function findFirstRoute(items: any[]): string { return ''; } -// Redirect root to default app +// Redirect root to home page function RootRedirect() { - const { apps, loading, error } = useMetadata(); - const navigate = useNavigate(); - const activeApps = apps.filter((a: any) => a.active !== false); - const defaultApp = activeApps.find((a: any) => a.isDefault === true) || activeApps[0]; - + const { loading } = useMetadata(); + if (loading) return ; - if (defaultApp) { - return ; - } - return ( -
- - {error ? 'Failed to Load Configuration' : 'No Apps Configured'} - - {error - ? 'There was an error loading the configuration. You can still create an app or access System Settings.' - : 'No applications have been registered. Create your first app or configure your system.'} - -
- - -
-
-
- ); + + // Always redirect to home page + return ; } /** @@ -531,6 +504,16 @@ export function App() { } /> } /> } /> + {/* Home Dashboard โ€” unified workspace landing page */} + } loadingFallback={}> + + }> + + + + + } /> {/* Top-level system routes โ€” accessible without any app */} } loadingFallback={}> diff --git a/apps/console/src/pages/home/AppCard.tsx b/apps/console/src/pages/home/AppCard.tsx new file mode 100644 index 00000000..742c9836 --- /dev/null +++ b/apps/console/src/pages/home/AppCard.tsx @@ -0,0 +1,99 @@ +/** + * AppCard + * + * Display card for an application with icon, name, description, and favorite toggle. + * + * @module + */ + +import { Star, StarOff } from 'lucide-react'; +import { Card, CardContent, Button } from '@object-ui/components'; +import { useObjectTranslation } from '@object-ui/i18n'; +import { resolveI18nLabel } from '../../utils'; +import { useFavorites } from '../../hooks/useFavorites'; +import { getIcon } from '../../utils/getIcon'; +import { cn } from '@object-ui/components'; + +interface AppCardProps { + app: any; + onClick: () => void; + isFavorite: boolean; +} + +export function AppCard({ app, onClick, isFavorite }: AppCardProps) { + const { t } = useObjectTranslation(); + const { toggleFavorite } = useFavorites(); + + const Icon = getIcon(app.icon); + const label = resolveI18nLabel(app.label, t) || app.name; + const description = resolveI18nLabel(app.description, t); + const primaryColor = app.branding?.primaryColor; + + const handleToggleFavorite = (e: React.MouseEvent) => { + e.stopPropagation(); + toggleFavorite({ + id: `app:${app.name}`, + label, + href: `/apps/${app.name}`, + type: 'object', + }); + }; + + return ( + + + {/* Favorite Button */} + + + {/* App Icon */} +
+ +
+ + {/* App Info */} +
+

{label}

+ {description && ( +

{description}

+ )} + {!description && ( +

+ {t('home.appCard.noDescription', { defaultValue: 'No description' })} +

+ )} +
+ + {/* App Badge (if default) */} + {app.isDefault && ( +
+ + {t('home.appCard.default', { defaultValue: 'Default' })} + +
+ )} +
+
+ ); +} diff --git a/apps/console/src/pages/home/HomePage.tsx b/apps/console/src/pages/home/HomePage.tsx new file mode 100644 index 00000000..df7d22f7 --- /dev/null +++ b/apps/console/src/pages/home/HomePage.tsx @@ -0,0 +1,153 @@ +/** + * HomePage + * + * Unified Home Dashboard (Workspace) that displays all available applications, + * quick actions, recent items, and favorites. Inspired by Airtable/Notion home pages. + * + * Features: + * - Display all active applications as cards + * - Quick actions for creating apps, importing data, etc. + * - Recent apps section (from useRecentItems) + * - Starred/Favorite apps section (from useFavorites) + * - Empty state guidance for new users + * - Responsive grid layout + * - i18n support + * + * @module + */ + +import { useNavigate } from 'react-router-dom'; +import { useMetadata } from '../../context/MetadataProvider'; +import { useRecentItems } from '../../hooks/useRecentItems'; +import { useFavorites } from '../../hooks/useFavorites'; +import { useObjectTranslation } from '@object-ui/i18n'; +import { resolveI18nLabel } from '../../utils'; +import { QuickActions } from './QuickActions'; +import { AppCard } from './AppCard'; +import { RecentApps } from './RecentApps'; +import { StarredApps } from './StarredApps'; +import { Empty, EmptyTitle, EmptyDescription, Button } from '@object-ui/components'; +import { Plus, Settings } from 'lucide-react'; + +export function HomePage() { + const navigate = useNavigate(); + const { t } = useObjectTranslation(); + const { apps, loading } = useMetadata(); + const { recentItems } = useRecentItems(); + const { favorites } = useFavorites(); + + // Filter active apps + const activeApps = apps.filter((a: any) => a.active !== false); + + // Get recent apps (only apps, not objects/dashboards) + const recentApps = recentItems + .filter(item => item.type === 'object' || item.type === 'dashboard' || item.type === 'page') + .slice(0, 6); + + // Get starred apps + const starredApps = favorites + .filter(item => item.type === 'object' || item.type === 'dashboard' || item.type === 'page') + .slice(0, 8); + + if (loading) { + return ( +
+
Loading workspace...
+
+ ); + } + + // Empty state - no apps configured + if (activeApps.length === 0) { + return ( +
+ + Welcome to ObjectUI + + Get started by creating your first application or configure your system settings. + +
+ + +
+
+
+ ); + } + + return ( +
+ {/* Header */} +
+
+
+
+

+ {t('home.title', { defaultValue: 'Home' })} +

+

+ {t('home.subtitle', { defaultValue: 'Your workspace dashboard' })} +

+
+
+ +
+
+
+
+ + {/* Main Content */} +
+ {/* Quick Actions */} + + + {/* Starred/Favorite Apps */} + {starredApps.length > 0 && ( + + )} + + {/* Recent Apps */} + {recentApps.length > 0 && ( + + )} + + {/* All Applications */} +
+

+ {t('home.allApps', { defaultValue: 'All Applications' })} +

+
+ {activeApps.map((app: any) => ( + navigate(`/apps/${app.name}`)} + isFavorite={favorites.some(f => f.id === `app:${app.name}`)} + /> + ))} +
+
+
+
+ ); +} diff --git a/apps/console/src/pages/home/QuickActions.tsx b/apps/console/src/pages/home/QuickActions.tsx new file mode 100644 index 00000000..06b05f84 --- /dev/null +++ b/apps/console/src/pages/home/QuickActions.tsx @@ -0,0 +1,88 @@ +/** + * QuickActions + * + * Quick action cards for common tasks like creating apps, importing data, + * accessing system settings, etc. + * + * @module + */ + +import { useNavigate } from 'react-router-dom'; +import { useObjectTranslation } from '@object-ui/i18n'; +import { Card, CardContent } from '@object-ui/components'; +import { Plus, Upload, Settings, Database, FileText } from 'lucide-react'; +import { cn } from '@object-ui/components'; + +interface QuickAction { + id: string; + label: string; + description: string; + icon: React.ElementType; + href: string; + color: string; +} + +export function QuickActions() { + const navigate = useNavigate(); + const { t } = useObjectTranslation(); + + const actions: QuickAction[] = [ + { + id: 'create-app', + label: t('home.quickActions.createApp', { defaultValue: 'Create App' }), + description: t('home.quickActions.createAppDesc', { defaultValue: 'Start with a new application' }), + icon: Plus, + href: '/create-app', + color: 'text-blue-600 dark:text-blue-400', + }, + { + id: 'manage-objects', + label: t('home.quickActions.manageObjects', { defaultValue: 'Manage Objects' }), + description: t('home.quickActions.manageObjectsDesc', { defaultValue: 'Configure data models' }), + icon: Database, + href: '/system/objects', + color: 'text-purple-600 dark:text-purple-400', + }, + { + id: 'system-settings', + label: t('home.quickActions.systemSettings', { defaultValue: 'System Settings' }), + description: t('home.quickActions.systemSettingsDesc', { defaultValue: 'Configure your workspace' }), + icon: Settings, + href: '/system', + color: 'text-gray-600 dark:text-gray-400', + }, + ]; + + return ( +
+

+ {t('home.quickActions.title', { defaultValue: 'Quick Actions' })} +

+
+ {actions.map((action) => { + const Icon = action.icon; + return ( + navigate(action.href)} + data-testid={`quick-action-${action.id}`} + > + +
+
+ +
+
+

{action.label}

+

{action.description}

+
+
+
+
+ ); + })} +
+
+ ); +} diff --git a/apps/console/src/pages/home/RecentApps.tsx b/apps/console/src/pages/home/RecentApps.tsx new file mode 100644 index 00000000..1ab535fd --- /dev/null +++ b/apps/console/src/pages/home/RecentApps.tsx @@ -0,0 +1,61 @@ +/** + * RecentApps + * + * Display section for recently accessed items (objects, dashboards, pages). + * + * @module + */ + +import { useNavigate } from 'react-router-dom'; +import { useObjectTranslation } from '@object-ui/i18n'; +import { Card, CardContent } from '@object-ui/components'; +import { Clock } from 'lucide-react'; +import { getIcon } from '../../utils/getIcon'; +import type { RecentItem } from '../../hooks/useRecentItems'; + +interface RecentAppsProps { + items: RecentItem[]; +} + +export function RecentApps({ items }: RecentAppsProps) { + const navigate = useNavigate(); + const { t } = useObjectTranslation(); + + if (items.length === 0) return null; + + return ( +
+
+ +

+ {t('home.recentApps.title', { defaultValue: 'Recently Accessed' })} +

+
+
+ {items.map((item) => { + const Icon = getIcon(item.type); + return ( + navigate(item.href)} + data-testid={`recent-item-${item.id}`} + > + +
+
+ +
+
+

{item.label}

+

{item.type}

+
+
+
+
+ ); + })} +
+
+ ); +} diff --git a/apps/console/src/pages/home/StarredApps.tsx b/apps/console/src/pages/home/StarredApps.tsx new file mode 100644 index 00000000..98b6dea7 --- /dev/null +++ b/apps/console/src/pages/home/StarredApps.tsx @@ -0,0 +1,61 @@ +/** + * StarredApps + * + * Display section for starred/favorite items (objects, dashboards, pages). + * + * @module + */ + +import { useNavigate } from 'react-router-dom'; +import { useObjectTranslation } from '@object-ui/i18n'; +import { Card, CardContent } from '@object-ui/components'; +import { Star } from 'lucide-react'; +import { getIcon } from '../../utils/getIcon'; +import type { FavoriteItem } from '../../hooks/useFavorites'; + +interface StarredAppsProps { + items: FavoriteItem[]; +} + +export function StarredApps({ items }: StarredAppsProps) { + const navigate = useNavigate(); + const { t } = useObjectTranslation(); + + if (items.length === 0) return null; + + return ( +
+
+ +

+ {t('home.starredApps.title', { defaultValue: 'Starred' })} +

+
+
+ {items.map((item) => { + const Icon = getIcon(item.type); + return ( + navigate(item.href)} + data-testid={`starred-item-${item.id}`} + > + +
+
+ +
+
+

{item.label}

+

{item.type}

+
+
+
+
+ ); + })} +
+
+ ); +} diff --git a/apps/console/src/pages/home/index.ts b/apps/console/src/pages/home/index.ts new file mode 100644 index 00000000..edfe52d9 --- /dev/null +++ b/apps/console/src/pages/home/index.ts @@ -0,0 +1,11 @@ +/** + * Home Page Components + * + * Exports for the unified home dashboard components + */ + +export { HomePage } from './HomePage'; +export { QuickActions } from './QuickActions'; +export { AppCard } from './AppCard'; +export { RecentApps } from './RecentApps'; +export { StarredApps } from './StarredApps'; diff --git a/apps/console/src/utils/getIcon.ts b/apps/console/src/utils/getIcon.ts new file mode 100644 index 00000000..03047ed9 --- /dev/null +++ b/apps/console/src/utils/getIcon.ts @@ -0,0 +1,23 @@ +/** + * Icon utilities + * + * Helpers for resolving Lucide icons by name. + */ + +import * as LucideIcons from 'lucide-react'; +import { Database } from 'lucide-react'; + +/** + * Resolve a Lucide icon by name (kebab-case or PascalCase) + * Falls back to Database icon if not found + */ +export function getIcon(name?: string): React.ElementType { + if (!name) return Database; + if ((LucideIcons as any)[name]) return (LucideIcons as any)[name]; + const pascal = name + .split('-') + .map(p => p.charAt(0).toUpperCase() + p.slice(1)) + .join(''); + if ((LucideIcons as any)[pascal]) return (LucideIcons as any)[pascal]; + return Database; +}