clay-server 4.1.0-beta.4 → 4.1.0-beta.5

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/lib/public/app.js CHANGED
@@ -30,7 +30,7 @@ import { initGitPanel } from './modules/git-panel.js';
30
30
  import { initWorkerPaneLock } from './modules/worker-pane-lock.js';
31
31
  import { initTerminal, openTerminal, closeTerminal, resetTerminals, handleTermList, handleTermCreated, handleTermOutput, handleTermResized, handleTermExited, handleTermClosed, sendTerminalCommand } from './modules/terminal.js';
32
32
  import { initContextSources, updateTerminalList, updateBrowserTabList, handleContextSourcesState, getActiveSources, hasActiveSources } from './modules/context-sources.js';
33
- import { initStickyNotes, handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted, hideNotes, showNotes, isNotesVisible, createNote, setBrowserRefresh } from './modules/sticky-notes.js';
33
+ import { initStickyNotes, handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted, hideNotes, showNotes, isNotesVisible, createNote, setBrowserRefresh, toggleNotesTemporaryVisibility } from './modules/sticky-notes.js';
34
34
  import { initNotesBrowser, openNotesBrowser, closeNotesBrowser, isNotesBrowserOpen, renderNotesBrowser, registerExclusiveClosers } from './modules/sticky-notes-browser.js';
35
35
  import { initTheme, getThemeColor, getComputedVar, onThemeChange, getCurrentTheme, getChatLayout } from './modules/theme.js';
36
36
  import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionResolved, markPermissionCancelled, renderElicitationRequest, markElicitationResolved, renderPlanBanner, renderPlanCard, handleTodoWrite, handleTaskCreate, handleTaskUpdate, startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, addTurnMeta, resetTurnMetaCost, enableMainInput, getTools, getPlanContent, setPlanContent, isPlanFilePath, getTodoTools, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, updateSubagentProgress, initSubagentStop, closeToolGroup, removeToolFromGroup } from './modules/tools.js';
@@ -269,6 +269,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
269
269
  splitPanes: null,
270
270
  splitGroups: [],
271
271
  splitDelegations: {},
272
+ notesTemporarilyHidden: false,
272
273
  dmMode: false,
273
274
  historyFrom: 0,
274
275
  loadingMore: false,
@@ -1133,6 +1134,13 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
1133
1134
  });
1134
1135
  }
1135
1136
 
1137
+ var stickyNotesToggleButtons = document.querySelectorAll("#sticky-notes-toggle-btn");
1138
+ for (var stickyToggleIndex = 0; stickyToggleIndex < stickyNotesToggleButtons.length; stickyToggleIndex++) {
1139
+ stickyNotesToggleButtons[stickyToggleIndex].addEventListener("click", function () {
1140
+ toggleNotesTemporaryVisibility();
1141
+ });
1142
+ }
1143
+
1136
1144
  // Sticky Notes badge click → archive toggle
1137
1145
  var stickyNotesBadge = $("sticky-notes-sidebar-count");
1138
1146
  if (stickyNotesBadge) {
@@ -2790,7 +2790,6 @@ body.mate-dm-active .activity-inline:not(.mention-activity-bar):not(.mate-pre-ac
2790
2790
  body.mate-dm-active .title-bar-content #todo-sticky,
2791
2791
  body.mate-dm-active .title-bar-content #ralph-sticky,
2792
2792
  body.mate-dm-active .dm-header-bar,
2793
- body.mate-dm-active .title-bar-content .status #sticky-notes-toggle-btn,
2794
2793
  body.mate-dm-active .title-bar-content .status #terminal-toggle-btn {
2795
2794
  display: none !important;
2796
2795
  }
@@ -13,6 +13,16 @@
13
13
 
14
14
  #sticky-notes-container.hidden { display: none; }
15
15
 
16
+ .sticky-note.sticky-note-flight-away {
17
+ pointer-events: none;
18
+ transform: var(--sticky-note-flight);
19
+ transition: transform var(--sticky-note-flight-duration, 240ms) cubic-bezier(0.55, 0, 1, 0.45);
20
+ }
21
+
22
+ .sticky-note:not(.sticky-note-flight-away) {
23
+ transition: transform var(--sticky-note-flight-duration, 320ms) cubic-bezier(0.22, 1.25, 0.36, 1), box-shadow 0.15s;
24
+ }
25
+
16
26
  /* --- Toggle button (in title bar, matches terminal-toggle-btn) --- */
17
27
  #sticky-notes-toggle-btn {
18
28
  display: flex;
@@ -827,11 +837,16 @@
827
837
  50% { box-shadow: 0 0 0 6px rgba(var(--accent-rgb, 99, 102, 241), 0); }
828
838
  }
