@rozenite/vite-plugin 1.13.0 → 2.0.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 (56) hide show
  1. package/README.md +12 -0
  2. package/dist/bundle-dts.d.ts.map +1 -1
  3. package/dist/client-plugin.d.ts.map +1 -1
  4. package/dist/dev-config-module.d.ts.map +1 -1
  5. package/dist/dev-host/assets/index-DU3zXL2c.css +1 -0
  6. package/dist/dev-host/assets/index-Xsb6uYhI.js +13 -0
  7. package/dist/dev-host/index.html +3 -3
  8. package/dist/dev-host/manifest.json +2 -2
  9. package/dist/index.cjs +24 -8
  10. package/dist/index.d.ts +9 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +24 -8
  13. package/dist/react-native-plugin.d.ts.map +1 -1
  14. package/dist/sdk-plugin.d.ts.map +1 -1
  15. package/dist/server-plugin.d.ts.map +1 -1
  16. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  17. package/package.json +9 -10
  18. package/src/bundle-dts.ts +7 -6
  19. package/src/client-plugin.ts +8 -6
  20. package/src/dev-config-module.ts +4 -1
  21. package/src/dev-host/App.tsx +219 -413
  22. package/src/dev-host/components/DispatchForm.tsx +108 -122
  23. package/src/dev-host/components/FlowList.tsx +81 -101
  24. package/src/dev-host/components/MessageDetailsPane.tsx +56 -83
  25. package/src/dev-host/components/MessageLogPane.tsx +84 -66
  26. package/src/dev-host/components/PanelTabs.tsx +29 -10
  27. package/src/dev-host/config.ts +6 -2
  28. package/src/dev-host/constants.ts +0 -19
  29. package/src/dev-host/flow-runtime.ts +33 -12
  30. package/src/dev-host/index.html +1 -1
  31. package/src/dev-host/main.tsx +1 -10
  32. package/src/dev-host/server.ts +9 -3
  33. package/src/dev-host/styles.css +162 -830
  34. package/src/dev-host/types.ts +5 -10
  35. package/src/dev-host/utils.ts +9 -6
  36. package/src/dev-host/vite.config.mts +2 -0
  37. package/src/index.ts +15 -5
  38. package/src/load-config.ts +4 -4
  39. package/src/package-json.ts +1 -1
  40. package/src/react-native-plugin.ts +3 -0
  41. package/src/sdk-plugin.ts +3 -0
  42. package/src/server-plugin.ts +3 -0
  43. package/src/utils.ts +1 -1
  44. package/dist/dev-host/assets/index-CkxvBMpp.css +0 -1
  45. package/dist/dev-host/assets/index-cNKHRwej.js +0 -11
  46. package/src/dev-host/components/MessagePayloadDetail.tsx +0 -32
  47. package/src/dev-host/components/ResizeHandle.tsx +0 -33
  48. package/src/dev-host/components/icons.tsx +0 -79
  49. package/src/dev-host/components/ui/Button.tsx +0 -91
  50. package/src/dev-host/components/ui/DropdownMenu.tsx +0 -84
  51. package/src/dev-host/components/ui/IconButton.tsx +0 -41
  52. package/src/dev-host/components/ui/Input.tsx +0 -73
  53. package/src/dev-host/components/ui/ScrollArea.tsx +0 -20
  54. package/src/dev-host/components/ui/Tabs.tsx +0 -166
  55. package/src/dev-host/components/ui/Textarea.tsx +0 -79
  56. package/src/dev-host/components/ui/ToggleGroup.tsx +0 -120
@@ -1,34 +1,14 @@
1
- import {
2
- useEffect,
3
- useRef,
4
- useState,
5
- type CSSProperties,
6
- type FormEvent,
7
- type PointerEvent as ReactPointerEvent,
8
- } from 'react';
9
- import {
10
- DEFAULT_COMMAND_WIDTH,
11
- DEFAULT_DEVTOOLS_HEIGHT,
12
- DETAILS_PANEL_WIDTH,
13
- MIN_COMMAND_WIDTH,
14
- MIN_DETAILS_WIDTH,
15
- MIN_DEVTOOLS_HEIGHT,
16
- MIN_IFRAME_HEIGHT,
17
- MIN_NARROW_IFRAME_HEIGHT,
18
- SPLITTER_SIZE,
19
- } from './constants.js';
1
+ import { useEffect, useRef, useState, type FormEvent } from 'react';
2
+ import { PluginHeader, PluginShell, Split, Tabs } from '@rozenite/ui';
20
3
  import type {
21
4
  DevHostFlowEntry,
22
5
  DevHostPanelEntry,
23
6
  DevHostPresetEntry,
24
7
  DevHostState,
25
8
  MessageEntry,
26
- ResizeHandleId,
27
- ResizeSession,
28
9
  } from './types.js';
