openclacky 0.9.35 → 0.9.36

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.
@@ -1359,144 +1359,36 @@ body {
1359
1359
 
1360
1360
  /* ── Chat panel ──────────────────────────────────────────────────────────── */
1361
1361
  #chat-panel { flex: 1; display: flex; flex-direction: column; overflow: hidden; position: relative; }
1362
- #chat-header {
1363
- padding: 10px 20px;
1364
- display: flex;
1365
- align-items: center;
1366
- gap: 10px;
1367
- min-height: 44px;
1368
- }
1369
- /* Title block — stacks the name + subtitle vertically so the header has
1370
- two lines of information (like Linear / Arc) without growing wider. */
1371
- #chat-title-block {
1372
- display: flex;
1373
- flex-direction: column;
1374
- min-width: 0; /* allow ellipsis inside */
1375
- flex: 1;
1376
- line-height: 1.25;
1377
- gap: 2px;
1378
- }
1379
- #chat-title {
1380
- font-weight: 600;
1381
- font-size: 15px;
1382
- color: var(--color-text-primary);
1383
- white-space: nowrap;
1384
- overflow: hidden;
1385
- text-overflow: ellipsis;
1386
- }
1387
- #chat-subtitle {
1388
- display: flex;
1389
- align-items: center;
1390
- gap: 6px;
1391
- font-size: 11px;
1392
- color: var(--color-text-secondary);
1393
- opacity: 0.75;
1394
- overflow: hidden;
1395
- white-space: nowrap;
1396
- text-overflow: ellipsis;
1397
- min-width: 0;
1398
- }
1399
- #chat-subtitle .chat-sub-source {
1400
- font-weight: 500;
1401
- color: var(--color-text-secondary);
1402
- flex-shrink: 0;
1403
- }
1404
- #chat-subtitle .chat-sub-dir {
1405
- font-family: var(--font-mono, monospace);
1406
- overflow: hidden;
1407
- text-overflow: ellipsis;
1408
- min-width: 0;
1409
- opacity: 0.85;
1410
- }
1411
- #chat-subtitle .chat-sub-sep {
1412
- opacity: 0.35;
1413
- flex-shrink: 0;
1414
- }
1415
-
1416
- /* Status badge styles - softed design */
1417
- .status-idle,
1418
- .status-running,
1419
- .status-error {
1420
- display: inline-flex;
1421
- align-items: center;
1422
- padding: 2px 8px;
1423
- border-radius: 4px;
1424
- font-size: 11px;
1425
- font-weight: 500;
1426
- text-transform: lowercase;
1427
- }
1428
1362
 
1429
- .status-idle {
1430
- background: var(--color-bg-secondary);
1431
- color: var(--color-text-secondary);
1432
- border: 1px solid var(--color-border-secondary);
1433
- }
1434
-
1435
- [data-theme="light"] .status-idle {
1436
- background: rgba(0, 0, 0, 0.04);
1437
- color: var(--color-text-secondary);
1438
- border: 1px solid rgba(0, 0, 0, 0.08);
1439
- }
1440
-
1441
- .status-running {
1442
- background: rgba(34, 197, 94, 0.1);
1443
- color: var(--color-success);
1444
- border: 1px solid rgba(34, 197, 94, 0.2);
1445
- }
1446
-
1447
- [data-theme="light"] .status-running {
1448
- background: rgba(34, 197, 94, 0.08);
1449
- color: #16a34a;
1450
- border: 1px solid rgba(34, 197, 94, 0.15);
1451
- }
1452
-
1453
- .status-error {
1454
- background: rgba(239, 68, 68, 0.1);
1455
- color: var(--color-error);
1456
- border: 1px solid rgba(239, 68, 68, 0.2);
1457
- }
1458
-
1459
- [data-theme="light"] .status-error {
1460
- background: rgba(239, 68, 68, 0.08);
1461
- color: #dc2626;
1462
- border: 1px solid rgba(239, 68, 68, 0.15);
1463
- }
1464
-
1465
- /* Delete session button in chat header */
1466
- /* Back button in chat header — hidden on desktop, shown on mobile via media query */
1363
+ /* Mobile-only floating back button — replaces the old in-header back button.
1364
+ Hidden on desktop; mobile media query enables it. Positioned absolutely so
1365
+ it doesn't take layout space or add visual chrome on desktop. */
1467
1366
  .chat-back-btn {
1468
1367
  display: none;
1368
+ }
1369
+ .chat-back-floating {
1370
+ position: absolute;
1371
+ top: 8px;
1372
+ left: 8px;
1373
+ z-index: 5;
1469
1374
  align-items: center;
1470
1375
  justify-content: center;
1471
1376
  width: 32px;
1472
1377
  height: 32px;
1473
- background: transparent;
1474
- border: none;
1378
+ background: var(--color-bg-primary);
1379
+ border: 1px solid var(--color-border-secondary);
1475
1380
  border-radius: 6px;
1476
1381
  color: var(--color-text-secondary);
1477
1382
  cursor: pointer;
1478
1383
  padding: 0;
1479
- flex-shrink: 0;
1480
1384
  transition: background .15s, color .15s;
1385
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
1481
1386
  }
