@ramme-io/create-app 1.1.9 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,24 +1,28 @@
1
1
  import React from 'react';
2
- import { PageHeader, Alert } from '@ramme-io/ui';
2
+ import { PageHeader, Alert, Button, Icon } from '@ramme-io/ui';
3
3
  import { appManifest } from '../config/app.manifest';
4
- import { getComponent } from '../core/component-registry';
5
4
  // Reuse the GhostOverlay for structure visualization
6
5
  import { GhostOverlay } from '../components/dev/GhostOverlay';
7
- // Import the component that renders a single block (you likely have this extracted from Dashboard.tsx)
6
+ // Import the Worker Bee
8
7
  import { DynamicBlock } from '../components/DynamicBlock';
8
+ // Import the Hook to control the overlay
9
+ import { useDevTools } from '../hooks/useDevTools';
9
10
 
10
11
  interface DynamicPageProps {
11
12
  pageId: string;
12
13
  }
13
14
 
14
- const DynamicPage: React.FC<DynamicPageProps> = ({ pageId }) => {
15
- // 1. Look up the page definition in the manifest
16
- const pageConfig = appManifest.pages?.find((p: { id: string; }) => p.id === pageId);
15
+ export const DynamicPage: React.FC<DynamicPageProps> = ({ pageId }) => {
16
+ // 1. Initialize DevTools
17
+ const { isGhostMode, toggleGhostMode } = useDevTools();
18
+
19
+ // 2. Look up the page definition
20
+ const pageConfig = (appManifest as any).pages?.find((p: any) => p.id === pageId);
17
21
 
18
22
  if (!pageConfig) {
19
23
  return (
20
24
  <div className="p-8">
21
- <Alert variant="danger" title="404: Page Definition Not Found">
25
+ <Alert variant="danger" title="Page Not Found">
22
26
  The manifest does not contain a page with ID: <code>{pageId}</code>.
23
27
  </Alert>
24
28
  </div>
@@ -26,33 +30,61 @@ const DynamicPage: React.FC<DynamicPageProps> = ({ pageId }) => {
26
30
  }
27
31
 
28
32
  return (
29
- <div className="space-y-8 fade-in">
33
+ <div className="space-y-8 fade-in p-6">
30
34
  <PageHeader
31
35
  title={pageConfig.title}
32
- description={pageConfig.description}
36
+ description={pageConfig.description}
37
+ actions={
38
+ <Button
39
+ variant={isGhostMode ? 'accent' : 'outline'}
40
+ size="sm"
41
+ onClick={toggleGhostMode}
42
+ title="Toggle Ghost Mode (Ctrl+Shift+G)"
43
+ >
44
+ <Icon name={isGhostMode ? 'eye' : 'eye-off'} className="mr-2" />
45
+ {isGhostMode ? 'Ghost Mode: ON' : 'Dev Tools'}
46
+ </Button>
47
+ }
33
48
  />
34
49
 
35
- {/* Render Sections */}
36
- {pageConfig.sections.map((section: { id: React.Key | null | undefined; title: string | number | boolean | React.ReactElement<any, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | React.ReactPortal | null | undefined; layout: { columns: any; }; blocks: any[]; }) => (
50
+ {/* 4. Render Sections */}
51
+ {pageConfig.sections.map((section: any) => (
37
52
  <div key={section.id} className="space-y-4">
38
53
  {section.title && <h3 className="text-xl font-semibold">{section.title}</h3>}
39
54
 
40
55
  <div
41
56
  className="grid gap-6"
42
57
  style={{
43
- gridTemplateColumns: `repeat(${section.layout?.columns || 3}, minmax(300px, 1fr))`
58
+ // ⚡️ IMPROVEMENT: Default to 1 column (Full Width) instead of 3.
59
+ // This fixes the "narrow table" issue immediately.
60
+ gridTemplateColumns: `repeat(${section.layout?.columns || 1}, minmax(0, 1fr))`
44
61
  }}
45
62
  >
46
- {section.blocks.map(block => (
47
- <GhostOverlay
48
- key={block.id}
49
- componentId={block.id}
50
- componentType={block.type}
51
- signalId={block.props.signalId}
52
- >
53
- <DynamicBlock block={block} />
54
- </GhostOverlay>
55
- ))}
63
+ {section.blocks.map((block: any) => {
64
+ // ⚡️ IMPROVEMENT: Calculate Spanning
65
+ // Allows a block to stretch across multiple columns if needed.
66
+ const colSpan = block.layout?.colSpan || 1;
67
+ const rowSpan = block.layout?.rowSpan || 1;
68
+
69
+ return (
70
+ <div
71
+ key={block.id}
72
+ style={{
73
+ gridColumn: `span ${colSpan}`,
74
+ gridRow: `span ${rowSpan}`
75
+ }}
76
+ >
77
+ <GhostOverlay
78
+ isActive={isGhostMode}
79
+ componentId={block.id}
80
+ componentType={block.type}
81
+ signalId={block.props.signalId}
82
+ >
83
+ <DynamicBlock block={block} />
84
+ </GhostOverlay>
85
+ </div>
86
+ );
87
+ })}
56
88
  </div>
57
89
  </div>
58
90
  ))}
@@ -0,0 +1,162 @@
1
+ import React from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import {
4
+ PageHeader,
5
+ Card,
6
+ Button,
7
+ Icon,
8
+ Badge,
9
+ SectionHeader
10
+ } from '@ramme-io/ui';
11
+
12
+ const Welcome: React.FC = () => {
13
+ const navigate = useNavigate();
14
+
15
+ return (
16
+ <div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700">
17
+
18
+ {/* HERO */}
19
+ <div className="relative overflow-hidden rounded-xl bg-gradient-to-r from-primary to-violet-600 text-primary-foreground p-8 md:p-12">
20
+ <div className="relative z-10 max-w-2xl">
21
+ <Badge variant="secondary" className="mb-4 bg-white/20 text-white border-none backdrop-blur-sm">
22
+ v1.2.0 Starter Kit
23
+ </Badge>
24
+ <h1 className="text-4xl md:text-5xl font-extrabold mb-4 tracking-tight">
25
+ Your Ramme App is Ready.
26
+ </h1>
27
+ <p className="text-lg md:text-xl text-primary-foreground/90 mb-8 leading-relaxed">
28
+ You have successfully scaffolded a production-ready prototype environment.
29
+ This kit comes pre-wired with Authentication, Mock Data, and the A.D.A.P.T. architecture.
30
+ </p>
31
+ <div className="flex flex-wrap gap-4">
32
+ <Button
33
+ size="lg"
34
+ variant="secondary"
35
+ className="font-semibold"
36
+ iconLeft="layout-dashboard"
37
+ onClick={() => navigate('/dashboard/app')}
38
+ >
39
+ Open Live Dashboard
40
+ </Button>
41
+ <Button
42
+ size="lg"
43
+ variant="outline"
44
+ className="bg-transparent border-white/30 text-white hover:bg-white/10 hover:text-white"
45
+ iconLeft="book-open"
46
+ onClick={() => navigate('/docs')}
47
+ >
48
+ Read Documentation
49
+ </Button>
50
+ </div>
51
+ </div>
52
+
53
+ {/* Decorative Background Icon */}
54
+ <div className="absolute -right-10 -bottom-10 opacity-10 rotate-12">
55
+ <Icon name="box" size={300} />
56
+ </div>
57
+ </div>
58
+
59
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
60
+
61
+ {/* SECTION 1: ARCHITECTURE */}
62
+ <div className="space-y-4">
63
+ <SectionHeader title="Project Architecture" />
64
+ <div className="grid gap-4">
65
+ <Card className="p-4 flex gap-4 hover:border-primary/50 transition-colors cursor-default">
66
+ <div className="p-3 rounded-lg bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400 h-fit">
67
+ <Icon name="database" size={24} />
68
+ </div>
69
+ <div>
70
+ <h3 className="font-semibold text-foreground">Data Lake</h3>
71
+ <p className="text-sm text-muted-foreground mt-1">
72
+ Mock data is seeded into <code>localStorage</code> on boot.
73
+ Edit <code>src/data/mockData.ts</code> to change the schema.
74
+ </p>
75
+ </div>
76
+ </Card>
77
+
78
+ <Card className="p-4 flex gap-4 hover:border-primary/50 transition-colors cursor-default">
79
+ <div className="p-3 rounded-lg bg-purple-100 text-purple-600 dark:bg-purple-900/30 dark:text-purple-400 h-fit">
80
+ <Icon name="git-branch" size={24} />
81
+ </div>
82
+ <div>
83
+ <h3 className="font-semibold text-foreground">Logic Engine</h3>
84
+ <p className="text-sm text-muted-foreground mt-1">
85
+ Workflows and signals are processed in real-time by
86
+ <code>useWorkflowEngine.ts</code>. Supports MQTT and simulated sensors.
87
+ </p>
88
+ </div>
89
+ </Card>
90
+
91
+ <Card className="p-4 flex gap-4 hover:border-primary/50 transition-colors cursor-default">
92
+ <div className="p-3 rounded-lg bg-orange-100 text-orange-600 dark:bg-orange-900/30 dark:text-orange-400 h-fit">
93
+ <Icon name="layout" size={24} />
94
+ </div>
95
+ <div>
96
+ <h3 className="font-semibold text-foreground">Dynamic Routing</h3>
97
+ <p className="text-sm text-muted-foreground mt-1">
98
+ Pages are generated from <code>app.manifest.ts</code>.
99
+ Visual blocks connect automatically to data sources.
100
+ </p>
101
+ </div>
102
+ </Card>
103
+ </div>
104
+ </div>
105
+
106
+ {/* SECTION 2: RESOURCES */}
107
+ <div className="space-y-4">
108
+ <SectionHeader title="Developer Resources" />
109
+
110
+ <Card className="p-6 space-y-6">
111
+ <div>
112
+ <h4 className="font-semibold flex items-center gap-2 mb-2">
113
+ <Icon name="palette" size={16} className="text-muted-foreground" />
114
+ Component Library
115
+ </h4>
116
+ <p className="text-sm text-muted-foreground mb-3">
117
+ Browse the full suite of accessible UI components available in this project.
118
+ </p>
119
+ <Button variant="outline" size="sm" onClick={() => navigate('/styleguide')}>
120
+ View Style Guide
121
+ </Button>
122
+ </div>
123
+
124
+ <div className="border-t border-border pt-6">
125
+ <h4 className="font-semibold flex items-center gap-2 mb-2">
126
+ <Icon name="settings" size={16} className="text-muted-foreground" />
127
+ Configuration
128
+ </h4>
129
+ <p className="text-sm text-muted-foreground mb-3">
130
+ Manage global settings, user profile templates, and billing layouts.
131
+ </p>
132
+ <Button variant="outline" size="sm" onClick={() => navigate('/settings')}>
133
+ Open Settings
134
+ </Button>
135
+ </div>
136
+
137
+ <div className="border-t border-border pt-6">
138
+ <h4 className="font-semibold flex items-center gap-2 mb-2">
139
+ <Icon name="github" size={16} className="text-muted-foreground" />
140
+ Community
141
+ </h4>
142
+ <p className="text-sm text-muted-foreground mb-3">
143
+ Need help? Check the docs or open an issue on GitHub.
144
+ </p>
145
+ <a
146
+ href="https://github.com/ramme-io/create-app"
147
+ target="_blank"
148
+ rel="noreferrer"
149
+ className="inline-flex"
150
+ >
151
+ <Button variant="ghost" size="sm">GitHub Repo &rarr;</Button>
152
+ </a>
153
+ </div>
154
+ </Card>
155
+ </div>
156
+
157
+ </div>
158
+ </div>
159
+ );
160
+ };
161
+
162
+ export default Welcome;
@@ -18,6 +18,7 @@ import { SitemapProvider } from '../../contexts/SitemapContext';
18
18
  import PageTitleUpdater from '../../components/PageTitleUpdater';
19
19
  import AppHeader from '../../components/AppHeader';
20
20
  import { AIChatWidget } from '../../components/AIChatWidget'; // <-- NEW: Import Widget
21
+ import { useWorkflowEngine } from '../../hooks/useWorkflowEngine';
21
22
 
22
23
  // NavLink wrapper - Correct
23
24
  const SidebarNavLink = React.forwardRef<HTMLAnchorElement, any>(
@@ -87,6 +88,7 @@ const AppSidebarContent: React.FC = () => {
87
88
  const DashboardLayout: React.FC = () => {
88
89
  // 1. STATE: Track if the chat window is open
89
90
  const [isChatOpen, setIsChatOpen] = useState(false);
91
+ useWorkflowEngine();
90
92
 
91
93
  return (
92
94
  <SitemapProvider value={dashboardSitemap}>
@@ -1,83 +1,44 @@
1
1
  import { type SitemapEntry } from '../../core/sitemap-entry';
2
-
3
- // Component Imports
2
+ import { appManifest } from '../../config/app.manifest';
4
3
  import Dashboard from '../../pages/Dashboard';
5
4
  import AiChat from '../../pages/AiChat';
6
- import DataGridPage from '../../pages/DataGridPage';
7
- import Styleguide from '../../pages/styleguide/Styleguide';
8
- import DataLayout from '../../layouts/DataLayout';
9
-
10
- // Style Guide Section Imports
11
- import TemplatesSection from '../../pages/styleguide/sections/templates/TemplatesSection';
12
- import LayoutSection from '../../pages/styleguide/sections/layout/LayoutSection';
13
- import ThemingSection from '../../pages/styleguide/sections/theming/ThemingSection';
14
- import NavigationSection from '../../pages/styleguide/sections/navigation/NavigationSection';
15
- import TablesSection from '../../pages/styleguide/sections/tables/TablesSection';
16
- import ChartsSection from '../../pages/styleguide/sections/charts/ChartsSection';
17
- import ElementsSection from '../../pages/styleguide/sections/elements/ElementsSection';
18
- import FormsSection from '../../pages/styleguide/sections/forms/FormsSection';
19
- import FeedbackSection from '../../pages/styleguide/sections/feedback/FeedbackSection';
20
- import UtilitiesSection from '../../pages/styleguide/sections/utilities/UtilitiesSection';
21
- import ColorsSection from '../../pages/styleguide/sections/colors/ColorsSection';
22
- import IconsSection from '../../pages/styleguide/sections/icons/IconsSection';
23
-
24
- // --- ADD THIS IMPORT ---
25
- import ItemSelectorPage from '../../pages/prototypes/ItemSelectorPage';
26
-
5
+ import Welcome from '../../pages/Welcome'; // ✅ Import the new page
27
6
 
28
7
  export const dashboardSitemap: SitemapEntry[] = [
8
+ // ✅ 1. The New Landing Page
29
9
  {
30
- id: 'dashboard',
31
- path: '',
32
- title: 'Home Overview',
33
- icon: 'layout-dashboard',
34
- component: Dashboard,
10
+ id: 'welcome',
11
+ path: 'welcome',
12
+ title: 'Start Here',
13
+ icon: 'rocket',
14
+ component: Welcome,
35
15
  },
36
-
37
- // --- ADD THIS NEW SITEMAP ENTRY ---
38
- {
39
- id: 'entity-selector-prototype',
40
- path: 'prototypes/entity-selector',
41
- title: 'Entity Prototype',
42
- icon: 'beaker',
43
- component: ItemSelectorPage,
44
- },
45
- {
16
+ ];
17
+
18
+ // A. Dynamic Pages from Manifest
19
+ if (appManifest.pages) {
20
+ appManifest.pages.forEach(page => {
21
+ const isDashboard = page.slug === 'dashboard';
22
+
23
+ dashboardSitemap.push({
24
+ id: page.id,
25
+ title: page.title,
26
+ // ✅ FIX: Map the main dashboard to 'app' instead of root ''
27
+ // This prevents conflict with the layout root
28
+ path: isDashboard ? 'app' : page.slug,
29
+ icon: isDashboard ? 'layout-dashboard' : 'file-text',
30
+ component: Dashboard,
31
+ });
32
+ });
33
+ }
34
+
35
+ // B. Dynamic Modules
36
+ if (appManifest.modules?.includes('ai-chat')) {
37
+ dashboardSitemap.push({
46
38
  id: 'ai-chat',
47
39
  path: 'ai-chat',
48
- title: 'AI Chat',
40
+ title: 'AI Assistant',
49
41
  icon: 'bot',
50
42
  component: AiChat,
51
- },
52
- {
53
- id: 'data',
54
- path: 'data',
55
- title: 'Data',
56
- icon: 'database',
57
- component: DataLayout,
58
- children: [
59
- { id: 'users', path: 'users', title: 'Users', component: DataGridPage, icon: 'table' }
60
- ],
61
- },
62
- {
63
- id: 'styleguide',
64
- path: 'styleguide',
65
- title: 'Style Guide',
66
- icon: 'palette',
67
- component: Styleguide,
68
- children: [
69
- { id: 'templates', path: 'templates', title: 'Templates', component: TemplatesSection },
70
- { id: 'layout', path: 'layout', title: 'Layout', component: LayoutSection },
71
- { id: 'theming', path: 'theming', title: 'Theming', component: ThemingSection },
72
- { id: 'navigation', path: 'navigation', title: 'Navigation', component: NavigationSection },
73
- { id: 'tables', path: 'tables', title: 'Tables', component: TablesSection },
74
- { id: 'charts', path: 'charts', title: 'Charts', component: ChartsSection },
75
- { id: 'elements', path: 'elements', title: 'Elements', component: ElementsSection },
76
- { id: 'forms', path: 'forms', title: 'Forms', component: FormsSection },
77
- { id: 'feedback', path: 'feedback', title: 'Feedback', component: FeedbackSection },
78
- { id: 'utilities', path: 'utilities', title: 'Utilities', component: UtilitiesSection },
79
- { id: 'colors', path: 'colors', title: 'Colors', component: ColorsSection },
80
- { id: 'icons', path: 'icons', title: 'Icons', component: IconsSection },
81
- ],
82
- },
83
- ];
43
+ });
44
+ }
@@ -1,67 +1,146 @@
1
1
  import { z } from 'zod';
2
2
 
3
3
  // ------------------------------------------------------------------
4
- // 1. Signal Schema
4
+ // 1. DATA RESOURCE DEFINITIONS (SaaS Layer)
5
5
  // ------------------------------------------------------------------
6
+
7
+ export const FieldSchema = z.object({
8
+ key: z.string(),
9
+ label: z.string(),
10
+ type: z.enum(['text', 'number', 'currency', 'date', 'boolean', 'status', 'email', 'image', 'textarea']),
11
+ required: z.boolean().optional(),
12
+ description: z.string().optional(),
13
+ defaultValue: z.any().optional(),
14
+ });
15
+ export type FieldDefinition = z.infer<typeof FieldSchema>;
16
+
17
+ export const ResourceSchema = z.object({
18
+ id: z.string(),
19
+ name: z.string(),
20
+ fields: z.array(FieldSchema),
21
+ defaultView: z.enum(['table', 'grid', 'list']).optional(),
22
+ features: z.object({
23
+ searchable: z.boolean().optional(),
24
+ creatable: z.boolean().optional(),
25
+ editable: z.boolean().optional(),
26
+ deletable: z.boolean().optional(),
27
+ exportable: z.boolean().optional(),
28
+ }).optional(),
29
+ });
30
+ export type ResourceDefinition = z.infer<typeof ResourceSchema>;
31
+
32
+
33
+ // ------------------------------------------------------------------
34
+ // 2. SIGNAL & IOT DEFINITIONS (Physical Layer)
35
+ // ------------------------------------------------------------------
36
+
6
37
  export const SignalSchema = z.object({
7
38
  id: z.string().min(1, "Signal ID is required"),
8
39
  label: z.string(),
9
40
  description: z.string().optional(),
10
-
11
- // Classification
12
41
  kind: z.enum(['sensor', 'actuator', 'setpoint', 'metric', 'status', 'kpi']),
13
-
14
- // Data Source Configuration
15
42
  source: z.enum(['mock', 'mqtt', 'http', 'derived', 'local']),
16
43
 
17
- // Protocol-Specific Config
18
- topic: z.string().optional(), // For MQTT
19
- endpoint: z.string().optional(), // For HTTP
20
- jsonPath: z.string().optional(), // For parsing nested API responses
44
+ // Connectivity
45
+ topic: z.string().optional(),
46
+ endpoint: z.string().optional(),
47
+ jsonPath: z.string().optional(),
21
48
  refreshRate: z.number().optional().default(1000),
22
49
 
23
- // Value Configuration
50
+ // Values
24
51
  defaultValue: z.any().optional(),
25
52
  unit: z.string().optional(),
26
-
27
- // Validation
28
53
  min: z.number().optional(),
29
54
  max: z.number().optional(),
30
55
  });
31
-
32
56
  export type SignalDefinition = z.infer<typeof SignalSchema>;
33
57
 
34
-
35
- // ------------------------------------------------------------------
36
- // 2. Entity Schema
37
- // ------------------------------------------------------------------
38
58
  export const EntitySchema = z.object({
39
59
  id: z.string(),
40
60
  name: z.string(),
41
61
  description: z.string().optional(),
42
-
43
- // Taxonomy
44
62
  type: z.string(),
45
63
  category: z.string().default('logical'),
46
-
47
- // Linkage
48
64
  signals: z.array(z.string()),
49
-
50
- // UI Hints
51
65
  ui: z.object({
52
66
  icon: z.string().optional(),
53
67
  color: z.string().optional(),
54
68
  dashboardComponent: z.string().optional(),
55
69
  }).optional(),
56
70
  });
57
-
58
- // <--- THIS WAS MISSING
59
71
  export type EntityDefinition = z.infer<typeof EntitySchema>;
60
72
 
73
+ // ------------------------------------------------------------------
74
+ // ✅ NEW: LOGIC & WORKFLOW DEFINITIONS
75
+ // ------------------------------------------------------------------
76
+
77
+ export const TriggerSchema = z.object({
78
+ id: z.string(),
79
+ type: z.enum(['signal_change', 'manual_action', 'schedule', 'webhook']),
80
+ config: z.record(z.string(), z.any()),
81
+ });
82
+
83
+ export const ActionSchema = z.object({
84
+ id: z.string(),
85
+ type: z.enum([
86
+ 'update_resource',
87
+ 'send_notification',
88
+ 'mqtt_publish',
89
+ 'api_call',
90
+ 'navigate',
91
+ 'agent_task'
92
+ ]),
93
+ config: z.record(z.string(), z.any()),
94
+ });
95
+
96
+ export const WorkflowSchema = z.object({
97
+ id: z.string(),
98
+ name: z.string(),
99
+ active: z.boolean().default(true),
100
+ trigger: TriggerSchema,
101
+ actions: z.array(ActionSchema),
102
+ });
103
+ export type WorkflowDefinition = z.infer<typeof WorkflowSchema>;
104
+ export type ActionDefinition = z.infer<typeof ActionSchema>;
105
+
61
106
 
62
107
  // ------------------------------------------------------------------
63
- // 3. App Specification (The Root Object)
108
+ // 3. UI LAYOUT DEFINITIONS (Presentation Layer)
64
109
  // ------------------------------------------------------------------
110
+
111
+ export const BlockSchema = z.object({
112
+ id: z.string(),
113
+ type: z.string(),
114
+ props: z.record(z.string(), z.any()),
115
+ layout: z.object({
116
+ colSpan: z.number().optional(),
117
+ rowSpan: z.number().optional(),
118
+ }).optional(),
119
+ });
120
+
121
+ export const PageSectionSchema = z.object({
122
+ id: z.string(),
123
+ title: z.string().optional(),
124
+ description: z.string().optional(),
125
+ layout: z.object({
126
+ columns: z.number().optional(),
127
+ variant: z.enum(['grid', 'stack', 'split']).optional()
128
+ }).optional(),
129
+ blocks: z.array(BlockSchema),
130
+ });
131
+
132
+ export const PageSchema = z.object({
133
+ id: z.string(),
134
+ slug: z.string(),
135
+ title: z.string(),
136
+ description: z.string().optional(),
137
+ icon: z.string().optional(),
138
+ sections: z.array(PageSectionSchema),
139
+ });
140
+ export type PageDefinition = z.infer<typeof PageSchema>;
141
+
142
+
143
+ // MASTER APP SPECIFICATION
65
144
  export const AppSpecificationSchema = z.object({
66
145
  meta: z.object({
67
146
  name: z.string(),
@@ -71,15 +150,23 @@ export const AppSpecificationSchema = z.object({
71
150
  createdAt: z.string().optional(),
72
151
  }),
73
152
 
153
+ config: z.object({
154
+ theme: z.enum(['light', 'dark', 'system', 'corporate', 'midnight', 'blueprint']).default('system'),
155
+ mockMode: z.boolean().default(true),
156
+ brokerUrl: z.string().optional(),
157
+ }),
158
+
159
+ modules: z.array(z.string()).optional(),
160
+ resources: z.array(ResourceSchema).optional(),
161
+
74
162
  domain: z.object({
75
163
  signals: z.array(SignalSchema),
76
164
  entities: z.array(EntitySchema),
165
+ // ✅ ADDED WORKFLOWS HERE
166
+ workflows: z.array(WorkflowSchema).optional(),
77
167
  }),
78
168
 
79
- config: z.object({
80
- theme: z.enum(['light', 'dark', 'system', 'corporate', 'midnight', 'blueprint']).default('system'),
81
- mockMode: z.boolean().default(true),
82
- }),
169
+ pages: z.array(PageSchema).optional(),
83
170
  });
84
171
 
85
172
  export type AppSpecification = z.infer<typeof AppSpecificationSchema>;