vibespot 0.6.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/ui/settings.js CHANGED
@@ -810,6 +810,11 @@ async function authCLI(cli, btn, apiKey) {
810
810
  // ---------------------------------------------------------------------------
811
811
 
812
812
  function getModelsForEngine(engine) {
813
+ // Use server-provided model catalog if available
814
+ if (settingsData && settingsData.models && settingsData.models[engine]) {
815
+ return settingsData.models[engine];
816
+ }
817
+ // Fallback to hardcoded defaults
813
818
  switch (engine) {
814
819
  case "claude-code":
815
820
  return [
@@ -819,10 +824,9 @@ function getModelsForEngine(engine) {
819
824
  ];
820
825
  case "anthropic-api":
821
826
  return [
822
- { id: "claude-sonnet-4-20250514", label: "Claude Sonnet 4 (default)" },
823
- { id: "claude-opus-4-20250514", label: "Claude Opus 4" },
827
+ { id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6" },
828
+ { id: "claude-opus-4-6", label: "Claude Opus 4.6" },
824
829
  { id: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5" },
825
- { id: "claude-sonnet-4-5-20250929", label: "Claude Sonnet 4.5" },
826
830
  ];
827
831
  case "openai-api":
828
832
  return [
@@ -834,9 +838,9 @@ function getModelsForEngine(engine) {
834
838
  case "gemini-cli":
835
839
  case "gemini-api":
836
840
  return [
837
- { id: "gemini-2.0-flash", label: "Gemini 2.0 Flash (default)" },
841
+ { id: "gemini-2.5-flash", label: "Gemini 2.5 Flash (default)" },
838
842
  { id: "gemini-2.5-pro", label: "Gemini 2.5 Pro" },
839
- { id: "gemini-2.5-flash", label: "Gemini 2.5 Flash" },
843
+ { id: "gemini-2.0-flash", label: "Gemini 2.0 Flash" },
840
844
  ];
841
845
  case "codex-cli":
842
846
  return [
@@ -852,7 +856,7 @@ function getModelsForEngine(engine) {
852
856
  function getCurrentModel(engine, config) {
853
857
  switch (engine) {
854
858
  case "claude-code": return config.claudeCodeModel || "sonnet";
855
- case "anthropic-api": return config.anthropicApiModel || "claude-sonnet-4-20250514";
859
+ case "anthropic-api": return config.anthropicApiModel || "claude-sonnet-4-6";
856
860
  case "openai-api": return config.openaiApiModel || "gpt-4o";
857
861
  default: return null;
858
862
  }
@@ -910,7 +914,6 @@ function escSettings(str) {
910
914
  // Event listeners
911
915
  // ---------------------------------------------------------------------------
912
916
 
913
- document.getElementById("btn-settings").addEventListener("click", openSettings);
914
917
  document.getElementById("settings-close").addEventListener("click", closeSettings);
915
918
  document.getElementById("settings-overlay").addEventListener("click", (e) => {
916
919
  if (e.target.id === "settings-overlay") closeSettings();
package/ui/setup.js CHANGED
@@ -225,6 +225,15 @@ function populateProjectRail(info) {
225
225
  <span class="project-rail__item-meta">${meta}</span>`;
226
226
  item.appendChild(infoEl);
227
227
 
228
+ // Double-click on name to rename
229
+ const nameSpan = infoEl.querySelector(".project-rail__item-name");
230
+ if (nameSpan) {
231
+ nameSpan.addEventListener("dblclick", (e) => {
232
+ e.stopPropagation();
233
+ startInlineRename(nameSpan, p);
234
+ });
235
+ }
236
+
228
237
  // Delete button (visible when expanded + hover)
229
238
  const delBtn = document.createElement("button");
230
239
  delBtn.className = "project-rail__item-delete";
@@ -263,8 +272,12 @@ function populateProjectRail(info) {
263
272
  railTooltip.classList.remove("project-rail__tooltip--visible");
264
273
  });
265
274
 
266
- // Click to open
275
+ // Click to open (blocked while AI is generating)
267
276
  item.addEventListener("click", () => {
277
+ if (typeof isStreaming !== "undefined" && isStreaming) {
278
+ showError("Cannot switch projects while AI is generating.");
279
+ return;
280
+ }
268
281
  if (p.sessionId) resumeSession(p.sessionId);
269
282
  else openTheme(p.name);
270
283
  });
@@ -287,6 +300,84 @@ document.getElementById("project-rail-add")?.addEventListener("click", () => {
287
300
  showSetup();
288
301
  });
289
302
 
303
+ // ---------------------------------------------------------------------------
304
+ // Inline rename
305
+ // ---------------------------------------------------------------------------
306
+
307
+ function startInlineRename(nameSpan, project) {
308
+ if (nameSpan.contentEditable === "true") return; // already editing
309
+
310
+ const oldName = project.name;
311
+ nameSpan.contentEditable = "true";
312
+ nameSpan.classList.add("project-rail__item-name--editing");
313
+ nameSpan.focus();
314
+
315
+ // Select all text
316
+ const range = document.createRange();
317
+ range.selectNodeContents(nameSpan);
318
+ const sel = window.getSelection();
319
+ sel.removeAllRanges();
320
+ sel.addRange(range);
321
+
322
+ function commit() {
323
+ nameSpan.contentEditable = "false";
324
+ nameSpan.classList.remove("project-rail__item-name--editing");
325
+
326
+ const newName = nameSpan.textContent.trim();
327
+ if (!newName || newName === oldName) {
328
+ nameSpan.textContent = oldName;
329
+ return;
330
+ }
331
+
332
+ fetch("/api/themes/rename", {
333
+ method: "POST",
334
+ headers: { "Content-Type": "application/json" },
335
+ body: JSON.stringify({ sessionId: project.sessionId, newName }),
336
+ })
337
+ .then((r) => r.json())
338
+ .then((data) => {
339
+ if (data.ok) {
340
+ // Update the in-memory project name + UI
341
+ project.name = data.newName;
342
+ nameSpan.textContent = data.newName;
343
+ const item = nameSpan.closest(".project-rail__item");
344
+ if (item) {
345
+ item.dataset.name = data.newName;
346
+ const bubble = item.querySelector(".project-rail__item-bubble");
347
+ if (bubble) bubble.textContent = data.newName.charAt(0).toUpperCase();
348
+ }
349
+ // Update topbar if this is the active project
350
+ if (currentAppTheme === oldName) {
351
+ currentAppTheme = data.newName;
352
+ const themeNameEl = document.getElementById("theme-name");
353
+ if (themeNameEl) themeNameEl.textContent = data.newName;
354
+ window.location.hash = "#/app/" + encodeURIComponent(data.newName);
355
+ }
356
+ updateRailActive();
357
+ } else {
358
+ nameSpan.textContent = oldName;
359
+ showError(data.error || "Rename failed");
360
+ }
361
+ })
362
+ .catch(() => {
363
+ nameSpan.textContent = oldName;
364
+ showError("Rename failed");
365
+ });
366
+ }
367
+
368
+ nameSpan.addEventListener("blur", commit, { once: true });
369
+ nameSpan.addEventListener("keydown", (e) => {
370
+ if (e.key === "Enter") {
371
+ e.preventDefault();
372
+ nameSpan.blur();
373
+ }
374
+ if (e.key === "Escape") {
375
+ nameSpan.textContent = oldName;
376
+ nameSpan.blur();
377
+ }
378
+ });
379
+ }
380
+
290
381
  // ---------------------------------------------------------------------------
291
382
  // Delete project confirmation
292
383
  // ---------------------------------------------------------------------------
@@ -714,20 +805,19 @@ function showSetup() {
714
805
  initSetup();
715
806
  }
716
807
 
717
- // Logo click → go back to dashboard (from chat) or setup (from dashboard)
808
+ // App back button → go back to dashboard from chat
809
+ document.getElementById("app-back")?.addEventListener("click", () => {
810
+ if (currentAppTheme && typeof showDashboard === "function") {
811
+ appScreen.classList.add("hidden");
812
+ showDashboard(currentAppTheme);
813
+ }
814
+ });
815
+
816
+ // Logo click → go back to setup (from dashboard)
718
817
  document.querySelectorAll(".topbar__brand").forEach((el) => {
719
818
  el.style.cursor = "pointer";
720
819
  el.addEventListener("click", () => {
721
820
  const dashEl = document.getElementById("dashboard-screen");
722
- // From chat → go to dashboard
723
- if (!appScreen.classList.contains("hidden") && currentAppTheme) {
724
- if (typeof showDashboard === "function") {
725
- appScreen.classList.add("hidden");
726
- showDashboard(currentAppTheme);
727
- return;
728
- }
729
- }
730
- // From dashboard → go to setup
731
821
  if (dashEl && !dashEl.classList.contains("hidden")) {
732
822
  showSetup();
733
823
  return;
package/ui/styles.css CHANGED
@@ -353,6 +353,67 @@ body { display: flex; }
353
353
  margin: 0 auto;
354
354
  }
355
355
 
356
+ .dashboard__theme-heading {
357
+ font-size: 28px;
358
+ font-weight: 700;
359
+ color: var(--text);
360
+ margin: 0 0 24px 0;
361
+ letter-spacing: -0.5px;
362
+ cursor: default;
363
+ }
364
+
365
+ .dashboard__theme-heading--editing {
366
+ border: 1px solid var(--accent);
367
+ border-radius: var(--radius);
368
+ padding: 2px 8px;
369
+ outline: none;
370
+ cursor: text;
371
+ }
372
+
373
+ .dashboard__theme-path {
374
+ display: flex;
375
+ align-items: center;
376
+ gap: 8px;
377
+ margin: -16px 0 24px 0;
378
+ color: var(--text-muted);
379
+ font-size: 13px;
380
+ font-family: "SF Mono", "Fira Code", "Fira Mono", monospace;
381
+ }
382
+
383
+ .dashboard__theme-path-text {
384
+ overflow: hidden;
385
+ text-overflow: ellipsis;
386
+ white-space: nowrap;
387
+ user-select: all;
388
+ cursor: text;
389
+ }
390
+
391
+ .dashboard__download-btn {
392
+ display: inline-flex;
393
+ align-items: center;
394
+ gap: 4px;
395
+ margin-left: auto;
396
+ padding: 4px 10px;
397
+ background: var(--surface-2);
398
+ color: var(--text-muted);
399
+ border: 1px solid var(--border);
400
+ border-radius: var(--radius);
401
+ font-size: 12px;
402
+ cursor: pointer;
403
+ white-space: nowrap;
404
+ transition: color 0.15s, border-color 0.15s;
405
+ }
406
+
407
+ .dashboard__download-btn:hover {
408
+ color: var(--text);
409
+ border-color: var(--text-muted);
410
+ }
411
+
412
+ .dashboard__download-btn:disabled {
413
+ opacity: 0.5;
414
+ cursor: default;
415
+ }
416
+
356
417
  .dashboard__section {
357
418
  margin-bottom: 36px;
358
419
  }
@@ -475,6 +536,17 @@ body { display: flex; }
475
536
  white-space: nowrap;
476
537
  overflow: hidden;
477
538
  text-overflow: ellipsis;
539
+ cursor: default;
540
+ }
541
+
542
+ .dashboard__template-label--editing {
543
+ background: var(--bg);
544
+ border: 1px solid var(--accent);
545
+ border-radius: 3px;
546
+ padding: 0 4px;
547
+ outline: none;
548
+ overflow: visible;
549
+ cursor: text;
478
550
  }
479
551
 
480
552
  .dashboard__template-meta {
@@ -702,10 +774,6 @@ body { display: flex; }
702
774
  background: var(--accent-gradient);
703
775
  border-radius: 8px;
704
776
  color: var(--text-on-accent);
705
- font-family: var(--font-display);
706
- font-weight: 700;
707
- font-size: 16px;
708
- line-height: 1;
709
777
  }
710
778
 
711
779
  .topbar__brand-name {
@@ -726,6 +794,15 @@ body { display: flex; }
726
794
  overflow: hidden;
727
795
  text-overflow: ellipsis;
728
796
  max-width: 180px;
797
+ cursor: default;
798
+ }
799
+
800
+ .topbar__project-pill--editing {
801
+ border-color: var(--accent);
802
+ color: var(--text);
803
+ outline: none;
804
+ overflow: visible;
805
+ cursor: text;
729
806
  }
730
807
 
731
808
  .topbar__center {
@@ -1532,22 +1609,6 @@ body { display: flex; }
1532
1609
  word-wrap: break-word;
1533
1610
  }
1534
1611
 
1535
- /* Streaming cursor */
1536
- .chat-msg--streaming .chat-msg__bubble::after {
1537
- content: "";
1538
- display: inline-block;
1539
- width: 6px;
1540
- height: 14px;
1541
- background: var(--accent);
1542
- border-radius: 1px;
1543
- margin-left: 2px;
1544
- vertical-align: text-bottom;
1545
- animation: cursorBlink 0.8s ease infinite;
1546
- }
1547
- @keyframes cursorBlink {
1548
- 0%, 100% { opacity: 1; }
1549
- 50% { opacity: 0.2; }
1550
- }
1551
1612
 
1552
1613
  .chat-msg__bubble p { margin-bottom: 8px; }
1553
1614
  .chat-msg__bubble p:last-child { margin-bottom: 0; }
@@ -2481,6 +2542,20 @@ body { display: flex; }
2481
2542
  padding: 8px;
2482
2543
  }
2483
2544
 
2545
+ .history__toggle {
2546
+ text-align: center;
2547
+ padding: 8px 16px 4px;
2548
+ }
2549
+
2550
+ .history__toggle-btn {
2551
+ background: none;
2552
+ border: none;
2553
+ color: var(--accent);
2554
+ font-size: 12px;
2555
+ cursor: pointer;
2556
+ text-decoration: underline;
2557
+ }
2558
+
2484
2559
  .history__loading,
2485
2560
  .history__empty {
2486
2561
  color: var(--text-dim);
@@ -2802,17 +2877,18 @@ body { display: flex; }
2802
2877
  }
2803
2878
 
2804
2879
  .project-rail__footer {
2805
- display: none;
2880
+ display: flex;
2881
+ flex-direction: column;
2882
+ gap: 4px;
2806
2883
  margin-top: auto;
2807
2884
  border-top: 1px solid var(--border);
2808
2885
  padding: 8px;
2809
2886
  flex-shrink: 0;
2887
+ align-items: center;
2810
2888
  }
2811
2889
 
2812
2890
  .project-rail--expanded .project-rail__footer {
2813
- display: flex;
2814
- flex-direction: column;
2815
- gap: 4px;
2891
+ align-items: stretch;
2816
2892
  }
2817
2893
 
2818
2894
  .project-rail__settings-btn {
@@ -2948,6 +3024,17 @@ body { display: flex; }
2948
3024
  overflow: hidden;
2949
3025
  text-overflow: ellipsis;
2950
3026
  color: var(--text);
3027
+ cursor: default;
3028
+ }
3029
+
3030
+ .project-rail__item-name--editing {
3031
+ background: var(--bg);
3032
+ border: 1px solid var(--accent);
3033
+ border-radius: 3px;
3034
+ padding: 0 4px;
3035
+ outline: none;
3036
+ overflow: visible;
3037
+ cursor: text;
2951
3038
  }
2952
3039
 
2953
3040
  .project-rail__item-meta {
@@ -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() {