@ramme-io/kernel 1.3.0

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 (124) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/LICENSE +21 -0
  3. package/dist/components/AutoForm.d.ts +24 -0
  4. package/dist/components/AutoForm.d.ts.map +1 -0
  5. package/dist/components/AutoForm.js +78 -0
  6. package/dist/components/AutoForm.js.map +1 -0
  7. package/dist/components/SmartTable.d.ts +14 -0
  8. package/dist/components/SmartTable.d.ts.map +1 -0
  9. package/dist/components/SmartTable.js +128 -0
  10. package/dist/components/SmartTable.js.map +1 -0
  11. package/dist/config/app.manifest.d.ts +7 -0
  12. package/dist/config/app.manifest.d.ts.map +1 -0
  13. package/dist/config/app.manifest.js +8 -0
  14. package/dist/config/app.manifest.js.map +1 -0
  15. package/dist/engine/renderers/DynamicBlock.d.ts +9 -0
  16. package/dist/engine/renderers/DynamicBlock.d.ts.map +1 -0
  17. package/dist/engine/renderers/DynamicBlock.js +67 -0
  18. package/dist/engine/renderers/DynamicBlock.js.map +1 -0
  19. package/dist/engine/renderers/DynamicPage.d.ts +11 -0
  20. package/dist/engine/renderers/DynamicPage.d.ts.map +1 -0
  21. package/dist/engine/renderers/DynamicPage.js +91 -0
  22. package/dist/engine/renderers/DynamicPage.js.map +1 -0
  23. package/dist/engine/renderers/route-generator.d.ts +16 -0
  24. package/dist/engine/renderers/route-generator.d.ts.map +1 -0
  25. package/dist/engine/renderers/route-generator.js +30 -0
  26. package/dist/engine/renderers/route-generator.js.map +1 -0
  27. package/dist/engine/renderers/sitemap-entry.d.ts +7 -0
  28. package/dist/engine/renderers/sitemap-entry.d.ts.map +1 -0
  29. package/dist/engine/renderers/sitemap-entry.js +2 -0
  30. package/dist/engine/renderers/sitemap-entry.js.map +1 -0
  31. package/dist/engine/runtime/ManifestContext.d.ts +115 -0
  32. package/dist/engine/runtime/ManifestContext.d.ts.map +1 -0
  33. package/dist/engine/runtime/ManifestContext.js +56 -0
  34. package/dist/engine/runtime/ManifestContext.js.map +1 -0
  35. package/dist/engine/runtime/MqttContext.d.ts +14 -0
  36. package/dist/engine/runtime/MqttContext.d.ts.map +1 -0
  37. package/dist/engine/runtime/MqttContext.js +70 -0
  38. package/dist/engine/runtime/MqttContext.js.map +1 -0
  39. package/dist/engine/runtime/SitemapContext.d.ts +31 -0
  40. package/dist/engine/runtime/SitemapContext.d.ts.map +1 -0
  41. package/dist/engine/runtime/SitemapContext.js +54 -0
  42. package/dist/engine/runtime/SitemapContext.js.map +1 -0
  43. package/dist/engine/runtime/data-seeder.d.ts +10 -0
  44. package/dist/engine/runtime/data-seeder.d.ts.map +1 -0
  45. package/dist/engine/runtime/data-seeder.js +35 -0
  46. package/dist/engine/runtime/data-seeder.js.map +1 -0
  47. package/dist/engine/runtime/useAction.d.ts +4 -0
  48. package/dist/engine/runtime/useAction.d.ts.map +1 -0
  49. package/dist/engine/runtime/useAction.js +55 -0
  50. package/dist/engine/runtime/useAction.js.map +1 -0
  51. package/dist/engine/runtime/useCrudLocalStorage.d.ts +19 -0
  52. package/dist/engine/runtime/useCrudLocalStorage.d.ts.map +1 -0
  53. package/dist/engine/runtime/useCrudLocalStorage.js +73 -0
  54. package/dist/engine/runtime/useCrudLocalStorage.js.map +1 -0
  55. package/dist/engine/runtime/useDataQuery.d.ts +39 -0
  56. package/dist/engine/runtime/useDataQuery.d.ts.map +1 -0
  57. package/dist/engine/runtime/useDataQuery.js +50 -0
  58. package/dist/engine/runtime/useDataQuery.js.map +1 -0
  59. package/dist/engine/runtime/useDynamicSitemap.d.ts +9 -0
  60. package/dist/engine/runtime/useDynamicSitemap.d.ts.map +1 -0
  61. package/dist/engine/runtime/useDynamicSitemap.js +38 -0
  62. package/dist/engine/runtime/useDynamicSitemap.js.map +1 -0
  63. package/dist/engine/runtime/useJustInTimeSeeder.d.ts +11 -0
  64. package/dist/engine/runtime/useJustInTimeSeeder.d.ts.map +1 -0
  65. package/dist/engine/runtime/useJustInTimeSeeder.js +88 -0
  66. package/dist/engine/runtime/useJustInTimeSeeder.js.map +1 -0
  67. package/dist/engine/runtime/useLiveBridge.d.ts +109 -0
  68. package/dist/engine/runtime/useLiveBridge.d.ts.map +1 -0
  69. package/dist/engine/runtime/useLiveBridge.js +21 -0
  70. package/dist/engine/runtime/useLiveBridge.js.map +1 -0
  71. package/dist/engine/runtime/useSignal.d.ts +11 -0
  72. package/dist/engine/runtime/useSignal.d.ts.map +1 -0
  73. package/dist/engine/runtime/useSignal.js +26 -0
  74. package/dist/engine/runtime/useSignal.js.map +1 -0
  75. package/dist/engine/runtime/useSignalStore.d.ts +31 -0
  76. package/dist/engine/runtime/useSignalStore.d.ts.map +1 -0
  77. package/dist/engine/runtime/useSignalStore.js +60 -0
  78. package/dist/engine/runtime/useSignalStore.js.map +1 -0
  79. package/dist/engine/runtime/useWorkflowEngine.d.ts +4 -0
  80. package/dist/engine/runtime/useWorkflowEngine.d.ts.map +1 -0
  81. package/dist/engine/runtime/useWorkflowEngine.js +85 -0
  82. package/dist/engine/runtime/useWorkflowEngine.js.map +1 -0
  83. package/dist/engine/types/manifest-types.d.ts +38 -0
  84. package/dist/engine/types/manifest-types.d.ts.map +1 -0
  85. package/dist/engine/types/manifest-types.js +5 -0
  86. package/dist/engine/types/manifest-types.js.map +1 -0
  87. package/dist/engine/types/sitemap-entry.d.ts +58 -0
  88. package/dist/engine/types/sitemap-entry.d.ts.map +1 -0
  89. package/dist/engine/types/sitemap-entry.js +19 -0
  90. package/dist/engine/types/sitemap-entry.js.map +1 -0
  91. package/dist/engine/validation/schema.d.ts +383 -0
  92. package/dist/engine/validation/schema.d.ts.map +1 -0
  93. package/dist/engine/validation/schema.js +156 -0
  94. package/dist/engine/validation/schema.js.map +1 -0
  95. package/dist/index.d.ts +22 -0
  96. package/dist/index.d.ts.map +1 -0
  97. package/dist/index.js +24 -0
  98. package/dist/index.js.map +1 -0
  99. package/package.json +36 -0
  100. package/src/components/AutoForm.tsx +141 -0
  101. package/src/components/SmartTable.tsx +316 -0
  102. package/src/config/app.manifest.ts +7 -0
  103. package/src/engine/renderers/DynamicBlock.tsx +84 -0
  104. package/src/engine/renderers/DynamicPage.tsx +196 -0
  105. package/src/engine/renderers/route-generator.tsx +47 -0
  106. package/src/engine/renderers/sitemap-entry.ts +6 -0
  107. package/src/engine/runtime/ManifestContext.tsx +81 -0
  108. package/src/engine/runtime/MqttContext.tsx +94 -0
  109. package/src/engine/runtime/SitemapContext.tsx +61 -0
  110. package/src/engine/runtime/data-seeder.ts +39 -0
  111. package/src/engine/runtime/useAction.ts +64 -0
  112. package/src/engine/runtime/useCrudLocalStorage.ts +82 -0
  113. package/src/engine/runtime/useDataQuery.ts +98 -0
  114. package/src/engine/runtime/useDynamicSitemap.tsx +43 -0
  115. package/src/engine/runtime/useJustInTimeSeeder.ts +101 -0
  116. package/src/engine/runtime/useLiveBridge.ts +24 -0
  117. package/src/engine/runtime/useSignal.ts +40 -0
  118. package/src/engine/runtime/useSignalStore.ts +94 -0
  119. package/src/engine/runtime/useWorkflowEngine.ts +89 -0
  120. package/src/engine/types/manifest-types.ts +45 -0
  121. package/src/engine/types/sitemap-entry.ts +66 -0
  122. package/src/engine/validation/schema.ts +189 -0
  123. package/src/index.ts +27 -0
  124. package/tsconfig.json +28 -0
