synthos 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,615 @@
1
+ (function() {
2
+ if (window.__synthOSChatPanel) return;
3
+ window.__synthOSChatPanel = true;
4
+
5
+ // 0. Themed tooltips for chat panel controls
6
+ (function() {
7
+ var style = document.createElement('style');
8
+ style.textContent =
9
+ '.synthos-tooltip {' +
10
+ 'position: fixed;' +
11
+ 'padding: 6px 10px;' +
12
+ 'background: var(--bg-tertiary, #0f0f23);' +
13
+ 'color: var(--text-secondary, #b794f6);' +
14
+ 'border: 1px solid var(--border-color, rgba(138,43,226,0.3));' +
15
+ 'border-radius: 6px;' +
16
+ 'font-size: 12px;' +
17
+ 'max-width: 150px;' +
18
+ 'text-align: center;' +
19
+ 'pointer-events: none;' +
20
+ 'z-index: 10000;' +
21
+ 'box-shadow: 0 2px 8px rgba(0,0,0,0.3);' +
22
+ 'opacity: 0;' +
23
+ 'transition: opacity 0.15s;' +
24
+ '}' +
25
+ '.synthos-tooltip.visible { opacity: 1; }';
26
+ document.head.appendChild(style);
27
+
28
+ var tip = document.createElement('div');
29
+ tip.className = 'synthos-tooltip';
30
+ document.body.appendChild(tip);
31
+
32
+ function show(el) {
33
+ tip.textContent = el.getAttribute('data-tooltip');
34
+ tip.style.display = 'block';
35
+ tip.classList.remove('visible');
36
+ var r = el.getBoundingClientRect();
37
+ var tw = tip.offsetWidth;
38
+ var left = r.left + (r.width / 2) - (tw / 2);
39
+ if (left < 4) left = 4;
40
+ if (left + tw > window.innerWidth - 4) left = window.innerWidth - tw - 4;
41
+ tip.style.left = left + 'px';
42
+ tip.style.top = (r.top - tip.offsetHeight - 6) + 'px';
43
+ void tip.offsetWidth;
44
+ tip.classList.add('visible');
45
+ }
46
+
47
+ function hide() {
48
+ tip.classList.remove('visible');
49
+ tip.style.display = 'none';
50
+ }
51
+ hide();
52
+
53
+ function attach(el, text) {
54
+ el.setAttribute('data-tooltip', text);
55
+ el.addEventListener('mouseenter', function() { show(el); });
56
+ el.addEventListener('mouseleave', hide);
57
+ }
58
+
59
+ // Pages link is never renamed
60
+ var pagesLink = document.getElementById('pagesLink');
61
+ if (pagesLink) attach(pagesLink, 'Browse all pages');
62
+
63
+ // Save and Reset tooltips deferred — locked-mode renames them on DOMContentLoaded
64
+ document.addEventListener('DOMContentLoaded', function() {
65
+ setTimeout(function() {
66
+ var s = document.getElementById('saveLink');
67
+ if (s) attach(s, s.textContent.trim() === 'Copy' ? 'Copy page as a new name' : 'Save page as a new name');
68
+ var r = document.getElementById('resetLink');
69
+ if (r) attach(r, r.textContent.trim() === 'Reload' ? 'Reload this page' : 'Reset page to default');
70
+ }, 0);
71
+ });
72
+
73
+ window.__synthOSTooltip = attach;
74
+ })();
75
+
76
+ // 1. Initial focus
77
+ var chatInput = document.getElementById('chatInput');
78
+ if (chatInput) chatInput.focus();
79
+
80
+ // 2. Form submit handler — show overlay + disable inputs
81
+ var chatForm = document.getElementById('chatForm');
82
+ if (chatForm) {
83
+ chatForm.addEventListener('submit', function() {
84
+ var overlay = document.getElementById('loadingOverlay');
85
+ if (overlay) overlay.style.display = 'flex';
86
+ chatForm.action = window.location.pathname;
87
+ setTimeout(function() {
88
+ var ci = document.getElementById('chatInput');
89
+ if (ci) ci.disabled = true;
90
+ var sb = document.querySelector('.chat-submit');
91
+ if (sb) sb.disabled = true;
92
+ document.querySelectorAll('.link-group a').forEach(function(a) {
93
+ a.style.pointerEvents = 'none';
94
+ a.style.opacity = '0.5';
95
+ });
96
+ }, 50);
97
+ });
98
+ }
99
+
100
+ // 3. Save link handler — themed modal with title, categories, greeting
101
+ (function() {
102
+ var saveLink = document.getElementById('saveLink');
103
+ if (!saveLink) return;
104
+
105
+ // Detect if current page is a Builders or System page (start with blank fields)
106
+ var isBuilder = window.pageInfo && Array.isArray(window.pageInfo.categories) &&
107
+ (window.pageInfo.categories.indexOf('Builders') !== -1 ||
108
+ window.pageInfo.categories.indexOf('System') !== -1);
109
+
110
+ // Original title for change detection
111
+ var originalTitle = (window.pageInfo && window.pageInfo.title) ? window.pageInfo.title : '';
112
+
113
+ // --- Create save modal ---
114
+ var modal = document.createElement('div');
115
+ modal.id = 'saveModal';
116
+ modal.className = 'modal-overlay';
117
+ modal.innerHTML =
118
+ '<div class="modal-content" style="max-width:480px;">' +
119
+ '<div class="modal-header">' +
120
+ '<span>Save Page</span>' +
121
+ '<button type="button" class="brainstorm-close-btn" id="saveCloseBtn">&times;</button>' +
122
+ '</div>' +
123
+ '<div class="modal-body" style="display:flex;flex-direction:column;gap:12px;padding:16px 20px;">' +
124
+ '<div>' +
125
+ '<label style="display:block;margin-bottom:4px;font-size:13px;color:var(--text-secondary);">Display Title <span style="color:var(--accent-primary);">*</span></label>' +
126
+ '<input type="text" id="saveTitleInput" class="brainstorm-input" placeholder="Enter a display title..." style="width:100%;box-sizing:border-box;">' +
127
+ '<div id="saveTitleError" style="color:#ff6b6b;font-size:12px;margin-top:4px;display:none;">Title is required</div>' +
128
+ '</div>' +
129
+ '<div>' +
130
+ '<label style="display:block;margin-bottom:4px;font-size:13px;color:var(--text-secondary);">Categories <span style="color:var(--accent-primary);">*</span></label>' +
131
+ '<input type="text" id="saveCategoriesInput" class="brainstorm-input" placeholder="e.g. Tool, Game, Utility" style="width:100%;box-sizing:border-box;">' +
132
+ '<div id="saveCategoriesError" style="color:#ff6b6b;font-size:12px;margin-top:4px;display:none;">At least one category is required</div>' +
133
+ '</div>' +
134
+ '<div>' +
135
+ '<label style="display:block;margin-bottom:4px;font-size:13px;color:var(--text-secondary);">Greeting <span style="font-size:11px;opacity:0.7;">(optional)</span></label>' +
136
+ '<input type="text" id="saveGreetingInput" class="brainstorm-input" placeholder="Available when title changes" style="width:100%;box-sizing:border-box;" disabled>' +
137
+ '<div style="font-size:11px;color:var(--text-secondary);margin-top:4px;opacity:0.7;" id="saveGreetingHint">Change the title to enable a custom greeting.</div>' +
138
+ '</div>' +
139
+ '</div>' +
140
+ '<div class="modal-footer" style="display:flex;justify-content:flex-end;gap:8px;padding:12px 20px;">' +
141
+ '<button type="button" class="brainstorm-send-btn" id="saveCancelBtn" style="background:transparent;border:1px solid var(--border-color);color:var(--text-secondary);">Cancel</button>' +
142
+ '<button type="button" class="brainstorm-send-btn" id="saveConfirmBtn">Save</button>' +
143
+ '</div>' +
144
+ '</div>';
145
+ document.body.appendChild(modal);
146
+
147
+ // --- Create error modal ---
148
+ var errorModal = document.createElement('div');
149
+ errorModal.id = 'errorModal';
150
+ errorModal.className = 'modal-overlay';
151
+ errorModal.innerHTML =
152
+ '<div class="modal-content" style="max-width:400px;">' +
153
+ '<div class="modal-header">' +
154
+ '<span>Error</span>' +
155
+ '<button type="button" class="brainstorm-close-btn" id="errorCloseBtn">&times;</button>' +
156
+ '</div>' +
157
+ '<div class="modal-body" style="padding:16px 20px;">' +
158
+ '<p id="errorMessage" style="margin:0;color:var(--text-primary);"></p>' +
159
+ '</div>' +
160
+ '<div class="modal-footer" style="display:flex;justify-content:flex-end;padding:12px 20px;">' +
161
+ '<button type="button" class="brainstorm-send-btn" id="errorOkBtn">OK</button>' +
162
+ '</div>' +
163
+ '</div>';
164
+ document.body.appendChild(errorModal);
165
+
166
+ // --- Element references ---
167
+ var titleInput = document.getElementById('saveTitleInput');
168
+ var categoriesInput = document.getElementById('saveCategoriesInput');
169
+ var greetingInput = document.getElementById('saveGreetingInput');
170
+ var greetingHint = document.getElementById('saveGreetingHint');
171
+ var titleError = document.getElementById('saveTitleError');
172
+ var categoriesError = document.getElementById('saveCategoriesError');
173
+
174
+ // --- Greeting enable/disable based on title change ---
175
+ titleInput.addEventListener('input', function() {
176
+ var changed = titleInput.value.trim() !== originalTitle;
177
+ greetingInput.disabled = !changed;
178
+ if (changed) {
179
+ greetingInput.placeholder = 'Enter a custom greeting...';
180
+ greetingHint.textContent = 'Replaces the initial Synthos greeting and removes chat history.';
181
+ } else {
182
+ greetingInput.placeholder = 'Available when title changes';
183
+ greetingInput.value = '';
184
+ greetingHint.textContent = 'Change the title to enable a custom greeting.';
185
+ }
186
+ });
187
+
188
+ // --- Open modal ---
189
+ function openSaveModal() {
190
+ // Pre-fill fields (blank for Builder pages)
191
+ titleInput.value = isBuilder ? '' : originalTitle;
192
+ categoriesInput.value = isBuilder ? '' : (
193
+ (window.pageInfo && Array.isArray(window.pageInfo.categories))
194
+ ? window.pageInfo.categories.join(', ')
195
+ : ''
196
+ );
197
+ greetingInput.value = '';
198
+ greetingInput.disabled = true;
199
+ greetingInput.placeholder = 'Available when title changes';
200
+ greetingHint.textContent = 'Change the title to enable a custom greeting.';
201
+ titleError.style.display = 'none';
202
+ categoriesError.style.display = 'none';
203
+ modal.classList.add('show');
204
+ titleInput.focus();
205
+ }
206
+
207
+ function closeSaveModal() {
208
+ modal.classList.remove('show');
209
+ }
210
+
211
+ function showError(msg) {
212
+ document.getElementById('errorMessage').textContent = msg;
213
+ errorModal.classList.add('show');
214
+ }
215
+
216
+ function closeError() {
217
+ errorModal.classList.remove('show');
218
+ }
219
+
220
+ // --- Submit ---
221
+ function submitSave() {
222
+ var title = titleInput.value.trim();
223
+ var cats = categoriesInput.value.trim();
224
+ var greeting = greetingInput.value.trim();
225
+ var valid = true;
226
+
227
+ // Validate
228
+ if (!title) {
229
+ titleError.style.display = 'block';
230
+ valid = false;
231
+ } else {
232
+ titleError.style.display = 'none';
233
+ }
234
+ if (!cats) {
235
+ categoriesError.style.display = 'block';
236
+ valid = false;
237
+ } else {
238
+ categoriesError.style.display = 'none';
239
+ }
240
+ if (!valid) return;
241
+
242
+ // Parse categories
243
+ var categories = cats.split(',').map(function(c) { return c.trim(); }).filter(Boolean);
244
+
245
+ // Disable button during save
246
+ var confirmBtn = document.getElementById('saveConfirmBtn');
247
+ confirmBtn.disabled = true;
248
+ confirmBtn.textContent = 'Saving...';
249
+
250
+ var body = { title: title, categories: categories };
251
+ if (greeting && !greetingInput.disabled) {
252
+ body.greeting = greeting;
253
+ }
254
+
255
+ fetch(window.location.pathname + '/save', {
256
+ method: 'POST',
257
+ headers: { 'Content-Type': 'application/json' },
258
+ body: JSON.stringify(body)
259
+ })
260
+ .then(function(res) {
261
+ return res.json().then(function(data) {
262
+ return { ok: res.ok, data: data };
263
+ });
264
+ })
265
+ .then(function(result) {
266
+ if (result.ok && result.data.redirect) {
267
+ window.location.href = result.data.redirect;
268
+ } else {
269
+ closeSaveModal();
270
+ showError(result.data.error || 'An unknown error occurred');
271
+ confirmBtn.disabled = false;
272
+ confirmBtn.textContent = 'Save';
273
+ }
274
+ })
275
+ .catch(function(err) {
276
+ closeSaveModal();
277
+ showError('Network error: ' + err.message);
278
+ confirmBtn.disabled = false;
279
+ confirmBtn.textContent = 'Save';
280
+ });
281
+ }
282
+
283
+ // --- Event listeners ---
284
+ saveLink.addEventListener('click', openSaveModal);
285
+ document.getElementById('saveCloseBtn').addEventListener('click', closeSaveModal);
286
+ document.getElementById('saveCancelBtn').addEventListener('click', closeSaveModal);
287
+ document.getElementById('saveConfirmBtn').addEventListener('click', submitSave);
288
+ document.getElementById('errorCloseBtn').addEventListener('click', closeError);
289
+ document.getElementById('errorOkBtn').addEventListener('click', closeError);
290
+
291
+ modal.addEventListener('click', function(e) {
292
+ if (e.target === modal) closeSaveModal();
293
+ });
294
+ errorModal.addEventListener('click', function(e) {
295
+ if (e.target === errorModal) closeError();
296
+ });
297
+
298
+ document.addEventListener('keydown', function(e) {
299
+ if (e.key === 'Escape') {
300
+ if (modal.classList.contains('show')) closeSaveModal();
301
+ if (errorModal.classList.contains('show')) closeError();
302
+ }
303
+ });
304
+
305
+ // Enter key in title/categories inputs triggers save
306
+ titleInput.addEventListener('keydown', function(e) {
307
+ if (e.key === 'Enter') { e.preventDefault(); submitSave(); }
308
+ });
309
+ categoriesInput.addEventListener('keydown', function(e) {
310
+ if (e.key === 'Enter') { e.preventDefault(); submitSave(); }
311
+ });
312
+ })();
313
+
314
+ // 4. Reset link handler
315
+ var resetLink = document.getElementById('resetLink');
316
+ if (resetLink) {
317
+ resetLink.addEventListener('click', function() {
318
+ window.location.href = window.location.pathname + '/reset';
319
+ });
320
+ }
321
+
322
+ // 5. Chat scroll to bottom
323
+ var chatMessages = document.getElementById('chatMessages');
324
+ if (chatMessages) {
325
+ chatMessages.scrollTo({
326
+ top: chatMessages.scrollHeight,
327
+ behavior: 'smooth'
328
+ });
329
+ }
330
+
331
+ // 6. Chat toggle button — create (if not already in markup), persist in localStorage
332
+ (function() {
333
+ var btn = document.querySelector('.chat-toggle');
334
+ if (!btn) {
335
+ btn = document.createElement('button');
336
+ btn.className = 'chat-toggle';
337
+ btn.setAttribute('aria-label', 'Toggle chat panel');
338
+ var dots = document.createElement('span');
339
+ dots.className = 'chat-toggle-dots';
340
+ for (var i = 0; i < 3; i++) {
341
+ var dot = document.createElement('span');
342
+ dot.className = 'chat-toggle-dot';
343
+ dots.appendChild(dot);
344
+ }
345
+ btn.appendChild(dots);
346
+ document.body.appendChild(btn);
347
+ }
348
+
349
+ var STORAGE_KEY = 'synthos-chat-collapsed';
350
+
351
+ if (localStorage.getItem(STORAGE_KEY) === 'true') {
352
+ document.body.classList.add('chat-collapsed');
353
+ }
354
+
355
+ btn.addEventListener('click', function() {
356
+ document.body.classList.toggle('chat-collapsed');
357
+ localStorage.setItem(STORAGE_KEY, document.body.classList.contains('chat-collapsed'));
358
+ });
359
+ })();
360
+
361
+ // 7. Focus management — prevent viewer content from stealing keystrokes
362
+ (function() {
363
+ var ci = document.getElementById('chatInput');
364
+ var vp = document.getElementById('viewerPanel');
365
+ if (!ci || !vp) return;
366
+
367
+ ci.addEventListener('mousedown', function(e) {
368
+ e.stopPropagation();
369
+ });
370
+
371
+ ['keydown', 'keyup', 'keypress'].forEach(function(type) {
372
+ document.addEventListener(type, function(e) {
373
+ if (document.activeElement === ci) {
374
+ e.stopImmediatePropagation();
375
+ }
376
+ }, true);
377
+ });
378
+
379
+ vp.setAttribute('tabindex', '-1');
380
+ ci.addEventListener('blur', function() {
381
+ vp.focus();
382
+ });
383
+ })();
384
+
385
+ // 8. Brainstorm — dynamic brainstorming UI (available on every v2 page)
386
+ (function() {
387
+ var chatInput = document.getElementById('chatInput');
388
+ if (!chatInput) return;
389
+
390
+ // --- Wrap chatInput in .chat-input-wrapper if not already wrapped ---
391
+ if (!chatInput.parentElement || !chatInput.parentElement.classList.contains('chat-input-wrapper')) {
392
+ var wrapper = document.createElement('div');
393
+ wrapper.className = 'chat-input-wrapper';
394
+ chatInput.parentNode.insertBefore(wrapper, chatInput);
395
+ wrapper.appendChild(chatInput);
396
+ }
397
+
398
+ // --- Create brainstorm icon button ---
399
+ var brainstormBtn = document.createElement('button');
400
+ brainstormBtn.type = 'button';
401
+ brainstormBtn.className = 'brainstorm-icon-btn';
402
+ if (window.__synthOSTooltip) window.__synthOSTooltip(brainstormBtn, 'Brainstorm ideas');
403
+ brainstormBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
404
+ '<circle cx="12" cy="12" r="3"></circle>' +
405
+ '<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"></path>' +
406
+ '</svg>';
407
+ chatInput.parentElement.appendChild(brainstormBtn);
408
+
409
+ // --- Create brainstorm modal ---
410
+ var modal = document.createElement('div');
411
+ modal.id = 'brainstormModal';
412
+ modal.className = 'modal-overlay brainstorm-modal';
413
+ modal.innerHTML =
414
+ '<div class="modal-content">' +
415
+ '<div class="modal-header">' +
416
+ '<span>Brainstorm</span>' +
417
+ '<button type="button" class="brainstorm-close-btn" id="brainstormCloseBtn">&times;</button>' +
418
+ '</div>' +
419
+ '<div class="brainstorm-messages" id="brainstormMessages"></div>' +
420
+ '<div class="brainstorm-input-row">' +
421
+ '<input type="text" class="brainstorm-input" id="brainstormInput" placeholder="What\'s on your mind...">' +
422
+ '<button type="button" class="brainstorm-send-btn" id="brainstormSendBtn">Send</button>' +
423
+ '</div>' +
424
+ '</div>';
425
+ document.body.appendChild(modal);
426
+
427
+ // --- State ---
428
+ var brainstormHistory = [];
429
+
430
+ // --- Helpers ---
431
+ function openBrainstorm() {
432
+ modal.classList.add('show');
433
+ // Grab text from chat input as initial topic
434
+ var topic = chatInput.value.trim();
435
+ if (topic) {
436
+ chatInput.value = '';
437
+ sendBrainstormText(topic, true);
438
+ } else {
439
+ // No topic — send context-only opener so LLM starts the brainstorm
440
+ sendBrainstormText('', true);
441
+ }
442
+ }
443
+
444
+ function closeBrainstorm() {
445
+ modal.classList.remove('show');
446
+ brainstormHistory = [];
447
+ document.getElementById('brainstormMessages').innerHTML = '';
448
+ }
449
+
450
+ function scrollBrainstormToBottom() {
451
+ var el = document.getElementById('brainstormMessages');
452
+ el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' });
453
+ }
454
+
455
+ function escapeHtml(str) {
456
+ var div = document.createElement('div');
457
+ div.appendChild(document.createTextNode(str));
458
+ return div.innerHTML;
459
+ }
460
+
461
+ function appendBrainstormMessage(role, text, prompt, suggestions, isOpener) {
462
+ var div = document.createElement('div');
463
+ div.className = 'brainstorm-message ' + (role === 'user' ? 'brainstorm-user' : 'brainstorm-assistant');
464
+ if (role === 'assistant') {
465
+ var html;
466
+ if (typeof marked !== 'undefined') {
467
+ html = marked.parse(text);
468
+ } else {
469
+ html = escapeHtml(text);
470
+ }
471
+ div.innerHTML = '<strong>SynthOS:</strong> ' + html;
472
+ // Clickable suggestion chips
473
+ if (suggestions && suggestions.length > 0) {
474
+ var chips = document.createElement('div');
475
+ chips.className = 'brainstorm-suggestions';
476
+ suggestions.forEach(function(s) {
477
+ var chip = document.createElement('button');
478
+ chip.type = 'button';
479
+ chip.className = 'brainstorm-suggestion-chip';
480
+ chip.textContent = s;
481
+ chip.addEventListener('click', function() {
482
+ submitSuggestion(s);
483
+ });
484
+ chips.appendChild(chip);
485
+ });
486
+ div.appendChild(chips);
487
+ }
488
+ // "Build It" button — skip on the opener response
489
+ if (prompt && !isOpener) {
490
+ var btnRow = document.createElement('div');
491
+ btnRow.className = 'brainstorm-build-row';
492
+ var buildBtn = document.createElement('button');
493
+ buildBtn.type = 'button';
494
+ buildBtn.className = 'brainstorm-build-btn';
495
+ buildBtn.textContent = 'Build It';
496
+ buildBtn.setAttribute('data-prompt', prompt);
497
+ buildBtn.addEventListener('click', function() {
498
+ chatInput.value = this.getAttribute('data-prompt');
499
+ closeBrainstorm();
500
+ chatInput.focus();
501
+ });
502
+ btnRow.appendChild(buildBtn);
503
+ div.appendChild(btnRow);
504
+ }
505
+ } else {
506
+ div.textContent = text;
507
+ }
508
+ document.getElementById('brainstormMessages').appendChild(div);
509
+ scrollBrainstormToBottom();
510
+ }
511
+
512
+ function submitSuggestion(text) {
513
+ // Disable old suggestion chips so they can't be double-clicked
514
+ var oldChips = document.querySelectorAll('#brainstormMessages .brainstorm-suggestion-chip');
515
+ for (var i = 0; i < oldChips.length; i++) {
516
+ oldChips[i].disabled = true;
517
+ }
518
+ sendBrainstormText(text, false);
519
+ }
520
+
521
+ function getBrainstormContext() {
522
+ var chatEl = document.getElementById('chatMessages');
523
+ var chatHistory = chatEl ? chatEl.innerText : '';
524
+ return '<CHAT_HISTORY>\n' + chatHistory;
525
+ }
526
+
527
+ // Send from the input field
528
+ function sendBrainstormMessage() {
529
+ var input = document.getElementById('brainstormInput');
530
+ var text = input.value.trim();
531
+ if (!text) return;
532
+ input.value = '';
533
+ sendBrainstormText(text, false);
534
+ }
535
+
536
+ // Core fetch — isOpener=true means this is the initial call when brainstorm opens
537
+ function sendBrainstormText(text, isOpener) {
538
+ var input = document.getElementById('brainstormInput');
539
+ var userMsg = text || (isOpener ? 'Look at the conversation so far and suggest what we could build or improve.' : '');
540
+ if (!userMsg) return;
541
+
542
+ // Show user message in chat (skip for auto-generated opener)
543
+ if (text) {
544
+ appendBrainstormMessage('user', text);
545
+ }
546
+ brainstormHistory.push({ role: 'user', content: userMsg });
547
+
548
+ var thinking = document.createElement('div');
549
+ thinking.className = 'brainstorm-thinking';
550
+ thinking.id = 'brainstormThinking';
551
+ thinking.textContent = 'Thinking...';
552
+ document.getElementById('brainstormMessages').appendChild(thinking);
553
+ scrollBrainstormToBottom();
554
+
555
+ input.disabled = true;
556
+ document.getElementById('brainstormSendBtn').disabled = true;
557
+
558
+ fetch('/api/brainstorm', {
559
+ method: 'POST',
560
+ headers: { 'Content-Type': 'application/json' },
561
+ body: JSON.stringify({
562
+ context: getBrainstormContext(),
563
+ messages: brainstormHistory
564
+ })
565
+ })
566
+ .then(function(res) {
567
+ if (!res.ok) throw new Error('Brainstorm request failed');
568
+ return res.json();
569
+ })
570
+ .then(function(data) {
571
+ var thinkingEl = document.getElementById('brainstormThinking');
572
+ if (thinkingEl) thinkingEl.remove();
573
+
574
+ var response = data.response || 'Sorry, I didn\'t get a response.';
575
+ var prompt = data.prompt || '';
576
+ var suggestions = Array.isArray(data.suggestions) ? data.suggestions : [];
577
+ appendBrainstormMessage('assistant', response, prompt, suggestions, isOpener);
578
+ brainstormHistory.push({
579
+ role: 'assistant',
580
+ content: response + '\n\n[Suggested prompt: ' + prompt + ']'
581
+ });
582
+ })
583
+ .catch(function(err) {
584
+ var thinkingEl = document.getElementById('brainstormThinking');
585
+ if (thinkingEl) thinkingEl.remove();
586
+ appendBrainstormMessage('assistant', 'Something went wrong: ' + err.message);
587
+ })
588
+ .finally(function() {
589
+ input.disabled = false;
590
+ document.getElementById('brainstormSendBtn').disabled = false;
591
+ input.focus();
592
+ });
593
+ }
594
+
595
+ // --- Event listeners ---
596
+ brainstormBtn.addEventListener('click', openBrainstorm);
597
+ document.getElementById('brainstormCloseBtn').addEventListener('click', closeBrainstorm);
598
+
599
+ modal.addEventListener('click', function(e) {
600
+ if (e.target === modal) closeBrainstorm();
601
+ });
602
+
603
+ document.addEventListener('keydown', function(e) {
604
+ if (e.key === 'Escape' && modal.classList.contains('show')) closeBrainstorm();
605
+ });
606
+
607
+ document.getElementById('brainstormSendBtn').addEventListener('click', sendBrainstormMessage);
608
+ document.getElementById('brainstormInput').addEventListener('keydown', function(e) {
609
+ if (e.key === 'Enter' && !e.shiftKey) {
610
+ e.preventDefault();
611
+ sendBrainstormMessage();
612
+ }
613
+ });
614
+ })();
615
+ })();
@@ -0,0 +1,12 @@
1
+ # SynthOS Tests
2
+
3
+ ## Tier 1 (implemented)
4
+ - `transformPage.spec.ts` — assignNodeIds, stripNodeIds, applyChangeList, parseChangeList, injectError
5
+ - `pages.spec.ts` — normalizePageName, parseMetadata
6
+ - `migrations.spec.ts` — postProcessV2
7
+
8
+ ## Tier 2 (TODO)
9
+ - `scripts.spec.ts` — listScripts, loadScripts, saveScripts
10
+ - `modelInstructions.spec.ts` — getModelInstructions provider routing
11
+ - `debugLog.spec.ts` — log formatting and filtering
12
+ - `init.spec.ts` — folder creation and default file copying