clay-server 2.8.0 → 2.8.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.
package/bin/cli.js CHANGED
@@ -121,7 +121,7 @@ for (var i = 0; i < args.length; i++) {
121
121
  console.log(" --list List all registered projects");
122
122
  console.log(" --headless Start daemon and exit immediately (implies --yes)");
123
123
  console.log(" --dangerously-skip-permissions");
124
- console.log(" Bypass all permission prompts (requires --pin)");
124
+ console.log(" Bypass all permission prompts");
125
125
  process.exit(0);
126
126
  }
127
127
  }
@@ -1243,7 +1243,30 @@ function setup(callback) {
1243
1243
  port = p;
1244
1244
  log(sym.bar);
1245
1245
 
1246
- promptPin(function (pin) {
1246
+ function askPin() {
1247
+ promptPin(function (pin) {
1248
+ if (dangerouslySkipPermissions && !pin) {
1249
+ log(sym.bar);
1250
+ log(sym.warn + " " + a.yellow + "WARNING: No PIN + skip permissions = anyone with the URL" + a.reset);
1251
+ log(sym.bar + " " + a.yellow + "can execute any command without approval." + a.reset);
1252
+ log(sym.bar);
1253
+ promptToggle("Continue without PIN?", null, false, function (confirmed) {
1254
+ if (!confirmed) {
1255
+ clearUp(6);
1256
+ log(sym.done + " PIN protection " + a.dim + "·" + a.reset + " " + a.yellow + "Required for skip permissions" + a.reset);
1257
+ log(sym.bar);
1258
+ askPin();
1259
+ return;
1260
+ }
1261
+ afterPin(pin);
1262
+ });
1263
+ } else {
1264
+ afterPin(pin);
1265
+ }
1266
+ });
1267
+ }
1268
+
1269
+ function afterPin(pin) {
1247
1270
  if (process.platform === "darwin") {
1248
1271
  promptToggle("Keep awake", "Prevent system sleep while relay is running", false, function (keepAwake) {
1249
1272
  callback(pin, keepAwake);
@@ -1251,7 +1274,9 @@ function setup(callback) {
1251
1274
  } else {
1252
1275
  callback(pin, false);
1253
1276
  }
1254
- });
1277
+ }
1278
+
1279
+ askPin();
1255
1280
  });
1256
1281
  });
1257
1282
  }
@@ -2398,15 +2423,10 @@ var currentVersion = require("../package.json").version;
2398
2423
  // No daemon running — first-time setup