@@ -0,0 +1,316 @@
1
+ import React, { useState, useMemo, useCallback } from 'react';
2
+ import {
3
+ DataTable,
4
+ Button,
5
+ Icon,
6
+ Card,
7
+ Badge,
8
+ useToast,
9
+ SearchInput,
10
+ type ColDef,
11
+ type GridApi
12
+ } from '@ramme-io/ui';
13
+ import { useJustInTimeSeeder } from '../engine/runtime/useJustInTimeSeeder';
14
+ import { useCrudLocalStorage } from '../engine/runtime/useCrudLocalStorage';
15
+ import { useManifest } from '../engine/runtime/ManifestContext';
16
+ import { AutoForm } from './AutoForm';
17
+ // ✅ IMPORT FieldDefinition specifically
18
+ import type { ResourceDefinition, FieldDefinition } from '../engine/validation/schema';
19
+
20
+ interface SmartTableProps {
21
+ dataId: string;
22
+ title?: string;
23
+ initialFilter?: Record<string, any>;
24
+ // Optional: Inject external dependency for metadata resolution
25
+ getResourceMeta?: (id: string) => { name: string; fields: FieldDefinition[] } | null;
26
+ }
27
+
28
+ export const SmartTable: React.FC<SmartTableProps> = ({
29
+ dataId,
30
+ title,
31
+ getResourceMeta
32
+ }) => {
33
+ const { addToast } = useToast();
34
+ const manifest = useManifest();
35
+
36
+ // --- 1. METADATA RESOLUTION ---
37
+ const meta = useMemo<ResourceDefinition | null>(() => {
38
+ const dynamicResource = manifest.resources?.find((r: ResourceDefinition) => r.id === dataId);
39
+ if (dynamicResource) return dynamicResource;
40
+
41
+ const staticMeta = getResourceMeta?.(dataId);
42
+ if (staticMeta) {
43
+ return {
44
+ ...staticMeta,
45
+ id: dataId,
46
+ } as unknown as ResourceDefinition;
47
+ }
48
+ return null;
49
+ }, [manifest, dataId]);
50
+
51
+ // --- 2. DATA HYDRATION ---
52
+ const seedData = useJustInTimeSeeder(dataId, meta);
53
+
54
+ const {
55
+ data: rowData,
56
+ createItem,
57
+ updateItem,
58
+ deleteItem
59
+ } = useCrudLocalStorage<any>(`ramme_db_${dataId}`, seedData);
60
+
61
+ // --- 3. UI STATE ---
62
+ const [isEditOpen, setIsEditOpen] = useState(false);
63
+ const [currentRecord, setCurrentRecord] = useState<any>(null);
64
+ const [gridApi, setGridApi] = useState<GridApi | null>(null);
65
+ const [selectedRows, setSelectedRows] = useState<any[]>([]);
66
+ const [quickFilterText, setQuickFilterText] = useState('');
67
+
68
+ // --- 4. COLUMN DEFINITIONS (Desktop) ---
69
+ const columns = useMemo<ColDef[]>(() => {
70
+ if (!meta?.fields) return [];
71
+
72
+ const generatedCols: ColDef[] = meta.fields.map((f: FieldDefinition) => {
73
+ const col: ColDef = {
74
+ field: f.key,
75
+ headerName: f.label,
76
+ filter: true,
77
+ sortable: true,
78
+ resizable: true,
79
+ flex: 1,
80
+ };
81
+
82
+ if (f.type === 'currency') {
83
+ col.valueFormatter = (p: any) => p.value ? `$${Number(p.value).toLocaleString()}` : '';
84
+ }
85
+ if (f.type === 'date') {
86
+ col.valueFormatter = (p: any) => p.value ? new Date(p.value).toLocaleDateString() : '';
87
+ }
88
+ if (f.type === 'status') {
89
+ col.cellRenderer = (p: any) => {
90
+ const statusColors: any = {
91
+ active: 'bg-green-100 text-green-800',
92
+ paid: 'bg-green-100 text-green-800',
93
+ pending: 'bg-yellow-100 text-yellow-800',
94
+ inactive: 'bg-slate-100 text-slate-600',
95
+ overdue: 'bg-red-100 text-red-800'
96
+ };
97
+ const colorClass = statusColors[String(p.value).toLowerCase()] || 'bg-slate-100 text-slate-800';
98
+ return (
99
+ <span className={`px-2 py-0.5 rounded-full text-xs font-medium ${colorClass}`}>
100
+ {p.value}
101
+ </span>
102
+ );
103
+ };
104
+ }
105
+ return col;
106
+ });
107
+
108
+ if (generatedCols.length > 0) {
109
+ generatedCols[0].headerCheckboxSelection = true;
110
+ generatedCols[0].checkboxSelection = true;
111
+ generatedCols[0].minWidth = 180;
112
+ }
113
+
114
+ generatedCols.push({
115
+ headerName: "Actions",
116
+ field: "id",
117
+ width: 100,
118
+ pinned: 'right',
119
+ cellRenderer: (params: any) => (
120
+ <div className="flex items-center gap-1">
121
+ {/* ✅ FIXED: Explicit React.MouseEvent type */}
122
+ <Button variant="ghost" size="icon" className="h-8 w-8" onClick={(e: React.MouseEvent) => { e.stopPropagation(); setCurrentRecord(params.data); setIsEditOpen(true); }}>
123
+ <Icon name="edit-2" size={14} className="text-slate-500" />
124
+ </Button>
125
+ </div>
126
+ )
127
+ });
128
+
129
+ return generatedCols;
130
+ }, [meta]);
131
+
132
+ // --- 5. FIELD HELPERS (Mobile) ---
133
+ // ✅ FIXED: Explicit FieldDefinition type for 'f'
134
+ const titleField = useMemo(() => meta?.fields.find((f: FieldDefinition) => f.type === 'text' && f.key !== 'id') || meta?.fields[0], [meta]);
135
+ const statusField = useMemo(() => meta?.fields.find((f: FieldDefinition) => f.type === 'status'), [meta]);
136
+
137
+ // --- 6. HANDLERS ---
138
+ const onGridReady = useCallback((params: any) => {
139
+ setGridApi(params.api);
140
+ }, []);
141
+
142
+ const onSelectionChanged = useCallback(() => {
143
+ if (gridApi) {
144
+ setSelectedRows(gridApi.getSelectedRows());
145
+ }
146
+ }, [gridApi]);
147
+
148
+ const handleBulkDelete = () => {
149
+ if (confirm(`Delete ${selectedRows.length} items?`)) {
150
+ selectedRows.forEach(row => deleteItem(row.id));
151
+ setSelectedRows([]);
152
+ addToast(`${selectedRows.length} items deleted`, 'success');
153
+ }
154
+ };
155
+
156
+ const handleSave = (record: any) => {
157
+ if (record.id && currentRecord?.id) {
158
+ updateItem(record);
159
+ addToast('Item updated', 'success');
160
+ } else {
161
+ const { id, ...newItem } = record;
162
+ createItem(newItem);
163
+ addToast('Item created', 'success');
164
+ }
165
+ setIsEditOpen(false);
166
+ };
167
+
168
+ return (
169
+ <Card className="flex flex-col h-[600px] border border-border shadow-sm overflow-hidden bg-card">
170
+
171
+ {/* --- HEADER --- */}
172
+ <div className="p-4 border-b border-border flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 bg-muted/5">
173
+ {selectedRows.length > 0 ? (
174
+ <div className="flex items-center gap-3 animate-in fade-in slide-in-from-left-2 duration-200">
175
+ <span className="bg-primary text-primary-foreground text-xs font-bold px-2 py-1 rounded-md">
176
+ {selectedRows.length} Selected
177
+ </span>
178
+ <Button size="sm" variant="danger" onClick={handleBulkDelete} iconLeft="trash-2">
179
+ Delete
180
+ </Button>
181
+ </div>
182
+ ) : (
183
+ <div className="flex items-center gap-2">
184
+ <div className="p-2 bg-primary/10 rounded-md text-primary">
185
+ <Icon name="table" size={18} />
186
+ </div>
187
+ <div>
188
+ <h3 className="text-base font-bold text-foreground leading-tight">
189
+ {title || meta?.name || dataId}
190
+ </h3>
191
+ <p className="text-xs text-muted-foreground">
192
+ {rowData.length} records found
193
+ </p>
194
+ </div>
195
+ </div>
196
+ )}
197
+
198
+ <div className="flex items-center gap-2 w-full sm:w-auto">
199
+ <div className="w-full sm:w-64">
200
+ <SearchInput
201
+ placeholder="Quick search..."
202
+ value={quickFilterText}
203
+ // ✅ FIXED: Explicit ChangeEvent type
204
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
205
+ setQuickFilterText(e.target.value);
206
+ gridApi?.updateGridOptions({ quickFilterText: e.target.value });
207
+ }}
208
+ />
209
+ </div>
210
+ <div className="h-6 w-px bg-border mx-1 hidden sm:block" />
211
+ <Button size="sm" variant="primary" iconLeft="plus" onClick={() => { setCurrentRecord({}); setIsEditOpen(true); }}>
212
+ Add
213
+ </Button>
214
+ </div>
215
+ </div>
216
+
217
+ {/* --- CONTENT AREA --- */}
218
+ <div className="flex-1 w-full bg-card relative overflow-hidden">
219
+
220
+ {/* 🖥️ DESKTOP VIEW: The Power Grid */}
221
+ <div className="hidden md:block h-full">
222
+ <DataTable
223
+ rowData={rowData}
224
+ columnDefs={columns}
225
+ onGridReady={onGridReady}
226
+ onSelectionChanged={onSelectionChanged}
227
+ rowSelection="multiple"
228
+ pagination={true}
229
+ paginationPageSize={10}
230
+ headerHeight={48}
231
+ rowHeight={48}
232
+ enableCellTextSelection={true}
233
+ />
234
+ </div>
235
+
236
+ {/* 📱 MOBILE VIEW: The Card List */}
237
+ <div className="block md:hidden h-full overflow-y-auto p-4 space-y-3 bg-muted/5">
238
+ {rowData.map((row) => (
239
+ <div key={row.id} className="bg-background border border-border rounded-lg p-4 shadow-sm relative group">
240
+
241
+ {/* Header: Title + Status */}
242
+ <div className="flex justify-between items-start mb-2">
243
+ <div>
244
+ <h4 className="font-semibold text-foreground">
245
+ {titleField ? row[titleField.key] : row.id}
246
+ </h4>
247
+ <p className="text-xs text-muted-foreground font-mono mt-0.5 opacity-70">{row.id}</p>
248
+ </div>
249
+ {statusField && (
250
+ <Badge variant={
251
+ ['active', 'paid'].includes(String(row[statusField.key]).toLowerCase()) ? 'success' :
252
+ ['pending'].includes(String(row[statusField.key]).toLowerCase()) ? 'warning' : 'secondary'
253
+ }>
254
+ {row[statusField.key]}
255
+ </Badge>
256
+ )}
257
+ </div>
258
+
259
+ {/* Body: Detailed Fields */}
260
+ <div className="space-y-1 text-sm text-muted-foreground mb-4">
261
+ {meta?.fields
262
+ // ✅ FIXED: Explicit FieldDefinition type for 'f'
263
+ .filter((f: FieldDefinition) => f.key !== titleField?.key && f.key !== statusField?.key && f.key !== 'id')
264
+ .slice(0, 3)
265
+ .map((f: FieldDefinition) => (
266
+ <div key={f.key} className="flex justify-between border-b border-dashed border-border/50 pb-1 last:border-0">
267
+ <span className="opacity-70">{f.label}:</span>
268
+ <span className="font-medium text-foreground">
269
+ {f.type === 'currency' ? `$${Number(row[f.key]).toLocaleString()}` :
270
+ f.type === 'date' ? new Date(row[f.key]).toLocaleDateString() : row[f.key]}
271
+ </span>
272
+ </div>
273
+ ))}
274
+ </div>
275
+
276
+ {/* Footer: Actions */}
277
+ <div className="flex gap-2 pt-2 border-t border-border/50">
278
+ <Button
279
+ size="sm"
280
+ variant="outline"
281
+ className="flex-1 h-8 text-xs"
282
+ onClick={() => { setCurrentRecord(row); setIsEditOpen(true); }}
283
+ >
284
+ <Icon name="edit-2" size={12} className="mr-2"/> Edit
285
+ </Button>
286
+ <Button
287
+ size="sm"
288
+ variant="ghost"
289
+ className="text-destructive hover:bg-destructive/10 px-3 h-8"
290
+ onClick={() => { if(confirm('Delete?')) deleteItem(row.id); }}
291
+ >
292
+ <Icon name="trash-2" size={14} />
293
+ </Button>
294
+ </div>
295
+
296
+ </div>
297
+ ))}
298
+
299
+ {rowData.length === 0 && (
300
+ <div className="text-center p-8 text-muted-foreground">No records found.</div>
301
+ )}
302
+ </div>
303
+
304
+ </div>
305
+
306
+ <AutoForm
307
+ isOpen={isEditOpen}
308
+ onClose={() => setIsEditOpen(false)}
309
+ onSubmit={handleSave}
310
+ title={meta?.name || 'Item'}
311
+ fields={meta?.fields || []}
312
+ initialData={currentRecord}
313
+ />
314
+ </Card>
315
+ );
316
+ };
@@ -0,0 +1,7 @@
1
+ // packages/kernel/src/config/app.manifest.ts
2
+ export const appManifest = {
3
+ name: "Ramme Starter",
4
+ version: "1.0.0",
5
+ capabilities: ["dashboard", "crm"],
6
+ navigation: []
7
+ };
@@ -0,0 +1,84 @@
1
+ import React from 'react';
2
+ // @ts-ignore
3
+ import { useGeneratedSignals } from '../runtime/useSignalStore';
4
+
5
+ interface DynamicBlockProps {
6
+ block: any;
7
+ getComponent: (name: string) => React.FC<any>;
8
+ getMockData: (id: string) => any[];
9
+ }
10
+
11
+ /**
12
+ * @file DynamicBlock.tsx
13
+ * @description The "Runtime Hydrator" for the application.
14
+ *
15
+ * ARCHITECTURAL ROLE:
16
+ * This component acts as the bridge between the Abstract Syntax Tree (JSON Manifest)
17
+ * and the concrete React UI.
18
+ *
19
+ * KEY RESPONSIBILITIES:
20
+ * 1. **Component Lookup:** Resolves string types ('StatCard') to actual React components.
21
+ * 2. **Signal Injection:** Subscribes to the real-time Signal Engine and feeds live values to props.
22
+ * 3. **Data Hydration:** Fetches static or async data (users, logs) based on `dataId`.
23
+ * 4. **Status Normalization:** Translates system-level signal states into UI-friendly status colors.
24
+ */
25
+
26
+ const mapSignalStatus = (status: string): string => {
27
+ switch (status) {
28
+ case 'fresh': return 'online';
29
+ case 'stale': return 'warning';
30
+ case 'disconnected': return 'offline';
31
+ case 'error': return 'error';
32
+ default: return 'offline';
33
+ }
34
+ };
35
+
36
+ export const DynamicBlock: React.FC<DynamicBlockProps> = ({ block, getComponent, getMockData }) => {
37
+ const Component = getComponent(block.type);
38
+ const signals = useGeneratedSignals();
39
+
40
+ const { signalId, dataId, ...staticProps } = block.props;
41
+
42
+ const dynamicProps: Record<string, any> = {
43
+ ...staticProps,
44
+ dataId,
45
+ signalId
46
+ };
47
+
48
+ // --- DATA INJECTION ---
49
+ if (dataId) {
50
+ const resolvedData = getMockData(dataId);
51
+ dynamicProps.data = resolvedData || [];
52
+ dynamicProps.rowData = resolvedData || [];
53
+ }
54
+
55
+ // --- SIGNAL INJECTION ---
56
+ if (signalId && signals && signalId in signals) {
57
+ // @ts-ignore
58
+ const signalState = signals[signalId];
59
+
60
+ if (signalState) {
61
+ // ✅ FIX: Check for null and handle objects vs raw values
62
+ const isSignalObject = typeof signalState === 'object' && signalState !== null;
63
+
64
+ // Extract Value
65
+ const rawValue = (isSignalObject && 'value' in signalState)
66
+ ? signalState.value
67
+ : signalState;
68
+
69
+ // Extract Status
70
+ const rawStatus = (isSignalObject && 'status' in signalState)
71
+ ? signalState.status
72
+ : 'fresh'; // Default for raw values
73
+
74
+ dynamicProps.value = typeof rawValue === 'number' ? rawValue : String(rawValue);
75
+
76
+ // ✅ FIX: Explicitly cast to String to satisfy TypeScript
77
+ dynamicProps.status = mapSignalStatus(String(rawStatus));
78
+ } else {
79
+ dynamicProps.status = mapSignalStatus('disconnected');
80
+ }
81
+ }
82
+
83
+ return <Component key={block.id} {...dynamicProps} />;
84
+ };
@@ -0,0 +1,196 @@
1
+ /**
2
+ * @file packages/kernel/src/engine/renderers/DynamicPage.tsx
3
+ * @description The main page renderer for the dashboard.
4
+ * * ARCHITECTURAL ROLE:
5
+ * This component is the "Engine Room" of the dashboard.
6
+ * It orchestrates the layout and delegates rendering to DynamicBlock.
7
+ */
8
+
9
+ import React, { useMemo, type ReactNode } from 'react';
10
+ import {
11
+ Badge,
12
+ StatCard,
13
+ DeviceCard,
14
+ BarChart,
15
+ LineChart,
16
+ Button
17
+ } from '@ramme-io/ui';
18
+
19
+ // ✅ INTERNAL: Import your SmartTable (Logic Component)
20
+ import { SmartTable } from '../../components/SmartTable';
21
+
22
+ // ❌ DELETED: No more static data dependency
23
+ // import { DATA_REGISTRY } from '../../data/mockData';
24
+
25
+ // ✅ NEW: Import the unified Data Hook
26
+ import { useJustInTimeSeeder } from '../runtime/useJustInTimeSeeder';
27
+
28
+ import { useManifest, useBridgeStatus } from '../runtime/ManifestContext';
29
+ import { Wifi, WifiOff, AlertTriangle, Loader2, Database, Wand2, LayoutTemplate } from 'lucide-react';
30
+
31
+ // ------------------------------------------------------------------
32
+ // 1. THE SAFETY REGISTRY
33
+ // ------------------------------------------------------------------
34
+ const COMPONENT_REGISTRY: Record<string, React.FC<any>> = {
35
+ 'SmartTable': SmartTable,
36
+ 'DataTable': SmartTable,
37
+ 'StatCard': StatCard,
38
+ 'DeviceCard': DeviceCard,
39
+ 'BarChart': BarChart,
40
+ 'LineChart': LineChart,
41
+ 'Button': Button,
42
+ 'Unknown': ({ type }: { type: string }) => (
43
+ <div className="h-24 w-full border-2 border-dashed border-amber-300 bg-amber-50 rounded-lg flex flex-col items-center justify-center text-amber-700 text-sm">
44
+ <LayoutTemplate className="mb-2 opacity-50" size={20} />
45
+ <span>Unknown Component: <strong>{type}</strong></span>
46
+ </div>
47
+ )
48
+ };
49
+
50
+ const getComponent = (name: string) => {
51
+ return COMPONENT_REGISTRY[name] || COMPONENT_REGISTRY['Unknown'];
52
+ };
53
+
54
+ // ------------------------------------------------------------------
55
+ // 2. ERROR BOUNDARY
56
+ // ------------------------------------------------------------------
57
+ interface ErrorBoundaryProps { children: ReactNode; }
58
+ interface ErrorBoundaryState { hasError: boolean; error: string; }
59
+
60
+ class BlockErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
61
+ constructor(props: ErrorBoundaryProps) {
62
+ super(props);
63
+ this.state = { hasError: false, error: '' };
64
+ }
65
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
66
+ return { hasError: true, error: error.message };
67
+ }
68
+ render() {
69
+ if (this.state.hasError) {
70
+ return (
71
+ <div className="h-full min-h-[100px] p-4 border-2 border-dashed border-red-300 bg-red-50/50 rounded-lg flex flex-col items-center justify-center text-red-600 text-xs">
72
+ <AlertTriangle size={16} className="mb-2 opacity-80" />
73
+ <span className="font-bold">Render Error</span>
74
+ <span className="opacity-75 text-center truncate max-w-[200px]">{this.state.error}</span>
75
+ </div>
76
+ );
77
+ }
78
+ return this.props.children;
79
+ }
80
+ }
81
+
82
+ // ------------------------------------------------------------------
83
+ // 3. DYNAMIC BLOCK (New Sub-Component)
84
+ // ------------------------------------------------------------------
85
+ // We extracted this so we can use hooks (useJustInTimeSeeder) inside.
86
+ const DynamicBlock = ({ block, manifest }: { block: any, manifest: any }) => {
87
+ const Component = getComponent(block.type);
88
+ const safeDataId = block.props.dataId?.toLowerCase();
89
+
90
+ // 1. Find the Schema (Resource Definition)
91
+ const resourceDef = useMemo(() =>
92
+ manifest.resources?.find((r: any) => r.id.toLowerCase() === safeDataId),
93
+ [manifest, safeDataId]);
94
+
95
+ // 2. Fetch Data (Using the Hook!)
96
+ // This automatically checks LocalStorage first, then falls back to JIT generation.
97
+ const resolvedData = useJustInTimeSeeder(safeDataId, resourceDef);
98
+
99
+ // 3. Detect if Data is JIT Generated (for the UI badge)
100
+ // Our JIT seeder prefixes IDs with 'jit_', so we can check that.
101
+ const isGenerated = resolvedData?.[0]?.id?.toString().startsWith('jit_');
102
+
103
+ // 4. Auto-Generate Columns (for SmartTable)
104
+ const autoColumns = useMemo(() => {
105
+ if (safeDataId && block.type === 'SmartTable' && !block.props.columnDefs && resourceDef) {
106
+ return resourceDef.fields.map((f: any) => ({
107
+ field: f.key, headerName: f.label, filter: true, flex: 1,
108
+ cellRenderer: f.type === 'status' ? (p: any) => <Badge variant="secondary">{p.value}</Badge> : undefined
109
+ }));
110
+ }
111
+ return undefined;
112
+ }, [safeDataId, block.type, resourceDef, block.props.columnDefs]);
113
+
114
+ return (
115
+ <div style={{ gridColumn: `span ${block.layout?.colSpan || 1}`, gridRow: `span ${block.layout?.rowSpan || 1}` }} className="relative group">
116
+ <BlockErrorBoundary>
117
+ {/* Developer Debug Overlay */}
118
+ <div className="absolute -top-3 right-0 opacity-0 group-hover:opacity-100 transition-opacity z-20 pointer-events-none translate-y-2 group-hover:translate-y-0 duration-200">
119
+ <div className={`text-[10px] px-2 py-1 rounded-md shadow-lg border border-white/10 flex items-center gap-1.5 font-mono ${isGenerated ? 'bg-amber-600 text-white' : 'bg-slate-800 text-slate-200'}`}>
120
+ <span className="font-bold">{block.type}</span>
121
+ {safeDataId && <> <span className="opacity-40">|</span> {isGenerated ? <Wand2 size={10} className="animate-pulse"/> : <Database size={10}/>} {safeDataId}</>}
122
+ </div>
123
+ </div>
124
+
125
+ <Component
126
+ {...block.props}
127
+ rowData={block.type === 'SmartTable' ? undefined : resolvedData}
128
+ columnDefs={block.props.columnDefs || autoColumns}
129
+ className="w-full h-full"
130
+ />
131
+ </BlockErrorBoundary>
132
+ </div>
133
+ );
134
+ };
135
+
136
+ // ------------------------------------------------------------------
137
+ // 4. THE MAIN PAGE COMPONENT
138
+ // ------------------------------------------------------------------
139
+ export const DynamicPage = ({ pageId }: { pageId: string }) => {
140
+ const manifest = useManifest();
141
+ const status = useBridgeStatus();
142
+ const isLive = status === 'live';
143
+
144
+ const page = useMemo(() => manifest.pages?.find((p: any) => p.id === pageId), [manifest, pageId]);
145
+
146
+ if (!page) {
147
+ return (
148
+ <div className="p-8 space-y-4 flex flex-col items-center justify-center h-[50vh]">
149
+ <Loader2 className="animate-spin text-primary mb-4" size={32} />
150
+ <p className="text-muted-foreground">Loading Blueprint...</p>
151
+ </div>
152
+ );
153
+ }
154
+
155
+ const statusBadge = (
156
+ <div className={`flex items-center gap-2 px-3 py-1.5 rounded-full text-xs font-bold border transition-colors duration-300 ${isLive ? 'bg-green-100 text-green-700 border-green-200' : 'bg-slate-100 text-slate-500 border-slate-200'}`}>
157
+ {isLive ? <Wifi size={14} className="text-green-600 animate-pulse"/> : <WifiOff size={14} />}
158
+ {isLive ? 'LIVE BRIDGE' : 'STATIC MODE'}
159
+ </div>
160
+ );
161
+
162
+ return (
163
+ <div className="p-6 md:p-8 max-w-7xl mx-auto w-full space-y-8">
164
+ {/* Header */}
165
+ <header className="flex flex-col md:flex-row md:items-center justify-between gap-4 border-b border-border/40 pb-6">
166
+ <div className="space-y-1">
167
+ <h1 className="text-3xl font-bold tracking-tight text-foreground">{page.title}</h1>
168
+ {page.description && <p className="text-muted-foreground">{page.description}</p>}
169
+ </div>
170
+ <div className="flex items-center gap-2">{statusBadge}</div>
171
+ </header>
172
+
173
+ {/* Main Grid */}
174
+ <div className="grid gap-8 pb-20">
175
+ {page.sections?.map((section: any) => (
176
+ <section key={section.id} className="space-y-6">
177
+ {section.title && (
178
+ <div className="flex items-center gap-2 pb-2 border-b border-border/60">
179
+ <h3 className="text-sm font-bold uppercase tracking-wider text-muted-foreground/80">{section.title}</h3>
180
+ </div>
181
+ )}
182
+ <div className="grid gap-6" style={{ gridTemplateColumns: `repeat(${section.layout?.columns || 1}, minmax(0, 1fr))` }}>
183
+ {section.blocks.map((block: any) => (
184
+ <DynamicBlock
185
+ key={block.id}
186
+ block={block}
187
+ manifest={manifest}
188
+ />
189
+ ))}
190
+ </div>
191
+ </section>
192
+ ))}
193
+ </div>
194
+ </div>
195
+ );
196
+ };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @file route-generator.tsx
3
+ * @description This file is the core engine of the Sitemap-Driven Architecture.
4
+ * Its purpose is to dynamically generate all application routes by reading directly
5
+ * from a designer-friendly `sitemap` array. This abstracts all routing logic
6
+ * away from our designers, empowering them to manage the app's structure from
7
+ * a single, simple configuration file.
8
+ */
9
+
10
+ import { Route, Navigate } from 'react-router-dom';
11
+ // CORRECTED IMPORT: Point to the canonical type definition
12
+ import type { SitemapEntry } from './sitemap-entry';
13
+
14
+ /**
15
+ * Recursively generates React Router `<Route>` components from a sitemap array.
16
+ * @param {SitemapEntry[]} routes - The array of sitemap entries to process.
17
+ * @returns {JSX.Element[]} An array of React Router `<Route>` components.
18
+ */
19
+
20
+ export const generateRoutes = (routes: SitemapEntry[]) => {
21
+ return routes.map((route) => {
22
+ // Check if the current route is a "parent" route with children
23
+ if (route.children && route.children.length > 0) {
24
+ // Get the path of the first child to use for redirection
25
+ const firstChildPath = route.children[0].path;
26
+
27
+ return (
28
+ // Create the parent <Route> (e.g., /data)
29
+ <Route key={route.id} path={route.path} element={<route.component />}>
30
+
31
+ {/* PROFESSIONAL PROTOTYPING: This is a key feature for DX.
32
+ It creates an "index" route that automatically redirects the user
33
+ from the parent path (e.g., /data) to its first child (e.g., /data/grid).
34
+ This prevents designers from ever seeing a blank parent page.
35
+ */}
36
+ <Route index element={<Navigate to={firstChildPath} replace />} />
37
+
38
+ {/* Recursively generate all nested child routes */}
39
+ {generateRoutes(route.children)}
40
+ </Route>
41
+ );
42
+ }
43
+
44
+ // Standard case: This is a simple route with no children.
45
+ return <Route key={route.id} path={route.path} element={<route.component />} />;
46
+ });
47
+ };
@@ -0,0 +1,6 @@
1
+ export interface SitemapEntry {
2
+ id: string;
3
+ path: string;
4
+ component: React.ComponentType;
5
+ children?: SitemapEntry[];
6
+ }