clay-server 2.13.0-beta.2 → 2.13.0-beta.4

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.
@@ -172,19 +172,12 @@ function ensurePanel() {
172
172
  });
173
173
  }
174
174
 
175
- // Inline add task
175
+ // Add task button (opens wizard modal)
176
176
  var addRow = document.createElement("div");
177
177
  addRow.className = "scheduler-add-row";
178
178
  addRow.innerHTML =
179
179
  '<div class="scheduler-add-trigger" id="scheduler-add-trigger">' +
180
180
  '<i data-lucide="plus-circle"></i> <span>Add new task</span>' +
181
- '</div>' +
182
- '<div class="scheduler-add-form hidden" id="scheduler-add-form">' +
183
- '<textarea id="scheduler-add-input" rows="2" placeholder="Describe what to build..."></textarea>' +
184
- '<div class="scheduler-add-actions">' +
185
- '<button type="button" class="scheduler-add-submit" id="scheduler-add-submit">Add</button>' +
186
- '<button type="button" class="scheduler-add-cancel" id="scheduler-add-cancel">Cancel</button>' +
187
- '</div>' +
188
181
  '</div>';
189
182
  sidebar.appendChild(addRow);
190
183
 
@@ -255,59 +248,12 @@ function ensurePanel() {
255
248
  closeScheduler();
256
249
  });
257
250
 
258
- // Inline add task
251
+ // Add task button — opens the Ralph wizard in "task" mode (step 1 skipped)
259
252
  var addTrigger = addRow.querySelector("#scheduler-add-trigger");
260
- var addForm = addRow.querySelector("#scheduler-add-form");
261
- var addInput = addRow.querySelector("#scheduler-add-input");
262
- var addSubmitBtn = addRow.querySelector("#scheduler-add-submit");
263
- var addCancelBtn = addRow.querySelector("#scheduler-add-cancel");
264
-
265
253
  addTrigger.addEventListener("click", function () {
266
- addTrigger.classList.add("hidden");
267
- addForm.classList.remove("hidden");
268
- addInput.value = "";
269
- addInput.focus();
270
- });
271
-
272
- addCancelBtn.addEventListener("click", function () {
273
- addForm.classList.add("hidden");
274
- addTrigger.classList.remove("hidden");
254
+ ctx.openRalphWizard("task");
275
255
  });
276
256
 
277
- addInput.addEventListener("keydown", function (e) {
278
- if (e.key === "Enter" && !e.shiftKey) {
279
- e.preventDefault();
280
- submitInlineTask();
281
- }
282
- if (e.key === "Escape") {
283
- addForm.classList.add("hidden");
284
- addTrigger.classList.remove("hidden");
285
- }
286
- });
287
-
288
- addSubmitBtn.addEventListener("click", function () {
289
- submitInlineTask();
290
- });
291
-
292
- var addSubmitting = false;
293
- function submitInlineTask() {
294
- if (addSubmitting) return;
295
- var task = addInput.value.trim();
296
- if (!task) { addInput.focus(); return; }
297
- ctx.requireClayRalph(function () {
298
- addSubmitting = true;
299
- addInput.value = "";
300
- addForm.classList.add("hidden");
301
- addTrigger.classList.remove("hidden");
302
- // Send wizard complete directly (skip modal)
303
- send({
304
- type: "ralph_wizard_complete",
305
- data: { name: task, task: task, maxIterations: 3, cron: null, source: "task" }
306
- });
307
- setTimeout(function () { addSubmitting = false; }, 1000);
308
- });
309
- }
310
-
311
257
  // Calendar controls
312
258
  calHdr.querySelector("#scheduler-prev").addEventListener("click", function () { navigate(-1); });
313
259
  calHdr.querySelector("#scheduler-next").addEventListener("click", function () { navigate(1); });