2399
2424
  if (autoYes) {
2400
2425
  var pin = cliPin || null;
2401
- if (dangerouslySkipPermissions && !pin) {
2402
- console.error(" " + sym.warn + " " + a.red + "--dangerously-skip-permissions requires --pin <pin>" + a.reset);
2403
- process.exit(1);
2404
- return;
2405
- }
2406
2426
  console.log(" " + sym.done + " Auto-accepted disclaimer");
2407
2427
  console.log(" " + sym.done + " PIN: " + (pin ? "Enabled" : "Skipped"));
2408
2428
  if (dangerouslySkipPermissions) {
2409
- console.log(" " + sym.warn + " " + a.yellow + "Skip permissions mode enabled" + a.reset);
2429
+ console.log(" " + sym.warn + " " + a.yellow + "Skip permissions mode enabled" + (pin ? "" : " (no PIN)") + a.reset);
2410
2430
  }
2411
2431
  var autoRc = loadClayrc();
2412
2432
  var autoRestorable = (autoRc.recentProjects || []).filter(function (p) {
@@ -2423,13 +2443,6 @@ var currentVersion = require("../package.json").version;
2423
2443
  await forkDaemon(pin, false, autoRestorable.length > 0 ? autoRestorable : undefined, addCwd);
2424
2444
  } else {
2425
2445
  setup(function (pin, keepAwake) {
2426
- if (dangerouslySkipPermissions && !pin) {
2427
- log(sym.warn + " " + a.red + "--dangerously-skip-permissions requires a PIN." + a.reset);
2428
- log(a.dim + " Please set a PIN to use skip permissions mode." + a.reset);
2429
- process.exit(1);
2430
- return;
2431
- }
2432
-
2433
2446
  // Check ~/.clayrc for previous projects to restore
2434
2447
  var rc = loadClayrc();
2435
2448
  var restorable = (rc.recentProjects || []).filter(function (p) {
package/lib/public/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { showToast, copyToClipboard, escapeHtml } from './modules/utils.js';
2
2
  import { refreshIcons, iconHtml, randomThinkingVerb } from './modules/icons.js';
3
- import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, closeMermaidModal, parseEmojis } from './modules/markdown.js';
3
+ import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, closeMermaidModal } from './modules/markdown.js';
4
4
  import { initSidebar, renderSessionList, handleSearchResults, updatePageTitle, getActiveSearchQuery, buildSearchTimeline, removeSearchTimeline, populateCliSessionList, renderIconStrip, initIconStrip, getEmojiCategories } from './modules/sidebar.js';
5
5
  import { initRewind, setRewindMode, showRewindModal, clearPendingRewindUuid, addRewindButton } from './modules/rewind.js';
6
6
  import { initNotifications, showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './modules/notifications.js';
@@ -181,7 +181,7 @@ import { initProfile } from './modules/profile.js';
181
181
  }
182
182
  // Normal update (no animation)
183
183
  greetEl.textContent = getGreeting();
184
- parseEmojis(greetEl);
184
+
185
185
  applyWeatherTooltip(greetEl);
186
186
  }
187
187
 
@@ -213,13 +213,13 @@ import { initProfile } from './modules/profile.js';
213
213
  if (step < totalSteps) {
214
214
  var idx = (startIdx + step) % SLOT_EMOJIS.length;
215
215
  greetEl.textContent = prefix + " " + SLOT_EMOJIS[idx];
216
- parseEmojis(greetEl);
216
+
217
217
  step++;
218
218
  setTimeout(tick, intervals[step - 1]);
219
219
  } else {
220
220
  // Final: land on actual weather
221
221
  greetEl.textContent = prefix + " " + weatherEmoji;
222
- parseEmojis(greetEl);
222
+
223
223
  applyWeatherTooltip(greetEl);
224
224
  }
225
225
  }
@@ -366,7 +366,7 @@ import { initProfile } from './modules/profile.js';
366
366
  })(projects[p]);
367
367
  }
368
368
  // Render emoji icons
369
- parseEmojis(projectsList);
369
+
370
370
  }
371
371
 
372
372
  // --- Week strip ---
@@ -451,7 +451,7 @@ import { initProfile } from './modules/profile.js';
451
451
  pbGrid.appendChild(card);
452
452
  })(pbs[pi]);
453
453
  }
454
- parseEmojis(pbGrid);
454
+
455
455
  }
456
456
  }
457
457
 
@@ -498,7 +498,7 @@ import { initProfile } from './modules/profile.js';
498
498
  }
499
499
 
500
500
  // Render twemoji for all emoji in the hub
501
- parseEmojis(homeHub);
501
+
502
502
  }
503
503
 
504
504
  function handleHubSchedules(msg) {
@@ -617,7 +617,7 @@ import { initProfile } from './modules/profile.js';
617
617
  var pIcon = cachedProjects[pi].icon || null;
618
618
  if (pIcon) {
619
619
  tbIcon.textContent = pIcon;
620
- parseEmojis(tbIcon);
620
+
621
621
  tbIcon.classList.add("has-icon");
622
622
  try { localStorage.setItem("clay-project-icon-" + (currentSlug || "default"), pIcon); } catch (e) {}
623
623
  } else {
@@ -736,7 +736,7 @@ import { initProfile } from './modules/profile.js';
736
736
  var _tbi = $("title-bar-project-icon");
737
737
  if (_tbi) {
738
738
  _tbi.textContent = _cachedProjectIcon;
739
- parseEmojis(_tbi);
739
+
740
740
  _tbi.classList.add("has-icon");
741
741
  }
742
742
  }
@@ -1848,7 +1848,7 @@ import { initProfile } from './modules/profile.js';
1848
1848
  bubble.appendChild(textEl);
1849
1849
  }
1850
1850
 
1851
- parseEmojis(bubble);
1851
+
1852
1852
  div.appendChild(bubble);
1853
1853
 
1854
1854
  // Action bar below bubble (icons visible on hover)
@@ -1975,7 +1975,6 @@ import { initProfile } from './modules/profile.js';
1975
1975
  if (highlightTimer) clearTimeout(highlightTimer);
1976
1976
  highlightTimer = setTimeout(function () {
1977
1977
  highlightCodeBlocks(contentEl);
1978
- parseEmojis(contentEl);
1979
1978
  }, 150);