1482
- .chat-back-btn:hover { background: var(--color-bg-hover); }
1387
+ .chat-back-floating:hover { background: var(--color-bg-hover); }
1483
1388
 
1484
- .btn-delete-session {
1485
- margin-left: auto;
1486
- display: flex;
1487
- align-items: center;
1488
- justify-content: center;
1489
- width: 28px;
1490
- height: 28px;
1491
- background: transparent;
1492
- border: none;
1493
- border-radius: 6px;
1494
- color: var(--color-text-tertiary);
1495
- cursor: pointer;
1496
- padding: 0;
1497
- transition: background .15s, color .15s;
1498
- }
1499
- .btn-delete-session:hover { background: var(--color-error-bg); color: var(--color-error); }
1389
+ /* Status badges (.status-idle/running/error) used to live in #chat-header.
1390
+ That header has been removed; the bottom #session-info-bar uses its own
1391
+ .sib-status-* classes instead. Legacy .status-* styles removed. */
1500
1392
 
1501
1393
  /* ── Messages ────────────────────────────────────────────────────────────── */
1502
1394
  #messages {
@@ -1508,6 +1400,61 @@ body {
1508
1400
  gap: 12px;
1509
1401
  }
1510
1402
 
1403
+ /* Empty-state hint: shown inside #messages when a session has no messages yet.
1404
+ Designed to be quiet and low-contrast so it guides without distracting. */
1405
+ .chat-empty-hint {
1406
+ margin: auto; /* vertical + horizontal centering inside flex column */
1407
+ max-width: 420px;
1408
+ padding: 24px 16px;
1409
+ display: flex;
1410
+ flex-direction: column;
1411
+ align-items: center;
1412
+ text-align: center;
1413
+ color: var(--color-text-secondary);
1414
+ user-select: none;
1415
+ pointer-events: none; /* purely decorative — don't block clicks/scroll */
1416
+ opacity: 0.85;
1417
+ }
1418
+ .chat-empty-hint .chat-empty-icon {
1419
+ color: var(--color-text-tertiary);
1420
+ margin-bottom: 14px;
1421
+ opacity: 0.55;
1422
+ }
1423
+ .chat-empty-hint .chat-empty-title {
1424
+ color: var(--color-text-primary);
1425
+ font-size: 15px;
1426
+ font-weight: 600;
1427
+ margin-bottom: 4px;
1428
+ }
1429
+ .chat-empty-hint .chat-empty-subtitle {
1430
+ color: var(--color-text-secondary);
1431
+ font-size: 13px;
1432
+ margin-bottom: 18px;
1433
+ opacity: 0.85;
1434
+ }
1435
+ .chat-empty-hint .chat-empty-tips {
1436
+ list-style: none;
1437
+ padding: 0;
1438
+ margin: 0;
1439
+ display: flex;
1440
+ flex-direction: column;
1441
+ gap: 6px;
1442
+ font-size: 12px;
1443
+ color: var(--color-text-tertiary);
1444
+ }
1445
+ .chat-empty-hint .chat-empty-tips li {
1446
+ position: relative;
1447
+ padding-left: 14px;
1448
+ line-height: 1.5;
1449
+ }
1450
+ .chat-empty-hint .chat-empty-tips li::before {
1451
+ content: "·";
1452
+ position: absolute;
1453
+ left: 4px;
1454
+ top: -1px;
1455
+ opacity: 0.6;
1456
+ }
1457
+
1511
1458
  /* New message notification banner */