29
10
  import { useFlowRunner } from './flow-runtime.js';
30
11
  import {
31
- clamp,
32
12
  createMessageEntry,
33
13
  formatPayloadForCommandInput,
34
14
  getInitialPanel,
@@ -41,11 +21,7 @@ import {
41
21
  } from './components/MessageDetailsPane.js';
42
22
  import { MessageLogPane } from './components/MessageLogPane.js';
43
23
  import { PanelTabs } from './components/PanelTabs.js';
44
- import { ResizeHandle } from './components/ResizeHandle.js';
45
- import { ToggleGroup } from './components/ui/ToggleGroup.js';
46
- import { RozeniteLogo } from './components/icons.js';
47
-
48
- type CSSVariables = CSSProperties & Record<`--${string}`, string>;
24
+ import './styles.css';
49
25
 
50
26
  type AppProps = DevHostState & {
51
27
  flows: DevHostFlowEntry[];
@@ -54,74 +30,85 @@ type AppProps = DevHostState & {
54
30
 
55
31
  type MobileDevtoolsTab = 'log' | 'actions';
56
32
 
57
- const getViewportMatch = () => {
58
- return window.matchMedia('(max-width: 960px)').matches;
33
+ const useNarrowViewport = () => {
34
+ const [isNarrow, setIsNarrow] = useState(
35
+ () => window.matchMedia('(max-width: 960px)').matches,
36
+ );
37
+
38
+ useEffect(() => {
39
+ const mediaQuery = window.matchMedia('(max-width: 960px)');
40
+ const handleChange = (event: MediaQueryListEvent) => {
41
+ setIsNarrow(event.matches);
42
+ };
43
+
44
+ setIsNarrow(mediaQuery.matches);
45
+ mediaQuery.addEventListener('change', handleChange);
46
+
47
+ return () => mediaQuery.removeEventListener('change', handleChange);
48
+ }, []);
49
+
50
+ return isNarrow;
59
51
  };
60
52
 
61
- export const App = ({ packageName, packageDescription, panels, flows, presets }: AppProps) => {
62
- const [activePanel, setActivePanel] = useState<DevHostPanelEntry | null>(() => {
63
- return getInitialPanel(panels);
64
- });
53
+ export const App = ({
54
+ packageName,
55
+ packageDescription,
56
+ panels,
57
+ flows,
58
+ presets,
59
+ }: AppProps) => {
60
+ const [activePanel, setActivePanel] = useState<DevHostPanelEntry | null>(() =>
61
+ getInitialPanel(panels),
62
+ );
65
63
  const [commandType, setCommandType] = useState('');
66
64
  const [commandPayload, setCommandPayload] = useState('');
67
65
  const [messages, setMessages] = useState<MessageEntry[]>([]);
68
- const [selectedMessageId, setSelectedMessageId] = useState<string | null>(null);
66
+ const [selectedMessageId, setSelectedMessageId] = useState<string | null>(
67
+ null,
68
+ );
69
69
  const [isDetailsOpen, setIsDetailsOpen] = useState(false);
70
- const [devToolsHeight, setDevToolsHeight] = useState(DEFAULT_DEVTOOLS_HEIGHT);
71
- const [commandWidth, setCommandWidth] = useState(DEFAULT_COMMAND_WIDTH);
72
- const [detailsWidth, setDetailsWidth] = useState(DETAILS_PANEL_WIDTH);
73
- const [activeResizeHandle, setActiveResizeHandle] = useState<ResizeHandleId | null>(null);
74
- const [isNarrowViewport, setIsNarrowViewport] = useState(getViewportMatch);
75
- const [activeMobileTab, setActiveMobileTab] = useState<MobileDevtoolsTab>('log');
70
+ const [activeMobileTab, setActiveMobileTab] =
71
+ useState<MobileDevtoolsTab>('log');
76
72
  const [iframeLoadNonce, setIframeLoadNonce] = useState(0);
77
- const workspaceRef = useRef<HTMLElement | null>(null);
78
- const logWorkspaceRef = useRef<HTMLDivElement | null>(null);
79
- const devtoolsRef = useRef<HTMLElement | null>(null);
80
- const resizeSessionRef = useRef<ResizeSession | null>(null);
81
73
  const iframeRef = useRef<HTMLIFrameElement | null>(null);
82
74
  const lastAutoRunLoadRef = useRef(0);
83
- const { flowRuns, runFlow, stopFlow, hasRunningFlow, registerMessage, resetMessages } = useFlowRunner({
75
+ const isNarrowViewport = useNarrowViewport();
76
+ const {
77
+ flowRuns,
78
+ runFlow,
79
+ stopFlow,
80
+ hasRunningFlow,
81
+ registerMessage,
82
+ resetMessages,
83
+ } = useFlowRunner({
84
84
  sendMessage: (type, payload) => {
85
85
  iframeRef.current?.contentWindow?.postMessage(
86
- {
87
- pluginId: packageName,
88
- type,
89
- payload,
90
- },
86
+ { pluginId: packageName, type, payload },
91
87
  '*',
92
88
  );
93
-
94
- appendMessage({
95
- direction: 'in',
96
- type,
97
- payload,
98
- });
89
+ appendMessage({ direction: 'in', type, payload });
99
90
  },
100
91
  });
101
92
 
102
93
  const activeSource = activePanel?.source ?? '';
103
94
  const activeLabel = activePanel?.label ?? '';
104
- const emptyState = panels.length === 0;
105
- const selectedMessage = messages.find((message) => message.id === selectedMessageId) ?? null;
95
+ const selectedMessage =
96
+ messages.find((message) => message.id === selectedMessageId) ?? null;
97
+ const panelDescription = packageDescription.trim();
106
98
  const trimmedCommandType = commandType.trim();
107
99
  const trimmedCommandPayload = commandPayload.trim();
108
- const hasCommandType = trimmedCommandType.length > 0;
109
- const hasValidCommandPayload = (() => {
110
- if (!trimmedCommandPayload) {
100
+ const canDispatch = (() => {
101
+ if (!trimmedCommandType || !trimmedCommandPayload) {
111
102
  return false;
112
103
  }
113
104
 
114
105
  try {
115
- JSON.parse(commandPayload);
106
+ JSON.parse(trimmedCommandPayload);
116
107
  return true;
117
108
  } catch {
118
109
  return false;
119
110
  }
120
111
  })();
121
- const canDispatch = hasCommandType && hasValidCommandPayload;
122
- const panelDescription = packageDescription.trim();
123
- const isDetailsVisible = isDetailsOpen && selectedMessage !== null;
124
- const iframeMinHeight = isNarrowViewport ? MIN_NARROW_IFRAME_HEIGHT : MIN_IFRAME_HEIGHT;
125
112
 
126
113
  useEffect(() => {
127
114
  document.title = `${packageName} Dev Host`;
@@ -145,106 +132,6 @@ export const App = ({ packageName, packageDescription, panels, flows, presets }:
145
132
  });
146
133
  }, [flows, iframeLoadNonce, runFlow]);
147
134
 
148
- useEffect(() => {
149
- const mediaQuery = window.matchMedia('(max-width: 960px)');
150
-
151
- const handleChange = (event: MediaQueryListEvent) => {
152
- setIsNarrowViewport(event.matches);
153
- };
154
-
155
- setIsNarrowViewport(mediaQuery.matches);
156
- mediaQuery.addEventListener('change', handleChange);
157
-
158
- return () => {
159
- mediaQuery.removeEventListener('change', handleChange);
160
- };
161
- }, []);
162
-
163
- useEffect(() => {
164
- const workspace = workspaceRef.current;
165
- if (!workspace) {
166
- return;
167
- }
168
-
169
- const bounds = workspace.getBoundingClientRect();
170
- const maxHeight = Math.max(MIN_DEVTOOLS_HEIGHT, bounds.height - iframeMinHeight - 12);
171
-
172
- setDevToolsHeight((current) => clamp(current, MIN_DEVTOOLS_HEIGHT, maxHeight));
173
- }, [iframeMinHeight]);
174
-
175
- useEffect(() => {
176
- if (!isNarrowViewport) {
177
- return;
178
- }
179
-
180
- setActiveMobileTab('log');
181
- }, [isNarrowViewport]);
182
-
183
- const selectPanel = (value: string) => {
184
- const nextPanel = panels.find((panel) => panel.source === value);
185
- if (!nextPanel) {
186
- return;
187
- }
188
-
189
- setActivePanel(nextPanel);
190
- const nextUrl = new URL(window.location.href);
191
- nextUrl.searchParams.set('panel', nextPanel.label);
192
- window.history.replaceState(null, '', nextUrl);
193
- };
194
-
195
- const appendMessage = (input: Omit<MessageEntry, 'id' | 'date'>) => {
196
- const nextEntry = createMessageEntry(input);
197
-
198
- registerMessage(nextEntry);
199
- setMessages((current) => [nextEntry, ...current]);
200
- };
201
-
202
- useEffect(() => {
203
- const handlePointerMove = (event: PointerEvent) => {
204
- resizeSessionRef.current?.onMove(event);
205
- };
206
-
207
- const stopDragging = () => {
208
- const session = resizeSessionRef.current;
209
- if (!session) {
210
- return;
211
- }
212
-
213
- if (session.element.hasPointerCapture(session.pointerId)) {
214
- session.element.releasePointerCapture(session.pointerId);
215
- }
216
-
217
- resizeSessionRef.current = null;
218
- setActiveResizeHandle(null);
219
- };
220
-
221
- window.addEventListener('pointermove', handlePointerMove);
222
- window.addEventListener('pointerup', stopDragging);
223
- window.addEventListener('pointercancel', stopDragging);
224
-
225
- return () => {
226
- window.removeEventListener('pointermove', handlePointerMove);
227
- window.removeEventListener('pointerup', stopDragging);
228
- window.removeEventListener('pointercancel', stopDragging);
229
- };
230
- }, []);
231
-
232
- const startResize = (
233
- handleId: ResizeHandleId,
234
- event: ReactPointerEvent<HTMLDivElement>,
235
- onMove: (event: PointerEvent) => void,
236
- ) => {
237
- event.preventDefault();
238
- event.currentTarget.setPointerCapture(event.pointerId);
239
- resizeSessionRef.current = {
240
- handleId,
241
- pointerId: event.pointerId,
242
- element: event.currentTarget,
243
- onMove,
244
- };
245
- setActiveResizeHandle(handleId);
246
- };
247
-
248
135
  useEffect(() => {
249
136
  const handleMessage = (event: MessageEvent) => {
250
137
  if (event.source !== iframeRef.current?.contentWindow) {
@@ -254,19 +141,14 @@ export const App = ({ packageName, packageDescription, panels, flows, presets }:
254
141
  if (
255
142
  typeof event.data !== 'object' ||
256
143
  event.data === null ||
257
- !('type' in event.data) ||
258
144
  event.data.type !== 'rozenite-message' ||
259
- !('payload' in event.data)
145
+ !('payload' in event.data) ||
146
+ !isPluginMessage(event.data.payload)
260
147
  ) {
261
148
  return;
262
149
  }
263
150
 
264
151
  const payload = event.data.payload;
265
-
266
- if (!isPluginMessage(payload)) {
267
- return;
268
- }
269
-
270
152
  appendMessage({
271
153
  direction: 'out',
272
154
  type: payload.type,
@@ -275,20 +157,25 @@ export const App = ({ packageName, packageDescription, panels, flows, presets }:
275
157
  };
276
158
 
277
159
  window.addEventListener('message', handleMessage);
278
-
279
- return () => {
280
- window.removeEventListener('message', handleMessage);
281
- };
160
+ return () => window.removeEventListener('message', handleMessage);
282
161
  }, []);
283
162
 
284
- const resetForm = () => {
285
- setCommandType('');
286
- setCommandPayload('');
163
+ const appendMessage = (input: Omit<MessageEntry, 'id' | 'date'>) => {
164
+ const nextEntry = createMessageEntry(input);
165
+ registerMessage(nextEntry);
166
+ setMessages((current) => [nextEntry, ...current]);
287
167
  };
288
168
 
289
- const applyPreset = (preset: DevHostPresetEntry) => {
290
- setCommandType(preset.type);
291
- setCommandPayload(formatPayloadForCommandInput(preset.payload));
169
+ const selectPanel = (value: string) => {
170
+ const nextPanel = panels.find((panel) => panel.source === value);
171
+ if (!nextPanel) {
172
+ return;
173
+ }
174
+
175
+ setActivePanel(nextPanel);
176
+ const nextUrl = new URL(window.location.href);
177
+ nextUrl.searchParams.set('panel', nextPanel.label);
178
+ window.history.replaceState(null, '', nextUrl);
292
179
  };
293
180
 
294
181
  const clearMessages = () => {
@@ -299,269 +186,188 @@ export const App = ({ packageName, packageDescription, panels, flows, presets }:
299
186
  setActiveMobileTab('log');
300
187
  };
301
188
 
302
- const resizeDevtoolsHeight = (event: PointerEvent) => {
303
- const workspace = workspaceRef.current;
304
- if (!workspace) {
305
- return;
306
- }
307
-
308
- const bounds = workspace.getBoundingClientRect();
309
- const nextHeight = bounds.bottom - event.clientY;
310
- const maxHeight = Math.max(MIN_DEVTOOLS_HEIGHT, bounds.height - iframeMinHeight - 12);
311
-
312
- setDevToolsHeight(clamp(nextHeight, MIN_DEVTOOLS_HEIGHT, maxHeight));
189
+ const resetForm = () => {
190
+ setCommandType('');
191
+ setCommandPayload('');
313
192
  };
314
193
 
315
- const resizeCommandPane = (event: PointerEvent) => {
316
- const devtools = devtoolsRef.current;
317
- if (!devtools) {
318
- return;
319
- }
320
-
321
- const bounds = devtools.getBoundingClientRect();
322
- const nextWidth = bounds.right - event.clientX;
323
- const maxWidth = Math.max(MIN_COMMAND_WIDTH, bounds.width - 280);
324
-
325
- setCommandWidth(clamp(nextWidth, MIN_COMMAND_WIDTH, maxWidth));
194
+ const applyPreset = (preset: DevHostPresetEntry) => {
195
+ setCommandType(preset.type);
196
+ setCommandPayload(formatPayloadForCommandInput(preset.payload));
326
197
  };
327
198
 
328
- const resizeDetailsPane = (event: PointerEvent) => {
329
- const logWorkspace = logWorkspaceRef.current;
330
- if (!logWorkspace || !isDetailsVisible || isNarrowViewport) {
199
+ const handleDispatch = (event: FormEvent<HTMLFormElement>) => {
200
+ event.preventDefault();
201
+ if (!canDispatch) {
331
202
  return;
332
203
  }
333
204
 
334
- const bounds = logWorkspace.getBoundingClientRect();
335
- const nextWidth = bounds.right - event.clientX;
336
- const maxWidth = Math.max(MIN_DETAILS_WIDTH, bounds.width - 280 - SPLITTER_SIZE);
337
-
338
- setDetailsWidth(clamp(nextWidth, MIN_DETAILS_WIDTH, maxWidth));
205
+ const payload = JSON.parse(trimmedCommandPayload) as unknown;
206
+ iframeRef.current?.contentWindow?.postMessage(
207
+ { pluginId: packageName, type: trimmedCommandType, payload },
208
+ '*',
209
+ );
210
+ appendMessage({
211
+ direction: 'in',
212
+ type: trimmedCommandType,
213
+ payload,
214
+ });
215
+ resetForm();
339
216
  };
340
217
 
341
218
  const handleMessageSelect = (messageId: string) => {
342
219
  setSelectedMessageId(messageId);
343
220
  setIsDetailsOpen(true);
344
-
345
221
  if (isNarrowViewport) {
346
222
  setActiveMobileTab('log');
347
223
  }
348
224
  };
349
225
 
350
- const handleDetailsClose = () => {
226
+ const useMessage = (message: MessageEntry) => {
227
+ const nextValues = getDispatcherValuesFromMessage(message);
228
+ setCommandType(nextValues.commandType);
229
+ setCommandPayload(nextValues.commandPayload);
351
230
  setIsDetailsOpen(false);
352
-
353
231
  if (isNarrowViewport) {
354
- setActiveMobileTab('log');
232
+ setActiveMobileTab('actions');
355
233
  }
356
234
  };
357
235
 
358
- const handleMobileTabChange = (value: string) => {
359
- const nextTab = value as MobileDevtoolsTab;
360
- setActiveMobileTab(nextTab);
361
- };
362
-
363
- const handleDispatch = (event: FormEvent<HTMLFormElement>) => {
364
- event.preventDefault();
365
-
366
- if (!canDispatch) {
367
- return;
368
- }
369
-
370
- const type = trimmedCommandType;
371
-
372
- let payload: unknown;
373
-
374
- try {
375
- payload = JSON.parse(commandPayload);
376
- } catch (error) {
377
- window.alert(
378
- error instanceof Error
379
- ? `Payload must be valid JSON. ${error.message}`
380
- : 'Payload must be valid JSON.',
381
- );
382
- return;
383
- }
384
-
385
- iframeRef.current?.contentWindow?.postMessage({ pluginId: packageName, type, payload }, '*');
236
+ const messageLog = (
237
+ <MessageLogPane
238
+ messages={messages}
239
+ onSelectMessage={handleMessageSelect}
240
+ onClearMessages={clearMessages}
241
+ />
242
+ );
386
243
 
387
- appendMessage({ direction: 'in', type, payload });
244
+ const actions = (
245
+ <DispatchForm
246
+ commandType={commandType}
247
+ commandPayload={commandPayload}
248
+ flows={flows}
249
+ flowRuns={flowRuns}
250
+ hasRunningFlow={hasRunningFlow}
251
+ presets={presets}
252
+ canDispatch={canDispatch}
253
+ onRunFlow={runFlow}
254
+ onStopFlow={stopFlow}
255
+ onCommandTypeChange={setCommandType}
256
+ onCommandPayloadChange={setCommandPayload}
257
+ onApplyPreset={applyPreset}
258
+ onReset={resetForm}
259
+ onSubmit={handleDispatch}
260
+ />
261
+ );
388
262
 
389
- resetForm();
390
- };
263
+ const devtools = isNarrowViewport ? (
264
+ <Tabs
265
+ className="dev-host-mobile-tabs"
266
+ value={activeMobileTab}
267
+ onValueChange={(value) => setActiveMobileTab(value as MobileDevtoolsTab)}
268
+ >
269
+ <Tabs.List
270
+ className="dev-host-mobile-tab-list"
271
+ aria-label="DevTools sections"
272
+ >
273
+ <Tabs.Tab value="log">Log</Tabs.Tab>
274
+ <Tabs.Tab value="actions">Actions</Tabs.Tab>
275
+ </Tabs.List>
276
+ <Tabs.Panel value="log" className="dev-host-mobile-tab-panel">
277
+ {isDetailsOpen && selectedMessage ? (
278
+ <MessageDetailsPane
279
+ selectedMessage={selectedMessage}
280
+ onClose={() => setIsDetailsOpen(false)}
281
+ onUseMessage={useMessage}
282
+ />
283
+ ) : (
284
+ messageLog
285
+ )}
286
+ </Tabs.Panel>
287
+ <Tabs.Panel value="actions" className="dev-host-mobile-tab-panel">
288
+ {actions}
289
+ </Tabs.Panel>
290
+ </Tabs>
291
+ ) : (
292
+ <Split direction="horizontal" className="dev-host-devtools">
293
+ <Split.Pane minSize={35}>
294
+ {isDetailsOpen && selectedMessage ? (
295
+ <Split direction="horizontal">
296
+ <Split.Pane minSize={55}>{messageLog}</Split.Pane>
297
+ <Split.Handle />
298
+ <Split.Pane defaultSize={30} minSize={20}>
299
+ <MessageDetailsPane
300
+ selectedMessage={selectedMessage}
301
+ onClose={() => setIsDetailsOpen(false)}
302
+ onUseMessage={useMessage}
303
+ />
304
+ </Split.Pane>
305
+ </Split>
306
+ ) : (
307
+ messageLog
308
+ )}
309
+ </Split.Pane>
310
+ <Split.Handle />
311
+ <Split.Pane defaultSize={28} minSize={20}>
312
+ {actions}
313
+ </Split.Pane>
314
+ </Split>
315
+ );
391
316
 
392
317
  return (
393
- <div className="rz-shell">
394
- <header className="rz-topbar">
395
- <div className="rz-topbar-brand" aria-label="Rozenite">
396
- <RozeniteLogo />
397
- </div>
398
-
399
- <div className="rz-topbar-panel-picker" title={panelDescription || undefined}>
400
- <PanelTabs panels={panels} activeSource={activeSource} onValueChange={selectPanel} />
318
+ <PluginShell className="dev-host">
319
+ <PluginHeader className="dev-host-header">
320
+ <div className="dev-host-header-title">
321
+ <PluginHeader.Title>Rozenite Dev Host</PluginHeader.Title>
322
+ <PluginHeader.Subtitle
323
+ className="dev-host-header-subtitle"
324
+ title={panelDescription}
325
+ >
326
+ {packageName}
327
+ </PluginHeader.Subtitle>
401
328
  </div>
402
- </header>
403
-
404
- <main
405
- ref={workspaceRef}
406
- className="rz-workspace"
407
- style={{ '--rz-devtools-height': `${devToolsHeight}px` } as CSSVariables}
408
- >
409
- <section className="rz-card">
410
- {emptyState ? (
411
- <div className="rz-empty-state">No panels were defined in rozenite.config.ts.</div>
412
- ) : (
329
+ <PluginHeader.Actions>
330
+ <PluginHeader.ThemeSwitcher />
331
+ <PanelTabs
332
+ panels={panels}
333
+ activeSource={activeSource}
334
+ onValueChange={selectPanel}
335
+ />
336
+ </PluginHeader.Actions>
337
+ </PluginHeader>
338
+
339
+ <PluginShell.Body className="dev-host-body">
340
+ <Split
341
+ direction="vertical"
342
+ className="dev-host-main-split"
343
+ autoSaveId="dev-host"
344
+ >
345
+ <Split.Pane
346
+ defaultSize={64}
347
+ minSize={30}
348
+ className="dev-host-preview"
349
+ >
350
+ {panels.length === 0 ? (
351
+ <div className="flex h-full items-center justify-center p-8 text-center text-sm text-muted-foreground">
352
+ No panels were defined in rozenite.config.ts.
353
+ </div>
354
+ ) : (
413
355
  <iframe
414
356
  key={activeSource}
415
357
  ref={iframeRef}
416
358
  title={activeLabel || 'Rozenite panel preview'}
417
359
  src={activeSource}
418
- className="rz-iframe"
419
- data-resizing={activeResizeHandle === 'devtools-height'}
360
+ className="dev-host-iframe"
420
361
  onLoad={() => setIframeLoadNonce((value) => value + 1)}
421
362
  />
422
- )}
423
- </section>
424
-
425
- <ResizeHandle
426
- className="rz-resize-handle"
427
- isDragging={activeResizeHandle === 'devtools-height'}
428
- orientation="horizontal"
429
- label="Resize DevTools"
430
- onPointerDown={(event) => startResize('devtools-height', event, resizeDevtoolsHeight)}
431
- />
432
-
433
- {isNarrowViewport ? (
434
- <section ref={devtoolsRef} className="rz-devtools-mobile">
435
- <div className="rz-devtools-mobile-tabs">
436
- <div className="rz-devtools-mobile-toggle">
437
- <ToggleGroup
438
- aria-label="DevTools sections"
439
- value={activeMobileTab}
440
- onChange={handleMobileTabChange}
441
- options={[
442
- { key: 'log', label: 'Log' },
443
- { key: 'actions', label: 'Actions' },
444
- ]}
445
- />
446
- </div>
447
-
448
- <div className="rz-devtools-mobile-panel">
449
- {activeMobileTab === 'log' ? (
450
- isDetailsVisible ? (
451
- <MessageDetailsPane
452
- selectedMessage={selectedMessage}
453
- isOpen={true}
454
- isNarrowViewport={true}
455
- activeResizeHandle={activeResizeHandle}
456
- onClose={handleDetailsClose}
457
- onUseMessage={(message) => {
458
- const nextValues = getDispatcherValuesFromMessage(message);
459
- setCommandType(nextValues.commandType);
460
- setCommandPayload(nextValues.commandPayload);
461
- setIsDetailsOpen(false);
462
- setActiveMobileTab('actions');
463
- }}
464
- onResizeStart={(event) => startResize('details-width', event, resizeDetailsPane)}
465
- />
466
- ) : (
467
- <MessageLogPane
468
- messages={messages}
469
- selectedMessageId={selectedMessageId}
470
- onSelectMessage={handleMessageSelect}
471
- onClearMessages={clearMessages}
472
- />
473
- )
474
- ) : (
475
- <DispatchForm
476
- commandType={commandType}
477
- commandPayload={commandPayload}
478
- flows={flows}
479
- flowRuns={flowRuns}
480
- hasRunningFlow={hasRunningFlow}
481
- presets={presets}
482
- canDispatch={canDispatch}
483
- onRunFlow={runFlow}
484
- onStopFlow={stopFlow}
485
- onCommandTypeChange={setCommandType}
486
- onCommandPayloadChange={setCommandPayload}
487
- onApplyPreset={applyPreset}
488
- onReset={resetForm}
489
- onSubmit={handleDispatch}
490
- />
491
- )}
492
- </div>
493
- </div>
494
- </section>
495
- ) : (
496
- <section
497
- ref={devtoolsRef}
498
- className="rz-devtools"
499
- style={
500
- {
501
- '--rz-command-width': `${commandWidth}px`,
502
- '--rz-command-splitter-width': `${SPLITTER_SIZE}px`,
503
- } as CSSVariables
504
- }
505
- >
506
- <div
507
- ref={logWorkspaceRef}
508
- className="rz-log-workspace"
509
- style={
510
- {
511
- '--rz-details-width': isDetailsVisible ? `${detailsWidth}px` : '0px',
512
- '--rz-details-splitter-width': isDetailsVisible ? `${SPLITTER_SIZE}px` : '0px',
513
- } as CSSVariables
514
- }
515
- >
516
- <MessageLogPane
517
- messages={messages}
518
- selectedMessageId={selectedMessageId}
519
- onSelectMessage={handleMessageSelect}
520
- onClearMessages={clearMessages}
521
- />
522
-
523
- <MessageDetailsPane
524
- selectedMessage={selectedMessage}
525
- isOpen={isDetailsOpen}
526
- isNarrowViewport={false}
527
- activeResizeHandle={activeResizeHandle}
528
- onClose={handleDetailsClose}
529
- onUseMessage={(message) => {
530
- const nextValues = getDispatcherValuesFromMessage(message);
531
- setCommandType(nextValues.commandType);
532
- setCommandPayload(nextValues.commandPayload);
533
- }}
534
- onResizeStart={(event) => startResize('details-width', event, resizeDetailsPane)}
535
- />
536
- </div>
537
-
538
- <ResizeHandle
539
- className="rz-column-resize-handle"
540
- isDragging={activeResizeHandle === 'command-width'}
541
- orientation="vertical"
542
- label="Resize command dispatcher"
543
- onPointerDown={(event) => startResize('command-width', event, resizeCommandPane)}
544
- />
545
-
546
- <DispatchForm
547
- commandType={commandType}
548
- commandPayload={commandPayload}
549
- flows={flows}
550
- flowRuns={flowRuns}
551
- hasRunningFlow={hasRunningFlow}
552
- presets={presets}
553
- canDispatch={canDispatch}
554
- onRunFlow={runFlow}
555
- onStopFlow={stopFlow}
556
- onCommandTypeChange={setCommandType}
557
- onCommandPayloadChange={setCommandPayload}
558
- onApplyPreset={applyPreset}
559
- onReset={resetForm}
560
- onSubmit={handleDispatch}
561
- />
562
- </section>
563
- )}
564
- </main>
565
- </div>
363
+ )}
364
+ </Split.Pane>
365
+ <Split.Handle />
366
+ <Split.Pane defaultSize={36} minSize={25}>
367
+ {devtools}
368
+ </Split.Pane>
369
+ </Split>
370
+ </PluginShell.Body>
371
+ </PluginShell>
566
372
  );
567
373
  };