dynim-core 1.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 (86) hide show
  1. package/README.md +290 -0
  2. package/dist/builder/ai-prompt-popover.d.ts +26 -0
  3. package/dist/builder/ai-prompt-popover.d.ts.map +1 -0
  4. package/dist/builder/ai-prompt-popover.js +180 -0
  5. package/dist/builder/builder-client.d.ts +48 -0
  6. package/dist/builder/builder-client.d.ts.map +1 -0
  7. package/dist/builder/builder-client.js +157 -0
  8. package/dist/builder/builder.d.ts +41 -0
  9. package/dist/builder/builder.d.ts.map +1 -0
  10. package/dist/builder/builder.js +537 -0
  11. package/dist/builder/bundle-manager.d.ts +60 -0
  12. package/dist/builder/bundle-manager.d.ts.map +1 -0
  13. package/dist/builder/bundle-manager.js +357 -0
  14. package/dist/builder/classifier/classname-analyzer.d.ts +6 -0
  15. package/dist/builder/classifier/classname-analyzer.d.ts.map +1 -0
  16. package/dist/builder/classifier/classname-analyzer.js +107 -0
  17. package/dist/builder/classifier/index.d.ts +20 -0
  18. package/dist/builder/classifier/index.d.ts.map +1 -0
  19. package/dist/builder/classifier/index.js +181 -0
  20. package/dist/builder/classifier/semantic-analyzer.d.ts +24 -0
  21. package/dist/builder/classifier/semantic-analyzer.d.ts.map +1 -0
  22. package/dist/builder/classifier/semantic-analyzer.js +94 -0
  23. package/dist/builder/classifier/size-analyzer.d.ts +7 -0
  24. package/dist/builder/classifier/size-analyzer.d.ts.map +1 -0
  25. package/dist/builder/classifier/size-analyzer.js +120 -0
  26. package/dist/builder/classifier/visual-analyzer.d.ts +6 -0
  27. package/dist/builder/classifier/visual-analyzer.d.ts.map +1 -0
  28. package/dist/builder/classifier/visual-analyzer.js +158 -0
  29. package/dist/builder/client.d.ts +22 -0
  30. package/dist/builder/client.d.ts.map +1 -0
  31. package/dist/builder/client.js +54 -0
  32. package/dist/builder/code-client.d.ts +101 -0
  33. package/dist/builder/code-client.d.ts.map +1 -0
  34. package/dist/builder/code-client.js +418 -0
  35. package/dist/builder/diff-state.d.ts +24 -0
  36. package/dist/builder/diff-state.d.ts.map +1 -0
  37. package/dist/builder/diff-state.js +134 -0
  38. package/dist/builder/dom-scanner.d.ts +20 -0
  39. package/dist/builder/dom-scanner.d.ts.map +1 -0
  40. package/dist/builder/dom-scanner.js +102 -0
  41. package/dist/builder/drag-engine.d.ts +41 -0
  42. package/dist/builder/drag-engine.d.ts.map +1 -0
  43. package/dist/builder/drag-engine.js +686 -0
  44. package/dist/builder/editor-overlays.d.ts +31 -0
  45. package/dist/builder/editor-overlays.d.ts.map +1 -0
  46. package/dist/builder/editor-overlays.js +202 -0
  47. package/dist/builder/editor-state.d.ts +50 -0
  48. package/dist/builder/editor-state.d.ts.map +1 -0
  49. package/dist/builder/editor-state.js +132 -0
  50. package/dist/builder/element-utils.d.ts +43 -0
  51. package/dist/builder/element-utils.d.ts.map +1 -0
  52. package/dist/builder/element-utils.js +227 -0
  53. package/dist/builder/fiber-capture.d.ts +28 -0
  54. package/dist/builder/fiber-capture.d.ts.map +1 -0
  55. package/dist/builder/fiber-capture.js +264 -0
  56. package/dist/builder/freeze-overlay.d.ts +26 -0
  57. package/dist/builder/freeze-overlay.d.ts.map +1 -0
  58. package/dist/builder/freeze-overlay.js +213 -0
  59. package/dist/builder/history-state.d.ts +41 -0
  60. package/dist/builder/history-state.d.ts.map +1 -0
  61. package/dist/builder/history-state.js +76 -0
  62. package/dist/builder/index.d.ts +62 -0
  63. package/dist/builder/index.d.ts.map +1 -0
  64. package/dist/builder/index.js +92 -0
  65. package/dist/builder/state.d.ts +27 -0
  66. package/dist/builder/state.d.ts.map +1 -0
  67. package/dist/builder/state.js +50 -0
  68. package/dist/builder/style-applier.d.ts +61 -0
  69. package/dist/builder/style-applier.d.ts.map +1 -0
  70. package/dist/builder/style-applier.js +311 -0
  71. package/dist/builder/tree-state.d.ts +71 -0
  72. package/dist/builder/tree-state.d.ts.map +1 -0
  73. package/dist/builder/tree-state.js +168 -0
  74. package/dist/builder/widget.d.ts +29 -0
  75. package/dist/builder/widget.d.ts.map +1 -0
  76. package/dist/builder/widget.js +181 -0
  77. package/dist/index.d.ts +11 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +12 -0
  80. package/package.json +25 -0
  81. package/src/styles/base.css +378 -0
  82. package/src/styles/builder.css +422 -0
  83. package/src/styles/editor.css +131 -0
  84. package/src/styles/themes/dark.css +24 -0
  85. package/src/styles/themes/light.css +21 -0
  86. package/src/styles/variables.css +63 -0
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Builder Mode Controller
3
+ * Manages the floating pill bar, chat widget, and visual editor
4
+ */
5
+ import { type WidgetConfig } from './widget';
6
+ import { type DiffEntry } from './diff-state';
7
+ export interface BuilderConfig {
8
+ logo?: string;
9
+ onSave?: (diffs: DiffEntry[]) => void;
10
+ onExit?: () => void;
11
+ onEnter?: () => void;
12
+ chatConfig?: WidgetConfig;
13
+ contentRoot?: HTMLElement;
14
+ pageId?: string;
15
+ apiBase?: string;
16
+ /** Session token for authentication */
17
+ sessionToken?: string;
18
+ /** Refresh token for session renewal */
19
+ refreshToken?: string;
20
+ /** Function to get session tokens */
21
+ getSession?: () => Promise<{
22
+ token: string;
23
+ refreshToken?: string;
24
+ }>;
25
+ }
26
+ export interface Builder {
27
+ enter: () => void;
28
+ exit: () => void;
29
+ save: () => Promise<void>;
30
+ isActive: () => boolean;
31
+ getChanges: () => DiffEntry[];
32
+ getEditorState: () => unknown;
33
+ getTreeState: () => unknown;
34
+ destroy: () => void;
35
+ }
36
+ export declare function createBuilder(config?: BuilderConfig): Builder;
37
+ declare const _default: {
38
+ create: typeof createBuilder;
39
+ };
40
+ export default _default;
41
+ //# sourceMappingURL=builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../src/builder/builder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAA6B,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAGxE,OAAO,EAAmC,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAW/E,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,IAAI,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qCAAqC;IACrC,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtE;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,QAAQ,EAAE,MAAM,OAAO,CAAC;IACxB,UAAU,EAAE,MAAM,SAAS,EAAE,CAAC;IAC9B,cAAc,EAAE,MAAM,OAAO,CAAC;IAC9B,YAAY,EAAE,MAAM,OAAO,CAAC;IAC5B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,MAAM,GAAE,aAAkB,GAAG,OAAO,CAskBjE;;;;AAMD,wBAAyC"}
@@ -0,0 +1,537 @@
1
+ /**
2
+ * Builder Mode Controller
3
+ * Manages the floating pill bar, chat widget, and visual editor
4
+ */
5
+ import { createState } from './state';
6
+ import { createWidget } from './widget';
7
+ import { createEditorState } from './editor-state';
8
+ import { createHistoryState } from './history-state';
9
+ import { createDiffState } from './diff-state';
10
+ import { createTreeState } from './tree-state';
11
+ import { createFreezeOverlay } from './freeze-overlay';
12
+ import { createOverlays } from './editor-overlays';
13
+ import { createDragEngine } from './drag-engine';
14
+ import { scanDOM } from './dom-scanner';
15
+ import { createAIPromptPopover } from './ai-prompt-popover';
16
+ import { createCodeClient } from './code-client';
17
+ import { createBuilderClient } from './builder-client';
18
+ import { buildElementIdentifier, getRelevantStyles } from './element-utils';
19
+ export function createBuilder(config = {}) {
20
+ const { logo = 'Builder', onSave, onExit, onEnter, chatConfig = {}, contentRoot = document.body, pageId = window.location.pathname, apiBase = 'http://localhost:8080', sessionToken, refreshToken, getSession } = config;
21
+ let isActive = false;
22
+ let chatWidget = null;
23
+ let state = null;
24
+ let client = null;
25
+ let editorState = null;
26
+ let historyState = null;
27
+ let diffState = null;
28
+ let treeState = null;
29
+ let freezeOverlay = null;
30
+ let overlays = null;
31
+ let dragEngine = null;
32
+ let aiPromptPopover = null;
33
+ const root = document.createElement('div');
34
+ root.className = 'builder';
35
+ root.innerHTML = `
36
+ <div class="builder-bar">
37
+ <div class="builder-bar__logo">
38
+ <svg class="builder-bar__icon" viewBox="0 0 24 24" fill="currentColor">
39
+ <path d="M22.7 19l-9.1-9.1c.9-2.3.4-5-1.5-6.9-2-2-5-2.4-7.4-1.3L9 6 6 9 1.6 4.7C.4 7.1.9 10.1 2.9 12.1c1.9 1.9 4.6 2.4 6.9 1.5l9.1 9.1c.4.4 1 .4 1.4 0l2.3-2.3c.5-.4.5-1.1.1-1.4z"/>
40
+ </svg>
41
+ <span>${logo}</span>
42
+ </div>
43
+ <div class="builder-bar__divider"></div>
44
+ <div class="builder-bar__actions">
45
+ <button class="builder-bar__btn builder-bar__btn--undo" title="Undo (Cmd+Z)" style="display: none;">
46
+ <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
47
+ <path d="M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z"/>
48
+ </svg>
49
+ </button>
50
+ <button class="builder-bar__btn builder-bar__btn--preview" title="Preview">
51
+ <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
52
+ <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/>
53
+ </svg>
54
+ Preview
55
+ </button>
56
+ <button class="builder-bar__btn builder-bar__btn--save" title="Save changes" disabled style="background: rgba(100, 116, 139, 0.5); color: rgba(255, 255, 255, 0.4); cursor: not-allowed; pointer-events: none;">
57
+ <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
58
+ <path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"/>
59
+ </svg>
60
+ Save
61
+ </button>
62
+ <button class="builder-bar__btn builder-bar__btn--exit">
63
+ <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
64
+ <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
65
+ </svg>
66
+ Exit
67
+ </button>
68
+ </div>
69
+ </div>
70
+
71
+ <div class="builder-modal" style="display: none;">
72
+ <div class="builder-modal__backdrop"></div>
73
+ <div class="builder-modal__dialog">
74
+ <div class="builder-modal__title">Unsaved Changes</div>
75
+ <div class="builder-modal__message">You have unsaved changes. What would you like to do?</div>
76
+ <div class="builder-modal__actions">
77
+ <button class="builder-modal__btn builder-modal__btn--cancel">Cancel</button>
78
+ <button class="builder-modal__btn builder-modal__btn--discard">Discard Changes</button>
79
+ <button class="builder-modal__btn builder-modal__btn--save">Save & Exit</button>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ `;
84
+ const saveBtn = root.querySelector('.builder-bar__btn--save');
85
+ const undoBtn = root.querySelector('.builder-bar__btn--undo');
86
+ const modal = root.querySelector('.builder-modal');
87
+ let builderClient = null;
88
+ function initCodeClient(stateRef) {
89
+ client = createCodeClient({
90
+ apiBase,
91
+ sessionToken,
92
+ refreshToken,
93
+ getSession,
94
+ onMessageUpdate: (message) => {
95
+ console.log('[Builder] Message update:', message.status);
96
+ // Update the chat UI with the latest text
97
+ const messages = stateRef.getState().messages;
98
+ const lastMessage = messages[messages.length - 1];
99
+ if (lastMessage?.role === 'assistant') {
100
+ stateRef.updateMessage(lastMessage.id, {
101
+ text: message.text || ''
102
+ });
103
+ }
104
+ if (message.status === 'done') {
105
+ console.log('[Builder] Chat complete');
106
+ }
107
+ else if (message.status === 'error') {
108
+ console.log('[Builder] Chat error:', message.error);
109
+ if (lastMessage?.role === 'assistant') {
110
+ stateRef.updateMessage(lastMessage.id, {
111
+ text: message.error || 'Something went wrong. Please try again.'
112
+ });
113
+ }
114
+ }
115
+ },
116
+ onStreamStart: () => {
117
+ console.log('[Builder] Stream started');
118
+ },
119
+ onStreamEnd: () => {
120
+ console.log('[Builder] Stream ended');
121
+ },
122
+ onError: (error) => {
123
+ console.error('[Builder] Backend error:', error);
124
+ const messages = stateRef.getState().messages;
125
+ const lastMessage = messages[messages.length - 1];
126
+ if (lastMessage?.role === 'assistant') {
127
+ stateRef.updateMessage(lastMessage.id, {
128
+ text: error.message || 'Sorry, something went wrong. Please try again.'
129
+ });
130
+ }
131
+ },
132
+ onAuthError: (error) => {
133
+ console.error('[Builder] Auth error:', error);
134
+ }
135
+ });
136
+ }
137
+ function initBuilderClient() {
138
+ builderClient = createBuilderClient({
139
+ apiBase,
140
+ sessionToken,
141
+ refreshToken,
142
+ getSession,
143
+ onError: (error) => {
144
+ console.error('[Builder] Visual builder error:', error);
145
+ },
146
+ onAuthError: (error) => {
147
+ console.error('[Builder] Auth error:', error);
148
+ }
149
+ });
150
+ }
151
+ function updateButtonStates() {
152
+ if (!historyState)
153
+ return;
154
+ // Show/hide undo button contextually
155
+ const canUndo = historyState.canUndo();
156
+ undoBtn.style.display = canUndo ? 'flex' : 'none';
157
+ const hasActualChanges = diffState ? diffState.getDiff().length > 0 : false;
158
+ saveBtn.disabled = !hasActualChanges;
159
+ if (hasActualChanges) {
160
+ saveBtn.style.background = '#3b82f6';
161
+ saveBtn.style.color = '#ffffff';
162
+ saveBtn.style.opacity = '1';
163
+ saveBtn.style.cursor = 'pointer';
164
+ saveBtn.style.pointerEvents = 'auto';
165
+ }
166
+ else {
167
+ saveBtn.style.background = 'rgba(100, 116, 139, 0.5)';
168
+ saveBtn.style.color = 'rgba(255, 255, 255, 0.4)';
169
+ saveBtn.style.opacity = '1';
170
+ saveBtn.style.cursor = 'not-allowed';
171
+ saveBtn.style.pointerEvents = 'none';
172
+ }
173
+ }
174
+ function performScan() {
175
+ const tree = scanDOM(contentRoot);
176
+ if (tree) {
177
+ treeState?.setTree(tree);
178
+ }
179
+ updateButtonStates();
180
+ }
181
+ function enter() {
182
+ if (isActive)
183
+ return;
184
+ isActive = true;
185
+ root.classList.add('builder--active');
186
+ document.body.classList.add('builder-mode-active');
187
+ editorState = createEditorState();
188
+ historyState = createHistoryState();
189
+ diffState = createDiffState();
190
+ treeState = createTreeState();
191
+ diffState.snapshotInitialState(contentRoot);
192
+ historyState.subscribe(updateButtonStates);
193
+ overlays = createOverlays();
194
+ overlays.mount();
195
+ dragEngine = createDragEngine({
196
+ contentRoot,
197
+ editorState,
198
+ historyState,
199
+ diffState,
200
+ treeState,
201
+ overlays,
202
+ freezeOverlay: null,
203
+ onRescan: performScan
204
+ });
205
+ freezeOverlay = createFreezeOverlay({
206
+ contentRoot,
207
+ onMouseDown: (e, el) => dragEngine?.handleMouseDown(e, el),
208
+ onMouseMove: (e, el) => dragEngine?.handleMouseMove(e, el),
209
+ onMouseUp: (e, el) => dragEngine?.handleMouseUp(e, el),
210
+ onMouseLeave: (e) => dragEngine?.handleMouseLeave(e),
211
+ onClick: (e, el) => dragEngine?.handleClick(e, el),
212
+ onDoubleClick: (e, el) => dragEngine?.handleDoubleClick(e, el),
213
+ onWheel: (e) => dragEngine?.handleWheel(e)
214
+ });
215
+ freezeOverlay.mount();
216
+ dragEngine.setFreezeOverlay(freezeOverlay);
217
+ dragEngine.attach();
218
+ aiPromptPopover = createAIPromptPopover({
219
+ onSubmit: (prompt) => {
220
+ const selectedEl = editorState?.getState().selectedElement;
221
+ // Always add user message first
222
+ state?.addMessage({ role: 'user', text: prompt });
223
+ if (!client) {
224
+ console.error('[Builder] Code client not initialized');
225
+ state?.addMessage({ role: 'assistant', text: 'Connection error: client not initialized.' });
226
+ }
227
+ else if (!selectedEl) {
228
+ // No element selected - send as general query
229
+ state?.addMessage({ role: 'assistant', text: '' });
230
+ client.sendCode(prompt);
231
+ }
232
+ else {
233
+ // Element selected - build context and send
234
+ state?.addMessage({ role: 'assistant', text: '' });
235
+ const el = buildElementIdentifier(selectedEl);
236
+ const styles = getRelevantStyles(selectedEl);
237
+ const elementContext = [
238
+ `[Element Context]`,
239
+ `Tag: <${el.tagName}>`,
240
+ el.id ? `ID: ${el.id}` : null,
241
+ el.classes.length > 0 ? `Classes: ${el.classes.join(' ')}` : null,
242
+ `Styles: ${Object.entries(styles).slice(0, 10).map(([k, v]) => `${k}: ${v}`).join('; ')}`,
243
+ `[User Request]`,
244
+ prompt
245
+ ].filter(Boolean).join('\n');
246
+ client.sendCode(elementContext);
247
+ }
248
+ // Always cleanup and open chat
249
+ aiPromptPopover?.hide();
250
+ editorState?.clearSelection();
251
+ overlays?.hideSelection();
252
+ chatWidget?.open();
253
+ },
254
+ onClose: () => {
255
+ editorState?.clearSelection();
256
+ overlays?.hideSelection();
257
+ aiPromptPopover?.hide();
258
+ }
259
+ });
260
+ aiPromptPopover.mount();
261
+ let chatWasOpenBeforeDrag = false;
262
+ let chatWasOpenBeforeSelection = false;
263
+ editorState.subscribe((newState, prevState) => {
264
+ if (chatWidget) {
265
+ const dragStarted = newState.isDragging && !prevState?.isDragging;
266
+ const dragEnded = !newState.isDragging && prevState?.isDragging;
267
+ if (dragStarted) {
268
+ chatWasOpenBeforeDrag = chatWidget.isOpen();
269
+ if (chatWasOpenBeforeDrag) {
270
+ chatWidget.close();
271
+ }
272
+ }
273
+ else if (dragEnded && chatWasOpenBeforeDrag) {
274
+ chatWidget.open();
275
+ }
276
+ const selectionStarted = newState.selectedElement && !prevState?.selectedElement;
277
+ const selectionEnded = !newState.selectedElement && prevState?.selectedElement;
278
+ if (selectionStarted && !newState.isDragging) {
279
+ chatWasOpenBeforeSelection = chatWidget.isOpen();
280
+ if (chatWasOpenBeforeSelection) {
281
+ chatWidget.close();
282
+ }
283
+ }
284
+ else if (selectionEnded && chatWasOpenBeforeSelection && !newState.isDragging) {
285
+ chatWidget.open();
286
+ chatWasOpenBeforeSelection = false;
287
+ }
288
+ }
289
+ if (newState.selectedElement && newState.selectionRect && !newState.isDragging) {
290
+ aiPromptPopover?.show(newState.selectionRect);
291
+ }
292
+ else {
293
+ aiPromptPopover?.hide();
294
+ }
295
+ });
296
+ setTimeout(performScan, 100);
297
+ if (!chatWidget) {
298
+ state = createState();
299
+ state.addMessage({
300
+ role: 'assistant',
301
+ text: 'Hi! I\'m here to help you build. Select an element by clicking on it, or drag elements to rearrange them.'
302
+ });
303
+ initCodeClient(state);
304
+ initBuilderClient(); // For visual builder operations (saveDiffs, preview, exit)
305
+ // Pre-warm the cache for faster chat responses (projectId comes from JWT)
306
+ if (client) {
307
+ client.warmCache();
308
+ }
309
+ // Create a StreamClient adapter for the widget
310
+ const streamClientAdapter = {
311
+ send: async (message) => {
312
+ if (!client) {
313
+ console.error('[Builder] Code client not initialized');
314
+ if (state) {
315
+ const messages = state.getState().messages;
316
+ const lastMessage = messages[messages.length - 1];
317
+ if (lastMessage?.role === 'assistant') {
318
+ state.updateMessage(lastMessage.id, {
319
+ text: 'Connection error: client not initialized.'
320
+ });
321
+ }
322
+ }
323
+ return;
324
+ }
325
+ await client.sendCode(message);
326
+ },
327
+ };
328
+ chatWidget = createWidget(null, state, streamClientAdapter, {
329
+ mode: 'floating',
330
+ position: 'bottom-right',
331
+ title: 'Build Assistant',
332
+ ...chatConfig
333
+ });
334
+ }
335
+ chatWidget.root.style.display = '';
336
+ chatWidget.open();
337
+ onEnter?.();
338
+ }
339
+ function cleanup() {
340
+ modal.style.display = 'none';
341
+ root.classList.remove('builder--active');
342
+ document.body.classList.remove('builder-mode-active');
343
+ dragEngine?.detach();
344
+ aiPromptPopover?.unmount();
345
+ overlays?.unmount();
346
+ freezeOverlay?.unmount();
347
+ dragEngine = null;
348
+ aiPromptPopover = null;
349
+ overlays = null;
350
+ freezeOverlay = null;
351
+ editorState = null;
352
+ historyState = null;
353
+ diffState = null;
354
+ treeState = null;
355
+ undoBtn.style.display = 'none';
356
+ if (chatWidget) {
357
+ chatWidget.close();
358
+ chatWidget.root.style.display = 'none';
359
+ }
360
+ onExit?.();
361
+ }
362
+ async function exit() {
363
+ if (!isActive)
364
+ return;
365
+ isActive = false;
366
+ // Notify server of exit (discarding changes)
367
+ if (builderClient) {
368
+ try {
369
+ await builderClient.exit(pageId, true);
370
+ console.log('[Builder] Exit notification sent');
371
+ }
372
+ catch (error) {
373
+ console.error('[Builder] Exit notification failed:', error);
374
+ }
375
+ }
376
+ while (historyState?.canUndo()) {
377
+ dragEngine?.executeUndo();
378
+ }
379
+ cleanup();
380
+ }
381
+ async function save() {
382
+ console.log('[Builder] Save called, isActive:', isActive);
383
+ if (!isActive)
384
+ return;
385
+ isActive = false;
386
+ const diffs = diffState?.getDiff() || [];
387
+ console.log('[Builder] Diffs count:', diffs.length);
388
+ console.log('[Builder] builderClient exists:', !!builderClient);
389
+ if (builderClient && diffs.length > 0) {
390
+ try {
391
+ console.log('[Builder] Saving diffs to backend:', { pageId, diffs });
392
+ const result = await builderClient.saveDiffs(pageId, diffs);
393
+ console.log('[Builder] Save result:', result);
394
+ }
395
+ catch (error) {
396
+ console.error('[Builder] Save failed:', error);
397
+ }
398
+ }
399
+ else {
400
+ console.log('[Builder] Skipping save - no client or no diffs');
401
+ }
402
+ onSave?.(diffs);
403
+ cleanup();
404
+ }
405
+ async function preview() {
406
+ if (!isActive)
407
+ return;
408
+ const diffs = diffState?.getDiff() || [];
409
+ console.log('[Builder] Preview called, diffs:', diffs.length);
410
+ if (builderClient) {
411
+ try {
412
+ const result = await builderClient.preview(pageId, diffs);
413
+ console.log('[Builder] Preview result:', result);
414
+ // TODO: Handle preview result (e.g., open preview URL, toggle preview mode)
415
+ if (result.previewUrl) {
416
+ window.open(result.previewUrl, '_blank');
417
+ }
418
+ }
419
+ catch (error) {
420
+ console.error('[Builder] Preview failed:', error);
421
+ }
422
+ }
423
+ }
424
+ function showModal() {
425
+ modal.style.display = '';
426
+ }
427
+ function hideModal() {
428
+ modal.style.display = 'none';
429
+ }
430
+ function hasUnsavedChanges() {
431
+ const hasDiffs = diffState ? diffState.getDiff().length > 0 : false;
432
+ const hasHistory = historyState ? historyState.canUndo() : false;
433
+ return hasDiffs || hasHistory;
434
+ }
435
+ function handleExitClick() {
436
+ if (hasUnsavedChanges()) {
437
+ showModal();
438
+ }
439
+ else {
440
+ exit();
441
+ }
442
+ }
443
+ root.addEventListener('click', (e) => {
444
+ const target = e.target;
445
+ if (target.closest('.builder-bar__btn--exit')) {
446
+ e.stopPropagation();
447
+ handleExitClick();
448
+ return;
449
+ }
450
+ if (target.closest('.builder-bar__btn--save')) {
451
+ e.stopPropagation();
452
+ save();
453
+ return;
454
+ }
455
+ if (target.closest('.builder-bar__btn--preview')) {
456
+ e.stopPropagation();
457
+ preview();
458
+ return;
459
+ }
460
+ if (target.closest('.builder-bar__btn--undo')) {
461
+ e.stopPropagation();
462
+ dragEngine?.executeUndo();
463
+ return;
464
+ }
465
+ if (target.closest('.builder-modal__btn--cancel')) {
466
+ e.stopPropagation();
467
+ hideModal();
468
+ return;
469
+ }
470
+ if (target.closest('.builder-modal__btn--discard')) {
471
+ e.stopPropagation();
472
+ hideModal();
473
+ exit();
474
+ return;
475
+ }
476
+ if (target.closest('.builder-modal__btn--save')) {
477
+ e.stopPropagation();
478
+ hideModal();
479
+ save();
480
+ return;
481
+ }
482
+ if (target.closest('.builder-modal__backdrop')) {
483
+ e.stopPropagation();
484
+ hideModal();
485
+ return;
486
+ }
487
+ });
488
+ document.addEventListener('click', (e) => {
489
+ const target = e.target;
490
+ if (target.closest('.builder-bar__btn--exit')) {
491
+ e.preventDefault();
492
+ e.stopPropagation();
493
+ handleExitClick();
494
+ }
495
+ else if (target.closest('.builder-bar__btn--save') && !saveBtn.disabled) {
496
+ e.preventDefault();
497
+ e.stopPropagation();
498
+ save();
499
+ }
500
+ else if (target.closest('.builder-modal__btn--discard')) {
501
+ e.preventDefault();
502
+ e.stopPropagation();
503
+ hideModal();
504
+ exit();
505
+ }
506
+ else if (target.closest('.builder-modal__btn--save')) {
507
+ e.preventDefault();
508
+ e.stopPropagation();
509
+ hideModal();
510
+ save();
511
+ }
512
+ else if (target.closest('.builder-modal__btn--cancel')) {
513
+ e.preventDefault();
514
+ e.stopPropagation();
515
+ hideModal();
516
+ }
517
+ }, true);
518
+ document.body.appendChild(root);
519
+ return {
520
+ enter,
521
+ exit,
522
+ save,
523
+ isActive: () => isActive,
524
+ getChanges: () => diffState?.getDiff() || [],
525
+ getEditorState: () => editorState?.getState(),
526
+ getTreeState: () => treeState?.getState(),
527
+ destroy: () => {
528
+ exit();
529
+ chatWidget?.destroy();
530
+ root.remove();
531
+ }
532
+ };
533
+ }
534
+ if (typeof window !== 'undefined') {
535
+ window.Builder = { create: createBuilder };
536
+ }
537
+ export default { create: createBuilder };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Bundle Manager - Handles loading and swapping React bundles with smooth crossfade transitions
3
+ *
4
+ * Uses dual containers to enable seamless bundle swapping:
5
+ * 1. Load new bundle into inactive container (hidden)
6
+ * 2. Wait for React to mount
7
+ * 3. Crossfade from active to new container
8
+ * 4. Cleanup old container
9
+ *
10
+ * This prevents any flash or blank screen during bundle updates.
11
+ */
12
+ export interface BundleManager {
13
+ /** Load a bundle with crossfade transition */
14
+ load(bundleUrl: string): Promise<void>;
15
+ /** Apply inline styles to element in active container */
16
+ applyStyles(selector: string, styles: Record<string, string>): void;
17
+ /** Replace className on element */
18
+ applyClassName(selector: string, className: string): void;
19
+ /** Add/remove CSS classes (for Tailwind) */
20
+ toggleClasses(selector: string, add: string[], remove: string[]): void;
21
+ /** Query element in active container */
22
+ querySelector<T extends Element = Element>(selector: string): T | null;
23
+ /** Query all elements in active container */
24
+ querySelectorAll<T extends Element = Element>(selector: string): NodeListOf<T>;
25
+ /** Get the active container element */
26
+ getActiveContainer(): HTMLElement;
27
+ /** Get the root element (#root) of active container */
28
+ getActiveRoot(): HTMLElement | null;
29
+ /** Check if a bundle is currently loaded */
30
+ isLoaded(): boolean;
31
+ /** Check if currently loading */
32
+ isLoading(): boolean;
33
+ /** Get current bundle URL */
34
+ getCurrentBundleUrl(): string | null;
35
+ /** Unload current bundle and clear containers */
36
+ unload(): void;
37
+ /** Destroy the bundle manager and cleanup DOM */
38
+ destroy(): void;
39
+ }
40
+ export interface BundleManagerConfig {
41
+ /** Parent element to mount preview containers into */
42
+ parent: HTMLElement;
43
+ /** Transition duration in ms (default: 300) */
44
+ transitionDuration?: number;
45
+ /** Timeout for bundle mount in ms (default: 5000) */
46
+ mountTimeout?: number;
47
+ /** Called when bundle starts loading */
48
+ onLoadStart?: (bundleUrl: string) => void;
49
+ /** Called when bundle is loaded and visible */
50
+ onLoadComplete?: (bundleUrl: string) => void;
51
+ /** Called on load error */
52
+ onError?: (error: Error, bundleUrl: string) => void;
53
+ /** Called when bundle is unloaded */
54
+ onUnload?: () => void;
55
+ }
56
+ /**
57
+ * Create a bundle manager instance
58
+ */
59
+ export declare function createBundleManager(config: BundleManagerConfig): BundleManager;
60
+ //# sourceMappingURL=bundle-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle-manager.d.ts","sourceRoot":"","sources":["../../src/builder/bundle-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AASH,MAAM,WAAW,aAAa;IAC5B,8CAA8C;IAC9C,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC,yDAAyD;IACzD,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IAEpE,mCAAmC;IACnC,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAE1D,4CAA4C;IAC5C,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAEvE,wCAAwC;IACxC,aAAa,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC;IAEvE,6CAA6C;IAC7C,gBAAgB,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IAE/E,uCAAuC;IACvC,kBAAkB,IAAI,WAAW,CAAC;IAElC,uDAAuD;IACvD,aAAa,IAAI,WAAW,GAAG,IAAI,CAAC;IAEpC,4CAA4C;IAC5C,QAAQ,IAAI,OAAO,CAAC;IAEpB,iCAAiC;IACjC,SAAS,IAAI,OAAO,CAAC;IAErB,6BAA6B;IAC7B,mBAAmB,IAAI,MAAM,GAAG,IAAI,CAAC;IAErC,iDAAiD;IACjD,MAAM,IAAI,IAAI,CAAC;IAEf,iDAAiD;IACjD,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,sDAAsD;IACtD,MAAM,EAAE,WAAW,CAAC;IAEpB,+CAA+C;IAC/C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,wCAAwC;IACxC,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAE1C,+CAA+C;IAC/C,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAE7C,2BAA2B;IAC3B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAEpD,qCAAqC;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,aAAa,CA8Y9E"}