1980
1979
 
1981
1980
  scrollToBottom();
@@ -1996,7 +1995,6 @@ import { initProfile } from './modules/profile.js';
1996
1995
  if (contentEl) {
1997
1996
  contentEl.innerHTML = renderMarkdown(currentFullText);
1998
1997
  highlightCodeBlocks(contentEl);
1999
- parseEmojis(contentEl);
2000
1998
  }
2001
1999
  }
2002
2000
  }
@@ -2008,7 +2006,6 @@ import { initProfile } from './modules/profile.js';
2008
2006
  if (contentEl) {
2009
2007
  highlightCodeBlocks(contentEl);
2010
2008
  renderMermaidBlocks(contentEl);
2011
- parseEmojis(contentEl);
2012
2009
  }
2013
2010
  if (currentFullText) {
2014
2011
  addCopyHandler(currentMsgEl, currentFullText);
@@ -58,6 +58,13 @@
58
58
  font-display: swap;
59
59
  }
60
60
 
61
+ /* --- Twemoji COLR Font (cross-platform consistent emoji via font, no JS parsing) --- */
62
+ @font-face {
63
+ font-family: 'Twemoji';
64
+ src: url('https://cdn.jsdelivr.net/npm/twemoji-colr-font@15.0.3/twemoji.woff2') format('woff2');
65
+ font-display: swap;
66
+ }
67
+
61
68
  /* --- Reset & Variables --- */
