clay-server 2.19.0 → 2.20.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.
@@ -20,6 +20,8 @@ var searchDebounce = null;
20
20
  var cachedSessions = [];
21
21
  var expandedLoopGroups = new Set();
22
22
  var expandedLoopRuns = new Set();
23
+ var expandedMobileLoopGroups = new Set();
24
+ var expandedMobileLoopRuns = new Set();
23
25
 
24
26
  // --- Cached project data for mobile sheet ---
25
27
  var cachedProjectList = [];
@@ -1104,7 +1106,242 @@ function createMobileSessionItem(s) {
1104
1106
  return el;
1105
1107
  }
1106
1108
 
1107
- // Helper: render sorted sessions into a container with date groups
1109
+ // Helper: create a mobile loop child element (individual session inside a group)
1110
+ function createMobileLoopChild(s) {
1111
+ var el = document.createElement("button");
1112
+ el.className = "mobile-loop-child" + (s.active ? " active" : "");
1113
+
1114
+ if (s.isProcessing) {
1115
+ var dot = document.createElement("span");
1116
+ dot.className = "mobile-session-processing";
1117
+ el.appendChild(dot);
1118
+ }
1119
+
1120
+ var textSpan = document.createElement("span");
1121
+ textSpan.className = "mobile-session-title";
1122
+ if (s.loop) {
1123
+ var isRalphChild = s.loop.source === "ralph";
1124
+ var roleName = s.loop.role === "crafting" ? "Crafting" : s.loop.role === "judge" ? "Judge" : (isRalphChild ? "Coder" : "Run");
1125
+ var iterSuffix = s.loop.role === "crafting" ? "" : " #" + s.loop.iteration;
1126
+ var roleCls = s.loop.role === "crafting" ? " crafting" : (!isRalphChild ? " scheduled" : "");
1127
+ var badge = document.createElement("span");
1128
+ badge.className = "mobile-loop-role-badge" + roleCls;
1129
+ badge.textContent = roleName + iterSuffix;
1130
+ textSpan.appendChild(badge);
1131
+ }
1132
+ el.appendChild(textSpan);
1133
+
1134
+ (function (id) {
1135
+ el.addEventListener("click", function () {
1136
+ if (ctx.ws && ctx.connected) {
1137
+ ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
1138
+ }
1139
+ dismissOverlayPanels();
1140
+ closeMobileSheet();
1141
+ });
1142
+ })(s.id);
1143
+
1144
+ return el;
1145
+ }
1146
+
1147
+ // Helper: create a mobile loop run sub-group (collapsible time group)
1148
+ function createMobileLoopRun(parentGk, startedAtKey, sessions, isRalph) {
1149
+ var runGk = parentGk + ":" + startedAtKey;
1150
+ var expanded = expandedMobileLoopRuns.has(runGk);
1151
+ var startedAt = Number(startedAtKey);
1152
+ var timeLabel = startedAt ? new Date(startedAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "Unknown";
1153
+
1154
+ var hasActive = false;
1155
+ var anyProcessing = false;
1156
+ var latestSession = sessions[0];
1157
+ for (var i = 0; i < sessions.length; i++) {
1158
+ if (sessions[i].active) hasActive = true;
1159
+ if (sessions[i].isProcessing) anyProcessing = true;
1160
+ if ((sessions[i].lastActivity || 0) > (latestSession.lastActivity || 0)) {
1161
+ latestSession = sessions[i];
1162
+ }
1163
+ }
1164
+
1165
+ var wrapper = document.createElement("div");
1166
+ wrapper.className = "mobile-loop-run-wrapper";
1167
+
1168
+ var header = document.createElement("button");
1169
+ header.className = "mobile-loop-run" + (hasActive ? " active" : "") + (expanded ? " expanded" : "") + (isRalph ? "" : " scheduled");
1170
+
1171
+ var chevron = document.createElement("span");
1172
+ chevron.className = "mobile-loop-chevron";
1173
+ chevron.innerHTML = iconHtml("chevron-right");
1174
+ header.appendChild(chevron);
1175
+
1176
+ var label = document.createElement("span");
1177
+ label.className = "mobile-loop-run-time";
1178
+ var labelHtml = "";
1179
+ if (anyProcessing) {
1180
+ labelHtml += '<span class="mobile-session-processing"></span> ';
1181
+ }
1182
+ labelHtml += escapeHtml(timeLabel);
1183
+ label.innerHTML = labelHtml;
1184
+ header.appendChild(label);
1185
+
1186
+ var countBadge = document.createElement("span");
1187
+ countBadge.className = "mobile-loop-count" + (isRalph ? "" : " scheduled");
1188
+ countBadge.textContent = String(sessions.length);
1189
+ header.appendChild(countBadge);
1190
+
1191
+ header.addEventListener("click", (function (rk) {
1192
+ return function (e) {
1193
+ e.stopPropagation();
1194
+ if (expandedMobileLoopRuns.has(rk)) {
1195
+ expandedMobileLoopRuns.delete(rk);
1196
+ } else {
1197
+ expandedMobileLoopRuns.add(rk);
1198
+ }
1199
+ refreshMobileChatSheet();
1200
+ };
1201
+ })(runGk));
1202
+
1203
+ wrapper.appendChild(header);
1204
+
1205
+ if (expanded) {
1206
+ var childContainer = document.createElement("div");
1207
+ childContainer.className = "mobile-loop-children";
1208
+ for (var k = 0; k < sessions.length; k++) {
1209
+ childContainer.appendChild(createMobileLoopChild(sessions[k]));
1210
+ }
1211
+ wrapper.appendChild(childContainer);
1212
+ }
1213
+
1214
+ return wrapper;
1215
+ }
1216
+
1217
+ // Helper: create a mobile loop group element (collapsible group header)
1218
+ function createMobileLoopGroup(loopId, children, groupKey) {
1219
+ var gk = groupKey || loopId;
1220
+
1221
+ // Sub-group children by startedAt (each run)
1222
+ var runMap = {};
1223
+ for (var i = 0; i < children.length; i++) {
1224
+ var runKey = String(children[i].loop && children[i].loop.startedAt || 0);
1225
+ if (!runMap[runKey]) runMap[runKey] = [];
1226
+ runMap[runKey].push(children[i]);
1227
+ }
1228
+ var runKeys = Object.keys(runMap);
1229
+
1230
+ // Sort each run's children by iteration then role
1231
+ for (var ri = 0; ri < runKeys.length; ri++) {
1232
+ runMap[runKeys[ri]].sort(function (a, b) {
1233
+ var ai = (a.loop && a.loop.iteration) || 0;
1234
+ var bi = (b.loop && b.loop.iteration) || 0;
1235
+ if (ai !== bi) return ai - bi;
1236
+ var ar = (a.loop && a.loop.role === "judge") ? 1 : 0;
1237
+ var br = (b.loop && b.loop.role === "judge") ? 1 : 0;
1238
+ return ar - br;
1239
+ });
1240
+ }
1241
+
1242
+ // Sort runs by startedAt descending (newest first)
1243
+ runKeys.sort(function (a, b) { return Number(b) - Number(a); });
1244
+
1245
+ var expanded = expandedMobileLoopGroups.has(gk);
1246
+ var hasActive = false;
1247
+ var anyProcessing = false;
1248
+ var latestSession = children[0];
1249
+ for (var ci = 0; ci < children.length; ci++) {
1250
+ if (children[ci].active) hasActive = true;
1251
+ if (children[ci].isProcessing) anyProcessing = true;
1252
+ if ((children[ci].lastActivity || 0) > (latestSession.lastActivity || 0)) {
1253
+ latestSession = children[ci];
1254
+ }
1255
+ }
1256
+
1257
+ var loopName = (children[0].loop && children[0].loop.name) || "Ralph Loop";
1258
+ var isRalph = children[0].loop && children[0].loop.source === "ralph";
1259
+ var isCrafting = false;
1260
+ for (var j = 0; j < children.length; j++) {
1261
+ if (children[j].loop && children[j].loop.role === "crafting") isCrafting = true;
1262
+ }
1263
+ var runCount = runKeys.length;
1264
+
1265
+ var wrapper = document.createElement("div");
1266
+ wrapper.className = "mobile-loop-wrapper";
1267
+
1268
+ // Group header row
1269
+ var header = document.createElement("button");
1270
+ header.className = "mobile-loop-group" + (hasActive ? " active" : "") + (expanded ? " expanded" : "") + (isRalph ? "" : " scheduled");
1271
+
1272
+ var chevron = document.createElement("span");
1273
+ chevron.className = "mobile-loop-chevron";
1274
+ chevron.innerHTML = iconHtml("chevron-right");
1275
+ header.appendChild(chevron);
1276
+
1277
+ var iconSpan = document.createElement("span");
1278
+ var groupIcon = isRalph ? "repeat" : "calendar-clock";
1279
+ iconSpan.className = "mobile-loop-icon" + (isRalph ? "" : " scheduled");
1280
+ iconSpan.innerHTML = iconHtml(groupIcon);
1281
+ header.appendChild(iconSpan);
1282
+
1283
+ if (anyProcessing) {
1284
+ var dot = document.createElement("span");
1285
+ dot.className = "mobile-session-processing";
1286
+ header.appendChild(dot);
1287
+ }
1288
+
1289
+ var nameSpan = document.createElement("span");
1290
+ nameSpan.className = "mobile-loop-name";
1291
+ nameSpan.textContent = loopName;
1292
+ header.appendChild(nameSpan);
1293
+
1294
+ if (isCrafting && children.length === 1) {
1295
+ var craftBadge = document.createElement("span");
1296
+ craftBadge.className = "mobile-loop-badge crafting";
1297
+ craftBadge.textContent = "Crafting";
1298
+ header.appendChild(craftBadge);
1299
+ } else {
1300
+ var countBadge = document.createElement("span");
1301
+ countBadge.className = "mobile-loop-count" + (isRalph ? "" : " scheduled");
1302
+ var countLabel = runCount === 1 ? String(children.length) : runCount + (runCount === 1 ? " run" : " runs");
1303
+ countBadge.textContent = countLabel;
1304
+ header.appendChild(countBadge);
1305
+ }
1306
+
1307
+ // Chevron toggles expansion
1308
+ header.addEventListener("click", (function (lid) {
1309
+ return function (e) {
1310
+ e.stopPropagation();
1311
+ if (expandedMobileLoopGroups.has(lid)) {
1312
+ expandedMobileLoopGroups.delete(lid);
1313
+ } else {
1314
+ expandedMobileLoopGroups.add(lid);
1315
+ }
1316
+ refreshMobileChatSheet();
1317
+ };
1318
+ })(gk));
1319
+
1320
+ wrapper.appendChild(header);
1321
+
1322
+ // Expanded: show runs
1323
+ if (expanded) {
1324
+ var childContainer = document.createElement("div");
1325
+ childContainer.className = "mobile-loop-children";
1326
+
1327
+ if (runCount === 1) {
1328
+ var singleRun = runMap[runKeys[0]];
1329
+ for (var sk = 0; sk < singleRun.length; sk++) {
1330
+ childContainer.appendChild(createMobileLoopChild(singleRun[sk]));
1331
+ }
1332
+ } else {
1333
+ for (var rk = 0; rk < runKeys.length; rk++) {
1334
+ childContainer.appendChild(createMobileLoopRun(gk, runKeys[rk], runMap[runKeys[rk]], isRalph));
1335
+ }
1336
+ }
1337
+
1338
+ wrapper.appendChild(childContainer);
1339
+ }
1340
+
1341
+ return wrapper;
1342
+ }
1343
+
1344
+ // Helper: render sorted sessions into a container with date groups (with loop session grouping)
1108
1345
  function renderMobileSessionsInto(container) {
1109
1346
  var newBtn = document.createElement("button");
1110
1347
  newBtn.className = "mobile-session-new";
@@ -1117,14 +1354,51 @@ function renderMobileSessionsInto(container) {
1117
1354
  });
1118
1355
  container.appendChild(newBtn);
1119
1356
 
1120
- var sorted = cachedSessions.slice().sort(function (a, b) {
1357
+ // Partition: loop sessions vs normal sessions (same logic as desktop renderSessionList)
1358
+ var loopGroups = {};
1359
+ var normalSessions = [];
1360
+ for (var i = 0; i < cachedSessions.length; i++) {
1361
+ var s = cachedSessions[i];
1362
+ if (s.loop && s.loop.loopId && s.loop.role === "crafting" && s.loop.source !== "ralph") {
1363
+ continue;
1364
+ } else if (s.loop && s.loop.loopId) {
1365
+ var startedAt = s.loop.startedAt || 0;
1366
+ var dateStr = startedAt ? new Date(startedAt).toISOString().slice(0, 10) : "unknown";
1367
+ var groupKey = s.loop.loopId + ":" + dateStr;
1368
+ if (!loopGroups[groupKey]) loopGroups[groupKey] = [];
1369
+ loopGroups[groupKey].push(s);
1370
+ } else {
1371
+ normalSessions.push(s);
1372
+ }
1373
+ }
1374
+
1375
+ // Build virtual items
1376
+ var items = [];
1377
+ for (var j = 0; j < normalSessions.length; j++) {
1378
+ items.push({ type: "session", data: normalSessions[j], lastActivity: normalSessions[j].lastActivity || 0 });
1379
+ }
1380
+ var groupKeys = Object.keys(loopGroups);
1381
+ for (var k = 0; k < groupKeys.length; k++) {
1382
+ var gk = groupKeys[k];
1383
+ var children = loopGroups[gk];
1384
+ var realLoopId = children[0].loop.loopId;
1385
+ var maxActivity = 0;
1386
+ for (var m = 0; m < children.length; m++) {
1387
+ var act = children[m].lastActivity || 0;
1388
+ if (act > maxActivity) maxActivity = act;
1389
+ }
1390
+ items.push({ type: "loop", loopId: realLoopId, groupKey: gk, children: children, lastActivity: maxActivity });
1391
+ }
1392
+
1393
+ // Sort by lastActivity descending
1394
+ items.sort(function (a, b) {
1121
1395
  return (b.lastActivity || 0) - (a.lastActivity || 0);
1122
1396
  });
1123
1397
 
1124
1398
  var currentGroup = "";
1125
- for (var si = 0; si < sorted.length; si++) {
1126
- var s = sorted[si];
1127
- var group = getDateGroup(s.lastActivity || 0);
1399
+ for (var n = 0; n < items.length; n++) {
1400
+ var item = items[n];
1401
+ var group = getDateGroup(item.lastActivity || 0);
1128
1402
  if (group !== currentGroup) {
1129
1403
  currentGroup = group;
1130
1404
  var header = document.createElement("div");
@@ -1132,7 +1406,11 @@ function renderMobileSessionsInto(container) {
1132
1406
  header.textContent = group;
1133
1407
  container.appendChild(header);
1134
1408
  }
1135
- container.appendChild(createMobileSessionItem(s));
1409
+ if (item.type === "loop") {
1410
+ container.appendChild(createMobileLoopGroup(item.loopId, item.children, item.groupKey));
1411
+ } else {
1412
+ container.appendChild(createMobileSessionItem(item.data));
1413
+ }
1136
1414
  }
1137
1415
  }
1138
1416
 
@@ -495,13 +495,16 @@ export function toggleDarkMode() {
495
495
  updateToggleIcon();
496
496
  }
497
497
 
498
- // Update the toggle switch checkbox state
498
+ // Update all theme toggle checkboxes
499
499
  function updateToggleIcon() {
500
- var checkbox = document.getElementById("theme-toggle-check");
501
- if (!checkbox) return;
502
500
  var mode = getEffectiveMode();
503
- // checked = light mode (thumb slides right, covering moon)
504
- checkbox.checked = (mode === "light");
501
+ var isLight = mode === "light";
502
+ // Legacy top-bar toggle (may not exist)
503
+ var checkbox = document.getElementById("theme-toggle-check");
504
+ if (checkbox) checkbox.checked = isLight;
505
+ // User settings toggle
506
+ var usToggle = document.getElementById("us-theme-toggle");
507
+ if (usToggle) usToggle.checked = isLight;
505
508
  }
506
509
 
507
510
  // --- Theme picker UI ---
@@ -671,13 +674,8 @@ export function initTheme() {
671
674
  // Load all themes from server, then apply properly
672
675
  loadThemes();
673
676
 
674
- // Wire up title bar toggle switch
675
- var toggleCheck = document.getElementById("theme-toggle-check");
676
- if (toggleCheck) {
677
- toggleCheck.addEventListener("change", function () {
678
- toggleDarkMode();
679
- });
680
- }
677
+ // Theme toggle is now in User Settings (us-theme-toggle)
678
+ // Wired up in user-settings.js initUserSettings()
681
679
 
682
680
  // Listen for system preference changes (only applies if user has no manual override)
683
681
  if (window.matchMedia) {
@@ -389,6 +389,9 @@ function submitAskUserAnswer(container, toolId, questions, answers, multiSelecti
389
389
  enableMainInput();
390
390
  if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
391
391
 
392
+ // Show user's answers inline
393
+ showAnswerSummary(container, questions, result);
394
+
392
395
  if (ctx.ws && ctx.connected) {
393
396
  ctx.ws.send(JSON.stringify({
394
397
  type: "ask_user_response",
@@ -398,11 +401,72 @@ function submitAskUserAnswer(container, toolId, questions, answers, multiSelecti
398
401
  }
399
402
  }
400
403
 
401
- export function markAskUserAnswered(toolId) {
404
+ function showAnswerSummary(container, questions, answers) {
405
+ if (!answers || Object.keys(answers).length === 0) return;
406
+ var existing = container.querySelector(".ask-user-answer-summary");
407
+ if (existing) return;
408
+ var summary = document.createElement("div");
409
+ summary.className = "ask-user-answer-summary";
410
+ for (var key in answers) {
411
+ if (!answers.hasOwnProperty(key)) continue;
412
+ var row = document.createElement("div");
413
+ row.className = "ask-user-answer-row";
414
+ var qi = parseInt(key, 10);
415
+ var label = (questions && questions[qi] && questions[qi].question) ? questions[qi].question : "Answer";
416
+ row.innerHTML = '<span class="ask-user-answer-label">' + escapeHtml(label) + '</span>' +
417
+ '<span class="ask-user-answer-value">' + escapeHtml(String(answers[key])) + '</span>';
418
+ summary.appendChild(row);
419
+ }
420
+ // Insert before submit button
421
+ var submitBtn = container.querySelector(".ask-user-submit");
422
+ if (submitBtn) {
423
+ submitBtn.parentNode.insertBefore(summary, submitBtn);
424
+ } else {
425
+ container.appendChild(summary);
426
+ }
427
+ }
428
+
429
+ export function markAskUserAnswered(toolId, answers) {
402
430
  var container = document.querySelector('.ask-user-container[data-tool-id="' + toolId + '"]');
403
431
  if (container && !container.classList.contains("answered")) {
404
432
  container.classList.add("answered");
405
433
  enableMainInput();
434
+ // Restore answers from history replay
435
+ if (answers && Object.keys(answers).length > 0) {
436
+ // Find matching questions from the tool_executing input
437
+ var questions = null;
438
+ var askQuestions = container.querySelectorAll(".ask-user-question");
439
+ if (askQuestions.length > 0) {
440
+ questions = [];
441
+ for (var qi = 0; qi < askQuestions.length; qi++) {
442
+ var qTextEl = askQuestions[qi].querySelector(".ask-user-question-text");
443
+ questions.push({ question: qTextEl ? qTextEl.textContent : "" });
444
+ }
445
+ }
446
+ showAnswerSummary(container, questions, answers);
447
+ // Also mark selected options to match the answers
448
+ for (var key in answers) {
449
+ if (!answers.hasOwnProperty(key)) continue;
450
+ var idx = parseInt(key, 10);
451
+ if (askQuestions[idx]) {
452
+ var answerVal = String(answers[key]);
453
+ var options = askQuestions[idx].querySelectorAll(".ask-user-option");
454
+ var matched = false;
455
+ for (var oi = 0; oi < options.length; oi++) {
456
+ var labelEl = options[oi].querySelector(".option-label");
457
+ if (labelEl && labelEl.textContent === answerVal) {
458
+ options[oi].classList.add("selected");
459
+ matched = true;
460
+ }
461
+ }
462
+ // If not matched to an option, fill the "Other" input
463
+ if (!matched) {
464
+ var otherInput = askQuestions[idx].querySelector(".ask-user-other input");
465
+ if (otherInput) otherInput.value = answerVal;
466
+ }
467
+ }
468
+ }
469
+ }
406
470
  }
407
471
  }
408
472
 
@@ -1345,17 +1409,22 @@ export function startThinking() {
1345
1409
  var el = thinkingGroup.el;
1346
1410
  el.classList.remove("done");
1347
1411
  el.querySelector(".thinking-content").textContent = "";
1348
- // Mate mode: restore dots row, hide thinking header
1412
+ // Mate mode: restore sparkle activity row, hide thinking header
1349
1413
  if (el.classList.contains("mate-thinking")) {
1350
- var dotsRow = el.querySelector(".mate-thinking-row");
1351
- if (dotsRow) dotsRow.style.display = "flex";
1414
+ var actRow = el.querySelector(".mate-thinking-activity");
1415
+ if (actRow) {
1416
+ actRow.style.display = "";
1417
+ actRow.querySelector(".activity-text").textContent = randomThinkingVerb() + "...";
1418
+ }
1352
1419
  var header = el.querySelector(".thinking-header");
1353
1420
  if (header) header.style.display = "none";
1354
1421
  }
1355
1422
  currentThinking = { el: el, fullText: "", startTime: Date.now() };
1356
1423
  refreshIcons();
1357
1424
  ctx.scrollToBottom();
1358
- ctx.setActivity(randomThinkingVerb() + "...");
1425
+ if (!el.classList.contains("mate-thinking")) {
1426
+ ctx.setActivity(randomThinkingVerb() + "...");
1427
+ }
1359
1428
  return;
1360
1429
  }
1361
1430
 
@@ -1369,9 +1438,10 @@ export function startThinking() {
1369
1438
  el.innerHTML =
1370
1439
  '<img class="dm-bubble-avatar dm-bubble-avatar-mate" src="' + escapeHtml(mateAvatar) + '" alt="">' +
1371
1440
  '<div class="dm-bubble-content">' +
1372
- '<div class="mate-thinking-row">' +
1373
- '<span class="dm-bubble-name">' + escapeHtml(mateName) + '</span>' +
1374
- '<span class="mate-thinking-dots"><span></span><span></span><span></span></span>' +
1441
+ '<div class="dm-bubble-header"><span class="dm-bubble-name">' + escapeHtml(mateName) + '</span></div>' +
1442
+ '<div class="activity-inline mate-thinking-activity">' +
1443
+ '<span class="activity-icon">' + iconHtml("sparkles") + '</span>' +
1444
+ '<span class="activity-text">' + randomThinkingVerb() + '...</span>' +
1375
1445
  '</div>' +
1376
1446
  '<div class="thinking-header" style="display:none">' +
1377
1447
  '<span class="thinking-chevron">' + iconHtml("chevron-right") + '</span>' +
@@ -1401,7 +1471,9 @@ export function startThinking() {
1401
1471
  ctx.scrollToBottom();
1402
1472
  thinkingGroup = { el: el, count: 0, totalDuration: 0 };
1403
1473
  currentThinking = { el: el, fullText: "", startTime: Date.now() };
1404
- ctx.setActivity(randomThinkingVerb() + "...");
1474
+ if (!ctx.isMateDm()) {
1475
+ ctx.setActivity(randomThinkingVerb() + "...");
1476
+ }
1405
1477
  }
1406
1478
 
1407
1479
  export function appendThinking(text) {
@@ -1422,10 +1494,10 @@ export function stopThinking(duration) {
1422
1494
  } else {
1423
1495
  currentThinking.el.querySelector(".thinking-duration").textContent = " " + secs.toFixed(1) + "s";
1424
1496
  }
1425
- // In mate mode: hide dots, show compact expandable thinking header
1497
+ // In mate mode: hide sparkle activity, show compact expandable thinking header
1426
1498
  if (currentThinking.el.classList.contains("mate-thinking")) {
1427
- var dotsRow = currentThinking.el.querySelector(".mate-thinking-row");
1428
- if (dotsRow) dotsRow.style.display = "none";
1499
+ var actRow = currentThinking.el.querySelector(".mate-thinking-activity");
1500
+ if (actRow) actRow.style.display = "none";
1429
1501
  var header = currentThinking.el.querySelector(".thinking-header");
1430
1502
  if (header) {
1431
1503
  header.style.display = "";
@@ -3,6 +3,7 @@
3
3
 
4
4
  import { refreshIcons } from './icons.js';
5
5
  import { showToast } from './utils.js';
6
+ import { toggleDarkMode, getCurrentTheme } from './theme.js';
6
7
 
7
8
  var ctx = null;
8
9
  var settingsEl = null;
@@ -66,17 +67,40 @@ export function initUserSettings(appCtx) {
66
67
  var pinInput = document.getElementById('us-pin-input');
67
68
  var pinSave = document.getElementById('us-pin-save');
68
69
  if (pinInput && pinSave) {
69
- pinInput.addEventListener('input', function () {
70
+ function validatePin() {
70
71
  pinSave.disabled = !/^\d{6}$/.test(pinInput.value);
71
- });
72
+ }
73
+ pinInput.addEventListener('input', validatePin);
74
+ pinInput.addEventListener('keyup', function (e) { e.stopPropagation(); validatePin(); });
72
75
  pinInput.addEventListener('keydown', stopProp);
73
- pinInput.addEventListener('keyup', stopProp);
74
76
  pinInput.addEventListener('keypress', stopProp);
75
77
  pinSave.addEventListener('click', function () {
76
78
  savePin(pinInput.value);
77
79
  });
78
80
  }
79
81
 
82
+ // Auto-continue toggle
83
+ var autoContinueToggle = document.getElementById('us-auto-continue');
84
+ if (autoContinueToggle) {
85
+ autoContinueToggle.addEventListener('change', function () {
86
+ fetch('/api/user/auto-continue', {
87
+ method: 'PUT',
88
+ headers: { 'Content-Type': 'application/json' },
89
+ body: JSON.stringify({ enabled: this.checked }),
90
+ }).then(function (r) { return r.json(); }).then(function (data) {
91
+ if (data.ok) showToast(data.autoContinueOnRateLimit ? 'Auto-continue on' : 'Auto-continue off');
92
+ }).catch(function () {});
93
+ });
94
+ }
95
+
96
+ // Theme toggle (light/dark)
97
+ var themeToggle = document.getElementById('us-theme-toggle');
98
+ if (themeToggle) {
99
+ themeToggle.addEventListener('change', function () {
100
+ toggleDarkMode();
101
+ });
102
+ }
103
+
80
104
  // Logout button
81
105
  var logoutBtn = document.getElementById('us-logout-btn');
82
106
  if (logoutBtn) {
@@ -139,6 +163,15 @@ function populateAccount() {
139
163
  if (accountNav && !data.username) {
140
164
  accountNav.style.display = 'none';
141
165
  }
166
+ // Auto-continue toggle
167
+ var acToggle = document.getElementById('us-auto-continue');
168
+ if (acToggle) acToggle.checked = !!data.autoContinueOnRateLimit;
169
+ // Theme toggle
170
+ var thToggle = document.getElementById('us-theme-toggle');
171
+ if (thToggle) {
172
+ var theme = getCurrentTheme();
173
+ thToggle.checked = theme && theme.variant === 'light';
174
+ }
142
175
  }).catch(function () {});
143
176
  }
144
177
 
@@ -148,36 +181,36 @@ function savePin(pin) {
148
181
  var pinMsg = document.getElementById('us-pin-msg');
149
182
 
150
183
  pinSave.disabled = true;
151
- pinSave.textContent = 'Saving...';
184
+ pinSave.textContent = 'Saving\u2026';
152
185
 
153
186
  fetch('/api/user/pin', {
154
187
  method: 'PUT',
155
188
  headers: { 'Content-Type': 'application/json' },
156
- body: JSON.stringify({ pin: pin }),
189
+ body: JSON.stringify({ newPin: pin }),
157
190
  }).then(function (r) { return r.json(); }).then(function (data) {
158
191
  if (data.ok) {
159
192
  pinInput.value = '';
160
- pinSave.textContent = 'Update PIN';
193
+ pinSave.textContent = 'Change PIN';
161
194
  if (pinMsg) {
162
- pinMsg.textContent = 'PIN updated successfully.';
195
+ pinMsg.textContent = 'Your PIN has been changed.';
163
196
  pinMsg.className = 'us-pin-msg us-pin-msg-ok';
164
197
  pinMsg.classList.remove('hidden');
165
198
  }
166
- showToast('PIN updated');
199
+ showToast('PIN changed');
167
200
  } else {
168
201
  pinSave.disabled = false;
169
- pinSave.textContent = 'Update PIN';
202
+ pinSave.textContent = 'Change PIN';
170
203
  if (pinMsg) {
171
- pinMsg.textContent = data.error || 'Failed to update PIN.';
204
+ pinMsg.textContent = data.error || 'Could not change your PIN. Please try again.';
172
205
  pinMsg.className = 'us-pin-msg us-pin-msg-err';
173
206
  pinMsg.classList.remove('hidden');
174
207
  }
175
208
  }
176
209
  }).catch(function () {
177
210
  pinSave.disabled = false;
178
- pinSave.textContent = 'Update PIN';
211
+ pinSave.textContent = 'Change PIN';
179
212
  if (pinMsg) {
180
- pinMsg.textContent = 'Network error.';
213
+ pinMsg.textContent = 'Connection lost. Check your network and try again.';
181
214
  pinMsg.className = 'us-pin-msg us-pin-msg-err';
182
215
  pinMsg.classList.remove('hidden');
183
216
  }