829
839
 
830
- /* --- Mobile: hide entirely --- */
840
+ /* --- Mobile browser sizing --- */
831
841
  @media (max-width: 768px) {
832
- #sticky-notes-container,
833
- #sticky-notes-toggle-btn {
834
- display: none !important;
842
+ /* The shared title-bar control remains the sole hide/show affordance on
843
+ mobile; the canvas itself must stay mountable and viewport-bounded. */
844
+ #sticky-notes-toggle-btn { display: flex; }
845
+
846
+ .sticky-note {
847
+ min-width: min(160px, calc(100% - 16px));
848
+ max-width: calc(100% - 16px);
849
+ max-height: calc(100% - 16px);
835
850
  }
836
851
 
837
852
  .notes-browser-grid {
@@ -841,3 +856,8 @@
841
856
 
842
857
  .notes-browser-topbar { padding: 10px 12px; }
843
858
  }
859
+
860
+ @media (prefers-reduced-motion: reduce) {
861
+ .sticky-note.sticky-note-flight-away,
862
+ .sticky-note:not(.sticky-note-flight-away) { transition-duration: 0ms; }
863
+ }
@@ -427,6 +427,7 @@
427
427
  </button>
428
428
  <button id="debate-pdf-btn" class="hidden" title="Export debate as PDF"><i data-lucide="download"></i></button>
429
429
  <button id="find-in-session-btn" title="Search in session (Ctrl+F)"><i data-lucide="search"></i></button>
430
+ <button type="button" id="sticky-notes-toggle-btn" aria-label="Hide sticky notes" aria-pressed="false" title="Hide sticky notes"><i data-lucide="eye-off"></i></button>
430
431
  <button id="terminal-toggle-btn" title="Terminal"><i data-lucide="square-terminal"></i><span id="terminal-count" class="hidden"></span></button>
431
432
  </div>
432
433
  </div>
@@ -303,7 +303,7 @@ export function closeNotesBrowser() {
303
303
  var sidebarBtn = document.getElementById("sticky-notes-sidebar-btn");
304
304
  if (sidebarBtn) sidebarBtn.classList.remove("active");
305
305
  // The canvas comes back so the open notes are reachable again.
306
- showNotes();
306
+ showNotes(false);
307
307
  }
308
308
 
309
309
  export function isNotesBrowserOpen() {
@@ -11,9 +11,11 @@ import { enhanceClayLogLinks } from './clay-log-links.js';
11
11
  import { isClosedNote, send, clampPos, clearNoteTimers, syncTitle } from './sticky-notes-shared.js';
12
12
  import { renderNote, closeColorPicker } from './sticky-notes-card.js';
13
13
  import { closeFormatToolbar, isFormatToolbarOpen } from './sticky-notes-editor.js';
14
+ import { store } from './store.js';
14
15
 
15
16
  var notes = new Map(); // id -> { data, el }
16
17
  var notesVisible = false;
18
+ var notesResizeObserver = null;
17
19
 
18
20
  // The canvas shows open notes only. Closed notes leave the canvas and live in
19
21
  // the browser's Closed tab until they are reopened.
@@ -37,6 +39,93 @@ function refreshBrowser() {
37
39
  if (browserRefresh) browserRefresh();
38
40
  }
39
41
 
42
+ function prefersReducedMotion() {
43
+ return window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
44
+ }
45
+
46
+ function setFlightAccessibility(el, hidden) {
47
+ el.inert = hidden;
48
+ el.setAttribute("aria-hidden", hidden ? "true" : "false");
49
+ }
50
+
51
+ function nearestEdgeTransform(el, container) {
52
+ // offsetLeft/Top and offsetWidth/Height are layout geometry, unaffected by
53
+ // an in-flight transform. Never derive the next vector from a translated
54
+ // boundingClientRect or repeated hide/show cycles will accumulate distance.
55
+ var left = el.offsetLeft;
56
+ var top = el.offsetTop;
57
+ var width = el.offsetWidth;
58
+ var height = el.offsetHeight;
59
+ var containerWidth = container.clientWidth;
60
+ var containerHeight = container.clientHeight;
61
+ var edges = [
62
+ { name: "left", distance: left, transform: "translateX(" + -(left + width + 20) + "px)" },
63
+ { name: "right", distance: containerWidth - left - width, transform: "translateX(" + (containerWidth - left + 20) + "px)" },
64
+ { name: "top", distance: top, transform: "translateY(" + -(top + height + 20) + "px)" },
65
+ { name: "bottom", distance: containerHeight - top - height, transform: "translateY(" + (containerHeight - top + 20) + "px)" },
66
+ ];
67
+ edges.sort(function (a, b) { return a.distance - b.distance; });
68
+ return edges[0];
69
+ }
70
+
71
+ function animateNote(el, hidden, immediate) {
72
+ if (!el || el.classList.contains("hidden")) return;
73
+ var container = document.getElementById("sticky-notes-container");
74
+ if (!container) return;
75
+ if (hidden) {
76
+ var edge = nearestEdgeTransform(el, container);
77
+ el.style.setProperty("--sticky-note-flight", edge.transform);
78
+ el.style.setProperty("--sticky-note-flight-duration", prefersReducedMotion() || immediate ? "0ms" : "240ms");
79
+ el.classList.add("sticky-note-flight-away");
80
+ setFlightAccessibility(el, true);
81
+ return;
82
+ }
83
+ el.style.setProperty("--sticky-note-flight-duration", prefersReducedMotion() || immediate ? "0ms" : "320ms");
84
+ el.classList.remove("sticky-note-flight-away");
85
+ setFlightAccessibility(el, false);
86
+ }
87
+
88
+ function syncFlightState(el, immediate) {
89
+ if (store.get("notesTemporarilyHidden")) animateNote(el, true, immediate);
90
+ else animateNote(el, false, immediate);
91
+ }
92
+
93
+ function recomputeFlightGeometry() {
94
+ if (!notesVisible) return;
95
+ reclampAllNotes();
96
+ if (store.get("notesTemporarilyHidden")) {
97
+ notes.forEach(function (entry) { if (entry && entry.el) syncFlightState(entry.el, true); });
98
+ }
99
+ }
100
+
101
+ function syncFlightControls() {
102
+ var buttons = document.querySelectorAll("#sticky-notes-toggle-btn");
103
+ var hidden = !!store.get("notesTemporarilyHidden");
104
+ var label = hidden ? "Show sticky notes" : "Hide sticky notes";
105
+ for (var i = 0; i < buttons.length; i++) {
106
+ buttons[i].setAttribute("aria-label", label);
107
+ buttons[i].setAttribute("aria-pressed", hidden ? "true" : "false");
108
+ buttons[i].title = label;
109
+ var icon = buttons[i].querySelector("[data-lucide]");
110
+ if (icon) icon.setAttribute("data-lucide", hidden ? "eye" : "eye-off");
111
+ }
112
+ refreshIcons();
113
+ }
114
+
115
+ export function toggleNotesTemporaryVisibility() {
116
+ var hidden = !store.get("notesTemporarilyHidden");
117
+ if (hidden) {
118
+ closeColorPicker();
119
+ closeFormatToolbar();
120
+ var active = document.activeElement;
121
+ var container = document.getElementById("sticky-notes-container");
122
+ if (container && active && container.contains(active) && active.blur) active.blur();
123
+ }
124
+ store.set({ notesTemporarilyHidden: hidden });
125
+ notes.forEach(function (entry) { if (entry && entry.el) syncFlightState(entry.el); });
126
+ syncFlightControls();
127
+ }
128
+
40
129
  // Read-only view of every note the client knows about, for the browser pane.
41
130
  export function listNoteData() {
42
131
  var out = [];
@@ -67,6 +156,7 @@ function reclampAllNotes() {
67
156
  var c = clampPos(curX, curY, noteW, noteH);
68
157
  el.style.left = c.x + "px";
69
158
  el.style.top = c.y + "px";
159
+ if (store.get("notesTemporarilyHidden")) syncFlightState(el, true);
70
160
  });
71
161
  }
72
162
 
@@ -86,17 +176,29 @@ export function initStickyNotes() {
86
176
  if (notesVisible && notes.size > 0) {
87
177
  reclampAllNotes();
88
178
  }
179
+ if (store.get("notesTemporarilyHidden")) notes.forEach(function (entry) { if (entry && entry.el) syncFlightState(entry.el, true); });
89
180
  }, 100);