1512
1459
  .new-message-banner {
1513
1460
  position: absolute;
@@ -5970,31 +5917,6 @@ body.setup-mode[data-theme="dark"] {
5970
5917
  padding: 0 12px;
5971
5918
  }
5972
5919
 
5973
- /* Chat sub-header: tighten padding */
5974
- #chat-header {
5975
- padding: 8px 12px;
5976
- gap: 8px;
5977
- }
5978
-
5979
- /* Truncate long session titles */
5980
- #chat-title {
5981
- font-size: 14px;
5982
- overflow: hidden;
5983
- text-overflow: ellipsis;
5984
- white-space: nowrap;
5985
- min-width: 0;
5986
- flex: 1;
5987
- }
5988
-
5989
- /* Status badge: smaller on mobile */
5990
- .status-idle,
5991
- .status-running,
5992
- .status-error {
5993
- font-size: 10px;
5994
- padding: 1px 6px;
5995
- flex-shrink: 0;
5996
- }
5997
-
5998
5920
  /* Session info bar: single-line, no hover-expand, font smaller */
5999
5921
  #session-info-bar {
6000
5922
  padding: 3px 12px;
@@ -6050,6 +5972,10 @@ body.setup-mode[data-theme="dark"] {
6050
5972
  .chat-back-btn {
6051
5973
  display: flex;
6052
5974
  }
5975
+ /* Reserve top space in messages so floating back button doesn't overlap content */
5976
+ #chat-panel #messages {
5977
+ padding-top: 52px;
5978
+ }
6053
5979
 
6054
5980
  /* Welcome page: vertically centered but shifted up, add horizontal padding */
6055
5981
  #welcome {