@@ -428,7 +374,16 @@ export function closeScheduler() {
428
374
  if (!panelOpen) return;
429
375
  panelOpen = false;
430
376
  stopNowLineTimer();
431
- if (currentMode === "crafting") unparentChat();
377
+ if (currentMode === "crafting") {
378
+ unparentChat();
379
+ // Switch back to previous session so crafting chat does not linger
380
+ if (craftingSessionId && logPreviousSessionId) {
381
+ send({ type: "switch_session", id: logPreviousSessionId });
382
+ logPreviousSessionId = null;
383
+ }
384
+ craftingTaskId = null;
385
+ craftingSessionId = null;
386
+ }
432
387
 
433
388
  if (panel) panel.classList.add("hidden");
434
389
  if (popoverEl) popoverEl.classList.add("hidden");
@@ -1572,6 +1527,10 @@ export function isSchedulerOpen() {
1572
1527
  export function enterCraftingMode(sessionId, taskId) {
1573
1528
  craftingSessionId = sessionId || null;
1574
1529
  craftingTaskId = taskId || null;
1530
+ // Remember the current session so we can restore it when crafting ends
1531
+ if (!logPreviousSessionId && ctx && ctx.activeSessionId && ctx.activeSessionId !== sessionId) {
1532
+ logPreviousSessionId = ctx.activeSessionId;
1533
+ }
1575
1534
  if (!panelOpen) openScheduler();
1576
1535
  if (taskId) {
1577
1536
  selectedTaskId = taskId;
@@ -1991,6 +1950,20 @@ function setupCreateModal() {
1991
1950
  updateRecurrenceBtn();
1992
1951
  });
1993
1952
 
1953
+ // Run mode toggle (single vs multi-round)
1954
+ var runModeContainer = createPopover.querySelector(".sched-create-run-mode");
1955
+ if (runModeContainer) {
1956
+ runModeContainer.addEventListener("click", function (e) {
1957
+ var btn = e.target.closest(".sched-run-mode-btn");
1958
+ if (!btn) return;
1959
+ var mode = btn.dataset.mode;
1960
+ var btns = runModeContainer.querySelectorAll(".sched-run-mode-btn");
1961
+ for (var i = 0; i < btns.length; i++) btns[i].classList.toggle("active", btns[i] === btn);
1962
+ var iterGroup = document.getElementById("sched-create-iter-group");
1963
+ if (iterGroup) iterGroup.classList.toggle("hidden", mode !== "multi");
1964
+ });
1965
+ }
1966
+
1994
1967
  // Submit
1995
1968
  document.getElementById("sched-create-submit").addEventListener("click", function () { submitCreateSchedule(); });
1996
1969
 
@@ -2238,8 +2211,15 @@ function openCreateModalWithRecord(rec, anchorEl) {
2238
2211
  }
2239
2212
  }
2240
2213
 
2241
- // Set iterations
2242
- if (rec.maxIterations) {
2214
+ // Set run mode and iterations
2215
+ var editRunMode = (rec.maxIterations && rec.maxIterations > 1) ? "multi" : "single";
2216
+ var editRunBtns = createPopover.querySelectorAll(".sched-run-mode-btn");
2217
+ for (var rb = 0; rb < editRunBtns.length; rb++) {
2218
+ editRunBtns[rb].classList.toggle("active", editRunBtns[rb].dataset.mode === editRunMode);
2219
+ }
2220
+ var editIterGroup = document.getElementById("sched-create-iter-group");
2221
+ if (editIterGroup) editIterGroup.classList.toggle("hidden", editRunMode !== "multi");
2222
+ if (rec.maxIterations && rec.maxIterations > 1) {
2243
2223
  var iterInput = document.getElementById("sched-create-iterations");
2244
2224
  if (iterInput) iterInput.value = rec.maxIterations;
2245
2225
  }
@@ -2293,6 +2273,13 @@ function openCreateModal(date, hour, anchorEl) {
2293
2273
  document.getElementById("sched-create-desc").value = "";
2294
2274
  var iterReset = document.getElementById("sched-create-iterations");
2295
2275
  if (iterReset) iterReset.value = "3";
2276
+ // Reset run mode to single
2277
+ var runModeBtns = createPopover.querySelectorAll(".sched-run-mode-btn");
2278
+ for (var rm = 0; rm < runModeBtns.length; rm++) {
2279
+ runModeBtns[rm].classList.toggle("active", runModeBtns[rm].dataset.mode === "single");
2280
+ }
2281
+ var iterGroup = document.getElementById("sched-create-iter-group");
2282
+ if (iterGroup) iterGroup.classList.add("hidden");
2296
2283
 
2297
2284
  // Reset color
2298
2285
  var colorDot = document.getElementById("sched-create-color-dot");
@@ -2728,10 +2715,15 @@ function submitCreateSchedule() {
2728
2715
  }
2729
2716
  }
2730
2717
 
2731
- var iterInput = document.getElementById("sched-create-iterations");
2732
- var maxIterations = iterInput ? (parseInt(iterInput.value, 10) || 3) : 3;
2733
- if (maxIterations < 1) maxIterations = 1;
2734
- if (maxIterations > 100) maxIterations = 100;
2718
+ var activeRunMode = createPopover.querySelector(".sched-run-mode-btn.active");
2719
+ var runMode = activeRunMode ? activeRunMode.dataset.mode : "single";
2720
+ var maxIterations = 1;
2721
+ if (runMode === "multi") {
2722
+ var iterInput = document.getElementById("sched-create-iterations");
2723
+ maxIterations = iterInput ? (parseInt(iterInput.value, 10) || 3) : 3;
2724
+ if (maxIterations < 2) maxIterations = 2;
2725
+ if (maxIterations > 100) maxIterations = 100;
2726
+ }
2735
2727
 
2736
2728
  send({
2737
2729
  type: "schedule_create",
@@ -93,14 +93,18 @@ function isSearchOpen() {
93
93
  return searchBarEl && !searchBarEl.classList.contains("hidden");
94
94
  }
95
95
 
96
- function openSearch() {
96
+ function openSearch(prefill) {
97
97
  if (!searchBarEl) return;
98
98
  searchBarEl.classList.remove("hidden");
99
99
  var btn = document.getElementById("find-in-session-btn");
100
100
  if (btn) btn.classList.add("active");
101
+ if (typeof prefill === "string" && prefill) {
102
+ searchInputEl.value = prefill;
103
+ performSearch(prefill);
104
+ }
101
105
  searchInputEl.focus();
102
106
  searchInputEl.select();
103
- if (searchInputEl.value) {
107
+ if (!prefill && searchInputEl.value) {
104
108
  performSearch(searchInputEl.value);
105
109
  }
106
110
  }
@@ -4,6 +4,8 @@ import { openProjectSettings } from './project-settings.js';
4
4
  import { triggerShare } from './qrcode.js';
5
5
  import { parseEmojis } from './markdown.js';
6
6
  import { showMateProfilePopover } from './profile.js';
7
+ import { closeArchive } from './sticky-notes.js';
8
+ import { closeScheduler } from './scheduler.js';
7
9
 
8
10
  var ctx;
9
11
 
@@ -18,6 +20,11 @@ var expandedLoopGroups = new Set();
18
20
  var cachedProjectList = [];
19
21
  var cachedCurrentSlug = null;
20
22
 
23
+ function dismissOverlayPanels() {
24
+ closeArchive();
25
+ closeScheduler();
26
+ }
27
+
21
28
  // --- Session presence (multi-user: who is viewing which session) ---
22
29
  var sessionPresence = {}; // { sessionId: [{ id, displayName, avatarStyle, avatarSeed }] }
23
30
 
@@ -72,20 +79,22 @@ function showSessionCtxMenu(anchorBtn, sessionId, title, cliSid, sessionData) {
72
79
  menu.appendChild(visItem);
73
80
  }
74
81
 
75
- var deleteItem = document.createElement("button");
76
- deleteItem.className = "session-ctx-item session-ctx-delete";
77
- deleteItem.innerHTML = iconHtml("trash-2") + " <span>Delete</span>";
78
- deleteItem.addEventListener("click", function (e) {
79
- e.stopPropagation();
80
- closeSessionCtxMenu();
81
- ctx.showConfirm('Delete "' + (title || "New Session") + '"? This session and its history will be permanently removed.', function () {
82
- var ws = ctx.ws;
83
- if (ws && ctx.connected) {
84
- ws.send(JSON.stringify({ type: "delete_session", id: sessionId }));
85
- }
82
+ if (!ctx.permissions || ctx.permissions.sessionDelete !== false) {
83
+ var deleteItem = document.createElement("button");
84
+ deleteItem.className = "session-ctx-item session-ctx-delete";
85
+ deleteItem.innerHTML = iconHtml("trash-2") + " <span>Delete</span>";
86
+ deleteItem.addEventListener("click", function (e) {
87
+ e.stopPropagation();
88
+ closeSessionCtxMenu();
89
+ ctx.showConfirm('Delete "' + (title || "New Session") + '"? This session and its history will be permanently removed.', function () {
90
+ var ws = ctx.ws;
91
+ if (ws && ctx.connected) {
92
+ ws.send(JSON.stringify({ type: "delete_session", id: sessionId }));
93
+ }
94
+ });
86
95
  });
87
- });
88
- menu.appendChild(deleteItem);
96
+ menu.appendChild(deleteItem);
97
+ }
89
98
 
90
99
  document.body.appendChild(menu);
91
100
  sessionCtxMenu = menu;
@@ -159,22 +168,24 @@ function showLoopCtxMenu(anchorBtn, loopId, loopName, childCount) {
159
168
  });
160
169
  menu.appendChild(renameItem);
161
170
 
162
- var deleteItem = document.createElement("button");
163
- deleteItem.className = "session-ctx-item session-ctx-delete";
164
- deleteItem.innerHTML = iconHtml("trash-2") + " <span>Delete</span>";
165
- deleteItem.addEventListener("click", function (e) {
166
- e.stopPropagation();
167
- closeSessionCtxMenu();
168
- var msg = 'Delete "' + (loopName || "Ralph Loop") + '"';
169
- if (childCount > 1) msg += " and its " + childCount + " sessions";
170
- msg += "? This cannot be undone.";
171
- ctx.showConfirm(msg, function () {
172
- if (ctx.ws && ctx.connected) {
173
- ctx.ws.send(JSON.stringify({ type: "delete_loop_group", loopId: loopId }));
174
- }
171
+ if (!ctx.permissions || ctx.permissions.sessionDelete !== false) {
172
+ var deleteItem = document.createElement("button");
173
+ deleteItem.className = "session-ctx-item session-ctx-delete";
174
+ deleteItem.innerHTML = iconHtml("trash-2") + " <span>Delete</span>";
175
+ deleteItem.addEventListener("click", function (e) {
176
+ e.stopPropagation();
177
+ closeSessionCtxMenu();
178
+ var msg = 'Delete "' + (loopName || "Ralph Loop") + '"';
179
+ if (childCount > 1) msg += " and its " + childCount + " sessions";
180
+ msg += "? This cannot be undone.";
181
+ ctx.showConfirm(msg, function () {
182
+ if (ctx.ws && ctx.connected) {
183
+ ctx.ws.send(JSON.stringify({ type: "delete_loop_group", loopId: loopId }));
184
+ }
185
+ });
175
186
  });
176
- });
177
- menu.appendChild(deleteItem);
187
+ menu.appendChild(deleteItem);
188
+ }
178
189
 
179
190
  document.body.appendChild(menu);
180
191
  sessionCtxMenu = menu;
@@ -282,6 +293,7 @@ function renderLoopChild(s) {
282
293
  return function () {
283
294
  if (ctx.ws && ctx.connected) {
284
295
  ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
296
+ dismissOverlayPanels();
285
297
  closeSidebar();
286
298
  }
287
299
  };
@@ -384,6 +396,7 @@ function renderLoopGroup(loopId, children, groupKey) {
384
396
  return function () {
385
397
  if (ctx.ws && ctx.connected) {
386
398
  ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
399
+ dismissOverlayPanels();
387
400
  closeSidebar();
388
401
  }
389
402
  };
@@ -450,6 +463,7 @@ function renderSessionItem(s) {
450
463
  return function () {
451
464
  if (ctx.ws && ctx.connected) {
452
465
  ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
466
+ dismissOverlayPanels();
453
467
  closeSidebar();
454
468
  }
455
469
  };
@@ -784,6 +798,7 @@ function renderSheetSessions(listEl) {
784
798
  if (ctx.ws && ctx.connected) {
785
799
  ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
786
800
  }
801
+ dismissOverlayPanels();
787
802
  closeMobileSheet();
788
803
  });
789
804
  })(s.id);
@@ -1110,6 +1125,23 @@ export function initSidebar(_ctx) {
1110
1125
  // Initial sync even if no resize handle
1111
1126
  syncUserIslandWidth();
1112
1127
 
1128
+ // --- User island tooltip on hover (collapsed sidebar) ---
1129
+ if (userIsland) {
1130
+ var profileArea = userIsland.querySelector(".user-island-profile");
1131
+ if (profileArea) {
1132
+ profileArea.addEventListener("mouseenter", function () {
1133
+ var layout = document.getElementById("layout");
1134
+ if (!layout || !layout.classList.contains("sidebar-collapsed")) return;
1135
+ var nameEl = userIsland.querySelector(".user-island-name");
1136
+ var text = nameEl ? nameEl.textContent : "";
1137
+ if (text) showIconTooltip(profileArea, text);
1138
+ });
1139
+ profileArea.addEventListener("mouseleave", function () {
1140
+ hideIconTooltip();
1141
+ });
1142
+ }
1143
+ }
1144
+
1113
1145
  // --- Schedule countdown timer ---
1114
1146
  startCountdownTimer();
1115
1147
  }
@@ -1842,23 +1874,25 @@ function showIconCtxMenu(anchorEl, slug, name) {
1842
1874
  });
1843
1875
  menu.appendChild(wtItem);
1844
1876
 
1845
- // --- Separator ---
1846
- var sep = document.createElement("div");
1847
- sep.className = "project-ctx-separator";
1848
- menu.appendChild(sep);
1849
-
1850
- // --- Remove Project ---
1851
- var removeItem = document.createElement("button");
1852
- removeItem.className = "project-ctx-item project-ctx-delete";
1853
- removeItem.innerHTML = iconHtml("trash-2") + " <span>Remove Project</span>";
1854
- removeItem.addEventListener("click", function (e) {
1855
- e.stopPropagation();
1856
- closeProjectCtxMenu();
1857
- if (ctx.ws && ctx.connected) {
1858
- ctx.ws.send(JSON.stringify({ type: "remove_project_check", slug: slug, name: name || slug }));
1859
- }
1860
- });
1861
- menu.appendChild(removeItem);
1877
+ if (!ctx.permissions || ctx.permissions.deleteProject !== false) {
1878
+ // --- Separator ---
1879
+ var sep = document.createElement("div");
1880
+ sep.className = "project-ctx-separator";
1881
+ menu.appendChild(sep);
1882
+
1883
+ // --- Remove Project ---
1884
+ var removeItem = document.createElement("button");
1885
+ removeItem.className = "project-ctx-item project-ctx-delete";
1886
+ removeItem.innerHTML = iconHtml("trash-2") + " <span>Remove Project</span>";
1887
+ removeItem.addEventListener("click", function (e) {
1888
+ e.stopPropagation();
1889
+ closeProjectCtxMenu();
1890
+ if (ctx.ws && ctx.connected) {
1891
+ ctx.ws.send(JSON.stringify({ type: "remove_project_check", slug: slug, name: name || slug }));
1892
+ }
1893
+ });
1894
+ menu.appendChild(removeItem);
1895
+ }
1862
1896
  }
1863
1897
 
1864
1898
  document.body.appendChild(menu);
@@ -1889,15 +1923,17 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
1889
1923
  menu.className = "project-ctx-menu";
1890
1924
 
1891
1925
  // --- Project Settings ---
1892
- var settingsItem = document.createElement("button");
1893
- settingsItem.className = "project-ctx-item";
1894
- settingsItem.innerHTML = iconHtml("settings") + " <span>Project Settings</span>";
1895
- settingsItem.addEventListener("click", function (e) {
1896
- e.stopPropagation();
1897
- closeProjectCtxMenu();
1898
- openProjectSettings(slug, { slug: slug, name: name, icon: icon, projectOwnerId: ctx.projectOwnerId });
1899
- });
1900
- menu.appendChild(settingsItem);
1926
+ if (!ctx.permissions || ctx.permissions.projectSettings !== false) {
1927
+ var settingsItem = document.createElement("button");
1928
+ settingsItem.className = "project-ctx-item";
1929
+ settingsItem.innerHTML = iconHtml("settings") + " <span>Project Settings</span>";
1930
+ settingsItem.addEventListener("click", function (e) {
1931
+ e.stopPropagation();
1932
+ closeProjectCtxMenu();
1933
+ openProjectSettings(slug, { slug: slug, name: name, icon: icon, projectOwnerId: ctx.projectOwnerId });
1934
+ });
1935
+ menu.appendChild(settingsItem);
1936
+ }
1901
1937
 
1902
1938
  // --- Share ---
1903
1939
  var shareItem = document.createElement("button");
@@ -1910,24 +1946,26 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
1910
1946
  });
1911
1947
  menu.appendChild(shareItem);
1912
1948
 
1913
- // --- Separator ---
1914
- var sep = document.createElement("div");
1915
- sep.className = "project-ctx-separator";
1916
- menu.appendChild(sep);
1949
+ if (!ctx.permissions || ctx.permissions.deleteProject !== false) {
1950
+ // --- Separator ---
1951
+ var sep = document.createElement("div");
1952
+ sep.className = "project-ctx-separator";
1953
+ menu.appendChild(sep);
1917
1954
 
1918
- // --- Delete ---
1919
- var deleteItem = document.createElement("button");
1920
- deleteItem.className = "project-ctx-item project-ctx-delete";
1921
- deleteItem.innerHTML = iconHtml("trash-2") + " <span>Remove Project</span>";
1922
- deleteItem.addEventListener("click", function (e) {
1923
- e.stopPropagation();
1924
- closeProjectCtxMenu();
1925
- // Check for tasks/schedules first before removing
1926
- if (ctx.ws && ctx.connected) {
1927
- ctx.ws.send(JSON.stringify({ type: "remove_project_check", slug: slug, name: name }));
1928
- }
1929
- });
1930
- menu.appendChild(deleteItem);
1955
+ // --- Delete ---
1956
+ var deleteItem = document.createElement("button");
1957
+ deleteItem.className = "project-ctx-item project-ctx-delete";
1958
+ deleteItem.innerHTML = iconHtml("trash-2") + " <span>Remove Project</span>";
1959
+ deleteItem.addEventListener("click", function (e) {
1960
+ e.stopPropagation();
1961
+ closeProjectCtxMenu();
1962
+ // Check for tasks/schedules first before removing
1963
+ if (ctx.ws && ctx.connected) {
1964
+ ctx.ws.send(JSON.stringify({ type: "remove_project_check", slug: slug, name: name }));
1965
+ }
1966
+ });
1967
+ menu.appendChild(deleteItem);
1968
+ }
1931
1969
 
1932
1970
  document.body.appendChild(menu);
1933
1971
  projectCtxMenu = menu;
@@ -2376,6 +2414,11 @@ function createIconItem(p, currentSlug) {
2376
2414
  }
2377
2415
  el.appendChild(projectBadge);
2378
2416
 
2417
+ // Pending permission shake for non-active projects
2418
+ if (p.pendingPermissions > 0 && !isActive) {
2419
+ el.classList.add("has-pending-perm");
2420
+ }
2421
+
2379
2422
  (function (name, elem) {
2380
2423
  elem.addEventListener("mouseenter", function () { showIconTooltip(elem, name); });
2381
2424
  elem.addEventListener("mouseleave", hideIconTooltip);
@@ -2497,7 +2540,8 @@ function showWorktreeModal(parentSlug, parentName) {
2497
2540
  if (ctx.ws && ctx.connected) {
2498
2541
  ctx.ws.send(JSON.stringify({
2499
2542
  type: "create_worktree",
2500
- branch: dirName,
2543
+ branch: branch,
2544
+ dirName: dirName,
2501
2545
  baseBranch: base
2502
2546
  }));
2503
2547
  }
@@ -2670,10 +2714,22 @@ export function renderIconStrip(projects, currentSlug) {
2670
2714
  });
2671
2715
  }
2672
2716
 
2717
+ // Pending permission shake for worktree items
2718
+ if (wt.pendingPermissions > 0 && !isWtActive) {
2719
+ wtEl.classList.add("has-pending-perm");
2720
+ }
2721
+
2673
2722
  itemsContainer.appendChild(wtEl);
2674
2723
  })(worktrees[wi]);
2675
2724
  }
2676
2725
 
2726
+ // Force expand if any worktree has pending permissions
2727
+ var hasWtPendingPerm = false;
2728
+ for (var wpi2 = 0; wpi2 < worktrees.length; wpi2++) {
2729
+ if (worktrees[wpi2].pendingPermissions > 0) { hasWtPendingPerm = true; break; }
2730
+ }
2731
+ if (hasWtPendingPerm) folder.classList.remove("collapsed");
2732
+
2677
2733
  // "+" button to add new worktree
2678
2734
  var addBtn = document.createElement("button");
2679
2735
  addBtn.className = "icon-strip-group-add";
@@ -381,6 +381,36 @@ function renderNote(data) {
381
381
  mdBtn.innerHTML = "<span class='sn-md-label'>MD</span>";
382
382
  header.appendChild(mdBtn);
383
383
 
384
+ var opacityWrap = document.createElement("div");
385
+ opacityWrap.className = "sticky-note-opacity";
386
+ var opacitySlider = document.createElement("input");
387
+ opacitySlider.type = "range";
388
+ opacitySlider.min = "20";
389
+ opacitySlider.max = "100";
390
+ opacitySlider.value = String(Math.round((data.opacity || 1) * 100));
391
+ opacitySlider.className = "sticky-note-opacity-slider";
392
+ opacitySlider.title = "Opacity";
393
+ opacityWrap.appendChild(opacitySlider);
394
+ header.appendChild(opacityWrap);
395
+
396
+ // Apply saved opacity via CSS variable (not element opacity, so header stays visible on hover)
397
+ if (typeof data.opacity === "number") {
398
+ el.style.setProperty("--note-opacity", data.opacity);
399
+ }
400
+
401
+ opacitySlider.addEventListener("input", function (e) {
402
+ e.stopPropagation();
403
+ var val = parseInt(opacitySlider.value, 10) / 100;
404
+ el.style.setProperty("--note-opacity", val);
405
+ });
406
+ opacitySlider.addEventListener("change", function (e) {
407
+ e.stopPropagation();
408
+ var val = parseInt(opacitySlider.value, 10) / 100;
409
+ el.style.setProperty("--note-opacity", val);
410
+ debouncedUpdate(data.id, { opacity: val }, 300);
411
+ });
412
+ opacitySlider.addEventListener("mousedown", function (e) { e.stopPropagation(); });
413
+
384
414
  el.appendChild(header);
385
415
 
386
416
  // Body
@@ -1008,6 +1038,13 @@ export function handleNoteUpdated(msg) {
1008
1038
  syncTitle(entry.el, msg.note.text);
1009
1039
  }
1010
1040
 
1041
+ // Handle opacity
1042
+ if (typeof msg.note.opacity === "number") {
1043
+ entry.el.style.setProperty("--note-opacity", msg.note.opacity);
1044
+ var slider = entry.el.querySelector(".sticky-note-opacity-slider");
1045
+ if (slider) slider.value = String(Math.round(msg.note.opacity * 100));
1046
+ }
1047
+
1011
1048
  // Handle hidden state
1012
1049
  if (msg.note.hidden) {
1013
1050
  entry.el.classList.add("hidden");
@@ -25,3 +25,4 @@
25
25
  @import url("css/session-search.css");
26
26
  @import url("css/tooltip.css");
27
27
  @import url("css/mates.css");
28
+ @import url("css/command-palette.css");
package/lib/scheduler.js CHANGED
@@ -262,6 +262,8 @@ function createLoopRegistry(opts) {
262
262
  source: data.source || null,
263
263
  color: data.color || null,
264
264
  recurrenceEnd: data.recurrenceEnd || null,
265
+ mode: data.mode || "loop",
266
+ prompt: data.prompt || null,
265
267
  runs: [],
266
268
  };
267
269
  if (rec.cron && rec.enabled) {
@@ -290,6 +292,8 @@ function createLoopRegistry(opts) {
290
292
  if (data.maxIterations !== undefined) rec.maxIterations = data.maxIterations;
291
293
  if (data.date !== undefined) rec.date = data.date;
292
294
  if (data.recurrenceEnd !== undefined) rec.recurrenceEnd = data.recurrenceEnd;
295
+ if (data.mode !== undefined) rec.mode = data.mode;
296
+ if (data.prompt !== undefined) rec.prompt = data.prompt;
293
297
  rec.updatedAt = Date.now();
294
298
  if (rec.cron && rec.enabled) {
295
299
  rec.nextRunAt = nextRunTime(rec.cron);
package/lib/sdk-bridge.js CHANGED
@@ -1151,6 +1151,7 @@ function createSDKBridge(opts) {
1151
1151
  decisionReason: opts.decisionReason || "",
1152
1152
  };
1153
1153
  sendAndRecord(session, permMsg);
1154
+ onProcessingChanged(); // update cross-project permission badge
1154
1155
 
1155
1156
  if (pushModule) {
1156
1157
  pushModule.sendPush({
@@ -1166,6 +1167,7 @@ function createSDKBridge(opts) {
1166
1167
  opts.signal.addEventListener("abort", function() {
1167
1168
  delete session.pendingPermissions[requestId];
1168
1169
  sendAndRecord(session, { type: "permission_cancel", requestId: requestId });
1170
+ onProcessingChanged(); // update cross-project permission badge
1169
1171
  resolve({ behavior: "deny", message: "Request cancelled" });
1170
1172
  });
1171
1173
  }
@@ -1327,14 +1329,14 @@ function createSDKBridge(opts) {
1327
1329
  session.pendingPermissions = {};
1328
1330
  session.pendingAskUser = {};
1329
1331
  session.pendingElicitations = {};
1330
- }
1331
- // Ralph Loop: notify completion so loop orchestrator can proceed
1332
- if (session.onQueryComplete) {
1333
- console.log("[sdk-bridge] Calling onQueryComplete for session " + session.localId + " (title: " + (session.title || "?") + ")");
1334
- try {
1335
- session.onQueryComplete(session);
1336
- } catch (err) {
1337
- console.error("[sdk-bridge] onQueryComplete error:", err.message || err);
1332
+ // Ralph Loop: notify completion so loop orchestrator can proceed
1333
+ if (session.onQueryComplete) {
1334
+ console.log("[sdk-bridge] Calling onQueryComplete for session " + session.localId + " (title: " + (session.title || "?") + ")");
1335
+ try {
1336
+ session.onQueryComplete(session);
1337
+ } catch (err) {
1338
+ console.error("[sdk-bridge] onQueryComplete error:", err.message || err);
1339
+ }
1338
1340
  }
1339
1341
  }
1340
1342
  }