clay-server 2.23.1 → 2.24.0-beta.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.
@@ -1759,4 +1759,101 @@ pre.mermaid-error {
1759
1759
  }
1760
1760
  }
1761
1761
 
1762
+ /* ==========================================================================
1763
+ Wide View Mode
1764
+ ========================================================================== */
1765
+
1766
+ body.wide-view {
1767
+ --content-width: 100%;
1768
+ }
1769
+
1770
+ body.wide-view .msg-user,
1771
+ body.wide-view .msg-assistant {
1772
+ display: flex;
1773
+ flex-direction: row;
1774
+ align-items: flex-start;
1775
+ gap: 8px;
1776
+ max-width: 100%;
1777
+ padding: 4px 16px;
1778
+ margin: 0;
1779
+ border-radius: 0;
1780
+ }
1781
+ body.wide-view .msg-user:hover,
1782
+ body.wide-view .msg-assistant:hover {
1783
+ background: var(--bg-alt);
1784
+ }
1785
+
1786
+ body.wide-view .msg-user {
1787
+ justify-content: flex-start;
1788
+ }
1789
+ body.wide-view .msg-user .dm-bubble-avatar-me {
1790
+ order: -1;
1791
+ }
1792
+ body.wide-view .msg-user .bubble {
1793
+ background: none;
1794
+ border-radius: 0;
1795
+ padding: 0;
1796
+ max-width: 100%;
1797
+ font-size: 15px;
1798
+ line-height: 1.46;
1799
+ white-space: pre-wrap;
1800
+ word-wrap: break-word;
1801
+ }
1802
+ body.wide-view .msg-user .msg-actions {
1803
+ display: none;
1804
+ }
1805
+
1806
+ body.wide-view .msg-assistant .md-content {
1807
+ background: none;
1808
+ border-radius: 0;
1809
+ padding: 0;
1810
+ }
1811
+
1812
+ body.wide-view .thinking-item,
1813
+ body.wide-view .tool-item,
1814
+ body.wide-view .tool-group {
1815
+ max-width: 100%;
1816
+ }
1817
+ body.wide-view .turn-meta {
1818
+ max-width: 100%;
1819
+ }
1762
1820
 
