@ramme-io/create-app 1.2.0 → 1.2.2

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.
Files changed (92) hide show
  1. package/package.json +1 -2
  2. package/template/package.json +41 -0
  3. package/template/pkg.json +1 -1
  4. package/template/src/App.tsx +65 -35
  5. package/template/src/components/AIChatWidget.tsx +2 -2
  6. package/template/src/components/AppHeader.tsx +2 -2
  7. package/template/src/components/AutoForm.tsx +13 -0
  8. package/template/src/{pages/styleguide → components}/NotFound.tsx +1 -1
  9. package/template/src/components/PageTitleUpdater.tsx +2 -2
  10. package/template/src/components/ProtectedRoute.tsx +18 -1
  11. package/template/src/components/ScrollToTop.tsx +19 -0
  12. package/template/src/config/app.manifest.ts +3 -1
  13. package/template/src/{core → config}/component-registry.tsx +1 -1
  14. package/template/src/config/navigation.ts +1 -1
  15. package/template/src/data/mock-charts.ts +32 -28
  16. package/template/src/{components → engine/renderers}/DynamicBlock.tsx +27 -7
  17. package/template/src/{pages → engine/renderers}/DynamicPage.tsx +23 -4
  18. package/template/src/{contexts → engine/runtime}/MqttContext.tsx +25 -11
  19. package/template/src/{contexts → engine/runtime}/SitemapContext.tsx +1 -1
  20. package/template/src/{core → engine/runtime}/data-seeder.ts +15 -5
  21. package/template/src/{hooks → engine/runtime}/useAction.ts +19 -8
  22. package/template/src/{hooks → engine/runtime}/useCrudLocalStorage.ts +27 -8
  23. package/template/src/{hooks → engine/runtime}/useDataQuery.ts +15 -1
  24. package/template/src/engine/runtime/useSignal.ts +51 -0
  25. package/template/src/engine/runtime/useSignalStore.ts +94 -0
  26. package/template/src/engine/runtime/useWorkflowEngine.ts +144 -0
  27. package/template/src/{core → engine/types}/manifest-types.ts +35 -3
  28. package/template/src/{types → engine/validation}/schema.ts +53 -2
  29. package/template/src/{pages → features/ai/pages}/AiChat.tsx +1 -1
  30. package/template/src/features/auth/AuthContext.tsx +118 -0
  31. package/template/src/features/auth/pages/AuthLayout.tsx +55 -0
  32. package/template/src/features/auth/pages/LoginPage.tsx +106 -0
  33. package/template/src/features/auth/pages/SignupPage.tsx +96 -0
  34. package/template/src/features/datagrid/SmartTable.tsx +222 -0
  35. package/template/src/features/onboarding/pages/Welcome.tsx +161 -0
  36. package/template/src/features/overview/index.ts +1 -0
  37. package/template/src/features/overview/pages/OverviewPage.tsx +127 -0
  38. package/template/src/{pages → features/playground/pages}/AccountingLedgerPage.tsx +1 -1
  39. package/template/src/{pages/prototypes → features/playground/pages}/ItemSelectorPage.tsx +1 -1
  40. package/template/src/{pages/settings → features/settings/pages}/BillingPage.tsx +1 -1
  41. package/template/src/features/settings/pages/ProfilePage.tsx +153 -0
  42. package/template/src/{pages/settings → features/settings/pages}/TeamPage.tsx +1 -1
  43. package/template/src/features/styleguide/Styleguide.tsx +75 -0
  44. package/template/src/features/users/components/UserDrawer.tsx +138 -0
  45. package/template/src/features/users/index.ts +2 -0
  46. package/template/src/features/users/pages/UsersPage.tsx +151 -0
  47. package/template/src/index.css +1 -1
  48. package/template/src/main.tsx +3 -3
  49. package/template/src/templates/dashboard/DashboardLayout.tsx +75 -106
  50. package/template/src/templates/dashboard/dashboard.sitemap.ts +34 -19
  51. package/template/src/templates/docs/DocsLayout.tsx +49 -38
  52. package/template/src/templates/docs/docs.sitemap.ts +22 -34
  53. package/template/src/templates/settings/SettingsLayout.tsx +83 -143
  54. package/template/src/templates/settings/settings.sitemap.ts +6 -6
  55. package/template/vite.config.ts +12 -9
  56. package/template/src/adaptors/.gitkeep +0 -0
  57. package/template/src/blocks/SmartTable.tsx +0 -191
  58. package/template/src/components/LocalSideNav.tsx +0 -120
  59. package/template/src/components/PageWithSideNav.tsx +0 -69
  60. package/template/src/config/dashboard.layout.ts +0 -110
  61. package/template/src/contexts/AuthContext.tsx +0 -64
  62. package/template/src/data/mockUsers.ts +0 -18
  63. package/template/src/generated/hooks.ts +0 -40
  64. package/template/src/hooks/useSignal.ts +0 -83
  65. package/template/src/hooks/useWorkflowEngine.ts +0 -6
  66. package/template/src/layouts/DataLayout.tsx +0 -37
  67. package/template/src/layouts/SideNavLayout.tsx +0 -28
  68. package/template/src/pages/Dashboard.tsx +0 -60
  69. package/template/src/pages/DataGridPage.tsx +0 -184
  70. package/template/src/pages/LoginPage.tsx +0 -58
  71. package/template/src/pages/settings/ProfilePage.tsx +0 -10
  72. package/template/src/pages/styleguide/Styleguide.tsx +0 -40
  73. package/template/src/templates/docs/pages/Introduction.tsx +0 -13
  74. package/template/src/types/signal.ts +0 -23
  75. /package/template/src/{core → engine/renderers}/route-generator.tsx +0 -0
  76. /package/template/src/{core → engine/types}/sitemap-entry.ts +0 -0
  77. /package/template/src/{pages → features}/GenericContentPage.tsx +0 -0
  78. /package/template/src/{hooks → features/assistant}/useMockChat.ts +0 -0
  79. /package/template/src/{components/dev → features/developer}/GhostOverlay.tsx +0 -0
  80. /package/template/src/{hooks → features/developer}/useDevTools.ts +0 -0
  81. /package/template/src/{pages → features}/styleguide/sections/charts/ChartsSection.tsx +0 -0
  82. /package/template/src/{pages → features}/styleguide/sections/colors/ColorsSection.tsx +0 -0
  83. /package/template/src/{pages → features}/styleguide/sections/elements/ElementsSection.tsx +0 -0
  84. /package/template/src/{pages → features}/styleguide/sections/feedback/FeedbackSection.tsx +0 -0
  85. /package/template/src/{pages → features}/styleguide/sections/forms/FormsSection.tsx +0 -0
  86. /package/template/src/{pages → features}/styleguide/sections/icons/IconsSection.tsx +0 -0
  87. /package/template/src/{pages → features}/styleguide/sections/layout/LayoutSection.tsx +0 -0
  88. /package/template/src/{pages → features}/styleguide/sections/navigation/NavigationSection.tsx +0 -0
  89. /package/template/src/{pages → features}/styleguide/sections/tables/TablesSection.tsx +0 -0
  90. /package/template/src/{pages → features}/styleguide/sections/templates/TemplatesSection.tsx +0 -0
  91. /package/template/src/{pages → features}/styleguide/sections/theming/ThemingSection.tsx +0 -0
  92. /package/template/src/{pages → features}/styleguide/sections/utilities/UtilitiesSection.tsx +0 -0
