@utopia-studio-design/design-system-cli 0.4.1 → 0.5.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 (125) hide show
  1. package/README.md +23 -0
  2. package/bin/utopia-ds-mcp.mjs +9 -2
  3. package/bin/utopia-ds.mjs +106 -8
  4. package/data/blocks/activity-feed/ActivityFeedBlock.tsx +255 -0
  5. package/data/blocks/activity-feed/README.md +16 -0
  6. package/data/blocks/activity-feed/activity-feed-contract.ts +96 -0
  7. package/data/blocks/activity-feed/activity-feed-fixture.ts +22 -0
  8. package/data/blocks/activity-feed/activity-feed.css +154 -0
  9. package/data/blocks/agent-status/AgentStatusBlock.tsx +313 -0
  10. package/data/blocks/agent-status/README.md +15 -0
  11. package/data/blocks/agent-status/agent-status-contract.ts +133 -0
  12. package/data/blocks/agent-status/agent-status-fixture.ts +79 -0
  13. package/data/blocks/agent-status/agent-status.css +216 -0
  14. package/data/blocks/analytics-dashboard/AnalyticsDashboardBlocks.tsx +107 -0
  15. package/data/blocks/analytics-dashboard/AnalyticsMetricBlocks.tsx +87 -0
  16. package/data/blocks/analytics-dashboard/README.md +17 -0
  17. package/data/blocks/analytics-dashboard/analytics-dashboard.css +92 -0
  18. package/data/blocks/calendar-application/CalendarApplicationBlocks.tsx +519 -0
  19. package/data/blocks/calendar-application/README.md +20 -0
  20. package/data/blocks/calendar-application/calendar-application.css +216 -0
  21. package/data/blocks/calendar-application/calendar-localization.ts +90 -0
  22. package/data/blocks/calendar-application/calendar-panel-contract.ts +72 -0
  23. package/data/blocks/dashboard-composer/CeramicPuckAdapter.tsx +380 -0
  24. package/data/blocks/dashboard-composer/DashboardComposerBlocks.tsx +337 -0
  25. package/data/blocks/dashboard-composer/README.md +16 -0
  26. package/data/blocks/dashboard-composer/RegistryDataBlocks.tsx +238 -0
  27. package/data/blocks/dashboard-composer/dashboard-composer.css +328 -0
  28. package/data/blocks/database-workspace/DatabaseWorkspaceBlocks.tsx +240 -0
  29. package/data/blocks/database-workspace/README.md +9 -0
  30. package/data/blocks/database-workspace/database-workspace.css +107 -0
  31. package/data/blocks/interaction-workbench/InteractionWorkbench.tsx +271 -0
  32. package/data/blocks/interaction-workbench/README.md +9 -0
  33. package/data/blocks/interaction-workbench/interaction-workbench.css +226 -0
  34. package/data/docs/calendar.md +164 -0
  35. package/data/docs/charts.md +95 -0
  36. package/data/docs/composition-registry.md +174 -0
  37. package/data/docs/dashboard-composer.md +94 -0
  38. package/data/docs/database.md +68 -0
  39. package/data/docs/examples/dashboard-layout.v1.json +65 -0
  40. package/data/docs/foundations.md +1 -1
  41. package/data/docs/interaction-foundation.md +109 -0
  42. package/data/docs/quick-start-ai.md +23 -1
  43. package/data/manifests/blocks.json +392 -0
  44. package/data/manifests/catalog.json +7 -4
  45. package/data/manifests/components.json +179 -0
  46. package/data/manifests/composition-registry.json +427 -0
  47. package/data/manifests/motion-profiles.json +1 -1
  48. package/data/manifests/patterns.json +113 -1
  49. package/data/manifests/templates.json +346 -0
  50. package/data/manifests/theme-renacore.json +142 -0
  51. package/data/manifests/themes.json +314 -28
  52. package/data/templates/analytics-dashboard/AnalyticsDashboardBlocks.tsx +107 -0
  53. package/data/templates/analytics-dashboard/AnalyticsMetricBlocks.tsx +87 -0
  54. package/data/templates/analytics-dashboard/README.md +6 -0
  55. package/data/templates/analytics-dashboard/analytics-dashboard.css +92 -0
  56. package/data/templates/analytics-dashboard/index.html +13 -0
  57. package/data/templates/analytics-dashboard/main.tsx +196 -0
  58. package/data/templates/analytics-dashboard/styles.css +120 -0
  59. package/data/templates/analytics-dashboard/template.manifest.json +17 -0
  60. package/data/templates/calendar-application/CalendarApplicationBlocks.tsx +519 -0
  61. package/data/templates/calendar-application/README.md +10 -0
  62. package/data/templates/calendar-application/calendar-application.css +216 -0
  63. package/data/templates/calendar-application/calendar-localization.ts +90 -0
  64. package/data/templates/calendar-application/index.html +14 -0
  65. package/data/templates/calendar-application/main.tsx +409 -0
  66. package/data/templates/calendar-application/styles.css +248 -0
  67. package/data/templates/calendar-application/template.manifest.json +27 -0
  68. package/data/templates/composition-registry-lab/README.md +9 -0
  69. package/data/templates/composition-registry-lab/index.html +14 -0
  70. package/data/templates/composition-registry-lab/main.tsx +217 -0
  71. package/data/templates/composition-registry-lab/sample-layout.json +63 -0
  72. package/data/templates/composition-registry-lab/styles.css +343 -0
  73. package/data/templates/composition-registry-lab/template.manifest.json +34 -0
  74. package/data/templates/dashboard-composer/ActivityFeedBlock.tsx +255 -0
  75. package/data/templates/dashboard-composer/AgentStatusBlock.tsx +313 -0
  76. package/data/templates/dashboard-composer/AnalyticsDashboardBlocks.tsx +107 -0
  77. package/data/templates/dashboard-composer/AnalyticsMetricBlocks.tsx +87 -0
  78. package/data/templates/dashboard-composer/CalendarApplicationBlocks.tsx +519 -0
  79. package/data/templates/dashboard-composer/CeramicPuckAdapter.tsx +380 -0
  80. package/data/templates/dashboard-composer/DashboardComposerBlocks.tsx +337 -0
  81. package/data/templates/dashboard-composer/DatabaseWorkspaceBlocks.tsx +240 -0
  82. package/data/templates/dashboard-composer/README.md +53 -0
  83. package/data/templates/dashboard-composer/RegistryDataBlocks.tsx +238 -0
  84. package/data/templates/dashboard-composer/activity-feed-contract.ts +96 -0
  85. package/data/templates/dashboard-composer/activity-feed-fixture.ts +22 -0
  86. package/data/templates/dashboard-composer/activity-feed.css +154 -0
  87. package/data/templates/dashboard-composer/agent-status-contract.ts +133 -0
  88. package/data/templates/dashboard-composer/agent-status-fixture.ts +79 -0
  89. package/data/templates/dashboard-composer/agent-status-lab/index.html +19 -0
  90. package/data/templates/dashboard-composer/agent-status-lab/main.tsx +151 -0
  91. package/data/templates/dashboard-composer/agent-status-lab/styles.css +139 -0
  92. package/data/templates/dashboard-composer/agent-status.css +216 -0
  93. package/data/templates/dashboard-composer/analytics-dashboard.css +92 -0
  94. package/data/templates/dashboard-composer/calendar-application.css +216 -0
  95. package/data/templates/dashboard-composer/calendar-localization.ts +90 -0
  96. package/data/templates/dashboard-composer/calendar-panel-contract.ts +72 -0
  97. package/data/templates/dashboard-composer/dashboard-composer.css +328 -0
  98. package/data/templates/dashboard-composer/database-workspace.css +107 -0
  99. package/data/templates/dashboard-composer/index.html +14 -0
  100. package/data/templates/dashboard-composer/main.tsx +550 -0
  101. package/data/templates/dashboard-composer/sample-layout.json +147 -0
  102. package/data/templates/dashboard-composer/styles.css +198 -0
  103. package/data/templates/dashboard-composer/template.manifest.json +34 -0
  104. package/data/templates/dashboard-composer/visual-editor/PuckEditorSurface.tsx +132 -0
  105. package/data/templates/dashboard-composer/visual-editor/VisualEditorApp.tsx +343 -0
  106. package/data/templates/dashboard-composer/visual-editor/index.html +17 -0
  107. package/data/templates/dashboard-composer/visual-editor/main.tsx +95 -0
  108. package/data/templates/dashboard-composer/visual-editor/styles.css +310 -0
  109. package/data/templates/database-workspace/DatabaseWorkspaceBlocks.tsx +240 -0
  110. package/data/templates/database-workspace/README.md +5 -0
  111. package/data/templates/database-workspace/database-workspace.css +107 -0
  112. package/data/templates/database-workspace/index.html +14 -0
  113. package/data/templates/database-workspace/main.tsx +247 -0
  114. package/data/templates/database-workspace/styles.css +121 -0
  115. package/data/templates/database-workspace/template.manifest.json +19 -0
  116. package/data/templates/interaction-lab/InteractionWorkbench.tsx +271 -0
  117. package/data/templates/interaction-lab/README.md +7 -0
  118. package/data/templates/interaction-lab/index.html +14 -0
  119. package/data/templates/interaction-lab/interaction-workbench.css +226 -0
  120. package/data/templates/interaction-lab/main.tsx +104 -0
  121. package/data/templates/interaction-lab/styles.css +224 -0
  122. package/data/templates/interaction-lab/template.manifest.json +36 -0
  123. package/lib/api.mjs +72 -5
  124. package/lib/composition-runtime.mjs +382 -0
  125. package/package.json +4 -1