1821
+ /* Mobile: force bubble layout regardless of user setting */
1822
+ @media (max-width: 768px) {
1823
+ body.wide-view { --content-width: var(--content-width, 720px); }
1824
+ body.wide-view .msg-user,
1825
+ body.wide-view .msg-assistant {
1826
+ display: revert;
1827
+ flex-direction: revert;
1828
+ align-items: revert;
1829
+ gap: revert;
1830
+ max-width: revert;
1831
+ padding: revert;
1832
+ margin: revert;
1833
+ border-radius: revert;
1834
+ }
1835
+ body.wide-view .msg-user:hover,
1836
+ body.wide-view .msg-assistant:hover { background: revert; }
1837
+ body.wide-view .msg-user { justify-content: revert; }
1838
+ body.wide-view .msg-user .dm-bubble-avatar-me { order: revert; }
1839
+ body.wide-view .msg-user .bubble {
1840
+ background: revert;
1841
+ border-radius: revert;
1842
+ padding: revert;
1843
+ max-width: revert;
1844
+ font-size: revert;
1845
+ line-height: revert;
1846
+ white-space: revert;
1847
+ word-wrap: revert;
1848
+ }
1849
+ body.wide-view .msg-user .msg-actions { display: revert; }
1850
+ body.wide-view .msg-assistant .md-content {
1851
+ background: revert;
1852
+ border-radius: revert;
1853
+ padding: revert;
1854
+ }
1855
+ body.wide-view .thinking-item,
1856
+ body.wide-view .tool-item,
1857
+ body.wide-view .tool-group { max-width: revert; }
1858
+ body.wide-view .turn-meta { max-width: revert; }
1859
+ }
@@ -140,8 +140,6 @@ button.top-bar-pill.pill-accent:hover { background: color-mix(in srgb, var(--acc
140
140
  position: absolute;
141
141
  inset: 0;
142
142
  background: rgba(0,0,0,0.55);
143
- backdrop-filter: blur(8px);
144
- -webkit-backdrop-filter: blur(8px);
145
143
  }
146
144
 
147
145
  .pwa-modal-card {
@@ -398,8 +396,6 @@ button.top-bar-pill.pill-accent:hover { background: color-mix(in srgb, var(--acc
398
396
  position: absolute;
399
397
  inset: 0;
400
398
  background: rgba(var(--shadow-rgb), 0.5);
401
- backdrop-filter: blur(2px);
402
- -webkit-backdrop-filter: blur(2px);
403
399
  }
404
400
 
405
401
  .confirm-dialog {
@@ -233,6 +233,9 @@
233
233
  padding: 4px 0;
234
234
  margin-bottom: 16px;
235
235
  }
236
+ .settings-card:has(.layout-switcher) {
237
+ padding: 14px 16px;
238
+ }
236
239
 
237
240
  .settings-field {
238
241
  padding: 12px 16px;
@@ -853,3 +856,53 @@
853
856
  outline: none;
854
857
  cursor: pointer;
855
858
  }
859
+
860
+ /* --- Layout switcher (Bubble / Channel) --- */
861
+ .layout-switcher {
862
+ display: grid;
863
+ grid-template-columns: 1fr 1fr;
864
+ gap: 12px;
865
+ padding: 4px 0;
866
+ }
867
+ .layout-option {
868
+ display: flex;
869
+ flex-direction: column;
870
+ align-items: center;
871
+ gap: 8px;
872
+ padding: 22px 16px;
873
+ background: var(--bg);
874
+ border: 2px solid var(--border);
875
+ border-radius: 10px;
876
+ cursor: pointer;
877
+ transition: border-color 0.15s, background 0.15s, transform 0.1s;
878
+ color: var(--text);
879
+ font-family: inherit;
880
+ text-align: center;
881
+ }
882
+ .layout-option:hover {
883
+ border-color: var(--text-dimmer);
884
+ background: var(--bg-alt);
885
+ }
886
+ .layout-option:active {
887
+ transform: scale(0.97);
888
+ }
889
+ .layout-option.selected {
890
+ border-color: var(--accent);
891
+ background: var(--accent-8);
892
+ }
893
+ .layout-option-icon {
894
+ font-size: 28px;
895
+ line-height: 1;
896
+ margin-bottom: 2px;
897
+ }
898
+ .layout-option-label {
899
+ font-weight: 700;
900
+ font-size: 14px;
901
+ color: var(--text);
902
+ }
903
+ .layout-option-desc {
904
+ font-size: 11.5px;
905
+ color: var(--text-muted);
906
+ line-height: 1.45;
907
+ margin-top: 2px;
908
+ }
@@ -26,7 +26,7 @@
26
26
  position: absolute;
27
27
  top: 0;
28
28
  right: 16px;
29
- z-index: 50;
29
+ z-index: 60;
30
30
  animation: sessionSearchSlideDown 0.15s ease-out;
31
31
  }
32
32
 
@@ -1146,8 +1146,6 @@
1146
1146
  inset: 0;
1147
1147
  background: rgba(var(--shadow-rgb), 0.6);
1148
1148
  z-index: 99;
1149
- backdrop-filter: blur(2px);
1150
- -webkit-backdrop-filter: blur(2px);
1151
1149
  }
1152
1150
 
1153
1151
  #sidebar-overlay.visible { display: block; }
@@ -1350,3 +1348,29 @@
1350
1348
  }
1351
1349
  }
1352
1350
 
1351
+ /* --- Skeleton loading placeholders --- */
1352
+ .skeleton-session-group {
1353
+ padding: 8px 12px;
1354
+ }
1355
+ .skeleton-session-label {
1356
+ width: 48px;
1357
+ height: 10px;
1358
+ border-radius: 4px;
1359
+ background: var(--text-muted, #888);
1360
+ margin-bottom: 10px;
1361
+ animation: skeleton-shimmer 1.5s ease-in-out infinite;
1362
+ }
1363
+ .skeleton-session-item {
1364
+ width: 100%;
1365
+ height: 28px;
1366
+ border-radius: 8px;
1367
+ background: var(--text-muted, #888);
1368
+ margin-bottom: 6px;
1369
+ animation: skeleton-shimmer 1.5s ease-in-out infinite;
1370
+ }
1371
+ @keyframes skeleton-shimmer {
1372
+ 0% { opacity: 0.1; }
1373
+ 50% { opacity: 0.18; }
1374
+ 100% { opacity: 0.1; }
1375
+ }
1376
+
@@ -59,9 +59,16 @@
59
59
  <img class="icon-strip-logo" src="icon-banded-76.png" width="38" height="38" alt="Clay">
60
60
  </div>
61
61
  <div class="icon-strip-separator"></div>
62
- <div class="icon-strip-projects" id="icon-strip-projects"></div>
62
+ <div class="icon-strip-projects" id="icon-strip-projects">
63
+ <div class="skeleton-icon-strip-item"></div>
64
+ <div class="skeleton-icon-strip-item"></div>
65
+ </div>
63
66
  <button class="icon-strip-add" id="icon-strip-add" title="Add project"><i data-lucide="plus"></i></button>
64
- <div class="icon-strip-users hidden" id="icon-strip-users"></div>
67
+ <div class="icon-strip-users" id="icon-strip-users">
68
+ <div class="skeleton-icon-strip-user"></div>
69
+ <div class="skeleton-icon-strip-user"></div>
70
+ <div class="skeleton-icon-strip-user"></div>
71
+ </div>
65
72
  <div class="icon-strip-me" id="icon-strip-me"></div>
66
73
  </div>
67
74
 
@@ -179,7 +186,14 @@
179
186
  <div id="project-list"></div>
180
187
  </div>
181
188
  <div id="sidebar-panel-sessions" class="sidebar-panel">
182
- <div id="session-list"></div>
189
+ <div id="session-list">
190
+ <div class="skeleton-session-group">
191
+ <div class="skeleton-session-label"></div>
192
+ <div class="skeleton-session-item"></div>
193
+ <div class="skeleton-session-item"></div>
194
+ <div class="skeleton-session-item"></div>
195
+ </div>
196
+ </div>
183
197
  </div>
184
198
  <div id="sidebar-panel-files" class="sidebar-panel hidden">
185
199
  <div id="file-tree"></div>
@@ -203,7 +217,7 @@
203
217
  <button id="mate-sticky-notes-btn"><i data-lucide="sticky-note"></i> <span>Sticky Notes</span></button>
204
218
  <button id="mate-skills-btn"><i data-lucide="puzzle"></i> <span>Skills</span></button>
205
219
  <button id="mate-scheduler-btn"><i data-lucide="calendar-clock"></i> <span>Scheduled Tasks</span></button>
206
- <button id="mate-debate-btn"><i data-lucide="mic"></i> <span>Start Debate</span></button>
220
+ <button id="mate-debate-btn"><i data-lucide="mic"></i> <span>Debate</span></button>
207
221
  </div>
208
222
  <div id="mate-sidebar-conversations">
209
223
  <div class="mate-sidebar-sessions-header">
@@ -288,6 +302,7 @@
288
302
  <div id="ralph-sticky" class="hidden"></div>
289
303
  <div id="debate-sticky" class="hidden"></div>
290
304
  <div class="status">
305
+ <button id="debate-pdf-btn" class="hidden" title="Export debate as PDF"><i data-lucide="download"></i></button>
291
306
  <button id="find-in-session-btn" title="Search in session (Ctrl+F)"><i data-lucide="search"></i></button>
292
307
  <button id="sticky-notes-toggle-btn" title="Sticky notes"><i data-lucide="sticky-note"></i><span class="sticky-notes-count hidden"></span></button>
293
308
  <button id="terminal-toggle-btn" title="Terminal"><i data-lucide="square-terminal"></i><span id="terminal-count" class="hidden"></span></button>
@@ -399,6 +414,7 @@
399
414
  <button id="attach-image-btn" type="button" aria-label="Attach image" title="Attach image"><i data-lucide="image"></i></button>
400
415
  <button id="stt-btn" type="button" aria-label="Voice input" title="Voice input"><i data-lucide="mic"></i></button>
401
416
  <button id="schedule-btn" type="button" aria-label="Schedule message" title="Schedule message"><i data-lucide="clock"></i></button>
417
+ <button id="ask-mate-btn" type="button" aria-label="Ask Mate"><span class="ask-mate-label">@ Ask Mate</span></button>
402
418
  </div>
403
419
  <div id="input-bottom-right">
404
420
  <div id="config-chip-wrap" class="hidden">
@@ -475,6 +491,7 @@
475
491
  <button class="file-viewer-btn hidden" id="file-viewer-render" title="Toggle rendered view"><i data-lucide="book-open"></i></button>
476
492
  <button class="file-viewer-btn hidden" id="file-viewer-pdf" title="Export PDF"><i data-lucide="file-down"></i></button>
477
493
  <button class="file-viewer-btn hidden" id="file-viewer-history" title="Edit history"><i data-lucide="clock"></i></button>
494
+ <button class="file-viewer-btn" id="file-viewer-refresh" title="Refresh"><i data-lucide="refresh-cw"></i></button>
478
495
  <button class="file-viewer-btn" id="file-viewer-copy" title="Copy contents"><i data-lucide="copy"></i></button>
479
496
  <button class="file-viewer-btn" id="file-viewer-fullscreen" title="Toggle fullscreen"><i data-lucide="maximize-2"></i></button>
480
497
  <button class="file-viewer-btn" id="file-viewer-close" title="Close"><i data-lucide="x"></i></button>
@@ -825,14 +842,34 @@
825
842
  <div class="us-section" data-section="us-appearance">
826
843
  <h2>Appearance</h2>
827
844
  <div class="settings-card">
828
- <label class="settings-toggle-row">
829
- <div>
830
- <span class="settings-label">Light theme</span>
831
- <div class="settings-hint">Use a light color scheme instead of dark.</div>
832
- </div>
833
- <input type="checkbox" id="us-theme-toggle">
834
- <span class="toggle-track"><span class="toggle-thumb"></span></span>
835
- </label>
845
+ <div class="settings-label" style="margin-bottom:10px;">Theme</div>
846
+ <div class="layout-switcher" id="us-theme-switcher">
847
+ <button class="layout-option" data-theme="light">
848
+ <span class="layout-option-icon">☀️</span>
849
+ <span class="layout-option-label">Light</span>
850
+ <span class="layout-option-desc">For people who open curtains. Bright, clean, productive.</span>
851
+ </button>
852
+ <button class="layout-option" data-theme="dark">
853
+ <span class="layout-option-icon">🌙</span>
854
+ <span class="layout-option-label">Dark</span>
855
+ <span class="layout-option-desc">For those who thrive after sunset. Your screen, your cave.</span>
856
+ </button>
857
+ </div>
858
+ </div>
859
+ <div class="settings-card">
860
+ <div class="settings-label" style="margin-bottom:10px;">Chat layout</div>
861
+ <div class="layout-switcher" id="us-layout-switcher">
862
+ <button class="layout-option" data-layout="bubble">
863
+ <span class="layout-option-icon">💬</span>
864
+ <span class="layout-option-label">Bubble</span>
865
+ <span class="layout-option-desc">Centered and quiet. Just you and the AI, no distractions.</span>
866
+ </button>
867
+ <button class="layout-option" data-layout="channel">
868
+ <span class="layout-option-icon">💻</span>
869
+ <span class="layout-option-label">Channel</span>
870
+ <span class="layout-option-desc">Full-width with faces. Feels like your team is right there.</span>
871
+ </button>
872
+ </div>
836
873
  </div>
837
874
  </div>
838
875
 
@@ -1244,7 +1281,10 @@
1244
1281
  <div class="confirm-dialog paste-modal-dialog">
1245
1282
  <div class="paste-modal-header">
1246
1283
  <span class="paste-modal-title">Pasted content</span>
1247
- <button class="paste-modal-close"><i data-lucide="x" style="width:16px;height:16px"></i></button>
1284
+ <div class="paste-modal-actions">
1285
+ <button class="paste-modal-copy" title="Copy to clipboard"><i data-lucide="copy" style="width:15px;height:15px"></i></button>
1286
+ <button class="paste-modal-close"><i data-lucide="x" style="width:16px;height:16px"></i></button>
1287
+ </div>
1248
1288
  </div>
1249
1289
  <pre class="paste-modal-body" id="paste-modal-body"></pre>
1250
1290
  </div>
@@ -1,5 +1,5 @@
1
1
  import { mateAvatarUrl } from './avatar.js';
2
- import { renderMarkdown, highlightCodeBlocks } from './markdown.js';
2
+ import { renderMarkdown, highlightCodeBlocks, buildPrintHtml, getPrintCss } from './markdown.js';
3
3
  import { escapeHtml } from './utils.js';
4
4
  import { iconHtml, refreshIcons } from './icons.js';
5
5
 
@@ -21,6 +21,19 @@ var turnDrainTimer = null;
21
21
  // --- Init ---
22
22
  export function initDebate(_ctx) {
23
23
  ctx = _ctx;
24
+
25
+ var pdfBtn = document.getElementById("debate-pdf-btn");
26
+ if (pdfBtn) {
27
+ pdfBtn.addEventListener("click", function () {
28
+ pdfBtn.disabled = true;
29
+ exportDebateAsPdf().then(function () {
30
+ pdfBtn.disabled = false;
31
+ }).catch(function (err) {
32
+ pdfBtn.disabled = false;
33
+ console.error("Debate PDF export failed:", err);
34
+ });
35
+ });
36
+ }
24
37
  }
25
38
 
26
39
  export function resetDebateState() {
@@ -31,6 +44,7 @@ export function resetDebateState() {
31
44
  flushTurnStream();
32
45
  currentTurnEl = null;
33
46
  currentTurnMateId = null;
47
+ showPdfBtn(false);
34
48
  // Remove preparing indicator if present
35
49
  if (ctx && ctx.messagesEl) {
36
50
  var prep = ctx.messagesEl.querySelector(".debate-preparing-indicator");
@@ -70,6 +84,14 @@ function showDebateInfoFloat(msg) {
70
84
  refreshIcons();
71
85
  }
72
86
 
87
+ function showPdfBtn(visible) {
88
+ var btn = document.getElementById("debate-pdf-btn");
89
+ if (btn) {
90
+ if (visible) btn.classList.remove("hidden");
91
+ else btn.classList.add("hidden");
92
+ }
93
+ }
94
+
73
95
  function hideDebateInfoFloat() {
74
96
  var floatEl = document.getElementById("debate-info-float");
75
97
  if (floatEl) {
@@ -88,6 +110,7 @@ export function handleDebateResumed(msg) {
88
110
 
89
111
  // Show float info panel again if we have it
90
112
  showDebateInfoFloat(msg);
113
+ showPdfBtn(true);
91
114
  }
92
115
 
93
116
  export function handleDebatePreparing(msg) {
@@ -134,6 +157,7 @@ export function handleDebateStarted(msg) {
134
157
 
135
158
  // Show float info panel
136
159
  showDebateInfoFloat(msg);
160
+ showPdfBtn(true);
137
161
 
138
162
  if (ctx.scrollToBottom) ctx.scrollToBottom();
139
163
  }
@@ -393,6 +417,20 @@ function renderEndedBanner(entry) {
393
417
  });
394
418
  resumeRow.appendChild(resumeBtn);
395
419
 
420
+ var pdfBtn = document.createElement("button");
421
+ pdfBtn.className = "debate-ended-resume-btn debate-ended-pdf-btn";
422
+ pdfBtn.innerHTML = iconHtml("download") + " PDF";
423
+ pdfBtn.addEventListener("click", function () {
424
+ pdfBtn.disabled = true;
425
+ exportDebateAsPdf().then(function () {
426
+ pdfBtn.disabled = false;
427
+ }).catch(function (err) {
428
+ pdfBtn.disabled = false;
429
+ console.error("Debate PDF export failed:", err);
430
+ });
431
+ });
432
+ resumeRow.appendChild(pdfBtn);
433
+
396
434
  endBanner.appendChild(resumeRow);
397
435
 
398
436
  // Enter in textarea = resume
@@ -407,6 +445,125 @@ function renderEndedBanner(entry) {
407
445
  refreshIcons();
408
446
  }
409
447
 
448
+ function exportDebateAsPdf() {
449
+ var popup = window.open("", "_blank", "width=900,height=700");
450
+ if (!popup) {
451
+ return Promise.resolve();
452
+ }
453
+
454
+ popup.document.write("<!DOCTYPE html><html><head><title>Preparing PDF\u2026</title></head><body><p style=\"font-family:sans-serif;padding:32px;color:#555\">Preparing PDF, please wait\u2026</p></body></html>");
455
+ popup.document.close();
456
+
457
+ // Build debate content HTML from DOM
458
+ var contentParts = [];
459
+ var topic = debateTopic || "Debate";
460
+
461
+ // Title
462
+ contentParts.push("<h1>" + escapeHtml(topic) + "</h1>");
463
+
464
+ // Metadata from info float
465
+ var floatEl = document.getElementById("debate-info-float");
466
+ if (floatEl) {
467
+ var modEl = floatEl.querySelector(".debate-info-mod");
468
+ var chips = floatEl.querySelectorAll(".debate-info-chip");
469
+ var metaParts = [];
470
+ if (modEl) metaParts.push("<strong>Moderator:</strong> " + escapeHtml(modEl.textContent.trim()));
471
+ if (chips.length > 0) {
472
+ var panelNames = [];
473
+ for (var c = 0; c < chips.length; c++) {
474
+ panelNames.push(escapeHtml(chips[c].textContent.trim()));
475
+ }
476
+ metaParts.push("<strong>Panel:</strong> " + panelNames.join(", "));
477
+ }
478
+ if (metaParts.length > 0) {
479
+ contentParts.push('<p class="debate-pdf-meta">' + metaParts.join(" &nbsp;|&nbsp; ") + "</p>");
480
+ }
481
+ }
482
+
483
+ contentParts.push("<hr>");
484
+
485
+ // Collect turns and user comments from DOM
486
+ var els = ctx.messagesEl.querySelectorAll(".debate-turn, .debate-user-comment");
487
+ for (var i = 0; i < els.length; i++) {
488
+ var el = els[i];
489
+ if (el.classList.contains("debate-turn")) {
490
+ var nameEl = el.querySelector(".debate-speaker-name");
491
+ var roleEl = el.querySelector(".debate-speaker-role");
492
+ var avatarEl = el.querySelector(".debate-speaker-avatar");
493
+ var contentEl = el.querySelector(".debate-turn-content");
494
+ var speaker = nameEl ? nameEl.textContent.trim() : "Speaker";
495
+ var role = roleEl ? roleEl.textContent.trim() : "";
496
+ var avatarHtml = "";
497
+ if (avatarEl && avatarEl.src) {
498
+ avatarHtml = '<img class="debate-pdf-avatar" src="' + avatarEl.src + '" width="22" height="22" /> ';
499
+ }
500
+ var heading = avatarHtml + escapeHtml(speaker);
501
+ if (role) heading += ' <span class="debate-pdf-role">(' + escapeHtml(role) + ')</span>';
502
+ contentParts.push('<h2 class="debate-pdf-speaker">' + heading + "</h2>");
503
+ if (contentEl) {
504
+ var clone = contentEl.cloneNode(true);
505
+ // Remove copy buttons from code blocks
506
+ var copyBtns = clone.querySelectorAll(".code-copy-btn");
507
+ for (var k = 0; k < copyBtns.length; k++) copyBtns[k].remove();
508
+ contentParts.push(clone.innerHTML);
509
+ }
510
+ } else if (el.classList.contains("debate-user-comment")) {
511
+ var textEl = el.querySelector(".debate-comment-text");
512
+ var commentText = textEl ? escapeHtml(textEl.textContent.trim()) : "";
513
+ if (commentText) {
514
+ contentParts.push('<blockquote><strong>You:</strong> ' + commentText + '</blockquote>');
515
+ }
516
+ }
517
+ }
518
+
519
+ var contentHtml = contentParts.join("\n");
520
+ var baseCss = getPrintCss();
521
+ var debateCss = [
522
+ ".debate-pdf-meta { color: #6b6860; font-size: 10pt; margin: 4pt 0 8pt; }",
523
+ ".debate-pdf-speaker { display: flex; align-items: center; gap: 6pt; }",
524
+ ".debate-pdf-avatar { width: 22pt; height: 22pt; border-radius: 50%; flex-shrink: 0; }",
525
+ ".debate-pdf-role { color: #6b6860; font-weight: 400; font-size: 11pt; }",
526
+ ].join("\n");
527
+
528
+ var fullHtml = "<!DOCTYPE html>\n" +
529
+ "<html lang=\"ko\"><head>\n" +
530
+ "<meta charset=\"UTF-8\">\n" +
531
+ "<title>" + escapeHtml(topic) + "</title>\n" +
532
+ "<link rel=\"preconnect\" href=\"https://cdn.jsdelivr.net\">\n" +
533
+ "<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css\">\n" +
534
+ "<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n" +
535
+ "<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n" +
536
+ "<link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,400;0,500;1,400&display=swap\">\n" +
537
+ "<style>\n" + baseCss + "\n" + debateCss + "\n</style>\n" +
538
+ "</head><body>\n" +
539
+ "<div class=\"file-viewer-markdown\">" + contentHtml + "</div>\n" +
540
+ "</body></html>";
541
+
542
+ return new Promise(function (resolve, reject) {
543
+ try {
544
+ popup.document.open();
545
+ popup.document.write(fullHtml);
546
+ popup.document.close();
547
+
548
+ popup.onload = function () {
549
+ popup.document.fonts.ready.then(function () {
550
+ popup.focus();
551
+ popup.print();
552
+ if (typeof popup.onafterprint !== "undefined") {
553
+ popup.onafterprint = function () { popup.close(); };
554
+ } else {
555
+ setTimeout(function () { popup.close(); }, 1000);
556
+ }
557
+ resolve();
558
+ });
559
+ };
560
+ } catch (err) {
561
+ popup.close();
562
+ reject(err);
563
+ }
564
+ });
565
+ }
566
+
410
567
  export function handleDebateError(msg) {
411
568
  if (ctx.messagesEl && debateActive) {
412
569
  var errEl = document.createElement("div");
@@ -91,6 +91,17 @@ export function initFileBrowser(_ctx) {
91
91
  }
92
92
  });
93
93
 
94
+ // File viewer refresh button
95
+ var viewerRefreshBtn = document.getElementById("file-viewer-refresh");
96
+ if (viewerRefreshBtn) {
97
+ viewerRefreshBtn.addEventListener("click", function () {
98
+ if (!currentFilePath) return;
99
+ viewerRefreshBtn.classList.add("spinning");
100
+ setTimeout(function () { viewerRefreshBtn.classList.remove("spinning"); }, 500);
101
+ requestFileContent(currentFilePath);
102
+ });
103
+ }
104
+
94
105
  // Refresh button
95
106
  var refreshBtn = document.getElementById("file-panel-refresh");
96
107
  if (refreshBtn) {
@@ -1,6 +1,6 @@
1
1
  import { iconHtml, refreshIcons } from './icons.js';
2
2
  import { setRewindMode, isRewindMode } from './rewind.js';
3
- import { checkForMention, showMentionMenu, hideMentionMenu, isMentionMenuVisible, mentionMenuKeydown, setMentionAtIdx, parseMentionFromInput, clearMentionState, sendMention, renderMentionUser, removeMentionChip } from './mention.js';
3
+ import { checkForMention, showMentionMenu, hideMentionMenu, isMentionMenuVisible, mentionMenuKeydown, setMentionAtIdx, parseMentionFromInput, clearMentionState, stickyReapplyMention, sendMention, renderMentionUser, removeMentionChip } from './mention.js';
4
4
 
5
5
  var ctx;
6
6
 
@@ -151,8 +151,8 @@ export function sendMessage() {
151
151
  // Render user message with mention chip (same as history replay)
152
152
  renderMentionUser({ mateName: mention.mateName, text: mentionText, images: mentionImages.length > 0 ? mentionImages : null, pastes: mentionPastes.length > 0 ? mentionPastes : null });
153
153
  sendMention(mention.mateId, mentionText, mentionPastes, mentionImages);
154
- clearMentionState();
155
154
  ctx.inputEl.value = "";
155
+ stickyReapplyMention();
156
156
  sendInputSync();
157
157
  clearPendingImages();
158
158
  autoResize();
@@ -726,6 +726,24 @@ export function initInput(_ctx) {
726
726
  });
727
727
  }
728
728
 
729
+ // Ask Mate button — insert @ to trigger mention menu
730
+ var askMateBtn = document.getElementById("ask-mate-btn");
731
+ if (askMateBtn) {
732
+ askMateBtn.addEventListener("click", function () {
733
+ var inputEl = document.getElementById("input");
734
+ if (!inputEl) return;
735
+ inputEl.focus();
736
+ // Insert @ at cursor position
737
+ var start = inputEl.selectionStart || 0;
738
+ var end = inputEl.selectionEnd || 0;
739
+ var val = inputEl.value;
740
+ inputEl.value = val.substring(0, start) + "@" + val.substring(end);
741
+ inputEl.selectionStart = inputEl.selectionEnd = start + 1;
742
+ // Trigger the mention detection
743
+ inputEl.dispatchEvent(new Event("input", { bubbles: true }));
744
+ });
745
+ }
746
+
729
747
  // Paste handler
730
748
  document.addEventListener("paste", function (e) {
731
749
  // Don't intercept paste when typing in modals or other non-chat inputs
@@ -293,7 +293,7 @@ export function exportMarkdownAsPdf(markdownEl, filename) {
293
293
  });
294
294
  }
295
295
 
296
- function buildPrintHtml(title, contentHtml) {
296
+ export function buildPrintHtml(title, contentHtml) {
297
297
  return "<!DOCTYPE html>\n" +
298
298
  "<html lang=\"ko\"><head>\n" +
299
299
  "<meta charset=\"UTF-8\">\n" +
@@ -309,7 +309,7 @@ function buildPrintHtml(title, contentHtml) {
309
309
  "</body></html>";
310
310
  }
311
311
 
312
- function getPrintCss() {
312
+ export function getPrintCss() {
313
313
  return [
314
314
  /* MS Word defaults: 2.54cm (1in) margins, 11pt, 115% line-height, 8pt after para */
315
315
  "@page { margin: 2.54cm; }",