@@ -1,17 +1,32 @@
1
- // src/hooks/useAction.ts
2
1
  import { useCallback } from 'react';
3
- import { useMqtt } from '../contexts/MqttContext';
4
- import { appManifest } from '../config/app.manifest';
2
+ import { useMqtt } from './MqttContext';
3
+ // User Verified Path
4
+ import { appManifest } from '../../config/app.manifest';
5
+
6
+ /**
7
+ * @file useAction.ts
8
+ * @description The "Effectuator" hook.
9
+ *
10
+ * ARCHITECTURAL ROLE:
11
+ * This hook handles outgoing commands from the UI. It abstracts the transport layer,
12
+ * automatically routing actions to the correct destination (MQTT, HTTP, or Mock Console)
13
+ * based on the entity's configuration in the manifest.
14
+ */
5
15
 
6
16
  export const useAction = () => {
7
17
  const { publish, isConnected } = useMqtt();
18
+
19
+ // Destructure config/domain from the manifest
8
20
  const { config, domain } = appManifest;
9
21
 
10
22
  const sendAction = useCallback(async (entityId: string, value: any) => {
11
23
  // 1. Find the Entity definition
24
+ // Note: If domain.entities is empty (early stage), this is where we'd add fallback logic
12
25
  const entity = domain.entities.find(e => e.id === entityId);
26
+
13
27
  if (!entity) {
14
- console.warn(`[Action] Entity not found: ${entityId}`);
28
+ // For development/debugging, we log this even if the entity is missing
29
+ console.warn(`[Action] Entity ID '${entityId}' not found in manifest.`);
15
30
  return;
16
31
  }
17
32
 
@@ -29,7 +44,6 @@ export const useAction = () => {
29
44
  // --- Mock Mode ---
30
45
  if (config.mockMode) {
31
46
  console.log(`%c[Mock Action] Setting ${entity.name} to:`, 'color: #10b981; font-weight: bold;', value);
32
- // In a real app, we would update the local cache optimistically here
33
47
  return;
34
48
  }
35
49
 
@@ -47,8 +61,6 @@ export const useAction = () => {
47
61
  // --- Live Mode (HTTP) ---
48
62
  if (signal.source === 'http' && signal.endpoint) {
49
63
  console.log(`[HTTP] POST to ${signal.endpoint}:`, value);
50
- // Note: This will likely fail (404/405) against a static .json file,
51
- // but this is the correct code for a real API.
52
64
  try {
53
65
  await fetch(signal.endpoint, {
54
66
  method: 'POST',
@@ -56,7 +68,6 @@ export const useAction = () => {
56
68
  body: JSON.stringify({ id: signal.id, value })
57
69
  });
58
70
  } catch (err) {
59
- // We expect this to fail on the static demo, so we suppress the error alert
60
71
  console.log('[HTTP] (Simulation) Request sent.');
61
72
  }
62
73
  }
@@ -1,10 +1,12 @@
1
1
  import { useState, useCallback } from 'react';
2
2
 
3
3
  /**
4
- * A generic hook for performing CRUD (Create, Read, Update, Delete)
4
+ * @hook useCrudLocalStorage
5
+ * @description A generic hook for performing CRUD (Create, Read, Update, Delete)
5
6
  * operations on an array of items stored in the browser's localStorage.
7
+ * * It serves as the "Database Engine" for the mock runtime.
6
8
  *
7
- * @param storageKey The unique key for this data in localStorage.
9
+ * @param storageKey The unique key for this data in localStorage (e.g., 'ramme_db_users').
8
10
  * @param initialData The default data to seed localStorage with if it's empty.
9
11
  * @returns An object with the current data and functions to manipulate it.
10
12
  */
@@ -12,28 +14,43 @@ export const useCrudLocalStorage = <T extends { id: any }>(
12
14
  storageKey: string,
13
15
  initialData: T[]
14
16
  ) => {
17
+ // 1. Initialize State from LocalStorage
15
18
  const [data, setData] = useState<T[]>(() => {
19
+ // Safety check for Server-Side Rendering
20
+ if (typeof window === 'undefined') return initialData;
21
+
16
22
  try {
17
23
  const item = window.localStorage.getItem(storageKey);
18
24
  if (item) {
19
- // If data exists in localStorage, parse and return it.
20
25
  return JSON.parse(item);
21
26
  } else {
22
- // Otherwise, seed localStorage with the initial data.
27
+ // Seed the "DB" if empty
23
28
  window.localStorage.setItem(storageKey, JSON.stringify(initialData));
24
29
  return initialData;
25
30
  }
26
31
  } catch (error) {
27
- console.error("Error reading from localStorage", error);
32
+ console.error(`[Data Lake] Error reading key "${storageKey}":`, error);
28
33
  return initialData;
29
34
  }
30
35
  });
31
36
 
37
+ // 2. CREATE
32
38
  const createItem = useCallback((newItem: Omit<T, 'id'>) => {
33
39
  setData(prevData => {
34
- // Find the highest existing ID to safely create a new one.
35
- const maxId = prevData.reduce((max, item) => (item.id > max ? item.id : max), 0);
36
- const fullNewItem = { ...newItem, id: maxId + 1 } as T;
40
+ // ROBUST ID GENERATION (Zero Jank)
41
+ // Detects if existing IDs are Numbers or Strings to prevent type conflicts.
42
+ let newId: any;
43
+ const isNumeric = prevData.length > 0 && typeof prevData[0].id === 'number';
44
+
45
+ if (isNumeric) {
46
+ const maxId = prevData.reduce((max, item) => (typeof item.id === 'number' && item.id > max ? item.id : max), 0);
47
+ newId = maxId + 1;
48
+ } else {
49
+ // Fallback for string IDs (e.g. 'usr_17354...')
50
+ newId = `id_${Date.now()}`;
51
+ }
52
+
53
+ const fullNewItem = { ...newItem, id: newId } as T;
37
54
 
38
55
  const updatedData = [...prevData, fullNewItem];
39
56
  window.localStorage.setItem(storageKey, JSON.stringify(updatedData));
@@ -41,6 +58,7 @@ export const useCrudLocalStorage = <T extends { id: any }>(
41
58
  });
42
59
  }, [storageKey]);
43
60
 
61
+ // 3. UPDATE
44
62
  const updateItem = useCallback((updatedItem: T) => {
45
63
  setData(prevData => {
46
64
  const updatedData = prevData.map(item =>
@@ -51,6 +69,7 @@ export const useCrudLocalStorage = <T extends { id: any }>(
51
69
  });
52
70
  }, [storageKey]);
53
71
 
72
+ // 4. DELETE
54
73
  const deleteItem = useCallback((id: T['id']) => {
55
74
  setData(prevData => {
56
75
  const updatedData = prevData.filter(item => item.id !== id);
@@ -1,6 +1,20 @@
1
1
  import { useMemo } from 'react';
2
2
 
3
- // --- 1. Export the Missing Types (Fixes ts(2305)) ---
3
+ /**
4
+ * @file useDataQuery.ts
5
+ * @description The "In-Memory Database Engine".
6
+ *
7
+ * ARCHITECTURAL ROLE:
8
+ * Since this application runs without a real backend, this hook acts as the
9
+ * SQL Query Engine. It takes raw arrays from the Data Lake and performs
10
+ * real-time filtering, sorting, and pagination before passing the result
11
+ * to the UI.
12
+ *
13
+ * CAPABILITIES:
14
+ * 1. WHERE: Supports complex filtering (equals, contains, gt, lt).
15
+ * 2. ORDER BY: Handles ascending/descending sorts on any field.
16
+ * 3. LIMIT/OFFSET: Calculates pagination slices automatically.
17
+ */
4
18
 
5
19
  export type SortDirection = 'asc' | 'desc';
6
20
 
@@ -0,0 +1,51 @@
1
+ import { useMemo } from 'react';
2
+ import { useSignalStore } from './useSignalStore';
3
+ // ✅ Import Manifest to get static metadata (min, max, units)
4
+ import { appManifest } from '../../config/app.manifest';
5
+
6
+ export interface SignalState {
7
+ id: string;
8
+ value: any;
9
+ unit?: string;
10
+ min?: number;
11
+ max?: number;
12
+ timestamp?: number;
13
+ status: 'fresh' | 'stale'; // Simplified status
14
+ }
15
+
16
+ /**
17
+ * @hook useSignal
18
+ * @description The "Signal Selector".
19
+ *
20
+ * ARCHITECTURAL ROLE:
21
+ * This hook is the bridge between the Static Manifest and the Dynamic Store.
22
+ * 1. It finds the signal definition in the Manifest (for min/max/unit).
23
+ * 2. It grabs the live value from the Zustand SignalStore.
24
+ * 3. It merges them into a single object for the UI to consume.
25
+ */
26
+ export const useSignal = (signalId: string): SignalState => {
27
+ // 1. Get Live Data from Store
28
+ const signalData = useSignalStore((state: { signals: { [x: string]: any; }; }) => state.signals[signalId]);
29
+
30
+ // 2. Get Static Definition from Manifest
31
+ // We use useMemo so we don't search the array on every render
32
+ const signalDef = useMemo(() => {
33
+ return appManifest.domain.signals.find((s) => s.id === signalId);
34
+ }, [signalId]);
35
+
36
+ // 3. Merge and Return
37
+ const value = signalData?.value ?? signalDef?.defaultValue ?? 0;
38
+
39
+ // Determine if data is stale (older than 10 seconds)
40
+ const isStale = signalData ? (Date.now() - signalData.timestamp > 10000) : true;
41
+
42
+ return {
43
+ id: signalId,
44
+ value: value,
45
+ unit: signalDef?.unit,
46
+ min: signalDef?.min,
47
+ max: signalDef?.max,
48
+ timestamp: signalData?.timestamp,
49
+ status: isStale ? 'stale' : 'fresh'
50
+ };
51
+ };
@@ -0,0 +1,94 @@
1
+ import { create } from 'zustand';
2
+ import { useEffect } from 'react';
3
+
4
+ /**
5
+ * @file useSignalStore.ts
6
+ * @description The "Short-Term Memory" of the application.
7
+ *
8
+ * ARCHITECTURAL ROLE:
9
+ * This Zustand store holds the instantaneous value of every Signal (IoT sensor).
10
+ * It acts as the buffer between the high-speed data stream (MQTT/Simulation)
11
+ * and the UI components.
12
+ */
13
+
14
+ // --- 1. SIGNAL STORE TYPES ---
15
+ export interface SignalValue {
16
+ value: any;
17
+ timestamp: number;
18
+ }
19
+
20
+ interface SignalStore {
21
+ signals: Record<string, SignalValue>;
22
+ // Single update (used by UI controls)
23
+ updateSignal: (id: string, value: any) => void;
24
+ // Batch update (used by MQTT/Simulation)
25
+ updateSignals: (updates: Record<string, any>) => void;
26
+ }
27
+
28
+ // Initial State matches your dashboard IDs to prevent "undefined" errors on load
29
+ const initialState = {
30
+ living_room_ac: { value: 72, timestamp: Date.now() },
31
+ living_room_hum: { value: 45, timestamp: Date.now() },
32
+ server_01: { value: 42, timestamp: Date.now() },
33
+ front_door_lock: { value: 'LOCKED', timestamp: Date.now() }
34
+ };
35
+
36
+ export const useSignalStore = create<SignalStore>((set) => ({
37
+ signals: initialState,
38
+
39
+ updateSignal: (id, value) => set((state) => ({
40
+ signals: {
41
+ ...state.signals,
42
+ [id]: { value, timestamp: Date.now() }
43
+ }
44
+ })),
45
+
46
+ updateSignals: (updates) => set((state) => {
47
+ const newSignals = { ...state.signals };
48
+ Object.entries(updates).forEach(([id, val]) => {
49
+ newSignals[id] = { value: val, timestamp: Date.now() };
50
+ });
51
+ return { signals: newSignals };
52
+ })
53
+ }));
54
+
55
+ /**
56
+ * Hook to access specific signals in a component.
57
+ * Usage: const signals = useGeneratedSignals();
58
+ */
59
+ export const useGeneratedSignals = () => {
60
+ return useSignalStore((state) => state.signals);
61
+ };
62
+
63
+ // --- 2. SIMULATION ENGINE (The Missing Piece) ---
64
+ /**
65
+ * A hook that generates fake data for testing when no MQTT broker is connected.
66
+ * You can toggle this on/off via the manifest config.
67
+ */
68
+ export const useSimulation = (isEnabled: boolean = true) => {
69
+ const { updateSignals } = useSignalStore();
70
+
71
+ useEffect(() => {
72
+ if (!isEnabled) return;
73
+
74
+ console.log("[System] Simulation Mode: ON 🎲");
75
+ const interval = setInterval(() => {
76
+ // Simulate random fluctuations
77
+ const updates: Record<string, any> = {};
78
+
79
+ // Randomize values slightly around a baseline
80
+ updates['living_room_ac'] = Number((72 + (Math.random() * 4 - 2)).toFixed(1));
81
+ updates['living_room_hum'] = Number((45 + (Math.random() * 6 - 3)).toFixed(1));
82
+ updates['server_01'] = Math.floor(Math.random() * 100);
83
+
84
+ // Randomly flip the lock status occasionally (1% chance)
85
+ if (Math.random() > 0.99) {
86
+ updates['front_door_lock'] = Math.random() > 0.5 ? 'LOCKED' : 'UNLOCKED';
87
+ }
88
+
89
+ updateSignals(updates);
90
+ }, 2000); // Update every 2 seconds
91
+
92
+ return () => clearInterval(interval);
93
+ }, [isEnabled, updateSignals]);
94
+ };
@@ -0,0 +1,144 @@
1
+ import { useEffect } from 'react';
2
+ import { useToast } from '@ramme-io/ui';
3
+ // ✅ 1. Correct Imports from Store
4
+ import { useGeneratedSignals, useSimulation } from './useSignalStore';
5
+ // ✅ 2. Correct Import from Manifest
6
+ import { appManifest } from '../../config/app.manifest';
7
+
8
+ // Minimal Types for internal use (until schema is formalized)
9
+ interface ActionDefinition {
10
+ type: string;
11
+ config: Record<string, any>;
12
+ }
13
+
14
+ interface WorkflowDefinition {
15
+ id: string;
16
+ name: string;
17
+ active: boolean;
18
+ trigger: {
19
+ type: string;
20
+ config: {
21
+ signalId: string;
22
+ condition: string;
23
+ };
24
+ };
25
+ actions: ActionDefinition[];
26
+ }
27
+
28
+ /**
29
+ * THE RUNTIME LOGIC ENGINE
30
+ * This hook breathes life into the application.
31
+ * It watches for signal changes and executes the workflows defined in the manifest.
32
+ */
33
+ export const useWorkflowEngine = () => {
34
+ const signals = useGeneratedSignals();
35
+ const { addToast } = useToast();
36
+
37
+ // 1. Activate the Data Simulation (Randomizer)
38
+ // This now checks internally if it should run (based on boolean arg)
39
+ useSimulation(appManifest.config.mockMode);
40
+
41
+ // 2. Define the Action Executor
42
+ const executeAction = async (action: ActionDefinition, context: any) => {
43
+ console.log(`[Engine] Executing: ${action.type}`, action);
44
+
45
+ switch (action.type) {
46
+ case 'send_notification':
47
+ addToast(action.config.message || 'Notification Sent', 'info');
48
+ break;
49
+
50
+ case 'update_resource':
51
+ // In a real app, this would call the API
52
+ addToast(`Updating Resource: ${JSON.stringify(action.config)}`, 'success');
53
+ break;
54
+
55
+ case 'agent_task':
56
+ // Simulate AI Agent processing
57
+ console.log(`[AI Agent] Thinking about: "${action.config.prompt}"...`);
58
+ addToast('AI Agent Analyzing...', 'info');
59
+
60
+ setTimeout(() => {
61
+ // Mock response based on prompt context
62
+ const response = action.config.prompt?.includes('health')
63
+ ? "System Operating Normally."
64
+ : "Optimization Recommended.";
65
+
66
+ addToast(`🤖 Agent: "${response}"`, 'success', 5000);
67
+ }, 1500);
68
+ break;
69
+
70
+ case 'navigate':
71
+ window.location.href = action.config.path;
72
+ break;
73
+
74
+ default:
75
+ console.warn('Unknown action type:', action.type);
76
+ }
77
+ };
78
+
79
+ // 3. Watch Signals & Trigger Workflows
80
+ useEffect(() => {
81
+ // Safety check for undefined domain/workflows
82
+ if (!appManifest.domain?.workflows) return;
83
+
84
+ // We cast to our local type since manifest might be "any"
85
+ (appManifest.domain.workflows as unknown as WorkflowDefinition[]).forEach((flow) => {
86
+ if (!flow.active) return;
87
+
88
+ // Check Trigger: Signal Change
89
+ if (flow.trigger.type === 'signal_change') {
90
+ const signalId = flow.trigger.config.signalId;
91
+ const condition = flow.trigger.config.condition; // e.g., "> 80"
92
+
93
+ const signal = signals[signalId];
94
+
95
+ if (signal) {
96
+ const val = signal.value; // Cleaned up access
97
+
98
+ try {
99
+ // Check if condition is met (e.g. "50 > 80")
100
+ const isMet = checkCondition(val, condition);
101
+
102
+ // Debounce: In a real app, we'd check timestamps to avoid firing every 2ms
103
+ // For now, we rely on the simulation being slow (2s)
104
+ if (isMet) {
105
+ console.log(`[Engine] Trigger Fired: ${flow.name}`);
106
+ flow.actions.forEach(action => executeAction(action, { signal: val }));
107
+ }
108
+ } catch (e) {
109
+ // Ignore parse errors
110
+ }
111
+ }
112
+ }
113
+ });
114
+ }, [signals, addToast]);
115
+
116
+ return {
117
+ // Exposed for manual triggering (e.g. Buttons)
118
+ triggerWorkflow: (workflowId: string) => {
119
+ // @ts-ignore
120
+ const flow = appManifest.domain?.workflows?.find(w => w.id === workflowId);
121
+ if (flow) {
122
+ // @ts-ignore
123
+ flow.actions.forEach(action => executeAction(action, { manual: true }));
124
+ }
125
+ }
126
+ };
127
+ };
128
+
129
+ // --- HELPER: Safe Condition Checker ---
130
+ const checkCondition = (value: number, condition: string): boolean => {
131
+ if (!condition) return false;
132
+ const parts = condition.trim().split(' ');
133
+ const operator = parts[0];
134
+ const target = parseFloat(parts[1]);
135
+
136
+ switch (operator) {
137
+ case '>': return value > target;
138
+ case '<': return value < target;
139
+ case '>=': return value >= target;
140
+ case '<=': return value <= target;
141
+ case '==': return value === target;
142
+ default: return false;
143
+ }
144
+ };
@@ -1,5 +1,17 @@
1
1
  // src/core/manifest-types.ts
2
2
 
3
+ import { type IconName } from '@ramme-io/ui';
4
+
5
+ /**
6
+ * Defines a simple navigation link structure used in menus.
7
+ */
8
+ export interface ManifestLink {
9
+ id: string;
10
+ title: string;
11
+ path: string;
12
+ icon?: IconName;
13
+ }
14
+
3
15
  /**
4
16
  * The fundamental unit of the UI.
5
17
  * Corresponds to a specific React component in the registry.
@@ -40,12 +52,32 @@ export interface PageDefinition {
40
52
  sections: PageSection[];
41
53
  }
42
54
 
55
+ // ✅ NEW: Full App Specification Types (Matches app.manifest.ts)
56
+ export interface AppMeta {
57
+ name: string;
58
+ version: string;
59
+ description: string;
60
+ author: string;
61
+ }
62
+
63
+ export interface AppConfig {
64
+ theme: 'system' | 'light' | 'dark';
65
+ mockMode: boolean;
66
+ brokerUrl?: string; // Added for MQTT
67
+ }
68
+
69
+ export interface AppDomain {
70
+ signals: any[];
71
+ entities: any[];
72
+ }
73
+
43
74
  /**
44
75
  * The "Brain" of the application.
45
76
  * This JSON structure drives the entire UI.
46
77
  */
47
- export interface AppManifest {
48
- appName: string;
49
- version: string;
78
+ export interface AppSpecification {
79
+ meta: AppMeta;
80
+ config: AppConfig;
81
+ domain: AppDomain;
50
82
  pages: PageDefinition[];
51
83
  }
@@ -1,5 +1,22 @@
1
1
  import { z } from 'zod';
2
2
 
3
+ /**
4
+ * @file schema.ts
5
+ * @description The "Application Constitution".
6
+ *
7
+ * ARCHITECTURAL ROLE:
8
+ * This file uses Zod to define the strict runtime validation rules for the
9
+ * App Manifest. While `manifest-types.ts` handles compile-time TypeScript checks,
10
+ * this file handles runtime validation, ensuring that any JSON loaded into the
11
+ * engine (whether from a file, API, or user input) is structurally sound.
12
+ *
13
+ * LAYERS DEFINED:
14
+ * 1. **SaaS Layer:** Data resources, fields, and tables.
15
+ * 2. **Physical Layer:** IoT signals, sensors, and entities.
16
+ * 3. **Logic Layer:** Workflows, triggers, and automated actions.
17
+ * 4. **Presentation Layer:** Pages, sections, and UI blocks.
18
+ */
19
+
3
20
  // ------------------------------------------------------------------
4
21
  // 1. DATA RESOURCE DEFINITIONS (SaaS Layer)
5
22
  // ------------------------------------------------------------------
@@ -70,6 +87,39 @@ export const EntitySchema = z.object({
70
87
  });
71
88
  export type EntityDefinition = z.infer<typeof EntitySchema>;
72
89
 
90
+ // ------------------------------------------------------------------
91
+ // ✅ NEW: LOGIC & WORKFLOW DEFINITIONS
92
+ // ------------------------------------------------------------------
93
+
94
+ export const TriggerSchema = z.object({
95
+ id: z.string(),
96
+ type: z.enum(['signal_change', 'manual_action', 'schedule', 'webhook']),
97
+ config: z.record(z.string(), z.any()),
98
+ });
99
+
100
+ export const ActionSchema = z.object({
101
+ id: z.string(),
102
+ type: z.enum([
103
+ 'update_resource',
104
+ 'send_notification',
105
+ 'mqtt_publish',
106
+ 'api_call',
107
+ 'navigate',
108
+ 'agent_task'
109
+ ]),
110
+ config: z.record(z.string(), z.any()),
111
+ });
112
+
113
+ export const WorkflowSchema = z.object({
114
+ id: z.string(),
115
+ name: z.string(),
116
+ active: z.boolean().default(true),
117
+ trigger: TriggerSchema,
118
+ actions: z.array(ActionSchema),
119
+ });
120
+ export type WorkflowDefinition = z.infer<typeof WorkflowSchema>;
121
+ export type ActionDefinition = z.infer<typeof ActionSchema>;
122
+
73
123
 
74
124
  // ------------------------------------------------------------------
75
125
  // 3. UI LAYOUT DEFINITIONS (Presentation Layer)
@@ -78,7 +128,6 @@ export type EntityDefinition = z.infer<typeof EntitySchema>;
78
128
  export const BlockSchema = z.object({
79
129
  id: z.string(),
80
130
  type: z.string(),
81
- // ✅ FIX: Explicitly define Key and Value types
82
131
  props: z.record(z.string(), z.any()),
83
132
  layout: z.object({
84
133
  colSpan: z.number().optional(),
@@ -115,7 +164,7 @@ export const AppSpecificationSchema = z.object({
115
164
  version: z.string(),
116
165
  description: z.string().optional(),
117
166
  author: z.string().optional(),
118
- createdAt: z.string().optional(), // <--- ADD THIS LINE
167
+ createdAt: z.string().optional(),
119
168
  }),
120
169
 
121
170
  config: z.object({
@@ -130,6 +179,8 @@ export const AppSpecificationSchema = z.object({
130
179
  domain: z.object({
131
180
  signals: z.array(SignalSchema),
132
181
  entities: z.array(EntitySchema),
182
+ // ✅ ADDED WORKFLOWS HERE
183
+ workflows: z.array(WorkflowSchema).optional(),
133
184
  }),
134
185
 
135
186
  pages: z.array(PageSchema).optional(),
@@ -7,7 +7,7 @@ import {
7
7
  Message,
8
8
  PromptInput,
9
9
  } from '@ramme-io/ui';
10
- import { useMockChat } from '../hooks/useMockChat'; // <--- The new brain
10
+ import { useMockChat } from '../../assistant/useMockChat'; // <--- The new brain
11
11
 
12
12
  const AiChat: React.FC = () => {
13
13
  const { messages, isLoading, sendMessage } = useMockChat();