clay-server 2.14.0 → 2.15.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.
@@ -468,7 +468,6 @@ function renderSessionItem(s) {
468
468
  dismissOverlayPanels();
469
469
  closeSidebar();
470
470
  if (pendingQuery) {
471
- closeSearch();
472
471
  setTimeout(function () { openSessionSearch(pendingQuery); }, 400);
473
472
  }
474
473
  }
@@ -556,17 +555,6 @@ export function handleSearchResults(msg) {
556
555
  }
557
556
  searchMatchIds = ids;
558
557
  renderSessionList(null);
559
-
560
- // Build timeline for current session if it matches
561
- var activeEl = ctx.sessionListEl.querySelector(".session-item.active");
562
- if (activeEl) {
563
- var activeId = parseInt(activeEl.dataset.sessionId, 10);
564
- if (ids.has(activeId)) {
565
- buildSearchTimeline(searchQuery);
566
- } else {
567
- removeSearchTimeline();
568
- }
569
- }
570
558
  }
571
559
 
572
560
  export function updateSessionPresence(presence) {
@@ -878,7 +866,6 @@ export function initSidebar(_ctx) {
878
866
  searchQuery = "";
879
867
  searchMatchIds = null;
880
868
  if (searchDebounce) { clearTimeout(searchDebounce); searchDebounce = null; }
881
- removeSearchTimeline();
882
869
  renderSessionList(null);
883
870
  }
884
871
 
@@ -901,7 +888,6 @@ export function initSidebar(_ctx) {
901
888
  if (searchDebounce) clearTimeout(searchDebounce);
902
889
  if (!searchQuery) {
903
890
  searchMatchIds = null;
904
- removeSearchTimeline();
905
891
  renderSessionList(null);
906
892
  return;
907
893
  }
@@ -1276,188 +1262,6 @@ export function populateCliSessionList(sessions) {
1276
1262
  }
1277
1263
  }
1278
1264
 
1279
- // --- Search hit timeline (right-side markers) ---
1280
- var searchTimelineScrollHandler = null;
1281
- var activeSearchQuery = ""; // query active in the timeline
1282
- var pendingSearchScrollTarget = null; // { historyIndex, snippet, query } for scroll after history load
1283
-
1284
- export function getActiveSearchQuery() {
1285
- return searchQuery;
1286
- }
1287
-
1288
- // Request server-side content search for the active session
1289
- export function buildSearchTimeline(query) {
1290
- removeSearchTimeline();
1291
- if (!query) return;
1292
- activeSearchQuery = query;
1293
- // Request full-history search from server
1294
- if (ctx.ws && ctx.connected) {
1295
- ctx.ws.send(JSON.stringify({ type: "search_session_content", query: query }));
1296
- }
1297
- }
1298
-
1299
- // Handle server response with full-history search results
1300
- export function handleSearchContentResults(msg) {
1301
- if (msg.query !== activeSearchQuery) return; // stale response
1302
- var savedQuery = activeSearchQuery;
1303
- removeSearchTimeline();
1304
- if (!msg.hits || msg.hits.length === 0) return;
1305
- activeSearchQuery = savedQuery;
1306
-
1307
- var hits = msg.hits;
1308
- var total = msg.total;
1309
- var messagesEl = ctx.messagesEl;
1310
-
1311
- var timeline = document.createElement("div");
1312
- timeline.className = "search-timeline";
1313
- timeline.id = "search-timeline";
1314
-
1315
- var track = document.createElement("div");
1316
- track.className = "rewind-timeline-track";
1317
- track.dataset.historyTotal = total;
1318
-
1319
- var viewport = document.createElement("div");
1320
- viewport.className = "rewind-timeline-viewport";
1321
- track.appendChild(viewport);
1322
-
1323
- for (var i = 0; i < hits.length; i++) {
1324
- var hit = hits[i];
1325
- // Position based on historyIndex relative to total history length
1326
- var pct = total <= 1 ? 50 : 6 + (hit.historyIndex / (total - 1)) * 88;
1327
-
1328
- var snippetText = hit.snippet;
1329
- if (snippetText.length > 24) snippetText = snippetText.substring(0, 24) + "\u2026";
1330
-
1331
- var marker = document.createElement("div");
1332
- marker.className = "rewind-timeline-marker search-hit-marker";
1333
- marker.innerHTML = iconHtml("search") + '<span class="marker-text">' + escapeHtml(snippetText) + '</span>';
1334
- marker.style.top = pct + "%";
1335
- // Store historyIndex for click handling and viewport check
1336
- marker.dataset.historyIndex = hit.historyIndex;
1337
-
1338
- (function(hitData, markerEl) {
1339
- markerEl.addEventListener("click", function() {
1340
- scrollToSearchHit(hitData.historyIndex, hitData.snippet, msg.query);
1341
- });
1342
- })(hit, marker);
1343
-
1344
- track.appendChild(marker);
1345
- }
1346
-
1347
- timeline.appendChild(track);
1348
-
1349
- // Position to align with messages area
1350
- var appEl = ctx.$("app");
1351
- var titleBarEl = document.querySelector(".title-bar-content");
1352
- var inputAreaEl = ctx.$("input-area");
1353
- var appRect = appEl.getBoundingClientRect();
1354
- var titleBarRect = titleBarEl ? titleBarEl.getBoundingClientRect() : { bottom: appRect.top };
1355
- var inputRect = inputAreaEl.getBoundingClientRect();
1356
-
1357
- timeline.style.top = (titleBarRect.bottom - appRect.top + 4) + "px";
1358
- timeline.style.bottom = (appRect.bottom - inputRect.top + 4) + "px";
1359
-
1360
- appEl.appendChild(timeline);
1361
- refreshIcons();
1362
-
1363
- searchTimelineScrollHandler = function() { updateSearchTimelineViewport(track, viewport); };
1364
- messagesEl.addEventListener("scroll", searchTimelineScrollHandler);
1365
- updateSearchTimelineViewport(track, viewport);
1366
- }
1367
-
1368
- function scrollToSearchHit(historyIndex, snippet, query) {
1369
- var historyFrom = ctx.getHistoryFrom ? ctx.getHistoryFrom() : 0;
1370
- if (historyIndex < historyFrom) {
1371
- // Need to load older history first
1372
- pendingSearchScrollTarget = { historyIndex: historyIndex, snippet: snippet, query: query };
1373
- if (ctx.ws && ctx.connected) {
1374
- ctx.ws.send(JSON.stringify({ type: "load_more_history", before: historyFrom, target: historyIndex }));
1375
- }
1376
- return;
1377
- }
1378
- // History is loaded, find matching element in DOM
1379
- findAndScrollToMatch(snippet, query);
1380
- }
1381
-
1382
- function findAndScrollToMatch(snippet, query) {
1383
- var messagesEl = ctx.messagesEl;
1384
- var q = query.toLowerCase();
1385
- var allMsgs = messagesEl.querySelectorAll(".msg-user, .msg-assistant");
1386
- for (var i = 0; i < allMsgs.length; i++) {
1387
- var msgEl = allMsgs[i];
1388
- var textEl = msgEl.querySelector(".bubble") || msgEl.querySelector(".md-content");
1389
- if (!textEl) continue;
1390
- var text = textEl.textContent || "";
1391
- if (text.toLowerCase().indexOf(q) === -1) continue;
1392
- // Check if the snippet content matches (strip ellipsis for comparison)
1393
- var cleanSnippet = snippet.replace(/^\u2026/, "").replace(/\u2026$/, "");
1394
- if (text.indexOf(cleanSnippet) !== -1) {
1395
- msgEl.scrollIntoView({ behavior: "smooth", block: "center" });
1396
- msgEl.classList.remove("search-blink");
1397
- void msgEl.offsetWidth;
1398
- msgEl.classList.add("search-blink");
1399
- return;
1400
- }
1401
- }
1402
- // Fallback: scroll to any element containing the query text
1403
- for (var j = 0; j < allMsgs.length; j++) {
1404
- var el = allMsgs[j];
1405
- var tEl = el.querySelector(".bubble") || el.querySelector(".md-content");
1406
- if (!tEl) continue;
1407
- if ((tEl.textContent || "").toLowerCase().indexOf(q) !== -1) {
1408
- el.scrollIntoView({ behavior: "smooth", block: "center" });
1409
- el.classList.remove("search-blink");
1410
- void el.offsetWidth;
1411
- el.classList.add("search-blink");
1412
- return;
1413
- }
1414
- }
1415
- }
1416
-
1417
- // Called after history_prepend completes, to scroll to pending target
1418
- export function onHistoryPrepended() {
1419
- if (!pendingSearchScrollTarget) return;
1420
- var target = pendingSearchScrollTarget;
1421
- pendingSearchScrollTarget = null;
1422
- requestAnimationFrame(function() {
1423
- findAndScrollToMatch(target.snippet, target.query);
1424
- });
1425
- }
1426
-
1427
- function updateSearchTimelineViewport(track, viewport) {
1428
- if (!track) return;
1429
- var messagesEl = ctx.messagesEl;
1430
- var scrollH = messagesEl.scrollHeight;
1431
- var viewH = messagesEl.clientHeight;
1432
-
1433
- // Map the visible scroll area to the timeline range (6% to 94%)
1434
- var historyFrom = ctx.getHistoryFrom ? ctx.getHistoryFrom() : 0;
1435
- var total = parseInt(track.dataset.historyTotal || "0", 10) || 1;
1436
- var timelineStart = 6 + (historyFrom / (total - 1 || 1)) * 88;
1437
- var timelineEnd = 94;
1438
- var timelineRange = timelineEnd - timelineStart;
1439
-
1440
- if (scrollH <= viewH) {
1441
- viewport.style.top = timelineStart + "%";
1442
- viewport.style.height = timelineRange + "%";
1443
- } else {
1444
- var scrollFrac = messagesEl.scrollTop / scrollH;
1445
- var viewFrac = viewH / scrollH;
1446
- viewport.style.top = (timelineStart + scrollFrac * timelineRange) + "%";
1447
- viewport.style.height = (viewFrac * timelineRange) + "%";
1448
- }
1449
- }
1450
-
1451
- export function removeSearchTimeline() {
1452
- var existing = document.getElementById("search-timeline");
1453
- if (existing) existing.remove();
1454
- if (searchTimelineScrollHandler && ctx.messagesEl) {
1455
- ctx.messagesEl.removeEventListener("scroll", searchTimelineScrollHandler);
1456
- searchTimelineScrollHandler = null;
1457
- }
1458
- activeSearchQuery = "";
1459
- }
1460
-
1461
1265
  // --- Icon Strip (Discord-style project icons) ---
1462
1266
  var iconStripTooltip = null;
1463
1267
 
package/lib/sessions.js CHANGED
@@ -310,7 +310,7 @@ function createSessionManager(opts) {
310
310
 
311
311
  function findTurnBoundary(history, targetIndex) {
312
312
  for (var i = targetIndex; i >= 0; i--) {
313
- if (history[i].type === "user_message") return i;
313
+ if (history[i] && history[i].type === "user_message") return i;
314
314
  }
315
315
  return 0;
316
316
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.14.0",
3
+ "version": "2.15.0-beta.1",
4
4
  "description": "Web UI for Claude Code. Any device. Push notifications.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",