90
181
  });
91
182
 
183
+ var canvas = document.getElementById("sticky-notes-container");
184
+ if (canvas && typeof ResizeObserver === "function") {
185
+ notesResizeObserver = new ResizeObserver(function () {
186
+ recomputeFlightGeometry();
187
+ });
188
+ notesResizeObserver.observe(canvas);
189
+ }
190
+
92
191
  }
93
192
 
94
193
  // --- Visibility ---
95
194
 
96
- export function showNotes() {
195
+ export function showNotes(restoreTemporary) {
97
196
  notesVisible = true;
197
+ if (restoreTemporary !== false) store.set({ notesTemporarilyHidden: false });
98
198
  var container = document.getElementById("sticky-notes-container");
99
199
  if (container) container.classList.remove("hidden");
200
+ notes.forEach(function (entry) { if (entry && entry.el) syncFlightState(entry.el); });
201
+ syncFlightControls();
100
202
  }
101
203
 
102
204
  export function hideNotes() {
@@ -104,6 +206,8 @@ export function hideNotes() {
104
206
  var container = document.getElementById("sticky-notes-container");
105
207
  if (container) container.classList.add("hidden");
106
208
  closeColorPicker();
209
+ notes.forEach(function (entry) { if (entry && entry.el) syncFlightState(entry.el, true); });
210
+ syncFlightControls();
107
211
  }
108
212
 
109
213
  export function createNote() {
@@ -158,6 +262,7 @@ export function handleNotesList(msg) {
158
262
  var el = renderNote(data);
159
263
  notes.set(data.id, { data: data, el: el });
160
264
  container.appendChild(el);
265
+ syncFlightState(el, true);
161
266
  created = true;
162
267
  }
163
268
 
@@ -181,11 +286,14 @@ export function handleNotesList(msg) {
181
286
  refreshBrowser();
182
287
 
183
288
  // Auto-show only when something is actually asking for attention. A board of
184
- // nothing but closed notes must not pop the canvas open.
289
+ // nothing but closed notes must not pop the canvas open. Temporary hiding is
290
+ // explicit view state and must survive reconnects and list updates.
185
291
  if (openCount() > 0 && !notesVisible) {
186
292
  notesVisible = true;
187
293
  container.classList.remove("hidden");
188
294
  }
295
+ if (created) reclampAllNotes();
296
+ syncFlightControls();
189
297
  }
190
298
 
191
299
  export function handleNoteCreated(msg) {
@@ -198,15 +306,18 @@ export function handleNoteCreated(msg) {
198
306
  var el = renderNote(msg.note);
199
307
  notes.set(msg.note.id, { data: msg.note, el: el });
200
308
  container.appendChild(el);
309
+ syncFlightState(el, true);
201
310
  updateBadge();
202
311
  updateSidebarBadge();
203
312
  refreshBrowser();
204
313
 
205
- // Show container if hidden
206
- if (!notesVisible) {
314
+ // Show container if hidden, unless the user explicitly flew the notes out.
315
+ if (!notesVisible && !store.get("notesTemporarilyHidden")) {
207
316
  notesVisible = true;
208
317
  container.classList.remove("hidden");
209
318
  }
319
+ reclampAllNotes();
320
+ syncFlightControls();
210
321
  }
211
322
 
212
323
  // Patch one existing note element in place from a server payload.
@@ -258,6 +369,7 @@ function patchNote(entry, next) {
258
369
 
259
370
  if (isClosedNote(next)) el.classList.add("hidden");
260
371
  else el.classList.remove("hidden");
372
+ syncFlightState(el, true);
261
373
 
262
374
  // The minimize button swaps its icon, which is the only path here that can
263
375
  // introduce fresh Lucide markup.
@@ -288,6 +400,7 @@ export function handleNoteUpdated(msg) {
288
400
  if (!entry) return;
289
401
 
290
402
  if (patchNote(entry, msg.note)) refreshIcons();
403
+ reclampAllNotes();
291
404
 
292
405
  // A close or reopen arrives as an ordinary update, so the canvas count and
293
406
  // the browser both follow from the same message.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "4.1.0-beta.4",
3
+ "version": "4.1.0-beta.5",
4
4
  "description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",