vibespot 0.7.0 → 0.7.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibespot",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "AI-powered HubSpot CMS landing page builder — vibe coding & React converter",
5
5
  "type": "module",
6
6
  "bin": {
package/ui/chat.js CHANGED
@@ -15,6 +15,8 @@ let streamTimerInterval = null;
15
15
  let lastStreamStatus = "";
16
16
  let currentSessionId = "";
17
17
  let currentTemplateId = "";
18
+ let renderScheduled = false;
19
+ let scrollScheduled = false;
18
20
 
19
21
  const messagesEl = document.getElementById("chat-messages");
20
22
  const inputEl = document.getElementById("chat-input");
@@ -47,11 +49,15 @@ function connectWebSocket() {
47
49
  };
48
50
 
49
51
  ws.onclose = () => {
52
+ stopStreamTimer();
53
+ if (isStreaming) finishStreaming();
50
54
  setStatus("Disconnected — reconnecting...");
51
55
  setTimeout(connectWebSocket, 2000);
52
56
  };
53
57
 
54
58
  ws.onerror = () => {
59
+ stopStreamTimer();
60
+ if (isStreaming) finishStreaming();
55
61
  setStatus("Connection error");
56
62
  };
57
63
  }
@@ -236,6 +242,16 @@ function handleStreamChunk(text) {
236
242
  if (!streamingMsgEl) return;
237
243
  streamBuffer += text;
238
244
 
245
+ if (!renderScheduled) {
246
+ renderScheduled = true;
247
+ requestAnimationFrame(flushStreamRender);
248
+ }
249
+ }
250
+
251
+ function flushStreamRender() {
252
+ renderScheduled = false;
253
+ if (!streamingMsgEl) return;
254
+
239
255
  // Hide incomplete code fences (AI is writing module code)
240
256
  let display = streamBuffer;
241
257
  const fenceCount = (display.match(/```/g) || []).length;
@@ -410,7 +426,16 @@ function escapeHtml(str) {
410
426
  }
411
427
 
412
428
  function scrollToBottom() {
413
- messagesEl.scrollTop = messagesEl.scrollHeight;
429
+ if (scrollScheduled) return;
430
+ scrollScheduled = true;
431
+ requestAnimationFrame(() => {
432
+ scrollScheduled = false;
433
+ // Only auto-scroll if user is near the bottom (within 150px)
434
+ const gap = messagesEl.scrollHeight - messagesEl.scrollTop - messagesEl.clientHeight;
435
+ if (gap < 150) {
436
+ messagesEl.scrollTop = messagesEl.scrollHeight;
437
+ }
438
+ });
414
439
  }
415
440
 
416
441
  function setStatus(text) {
@@ -488,7 +513,11 @@ async function refreshHistoryPanel() {
488
513
  }
489
514
 
490
515
  list.innerHTML = toggleHtml;
491
- for (const commit of data.commits) {
516
+ const HISTORY_LIMIT = 50;
517
+ const commits = data.commits.slice(0, HISTORY_LIMIT);
518
+ const frag = document.createDocumentFragment();
519
+
520
+ for (const commit of commits) {
492
521
  const isInitial = commit.message.startsWith("Initial ");
493
522
  const isRollback = commit.message.includes("Rollback to:");
494
523
 
@@ -507,7 +536,15 @@ async function refreshHistoryPanel() {
507
536
  <div class="history-item__msg">${escapeHtml(displayMsg)}</div>
508
537
  ${!isInitial ? `<button class="history-item__rollback" data-hash="${escapeHtml(commit.fullHash)}">Restore</button>` : ""}
509
538
  `;
510
- list.appendChild(item);
539
+ frag.appendChild(item);
540
+ }
541
+ list.appendChild(frag);
542
+
543
+ if (data.commits.length > HISTORY_LIMIT) {
544
+ const more = document.createElement("div");
545
+ more.className = "history__show-more";
546
+ more.textContent = `Showing ${HISTORY_LIMIT} of ${data.commits.length} versions`;
547
+ list.appendChild(more);
511
548
  }
512
549
 
513
550
  list.querySelectorAll(".history-item__rollback").forEach((btn) => {
package/ui/dashboard.js CHANGED
@@ -115,25 +115,20 @@ function renderTemplateList(templates) {
115
115
  list.appendChild(item);
116
116
  }
117
117
 
118
- // Attach click handlers
119
- list.querySelectorAll(".dashboard__template-open").forEach((btn) => {
120
- btn.addEventListener("click", () => openTemplate(btn.dataset.id));
121
- });
122
- list.querySelectorAll(".dashboard__template-delete").forEach((btn) => {
123
- btn.addEventListener("click", () => confirmDeleteTemplate(btn.dataset.id));
124
- });
125
-
126
- // Double-click on label to rename
127
- list.querySelectorAll(".dashboard__template-label").forEach((labelEl) => {
118
+ // Event delegation — single listener handles all template actions
119
+ list.onclick = (e) => {
120
+ const openBtn = e.target.closest(".dashboard__template-open");
121
+ if (openBtn) return openTemplate(openBtn.dataset.id);
122
+ const delBtn = e.target.closest(".dashboard__template-delete");
123
+ if (delBtn) return confirmDeleteTemplate(delBtn.dataset.id);
124
+ };
125
+ list.ondblclick = (e) => {
126
+ const labelEl = e.target.closest(".dashboard__template-label");
127
+ if (!labelEl) return;
128
128
  const item = labelEl.closest(".dashboard__template-item");
129
129
  const templateId = item?.querySelector(".dashboard__template-open")?.dataset.id;
130
- if (!templateId) return;
131
-
132
- labelEl.addEventListener("dblclick", (e) => {
133
- e.stopPropagation();
134
- startTemplateRename(labelEl, templateId);
135
- });
136
- });
130
+ if (templateId) startTemplateRename(labelEl, templateId);
131
+ };
137
132
  }
138
133
 
139
134
  function startTemplateRename(labelEl, templateId) {
package/ui/dialog.js CHANGED
@@ -6,10 +6,9 @@
6
6
  // HTML-escape helper (standalone so dialog.js has no load-order dependency)
7
7
  if (typeof esc === "undefined") {
8
8
  // eslint-disable-next-line no-var
9
+ var _escMap = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };
9
10
  var esc = function (str) {
10
- const el = document.createElement("span");
11
- el.textContent = String(str);
12
- return el.innerHTML;
11
+ return String(str).replace(/[&<>"']/g, function (c) { return _escMap[c]; });
13
12
  };
14
13
  }
15
14
 
package/ui/favicon.ico CHANGED
Binary file
@@ -38,6 +38,7 @@ async function openFieldEditor(moduleName) {
38
38
  }
39
39
 
40
40
  function closeFieldEditor() {
41
+ if (updateTimer) { clearTimeout(updateTimer); updateTimer = null; }
41
42
  currentEditModule = null;
42
43
  if (typeof showModuleListView === "function") {
43
44
  showModuleListView();
@@ -354,11 +354,23 @@ function setUploadState(state, data) {
354
354
  }
355
355
  }
356
356
 
357
+ let _logBuffer = "";
358
+ let _logFlushScheduled = false;
359
+
357
360
  function appendUploadLog(text) {
358
- const log = document.getElementById("upload-log");
359
- if (!log) return;
360
- log.textContent += text;
361
- log.scrollTop = log.scrollHeight;
361
+ _logBuffer += text;
362
+ if (!_logFlushScheduled) {
363
+ _logFlushScheduled = true;
364
+ requestAnimationFrame(() => {
365
+ _logFlushScheduled = false;
366
+ const log = document.getElementById("upload-log");
367
+ if (log) {
368
+ log.textContent += _logBuffer;
369
+ log.scrollTop = log.scrollHeight;
370
+ }
371
+ _logBuffer = "";
372
+ });
373
+ }
362
374
  }
363
375
 
364
376
  function fixUploadWithAI() {