62
69
  * {
63
70
  margin: 0;
@@ -159,7 +166,7 @@ html, body {
159
166
  height: 100%;
160
167
  background: var(--bg);
161
168
  color: var(--text);
162
- font-family: "Pretendard", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
169
+ font-family: "Pretendard", "Twemoji", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
163
170
  font-size: 15px;
164
171
  line-height: 1.6;
165
172
  overflow: hidden;
@@ -12,7 +12,7 @@
12
12
  <title>Clay</title>
13
13
  <link rel="preconnect" href="https://fonts.googleapis.com">
14
14
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
15
- <link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500&family=Nunito:wght@700;800&display=swap" rel="stylesheet">
15
+ <link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500;700&family=Nunito:wght@700;800&display=swap" rel="stylesheet">
16
16
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5/css/xterm.min.css">
17
17
  <script>
18
18
  (function(){try{var k="clay-theme-vars",v=localStorage.getItem(k),r=document.documentElement;if(v){var o=JSON.parse(v),p;for(p in o)r.style.setProperty(p,o[p]);var vt=localStorage.getItem(k.replace("-vars","-variant"));if(vt==="light"){r.classList.add("light-theme");r.classList.remove("dark-theme")}else{r.classList.add("dark-theme");r.classList.remove("light-theme")}var m=document.querySelector('meta[name="theme-color"]');if(m&&o["--bg"])m.setAttribute("content",o["--bg"])}else{var sl=window.matchMedia&&window.matchMedia("(prefers-color-scheme: light)").matches;if(sl){r.classList.add("light-theme");r.classList.remove("dark-theme")}}}catch(e){}})();
@@ -219,7 +219,7 @@
219
219
  <div id="app">
220
220
  <div id="connect-overlay">
221
221
  <canvas id="ascii-logo-canvas"></canvas>
222
- <p id="connect-overlay-msg">Server offline. Will reconnect automatically.</p>
222
+ <p id="connect-overlay-msg">Reconnecting to server…</p>
223
223
  </div>
224
224
  <div id="sticky-notes-container" class="hidden"></div>
225
225
  <div id="messages"></div>
@@ -1361,7 +1361,6 @@
1361
1361
  <script src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js"></script>
1362
1362
  <script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5/lib/xterm.min.js"></script>
1363
1363
  <script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0/lib/addon-fit.min.js"></script>
1364
- <script src="https://cdn.jsdelivr.net/npm/twemoji@14.0.2/dist/twemoji.min.js"></script>
1365
1364
  <script type="module" src="app.js"></script>
1366
1365
  </body>
1367
1366
  </html>
@@ -13,7 +13,7 @@ var ASCII_LINES = [
13
13
  " _______\\/////////__\\///////////////__\\///________\\///________\\///_______",
14
14
  ];
15
15
 
16
- // Tri-accent gradient stops: Green → Indigo → Terracotta
16
+ // Tri-accent gradient stops: Green → Indigo → Terracotta (matches CLI)
17
17
  var GRADIENT_STOPS = [
18
18
  [9, 229, 163],
19
19
  [88, 87, 252],
@@ -42,6 +42,7 @@ var offsetY = 0;
42
42
  var centerX = 0;
43
43
  var centerY = 0;
44
44
  var maxCol = 0;
45
+ var glyphCache = {}; // key: char → offscreen canvas (white glyph)
45
46
  var running = false;
46
47
 
47
48
  function getGradientColor(row, totalRows) {
@@ -61,9 +62,10 @@ function getGradientColor(row, totalRows) {
61
62
  return [r, g, b];
62
63
  }
63
64
 
64
- function easeOutElastic(t) {
65
- if (t === 0 || t === 1) return t;
66
- return Math.pow(2, -10 * t) * Math.sin((t - 0.1) * 5 * Math.PI) + 1;
65
+ function easeOutBack(t) {
66
+ var s = 1.4;
67
+ var t1 = t - 1;
68
+ return t1 * t1 * ((s + 1) * t1 + s) + 1;
67
69
  }
68
70
 
69
71
  function easeOutCubic(t) {
@@ -82,7 +84,7 @@ function buildParticles() {
82
84
  var line = ASCII_LINES[row];
83
85
  for (var col = 0; col < line.length; col++) {
84
86
  var ch = line[col];
85
- if (ch === " " || ch === "_") continue;
87
+ if (ch === " ") continue;
86
88
  if (col > maxCol) maxCol = col;
87
89
  var gc = getGradientColor(row, ASCII_LINES.length);
88
90
  particles.push({
@@ -101,9 +103,9 @@ function buildParticles() {
101
103
  finalR: gc[0],
102
104
  finalG: gc[1],
103
105
  finalB: gc[2],
104
- r: 160,
105
- g: 160,
106
- b: 160,
106
+ r: 220,
107
+ g: 220,
108
+ b: 220,
107
109
  scatterX: 0,
108
110
  scatterY: 0,
109
111
  });
@@ -133,12 +135,12 @@ function computeLayout() {
133
135
  }
134
136
 
135
137
  // Responsive font size
136
- fontSize = Math.floor(w / maxLen * 1.05);
137
- fontSize = Math.max(6, Math.min(fontSize, 20));
138
+ fontSize = Math.floor(w / maxLen * 1.3);
139
+ fontSize = Math.max(8, Math.min(fontSize, 28));
138
140
 
139
- ctx.font = fontSize + "px Menlo, Monaco, Consolas, 'Courier New', monospace";
141
+ ctx.font = "bold " + fontSize + "px 'Roboto Mono', Menlo, Monaco, Consolas, monospace";
140
142
  charWidth = ctx.measureText("M").width;
141
- lineHeight = fontSize * 1.5;
143
+ lineHeight = fontSize * 1.2;
142
144
 
143
145
  var totalW = maxLen * charWidth;
144
146
  var totalH = ASCII_LINES.length * lineHeight;
@@ -153,6 +155,30 @@ function computeLayout() {
153
155
  p.targetX = offsetX + p.col * charWidth;
154
156
  p.targetY = offsetY + p.row * lineHeight + lineHeight * 0.8;
155
157
  }
158
+
159
+ // Build glyph cache — pre-render each unique char as white on offscreen canvas
160
+ glyphCache = {};
161
+ var dpr2 = window.devicePixelRatio || 1;
162
+ var pad = Math.ceil(fontSize * 0.3);
163
+ var gw = Math.ceil(charWidth * dpr2) + pad * 2;
164
+ var gh = Math.ceil(fontSize * 1.6 * dpr2) + pad * 2;
165
+ var fontStr = "bold " + fontSize + "px 'Roboto Mono', Menlo, Monaco, Consolas, monospace";
166
+ var chars = {};
167
+ for (var i = 0; i < particles.length; i++) chars[particles[i].char] = 1;
168
+ var keys = Object.keys(chars);
169
+ for (var i = 0; i < keys.length; i++) {
170
+ var ch = keys[i];
171
+ var oc = document.createElement("canvas");
172
+ oc.width = gw;
173
+ oc.height = gh;
174
+ var ox = oc.getContext("2d");
175
+ ox.scale(dpr2, dpr2);
176
+ ox.font = fontStr;
177
+ ox.textBaseline = "alphabetic";
178
+ ox.fillStyle = "#fff";
179
+ ox.fillText(ch, pad / dpr2, (gh - pad) / dpr2);
180
+ glyphCache[ch] = oc;
181
+ }
156
182
  }
157
183
 
158
184
  function randomScatter(p) {
@@ -167,9 +193,9 @@ function randomScatter(p) {
167
193
  p.vx = 0;
168
194
  p.vy = 0;
169
195
  p.vr = 0;
170
- p.r = 160;
171
- p.g = 160;
172
- p.b = 160;
196
+ p.r = 220;
197
+ p.g = 220;
198
+ p.b = 220;
173
199
  }
174
200
 
175
201
  function setPhase(newPhase) {
@@ -212,7 +238,7 @@ function updateParticles(dt) {
212
238
  var delay = (p.col / (maxCol || 1)) * 0.6;
213
239
  var localT = Math.max(0, phaseTime - delay) / (PHASE_SCATTER_IN - 0.6);
214
240
  localT = Math.min(localT, 1);
215
- var ease = easeOutElastic(localT);
241
+ var ease = easeOutBack(localT);
216
242
 
217
243
  p.x = lerp(p.scatterX, p.targetX, ease);
218
244
  p.y = lerp(p.scatterY, p.targetY, ease);
@@ -238,9 +264,9 @@ function updateParticles(dt) {
238
264
  var localT = (sweepProgress - colNorm * 0.6) / 0.4;
239
265
  localT = Math.max(0, Math.min(1, localT));
240
266
  var ease = easeOutCubic(localT);
241
- p.r = Math.round(lerp(160, p.finalR, ease));
242
- p.g = Math.round(lerp(160, p.finalG, ease));
243
- p.b = Math.round(lerp(160, p.finalB, ease));
267
+ p.r = Math.round(lerp(220, p.finalR, ease));
268
+ p.g = Math.round(lerp(220, p.finalG, ease));
269
+ p.b = Math.round(lerp(220, p.finalB, ease));
244
270
  }
245
271
  if (phaseTime >= PHASE_COLOR_SWEEP) {
246
272
  for (var i = 0; i < particles.length; i++) {
@@ -252,10 +278,10 @@ function updateParticles(dt) {
252
278
  setPhase("hold");
253
279
  }
254
280
  } else if (phase === "hold") {
255
- var breath = Math.sin(phaseTime * Math.PI * 0.8) * 0.08;
281
+ var breath = Math.sin(phaseTime * Math.PI * 0.8) * 0.04;
256
282
  for (var i = 0; i < particles.length; i++) {
257
283
  var p = particles[i];
258
- p.opacity = 0.92 + breath;
284
+ p.opacity = 0.96 + breath;
259
285
  // Subtle micro-jitter
260
286
  p.x = p.targetX + Math.sin(phaseTime * 2.3 + i * 0.7) * 0.4;
261
287
  p.y = p.targetY + Math.cos(phaseTime * 1.9 + i * 0.5) * 0.3;
@@ -297,7 +323,7 @@ function updateParticles(dt) {
297
323
  var delay = ((maxCol - p.col) / (maxCol || 1)) * 0.5;
298
324
  var localT = Math.max(0, phaseTime - delay) / (PHASE_REASSEMBLE - 0.5);
299
325
  localT = Math.min(localT, 1);
300
- var ease = easeOutElastic(localT);
326
+ var ease = easeOutBack(localT);
301
327
 
302
328
  p.x = lerp(p.scatterX, p.targetX, ease);
303
329
  p.y = lerp(p.scatterY, p.targetY, ease);
@@ -319,29 +345,56 @@ function updateParticles(dt) {
319
345
 
320
346
  function drawFrame() {
321
347
  if (!ctx) return;
322
- var w = canvas.width / (window.devicePixelRatio || 1);
323
- var h = canvas.height / (window.devicePixelRatio || 1);
348
+ var dpr = window.devicePixelRatio || 1;
349
+ var w = canvas.width / dpr;
350
+ var h = canvas.height / dpr;
324
351
  ctx.clearRect(0, 0, w, h);
325
- ctx.font = fontSize + "px Menlo, Monaco, Consolas, 'Courier New', monospace";
326
- ctx.textBaseline = "alphabetic";
352
+
353
+ var pad = Math.ceil(fontSize * 0.3);
354
+ var drawW = (glyphCache._w || (Math.ceil(charWidth * dpr) + pad * 2)) / dpr;
355
+ var drawH = (glyphCache._h || (Math.ceil(fontSize * 1.6 * dpr) + pad * 2)) / dpr;
356
+ var padCSS = pad / dpr;
357
+
358
+ // Use globalCompositeOperation to tint white glyphs
359
+ // 1) Draw glyphs as white (source-over)
360
+ // 2) Multiply with color overlay
361
+ // Instead, use simpler approach: drawImage with globalAlpha, then tint via composite
327
362
 
328
363
  for (var i = 0; i < particles.length; i++) {
329
364
  var p = particles[i];
330
365
  if (p.opacity <= 0.01) continue;
331
366
 
332
- ctx.save();
367
+ var gc = glyphCache[p.char];
368
+ if (!gc) continue;
369
+
370
+ var dx = p.x - padCSS;
371
+ var dy = p.y - drawH + padCSS;
372
+
333
373
  ctx.globalAlpha = p.opacity;
334
- ctx.fillStyle = "rgb(" + p.r + "," + p.g + "," + p.b + ")";
335
374
 
336
375
  if (Math.abs(p.rotation) > 0.01) {
376
+ ctx.save();
337
377
  ctx.translate(p.x + charWidth / 2, p.y - fontSize / 3);
338
378
  ctx.rotate(p.rotation);
339
- ctx.fillText(p.char, -charWidth / 2, fontSize / 3);
379
+ ctx.drawImage(gc, -drawW / 2, -drawH / 2, drawW, drawH);
380
+ ctx.restore();
340
381
  } else {
341
- ctx.fillText(p.char, p.x, p.y);
382
+ ctx.drawImage(gc, dx, dy, drawW, drawH);
342
383
  }
343
- ctx.restore();
344
384
  }
385
+
386
+ // Tint pass — multiply white glyphs with particle colors
387
+ ctx.globalCompositeOperation = "source-atop";
388
+ for (var i = 0; i < particles.length; i++) {
389
+ var p = particles[i];
390
+ if (p.opacity <= 0.01) continue;
391
+ ctx.globalAlpha = 1;
392
+ ctx.fillStyle = "rgb(" + p.r + "," + p.g + "," + p.b + ")";
393
+ var dx = p.x - padCSS;
394
+ var dy = p.y - drawH + padCSS;
395
+ ctx.fillRect(dx, dy, drawW, drawH);
396
+ }
397
+ ctx.globalCompositeOperation = "source-over";
345
398
  }
346
399
 
347
400
  function tick(now) {
@@ -1,6 +1,6 @@
1
1
  import { iconHtml, refreshIcons } from './icons.js';
2
2
  import { escapeHtml, copyToClipboard } from './utils.js';
3
- import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, exportMarkdownAsPdf, parseEmojis } from './markdown.js';
3
+ import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, exportMarkdownAsPdf } from './markdown.js';
4
4
  import { closeSidebar } from './sidebar.js';
5
5
  import { renderUnifiedDiff, renderSplitDiff } from './diff.js';
6
6
  import { initFileIcons, getFileIconSvg, getFolderIconSvg } from './fileicons.js';
@@ -198,7 +198,6 @@ function renderBody() {
198
198
  }
199
199
  highlightCodeBlocks(bodyEl);
200
200
  renderMermaidBlocks(bodyEl);
201
- parseEmojis(bodyEl);
202
201
  renderBtn.classList.add("active");
203
202
  renderBtn.title = "Show raw";
204
203
  pdfBtn.classList.remove("hidden");
@@ -27,112 +27,12 @@ export function renderMarkdown(text) {
27
27
  }
28
28
 
29
29
  /**
30
- * Parse all Unicode emojis inside an element into Twemoji SVGs.
31
- * Safe to call on any DOM element skips <pre>/<code> to avoid mangling.
32
- * With the global MutationObserver active, manual calls are rarely needed.
30
+ * parseEmojis no-op stub.
31
+ * Emoji rendering is now handled by the Twemoji COLR font via CSS @font-face.
32
+ * No DOM manipulation needed the font renders emoji glyphs directly.
33
+ * This stub is kept so existing callers don't break.
33
34
  */
34
- export function parseEmojis(el) {
35
- if (typeof twemoji === "undefined" || !el) return;
36
- twemoji.parse(el, { folder: "svg", ext: ".svg" });
37
- }
38
-
39
- /**
40
- * Global Twemoji auto-parser.
41
- * Observes all DOM mutations and automatically converts Unicode emojis to
42
- * Twemoji SVGs. This eliminates the need to call parseEmojis() manually
43
- * after every DOM update.
44
- */
45
- var twemojiOpts = { folder: "svg", ext: ".svg" };
46
- var SKIP_TAGS = { PRE: 1, CODE: 1, SCRIPT: 1, STYLE: 1, TEXTAREA: 1, INPUT: 1 };
47
- var emojiRegex = /[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{27BF}\u{2700}-\u{27BF}\u{FE00}-\u{FE0F}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{200D}\u{20E3}\u{E0020}-\u{E007F}]/u;
48
- var pendingNodes = [];
49
- var flushScheduled = false;
50
-
51
- function shouldSkip(node) {
52
- var el = node.nodeType === 3 ? node.parentElement : node;
53
- while (el) {
54
- if (SKIP_TAGS[el.tagName]) return true;
55
- // Already parsed — img.emoji is what twemoji produces
56
- if (el.tagName === "IMG" && el.classList && el.classList.contains("emoji")) return true;
57
- el = el.parentElement;
58
- }
59
- return false;
60
- }
61
-
62
- function flushTwemoji() {
63
- flushScheduled = false;
64
- if (typeof twemoji === "undefined") return;
65
- var nodes = pendingNodes;
66
- pendingNodes = [];
67
- for (var i = 0; i < nodes.length; i++) {
68
- var node = nodes[i];
69
- if (!node.isConnected) continue;
70
- if (shouldSkip(node)) continue;
71
- // For text nodes, parse the parent element
72
- var target = node.nodeType === 3 ? node.parentElement : node;
73
- if (target) twemoji.parse(target, twemojiOpts);
74
- }
75
- }
76
-
77
- function queueTwemoji(node) {
78
- pendingNodes.push(node);
79
- if (!flushScheduled) {
80
- flushScheduled = true;
81
- requestAnimationFrame(flushTwemoji);
82
- }
83
- }
84
-
85
- // Start observing once DOM is ready
86
- (function initTwemojiObserver() {
87
- if (typeof twemoji === "undefined") return;
88
- if (typeof MutationObserver === "undefined") return;
89
-
90
- var observer = new MutationObserver(function (mutations) {
91
- for (var m = 0; m < mutations.length; m++) {
92
- var mut = mutations[m];
93
- // New nodes added
94
- if (mut.type === "childList") {
95
- for (var n = 0; n < mut.addedNodes.length; n++) {
96
- var added = mut.addedNodes[n];
97
- if (added.nodeType === 1) {
98
- // Element — check if it contains emoji text
99
- if (emojiRegex.test(added.textContent || "")) {
100
- queueTwemoji(added);
101
- }
102
- } else if (added.nodeType === 3) {
103
- // Text node
104
- if (emojiRegex.test(added.data || "")) {
105
- queueTwemoji(added);
106
- }
107
- }
108
- }
109
- }
110
- // Text content changed
111
- if (mut.type === "characterData") {
112
- if (emojiRegex.test(mut.target.data || "")) {
113
- queueTwemoji(mut.target);
114
- }
115
- }
116
- }
117
- });
118
-
119
- function start() {
120
- // Parse everything already in the page
121
- twemoji.parse(document.body, twemojiOpts);
122
- // Watch for future changes
123
- observer.observe(document.body, {
124
- childList: true,
125
- subtree: true,
126
- characterData: true,
127
- });
128
- }
129
-
130
- if (document.readyState === "loading") {
131
- document.addEventListener("DOMContentLoaded", start);
132
- } else {
133
- start();
134
- }
135
- })();
35
+ export function parseEmojis() {}
136
36
 
137
37
  var langAliases = { jsonl: "json", dotenv: "bash" };
138
38
 
@@ -1,7 +1,6 @@
1
1
  // project-settings.js — Project settings panel (profile, defaults, instructions, env)
2
2
  import { refreshIcons } from './icons.js';
3
3
  import { showToast } from './utils.js';
4
- import { parseEmojis } from './markdown.js';
5
4
 
6
5
  var ctx = null;
7
6
  var panelEl = null;
@@ -253,7 +252,6 @@ function updateIconPreview(icon) {
253
252
  var removeBtn = document.getElementById("ps-icon-remove-btn");
254
253
  if (preview) {
255
254
  preview.textContent = icon || "";
256
- if (icon) parseEmojis(preview);
257
255
  }
258
256
  if (removeBtn) {
259
257
  removeBtn.classList.toggle("hidden", !icon);
@@ -338,7 +336,7 @@ function showPsEmojiPicker() {
338
336
  grid.appendChild(btn);
339
337
  })(emojis[i]);
340
338
  }
341
- parseEmojis(grid);
339
+
342
340
  scrollArea.scrollTop = 0;
343
341
  }
344
342
 
@@ -350,7 +348,6 @@ function showPsEmojiPicker() {
350
348
  }
351
349
 
352
350
  buildGrid(EMOJI_CATEGORIES[0].emojis);
353
- parseEmojis(tabBar);
354
351
 
355
352
  anchor.innerHTML = "";
356
353
  anchor.appendChild(picker);
@@ -2,7 +2,6 @@ import { escapeHtml, copyToClipboard } from './utils.js';
2
2
  import { iconHtml, refreshIcons } from './icons.js';
3
3
  import { openProjectSettings } from './project-settings.js';
4
4
  import { triggerShare } from './qrcode.js';
5
- import { parseEmojis } from './markdown.js';
6
5
 
7
6
  var ctx;
8
7
 
@@ -1714,7 +1713,7 @@ function showEmojiPicker(slug, anchorEl) {
1714
1713
  grid.appendChild(btn);
1715
1714
  })(emojis[i]);
1716
1715
  }
1717
- parseEmojis(grid);
1716
+
1718
1717
  scrollArea.scrollTop = 0;
1719
1718
  }
1720
1719
 
@@ -1728,8 +1727,7 @@ function showEmojiPicker(slug, anchorEl) {
1728
1727
  // Start with first category (Frequent)
1729
1728
  buildGrid(EMOJI_CATEGORIES[0].emojis);
1730
1729
 
1731
- // Parse tabs with twemoji
1732
- parseEmojis(tabBar);
1730
+
1733
1731
 
1734
1732
  document.body.appendChild(picker);
1735
1733
  emojiPickerEl = picker;
@@ -1993,7 +1991,6 @@ export function renderIconStrip(projects, currentSlug) {
1993
1991
  var emojiSpan = document.createElement("span");
1994
1992
  emojiSpan.className = "project-emoji";
1995
1993
  emojiSpan.textContent = p.icon;
1996
- parseEmojis(emojiSpan);
1997
1994
  el.appendChild(emojiSpan);
1998
1995
  } else {
1999
1996
  el.appendChild(document.createTextNode(getProjectAbbrev(p.name)));
@@ -1,5 +1,4 @@
1
1
  import { refreshIcons, iconHtml } from './icons.js';
2
- import { parseEmojis } from './markdown.js';
3
2
 
4
3
  var ctx;
5
4
  var notes = new Map(); // id -> { data, el }
@@ -1124,7 +1123,6 @@ function renderArchiveCards() {
1124
1123
  var bodyLines = (noteData.data.text || "").split("\n").slice(1).join("\n").trim();
1125
1124
  if (bodyLines) {
1126
1125
  body.innerHTML = renderMiniMarkdown("_\n" + bodyLines).replace('<div class="sn-title">_</div>', "");
1127
- parseEmojis(body);
1128
1126
  }
1129
1127
  card.appendChild(body);
1130
1128
 
@@ -1,6 +1,6 @@
1
1
  import { escapeHtml, copyToClipboard } from './utils.js';
2
2
  import { iconHtml, refreshIcons, randomThinkingVerb } from './icons.js';
3
- import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, parseEmojis } from './markdown.js';
3
+ import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks } from './markdown.js';
4
4
  import { renderUnifiedDiff, renderSplitDiff, renderPatchDiff } from './diff.js';
5
5
  import { openFile } from './filebrowser.js';
6
6
 
@@ -744,7 +744,6 @@ export function renderPlanCard(content) {
744
744
  body.innerHTML = renderMarkdown(content);
745
745
  highlightCodeBlocks(body);
746
746
  renderMermaidBlocks(body);
747
- parseEmojis(body);
748
747
 
749
748
  var copyBtn = header.querySelector(".plan-card-copy");
750
749
  if (copyBtn) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.8.0",
3
+ "version": "2.8.2",
4
4
  "description": "Web UI for Claude Code. Any device. Push notifications.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",