@@ -437,9 +437,7 @@ WS.onEvent(ev => {
437
437
  case "session_renamed": {
438
438
  Sessions.patch(ev.session_id, { name: ev.name });
439
439
  Sessions.renderList();
440
- if (ev.session_id === Sessions.activeId) {
441
- $("chat-title").textContent = ev.name;
442
- }
440
+ // Title is now shown only in the sidebar; chat-header element was removed.
443
441
  break;
444
442
  }
445
443
 
@@ -847,7 +845,7 @@ $("sidebar-overlay").addEventListener("click", _closeSidebar);
847
845
  // On mobile: start with sidebar hidden
848
846
  if (_isMobile()) _closeSidebar();
849
847
 
850
- // Mobile back button in chat-header: tap to go back to welcome
848
+ // Mobile floating back button (visible only on mobile via CSS): tap to go back to welcome.
851
849
  const _btnBack = document.querySelector(".chat-back-btn");
852
850
  if (_btnBack) _btnBack.addEventListener("click", () => Router.navigate("welcome"));
853
851
 
@@ -985,9 +983,14 @@ document.addEventListener("change", e => {
985
983
  if ($("theme-toggle-header")) {
986
984
  $("theme-toggle-header").addEventListener("click", () => Theme.toggle());
987
985
  }
988
- $("btn-delete-session").addEventListener("click", () => {
989
- if (Sessions.activeId) Sessions.deleteSession(Sessions.activeId);
990
- });
986
+ // btn-delete-session was removed with chat-header; deletion is now triggered from
987
+ // the bottom session-info-bar actions dropdown (see Sessions._showActionsMenu).
988
+ const _btnDeleteSession = $("btn-delete-session");
989
+ if (_btnDeleteSession) {
990
+ _btnDeleteSession.addEventListener("click", () => {
991
+ if (Sessions.activeId) Sessions.deleteSession(Sessions.activeId);
992
+ });
993
+ }
991
994
 
992
995
  // Load older history when the user scrolls to the top of the message list
993
996
  $("messages").addEventListener("scroll", () => {
@@ -1617,7 +1620,7 @@ window.bootAfterBrand = async function() {
1617
1620
  dropdown.innerHTML = "";
1618
1621
 
1619
1622
  models.forEach(m => {
1620
- console.log("[Model Switcher] Adding model:", m.model, "current:", currentModel);
1623
+ console.log("[Model Switcher] Adding model:", m.model, "id:", m.id, "current:", currentModel);
1621
1624
  const opt = document.createElement("div");
1622
1625
  opt.className = "sib-model-option";
1623
1626
  if (m.model === currentModel) opt.classList.add("current");
@@ -1633,7 +1636,8 @@ window.bootAfterBrand = async function() {
1633
1636
  opt.appendChild(badge);
1634
1637
  }
1635
1638
 
1636
- opt.addEventListener("click", () => _switchModel(sessionId, m.model));
1639
+ // Switch by id (stable across reorders/edits). Keep model name for UI update.
1640
+ opt.addEventListener("click", () => _switchModel(sessionId, m.id, m.model));
1637
1641
  dropdown.appendChild(opt);
1638
1642
  });
1639
1643
  console.log("[Model Switcher] Dropdown populated, children count:", dropdown.children.length);
@@ -1644,7 +1648,9 @@ window.bootAfterBrand = async function() {
1644
1648
  }
1645
1649
 
1646
1650
  // Switch session model via API
1647
- async function _switchModel(sessionId, newModel) {
1651
+ // modelId — stable runtime id (required by backend)
1652
+ // modelName — display name, used for optimistic UI update
1653
+ async function _switchModel(sessionId, modelId, modelName) {
1648
1654
  const dropdown = $("sib-model-dropdown");
1649
1655
  if (dropdown) {
1650
1656
  dropdown.style.display = "none";
@@ -1655,20 +1661,20 @@ window.bootAfterBrand = async function() {
1655
1661
  const res = await fetch(`/api/sessions/${sessionId}/model`, {
1656
1662
  method: "PATCH",
1657
1663
  headers: { "Content-Type": "application/json" },
1658
- body: JSON.stringify({ model: newModel })
1664
+ body: JSON.stringify({ model_id: modelId })
1659
1665
  });
1660
-
1666
+
1661
1667
  const data = await res.json();
1662
-
1668
+
1663
1669
  if (!res.ok) {
1664
1670
  throw new Error(data.error || "Unknown error");
1665
1671
  }
1666
-
1672
+
1667
1673
  // Update UI optimistically (will be confirmed by session_update broadcast)
1668
1674
  const sibModel = $("sib-model");
1669
- if (sibModel) sibModel.textContent = newModel;
1670
-
1671
- console.log(`Switched session ${sessionId} to model ${newModel}`);
1675
+ if (sibModel) sibModel.textContent = modelName;
1676
+
1677
+ console.log(`Switched session ${sessionId} to model ${modelName} (${modelId})`);
1672
1678
  } catch (e) {
1673
1679
  console.error("Failed to switch model:", e);
1674
1680
  alert("Failed to switch model: " + e.message);
@@ -46,6 +46,11 @@ const I18n = (() => {
46
46
  "chat.retry": "Retry",
47
47
  "chat.copy": "Copy",
48
48
  "chat.copied": "Copied",
49
+ "chat.empty.title": "Start the conversation",
50
+ "chat.empty.subtitle": "Ask anything, or use a skill to get going.",
51
+ "chat.empty.tip1": "Type / to browse skills",
52
+ "chat.empty.tip2": "Attach images, PDFs, or docs for context",
53
+ "chat.empty.tip3": "Shift+Enter for a new line",
49
54
 
50
55
  // ── Session list ──
51
56
  "sessions.empty": "No sessions yet",
@@ -422,6 +427,11 @@ const I18n = (() => {
422
427
  "chat.retry": "重试",
423
428
  "chat.copy": "复制",
424
429
  "chat.copied": "已复制",
430
+ "chat.empty.title": "开始新的对话",
431
+ "chat.empty.subtitle": "直接提问,或用一个 Skill 启动。",
432
+ "chat.empty.tip1": "输入 / 浏览 Skill",
433
+ "chat.empty.tip2": "可粘贴或拖入图片、PDF、文档",
434
+ "chat.empty.tip3": "Shift+Enter 换行",
425
435
 
426
436
  // ── Session list ──
427
437
  "sessions.empty": "暂无对话",
@@ -234,24 +234,14 @@
234
234
 
235
235
  <!-- Chat panel -->
236
236
  <div id="chat-panel" style="display:none">
237
- <header id="chat-header">
238
- <!-- Mobile-only back button: shown inside a session -->
239
- <button id="btn-back-mobile" class="chat-back-btn" title="Back">
240
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
241
- <path d="M15 18l-6-6 6-6"/>
242
- </svg>
243
- </button>
244
- <span id="chat-title-block">
245
- <span id="chat-title">Session</span>
246
- <span id="chat-subtitle"></span>
247
- </span>
248
- <span id="chat-status" class="status-idle">idle</span>
249
- <button id="btn-delete-session" class="btn-delete-session" title="Delete session">
250
- <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
251
- <polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/>
252
- </svg>
253
- </button>
254
- </header>
237
+ <!-- Mobile-only floating back button: shown inside a session on small screens.
238
+ Desktop: hidden. All other session info (title, status, dir, delete) is
239
+ provided by the bottom #session-info-bar. -->
240
+ <button id="btn-back-mobile" class="chat-back-btn chat-back-floating" title="Back">
241
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
242
+ <path d="M15 18l-6-6 6-6"/>
243
+ </svg>
244
+ </button>
255
245
  <div id="messages"></div>
256
246
  <!-- New message notification banner -->
257
247
  <div id="new-message-banner" class="new-message-banner" style="display:none">
@@ -183,6 +183,75 @@ const Sessions = (() => {
183
183
  banner.style.display = "none";
184
184
  }
185
185
 
186
+ // ── Empty-state hint ──────────────────────────────────────────────────
187
+ //
188
+ // Shows a small centered hint inside #messages when the message list is
189
+ // empty (e.g. just-created session with no history). Uses a MutationObserver
190
+ // so we don't have to instrument every append/clear call site.
191
+
192
+ const _EMPTY_HINT_ID = "chat-empty-hint";
193
+
194
+ function _buildEmptyHintHtml() {
195
+ const title = I18n.t("chat.empty.title");
196
+ const subtitle = I18n.t("chat.empty.subtitle");
197
+ const tip1 = I18n.t("chat.empty.tip1");
198
+ const tip2 = I18n.t("chat.empty.tip2");
199
+ const tip3 = I18n.t("chat.empty.tip3");
200
+ return `
201
+ <div class="chat-empty-icon" aria-hidden="true">
202
+ <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
203
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
204
+ </svg>
205
+ </div>
206
+ <div class="chat-empty-title">${escapeHtml(title)}</div>
207
+ <div class="chat-empty-subtitle">${escapeHtml(subtitle)}</div>
208
+ <ul class="chat-empty-tips">
209
+ <li>${escapeHtml(tip1)}</li>
210
+ <li>${escapeHtml(tip2)}</li>
211
+ <li>${escapeHtml(tip3)}</li>
212
+ </ul>
213
+ `;
214
+ }
215
+
216
+ function _updateEmptyHint() {
217
+ const messages = $("messages");
218
+ if (!messages) return;
219
+ // Check if there's any real content besides the hint itself
220
+ const hasReal = Array.from(messages.children).some(
221
+ (el) => el.id !== _EMPTY_HINT_ID
222
+ );
223
+ const existing = document.getElementById(_EMPTY_HINT_ID);
224
+ // While history is still loading, don't flash the hint — wait until the
225
+ // first fetch completes so we know whether the session is actually empty.
226
+ const loading = !!(_activeId && _historyState[_activeId] && _historyState[_activeId].loading);
227
+ if (hasReal || loading) {
228
+ if (existing) existing.remove();
229
+ } else {
230
+ if (!existing) {
231
+ const el = document.createElement("div");
232
+ el.id = _EMPTY_HINT_ID;
233
+ el.className = "chat-empty-hint";
234
+ el.innerHTML = _buildEmptyHintHtml();
235
+ messages.appendChild(el);
236
+ }
237
+ }
238
+ }
239
+
240
+ function _initEmptyHint() {
241
+ const messages = $("messages");
242
+ if (!messages) return;
243
+ // Re-evaluate whenever children change (append/insertBefore/innerHTML="")
244
+ const observer = new MutationObserver(() => _updateEmptyHint());
245
+ observer.observe(messages, { childList: true });
246
+ // Re-render hint text on language change
247
+ document.addEventListener("langchange", () => {
248
+ const existing = document.getElementById(_EMPTY_HINT_ID);
249
+ if (existing) existing.innerHTML = _buildEmptyHintHtml();
250
+ });
251
+ // Initial paint
252
+ _updateEmptyHint();
253
+ }
254
+
186
255
  function _initNewMessageBanner() {
187
256
  const banner = $("new-message-banner");
188
257
  const messages = $("messages");
@@ -640,6 +709,9 @@ const Sessions = (() => {
640
709
  }
641
710
  } finally {
642
711
  state.loading = false;
712
+ // After loading finishes, re-evaluate the empty-state hint in case
713
+ // the session is genuinely empty (no events + no existing DOM content).
714
+ if (id === _activeId) _updateEmptyHint();
643
715
  }
644
716
  }
645
717
 
@@ -891,6 +963,7 @@ const Sessions = (() => {
891
963
  // ── Init ──────────────────────────────────────────────────────────────
892
964
  init() {
893
965
  _initNewMessageBanner();
966
+ _initEmptyHint();
894
967
  // Re-render session list (badges/labels) when the user switches language
895
968
  document.addEventListener("langchange", () => Sessions.renderList());
896
969
  // Browsers block file:// navigation from http:// pages. Intercept clicks on
@@ -1260,8 +1333,8 @@ const Sessions = (() => {
1260
1333
  Sessions.patch(sessionId, { name: newName });
1261
1334
  Sessions.renderList();
1262
1335
  if (sessionId === Sessions.activeId) {
1263
- const titleEl = document.getElementById("chat-title");
1264
- if (titleEl) titleEl.textContent = newName;
1336
+ // chat-header was removed — no title element to update here.
1337
+ // Sidebar re-renders with the new name above.
1265
1338
  }
1266
1339
  } else {
1267
1340
  console.error("Rename failed:", await res.text());
@@ -1399,64 +1472,20 @@ const Sessions = (() => {
1399
1472
  },
1400
1473
 
1401
1474
  updateStatusBar(status) {
1402
- $("chat-status").textContent = status || "idle";
1403
- if (status === "running") {
1404
- $("chat-status").className = "status-running";
1405
- } else if (status === "error") {
1406
- $("chat-status").className = "status-error";
1407
- } else {
1408
- $("chat-status").className = "status-idle";
1409
- }
1410
- $("btn-interrupt").style.display = status === "running" ? "" : "none";
1475
+ // chat-header was removed; status text is now shown in the bottom session-info-bar (#sib-status).
1476
+ // Here we only update the interrupt button visibility.
1477
+ const interrupt = $("btn-interrupt");
1478
+ if (interrupt) interrupt.style.display = status === "running" ? "" : "none";
1411
1479
  },
1412
1480
 
1413
1481
  /**
1414
- /**
1415
- * Populate the chat header (title + subtitle) for the given session.
1416
- * Subtitle shows a source tag (Auto / Channel / Setup — no emoji) and
1417
- * the working directory gives the user immediate context for what
1418
- * this session is without glancing down at the status bar.
1482
+ * No-op: the chat header element (#chat-header) was removed. All session
1483
+ * metadata (title, source, working dir, status) is now shown in the
1484
+ * sidebar and the bottom #session-info-bar. Kept as a stub so existing
1485
+ * call sites don't need to be updated.
1419
1486
  */
1420
- updateChatHeader(s) {
1421
- const titleEl = $("chat-title");
1422
- const subEl = $("chat-subtitle");
1423
- if (!titleEl || !subEl) return;
1424
-
1425
- if (!s) {
1426
- titleEl.textContent = "";
1427
- subEl.innerHTML = "";
1428
- subEl.style.display = "none";
1429
- return;
1430
- }
1431
-
1432
- titleEl.textContent = s.name || _relativeTime(s.created_at);
1433
-
1434
- // Build subtitle pieces. No emoji — keeps the UI clean and avoids
1435
- // the "AI-generated" feel that overuse of emoji creates.
1436
- const parts = [];
1437
- let sourceLabel = "";
1438
- if (s.source === "cron") sourceLabel = I18n.t("sessions.badge.cron");
1439
- else if (s.source === "channel") sourceLabel = I18n.t("sessions.badge.channel");
1440
- else if (s.source === "setup") sourceLabel = I18n.t("sessions.badge.setup");
1441
-
1442
- if (sourceLabel) {
1443
- parts.push(
1444
- `<span class="chat-sub-source">${escapeHtml(sourceLabel)}</span>`
1445
- );
1446
- }
1447
- if (s.working_dir) {
1448
- parts.push(
1449
- `<span class="chat-sub-dir" title="${escapeHtml(s.working_dir)}">${escapeHtml(s.working_dir)}</span>`
1450
- );
1451
- }
1452
-
1453
- if (parts.length === 0) {
1454
- subEl.innerHTML = "";
1455
- subEl.style.display = "none";
1456
- } else {
1457
- subEl.innerHTML = parts.join(`<span class="chat-sub-sep">·</span>`);
1458
- subEl.style.display = "";
1459
- }
1487
+ updateChatHeader(_s) {
1488
+ // intentionally empty
1460
1489
  },
1461
1490
 
1462
1491
  /** Update the session info bar below the chat header with current session metadata. */
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openclacky
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.35
4
+ version: 0.9.36
5
5
  platform: ruby
6
6
  authors:
7
7
  - windy