@@ -0,0 +1,343 @@
1
+ import {
2
+ Component,
3
+ Suspense,
4
+ createContext,
5
+ lazy,
6
+ useContext,
7
+ useEffect,
8
+ useMemo,
9
+ useState,
10
+ type ErrorInfo,
11
+ type ReactNode,
12
+ } from 'react'
13
+ import type { Data } from '@puckeditor/core'
14
+ import { Badge } from '@utopia-studio-design/design-system/Badge'
15
+ import { Button } from '@utopia-studio-design/design-system/Button'
16
+ import type { SchedulerEvent } from '@utopia-studio-design/design-system/Calendar'
17
+ import type {
18
+ CeramicLayoutBlock,
19
+ CeramicLayoutDocument,
20
+ } from '@utopia-studio-design/design-system/Composition'
21
+ import { AgentStatusBlock } from '../AgentStatusBlock'
22
+ import { resolveAgentStatusConfiguration } from '../agent-status-contract'
23
+ import { getAgentStatusFixture } from '../agent-status-fixture'
24
+ import { ActivityFeedBlock } from '../ActivityFeedBlock'
25
+ import { resolveActivityFeedConfiguration } from '../activity-feed-contract'
26
+ import { getActivityFeedFixture } from '../activity-feed-fixture'
27
+ import {
28
+ KPIGrid,
29
+ type MetricDefinition,
30
+ } from '../AnalyticsMetricBlocks'
31
+ import { SchedulerShell } from '../CalendarApplicationBlocks'
32
+ import { resolveCalendarPanelConfiguration } from '../calendar-panel-contract'
33
+ import {
34
+ TodoListBlock,
35
+ type CeramicBlockRenderer,
36
+ type TodoFilter,
37
+ type TodoItem,
38
+ } from '../DashboardComposerBlocks'
39
+ import {
40
+ ChartPanelRegistryBlock,
41
+ IssueTableRegistryBlock,
42
+ } from '../RegistryDataBlocks'
43
+ import {
44
+ ceramicLayoutToPuckData,
45
+ createCeramicPuckConfig,
46
+ puckDataToCeramicLayout,
47
+ } from '../CeramicPuckAdapter'
48
+ import '../agent-status.css'
49
+ import '../activity-feed.css'
50
+ import '../analytics-dashboard.css'
51
+ import '../calendar-application.css'
52
+ import '../dashboard-composer.css'
53
+ import initialLayoutDocument from '../sample-layout.json'
54
+
55
+ const PuckEditorSurface = lazy(() => import('./PuckEditorSurface'))
56
+
57
+ const initialLayout = initialLayoutDocument as CeramicLayoutDocument
58
+ const anchorDate = new Date(2026, 2, 9, 12)
59
+
60
+ const initialTasks: TodoItem[] = [
61
+ { id: 'puck-todo-1', title: 'Review the visual editor boundary', completed: true, dueLabel: 'Today', priority: 'high' },
62
+ { id: 'puck-todo-2', title: 'Rearrange the Calendar block', completed: false, dueLabel: 'Today', priority: 'medium' },
63
+ { id: 'puck-todo-3', title: 'Publish validated Layout JSON', completed: false, dueLabel: 'Tomorrow', priority: 'low' },
64
+ ]
65
+
66
+ const events: SchedulerEvent[] = [
67
+ {
68
+ id: 'puck-review',
69
+ title: 'Puck adapter review',
70
+ start: '2026-03-09T01:00:00.000Z',
71
+ end: '2026-03-09T02:00:00.000Z',
72
+ tone: 'accent',
73
+ },
74
+ {
75
+ id: 'registry-check',
76
+ title: 'Registry contract check',
77
+ start: '2026-03-10T04:00:00.000Z',
78
+ end: '2026-03-10T05:00:00.000Z',
79
+ tone: 'positive',
80
+ },
81
+ ]
82
+
83
+ const arabicEventTitles: Record<string, string> = {
84
+ 'puck-review': 'مراجعة محرر Puck',
85
+ 'registry-check': 'فحص عقد السجل',
86
+ }
87
+
88
+ const metrics: MetricDefinition[] = [
89
+ { id: 'open', label: 'Open tasks', value: 2, change: -33, changeLabel: 'vs yesterday' },
90
+ { id: 'completed', label: 'Completed', value: 8, change: 14, changeLabel: 'this week' },
91
+ { id: 'focus', label: 'Focus hours', value: 12.5, change: 6, changeLabel: 'this week' },
92
+ ]
93
+
94
+ interface EditorFixtureData {
95
+ compact: boolean
96
+ tasks: TodoItem[]
97
+ setTasks: (tasks: TodoItem[]) => void
98
+ }
99
+
100
+ const EditorFixtureContext = createContext<EditorFixtureData | null>(null)
101
+
102
+ function useEditorFixture() {
103
+ const value = useContext(EditorFixtureContext)
104
+ if (!value) throw new Error('Puck block renderer must be used inside EditorFixtureContext.')
105
+ return value
106
+ }
107
+
108
+ function TodoRenderer({ block }: { block: CeramicLayoutBlock }) {
109
+ const { tasks, setTasks } = useEditorFixture()
110
+ return (
111
+ <TodoListBlock
112
+ density={(block.props.density as 'compact' | 'default' | 'comfortable') ?? 'default'}
113
+ filter={(block.props.filter as TodoFilter) ?? 'all'}
114
+ items={tasks}
115
+ showCompleted={block.props.showCompleted !== false}
116
+ title={(block.props.title as string) ?? 'Today’s tasks'}
117
+ onItemsChange={setTasks}
118
+ />
119
+ )
120
+ }
121
+
122
+ function CalendarRenderer({
123
+ block,
124
+ direction,
125
+ }: {
126
+ block: CeramicLayoutBlock
127
+ direction: 'ltr' | 'rtl'
128
+ }) {
129
+ const { compact } = useEditorFixture()
130
+ const configuration = resolveCalendarPanelConfiguration(block.props, { compact, direction })
131
+ const displayEvents = configuration.locale.startsWith('ar')
132
+ ? events.map((event) => ({
133
+ ...event,
134
+ title: arabicEventTitles[event.id] ?? event.title,
135
+ }))
136
+ : events
137
+ return (
138
+ <SchedulerShell
139
+ {...configuration}
140
+ date={anchorDate}
141
+ events={displayEvents}
142
+ permissions={{ create: false, edit: false, move: false, resize: false, duplicate: false, delete: false }}
143
+ onDateChange={() => undefined}
144
+ onEventsChange={() => undefined}
145
+ onViewChange={() => undefined}
146
+ />
147
+ )
148
+ }
149
+
150
+ function MetricsRenderer({ block }: { block: CeramicLayoutBlock }) {
151
+ const metricIds = new Set((block.props.metrics as string[]) ?? metrics.map((metric) => metric.id))
152
+ return (
153
+ <KPIGrid
154
+ density={(block.props.density as 'compact' | 'default' | 'comfortable') ?? 'compact'}
155
+ metrics={metrics.filter((metric) => metricIds.has(metric.id))}
156
+ />
157
+ )
158
+ }
159
+
160
+ function ActivityRenderer({
161
+ block,
162
+ direction,
163
+ }: {
164
+ block: CeramicLayoutBlock
165
+ direction: 'ltr' | 'rtl'
166
+ }) {
167
+ const configuration = resolveActivityFeedConfiguration(block.props, { direction })
168
+ return (
169
+ <ActivityFeedBlock
170
+ {...configuration}
171
+ items={getActivityFeedFixture(configuration.locale)}
172
+ />
173
+ )
174
+ }
175
+
176
+ function AgentStatusRenderer({
177
+ block,
178
+ direction,
179
+ }: {
180
+ block: CeramicLayoutBlock
181
+ direction: 'ltr' | 'rtl'
182
+ }) {
183
+ const configuration = resolveAgentStatusConfiguration(block.props, { direction })
184
+ return (
185
+ <AgentStatusBlock
186
+ {...configuration}
187
+ items={getAgentStatusFixture(configuration.locale)}
188
+ />
189
+ )
190
+ }
191
+
192
+ const renderers: Record<string, CeramicBlockRenderer> = {
193
+ 'agent-status': AgentStatusRenderer,
194
+ 'activity-feed': ActivityRenderer,
195
+ 'calendar-panel': CalendarRenderer,
196
+ 'chart-panel': ChartPanelRegistryBlock,
197
+ 'issue-table': IssueTableRegistryBlock,
198
+ 'metric-grid': MetricsRenderer,
199
+ 'todo-list': TodoRenderer,
200
+ }
201
+
202
+ class EditorLoadBoundary extends Component<
203
+ { children: ReactNode },
204
+ { failed: boolean }
205
+ > {
206
+ state = { failed: false }
207
+
208
+ static getDerivedStateFromError() {
209
+ return { failed: true }
210
+ }
211
+
212
+ componentDidCatch(error: Error, info: ErrorInfo) {
213
+ console.error('The optional Puck editor failed to load.', error, info)
214
+ }
215
+
216
+ render() {
217
+ if (this.state.failed) {
218
+ return (
219
+ <div className="ceramic-puck-editor-state" role="alert">
220
+ <strong>Visual editor could not load.</strong>
221
+ <p>The Ceramic layout is safe. Reload this optional editing layer to try again.</p>
222
+ <Button onClick={() => window.location.reload()}>Retry loading editor</Button>
223
+ </div>
224
+ )
225
+ }
226
+
227
+ return this.props.children
228
+ }
229
+ }
230
+
231
+ export default function VisualEditorApp() {
232
+ const [layout, setLayout] = useState<CeramicLayoutDocument>(() => structuredClone(initialLayout))
233
+ const [puckData, setPuckData] = useState<Data>(() => ceramicLayoutToPuckData(initialLayout))
234
+ const [tasks, setTasks] = useState(initialTasks)
235
+ const [direction, setDirection] = useState<'ltr' | 'rtl'>('ltr')
236
+ const [dark, setDark] = useState(false)
237
+ const [compact, setCompact] = useState(() => window.matchMedia('(max-width: 48rem)').matches)
238
+ const [feedback, setFeedback] = useState('Registry connected. Reorder a block or edit a registered field.')
239
+ const [valid, setValid] = useState(true)
240
+ const config = useMemo(
241
+ () => createCeramicPuckConfig({
242
+ colorMode: dark ? 'dark' : 'light',
243
+ direction,
244
+ layout,
245
+ renderers,
246
+ }),
247
+ [dark, direction, layout],
248
+ )
249
+ const fixture = useMemo(() => ({ compact, tasks, setTasks }), [compact, tasks])
250
+
251
+ useEffect(() => {
252
+ const media = window.matchMedia('(max-width: 48rem)')
253
+ const handleChange = () => setCompact(media.matches)
254
+ media.addEventListener('change', handleChange)
255
+ return () => media.removeEventListener('change', handleChange)
256
+ }, [])
257
+
258
+ const inspectDraft = (data: Data) => {
259
+ setPuckData(data)
260
+ const result = puckDataToCeramicLayout(data, layout)
261
+ setValid(result.ok)
262
+ setFeedback(
263
+ result.ok
264
+ ? 'Draft is valid Ceramic Layout JSON. Publish to apply it to this demo session.'
265
+ : `Draft rejected: ${result.issues[0] ?? 'Unknown registry violation.'}`,
266
+ )
267
+ }
268
+
269
+ const publish = (data: Data) => {
270
+ const result = puckDataToCeramicLayout(data, layout)
271
+ if (!result.ok || !result.layout) {
272
+ setValid(false)
273
+ setFeedback(`Publish rejected: ${result.issues[0] ?? 'Unknown registry violation.'}`)
274
+ return
275
+ }
276
+ setLayout(result.layout)
277
+ setPuckData(ceramicLayoutToPuckData(result.layout))
278
+ setValid(true)
279
+ setFeedback('Published to this demo session after Ceramic mutation validation.')
280
+ }
281
+
282
+ return (
283
+ <EditorFixtureContext.Provider value={fixture}>
284
+ <main
285
+ className="ceramic-puck-editor"
286
+ data-color-mode={dark ? 'dark' : 'light'}
287
+ data-preview-direction={direction}
288
+ data-theme="utopia-default"
289
+ dir="ltr"
290
+ >
291
+ <header className="ceramic-puck-editor-header">
292
+ <div>
293
+ <span>Optional editing layer</span>
294
+ <h1>Ceramic blocks, arranged with Puck</h1>
295
+ <p>Puck edits only registered semantic fields. Ceramic still validates the published Layout JSON.</p>
296
+ </div>
297
+ <div aria-label="Visual editor controls">
298
+ <Button asChild variant="outline">
299
+ <a href="/templates/dashboard-composer/">Back to runtime</a>
300
+ </Button>
301
+ <Button variant="outline" onClick={() => setDirection((value) => value === 'ltr' ? 'rtl' : 'ltr')}>
302
+ {direction === 'ltr' ? 'Switch canvas to RTL' : 'Switch canvas to LTR'}
303
+ </Button>
304
+ <Button variant="outline" onClick={() => setDark((value) => !value)}>
305
+ {dark ? 'Switch to light' : 'Switch to dark'}
306
+ </Button>
307
+ </div>
308
+ <output aria-live="polite" data-valid={valid}>
309
+ <Badge variant={valid ? 'secondary' : 'destructive'}>
310
+ {valid ? 'Registry valid' : 'Registry rejected'}
311
+ </Badge>
312
+ <span>{feedback}</span>
313
+ </output>
314
+ </header>
315
+
316
+ <section aria-label="Puck visual editor" className="ceramic-puck-editor-shell">
317
+ <EditorLoadBoundary>
318
+ <Suspense
319
+ fallback={(
320
+ <div className="ceramic-puck-editor-state" role="status" aria-live="polite">
321
+ <strong>Loading visual editor…</strong>
322
+ <p>The Ceramic shell is ready while the optional editing layer loads.</p>
323
+ </div>
324
+ )}
325
+ >
326
+ <PuckEditorSurface
327
+ config={config}
328
+ data={puckData}
329
+ onChange={inspectDraft}
330
+ onPublish={publish}
331
+ />
332
+ </Suspense>
333
+ </EditorLoadBoundary>
334
+ </section>
335
+
336
+ <details className="ceramic-puck-json">
337
+ <summary>Inspect the last validated Layout JSON</summary>
338
+ <pre>{JSON.stringify(layout, null, 2)}</pre>
339
+ </details>
340
+ </main>
341
+ </EditorFixtureContext.Provider>
342
+ )
343
+ }
@@ -0,0 +1,17 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta
7
+ name="description"
8
+ content="A Puck visual editor generated from the Ceramic Composition Registry."
9
+ />
10
+ <link rel="icon" href="data:," />
11
+ <title>Ceramic Visual Editor — Dashboard Composer</title>
12
+ </head>
13
+ <body>
14
+ <div id="root"></div>
15
+ <script type="module" src="./main.tsx"></script>
16
+ </body>
17
+ </html>
@@ -0,0 +1,95 @@
1
+ import {
2
+ Component,
3
+ StrictMode,
4
+ Suspense,
5
+ lazy,
6
+ type ErrorInfo,
7
+ type ReactNode,
8
+ } from 'react'
9
+ import { createRoot } from 'react-dom/client'
10
+ import '@utopia-studio-design/design-system/core.css'
11
+ import '@utopia-studio-design/design-system/themes/utopia-default.css'
12
+ import './styles.css'
13
+
14
+ const VisualEditorApp = lazy(() => import('./VisualEditorApp'))
15
+
16
+ function EditorBootShell({
17
+ failed = false,
18
+ }: {
19
+ failed?: boolean
20
+ }) {
21
+ return (
22
+ <main
23
+ className="ceramic-puck-editor"
24
+ data-color-mode="light"
25
+ data-preview-direction="ltr"
26
+ data-theme="utopia-default"
27
+ dir="ltr"
28
+ >
29
+ <header className="ceramic-puck-editor-header">
30
+ <div>
31
+ <span>Optional editing layer</span>
32
+ <h1>Ceramic blocks, arranged with Puck</h1>
33
+ <p>Puck edits only registered semantic fields. Ceramic still validates the published Layout JSON.</p>
34
+ </div>
35
+ </header>
36
+ <section aria-label="Puck visual editor" className="ceramic-puck-editor-shell">
37
+ <div
38
+ className="ceramic-puck-editor-state"
39
+ role={failed ? 'alert' : 'status'}
40
+ aria-live="polite"
41
+ >
42
+ <strong>{failed ? 'Visual editor could not start.' : 'Preparing Ceramic editor…'}</strong>
43
+ <p>
44
+ {failed
45
+ ? 'The saved layout is safe. Reload the optional editing route to try again.'
46
+ : 'The page shell is ready while registered blocks and editing controls load.'}
47
+ </p>
48
+ {failed ? (
49
+ <button
50
+ className="ceramic-puck-retry"
51
+ type="button"
52
+ onClick={() => window.location.reload()}
53
+ >
54
+ Retry loading editor
55
+ </button>
56
+ ) : null}
57
+ </div>
58
+ </section>
59
+ </main>
60
+ )
61
+ }
62
+
63
+ class EditorBootBoundary extends Component<
64
+ { children: ReactNode },
65
+ { failed: boolean }
66
+ > {
67
+ state = { failed: false }
68
+
69
+ static getDerivedStateFromError() {
70
+ return { failed: true }
71
+ }
72
+
73
+ componentDidCatch(error: Error, info: ErrorInfo) {
74
+ console.error('The Ceramic visual editor runtime failed to load.', error, info)
75
+ }
76
+
77
+ render() {
78
+ return this.state.failed ? <EditorBootShell failed /> : this.props.children
79
+ }
80
+ }
81
+
82
+ const root = createRoot(document.getElementById('root')!)
83
+ root.render(
84
+ <StrictMode>
85
+ <EditorBootBoundary>
86
+ <Suspense fallback={<EditorBootShell />}>
87
+ <VisualEditorApp />
88
+ </Suspense>
89
+ </EditorBootBoundary>
90
+ </StrictMode>,
91
+ )
92
+
93
+ if (import.meta.hot) {
94
+ import.meta.hot.dispose(() => root.unmount())
95
+ }