clay-server 3.6.0-beta.1 → 3.6.0

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.
Files changed (58) hide show
  1. package/README.md +54 -196
  2. package/bin/cli.js +7 -42
  3. package/lib/builtin-mates.js +1 -1
  4. package/lib/cli-wordmark.js +32 -0
  5. package/lib/pages.js +47 -35
  6. package/lib/public/app.js +2 -10
  7. package/lib/public/apple-touch-icon.png +0 -0
  8. package/lib/public/clay-studio-favicon-32.png +0 -0
  9. package/lib/public/clay-studio-symbol.png +0 -0
  10. package/lib/public/clay-studio-wordmark.svg +20 -0
  11. package/lib/public/css/base.css +143 -61
  12. package/lib/public/css/filebrowser.css +54 -8
  13. package/lib/public/css/icon-strip.css +33 -19
  14. package/lib/public/css/input.css +15 -5
  15. package/lib/public/css/messages.css +17 -14
  16. package/lib/public/css/mobile-nav.css +5 -5
  17. package/lib/public/css/overlays.css +58 -6
  18. package/lib/public/css/pane.css +0 -1
  19. package/lib/public/css/session-actions.css +9 -3
  20. package/lib/public/css/sidebar.css +104 -161
  21. package/lib/public/css/title-bar.css +51 -18
  22. package/lib/public/css/user-settings.css +114 -8
  23. package/lib/public/icon-192.png +0 -0
  24. package/lib/public/icon-512.png +0 -0
  25. package/lib/public/index.html +32 -76
  26. package/lib/public/manifest.json +1 -6
  27. package/lib/public/modules/app-connection.js +0 -3
  28. package/lib/public/modules/app-favicon.js +22 -29
  29. package/lib/public/modules/app-messages.js +1 -2
  30. package/lib/public/modules/app-notifications.js +3 -3
  31. package/lib/public/modules/command-palette.js +1 -1
  32. package/lib/public/modules/server-settings.js +0 -77
  33. package/lib/public/modules/sidebar-projects.js +1 -1
  34. package/lib/public/modules/sidebar-sessions.js +18 -5
  35. package/lib/public/modules/sidebar.js +2 -2
  36. package/lib/public/modules/theme.js +88 -249
  37. package/lib/public/modules/user-settings.js +2 -3
  38. package/lib/public/style.css +10 -10
  39. package/lib/public/sw.js +4 -2
  40. package/lib/themes/clay-dark.json +11 -0
  41. package/lib/themes/clay-light.json +11 -0
  42. package/package.json +1 -1
  43. package/lib/public/apple-touch-icon-dark.png +0 -0
  44. package/lib/public/clay-logo.png +0 -0
  45. package/lib/public/favicon-banded-32.png +0 -0
  46. package/lib/public/favicon-banded.png +0 -0
  47. package/lib/public/favicon-dark.svg +0 -1
  48. package/lib/public/favicon.svg +0 -1
  49. package/lib/public/icon-192-dark.png +0 -0
  50. package/lib/public/icon-512-dark.png +0 -0
  51. package/lib/public/icon-banded-76.png +0 -0
  52. package/lib/public/icon-banded-96.png +0 -0
  53. package/lib/public/icon-mono.svg +0 -1
  54. package/lib/public/modules/ascii-logo.js +0 -442
  55. package/lib/public/wordmark-banded-20.png +0 -0
  56. package/lib/public/wordmark-banded-32.png +0 -0
  57. package/lib/public/wordmark-banded-64.png +0 -0
  58. package/lib/public/wordmark-banded-80.png +0 -0
@@ -1,4 +1,4 @@
1
- import { iconHtml, refreshIcons } from './icons.js';
1
+ import { refreshIcons } from './icons.js';
2
2
  import { setTerminalTheme } from './terminal.js';
3
3
  import { setTuiSessionTheme } from './session-tui-view.js';
4
4
  import { setTuiAttentionTheme } from './tui-attention.js';
@@ -58,81 +58,26 @@ function luminance(hex) {
58
58
  return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) / 255;
59
59
  }
60
60
 
61
- // --- Dracula default: exact CSS values for initial render (before API loads) ---
62
- var defaultExactVars = {
63
- "--bg": "#282a36",
64
- "--bg-alt": "#363447",
65
- "--text": "#f0f1f4",
66
- "--text-secondary": "#f8f8f2",
67
- "--text-muted": "#9ea8c7",
68
- "--text-dimmer": "#6272a4",
69
- "--accent": "#ffb86c",
70
- "--accent-hover": "#ffc17e",
71
- "--accent-bg": "rgba(255, 184, 108, 0.12)",
72
- "--code-bg": "#22242e",
73
- "--border": "#44475a",
74
- "--border-subtle": "#333644",
75
- "--input-bg": "#3d3e51",
76
- "--user-bubble": "#404154",
77
- "--error": "#ff5555",
78
- "--success": "#50fa7b",
79
- "--warning": "#f1fa8c",
80
- "--sidebar-bg": "#242631",
81
- "--sidebar-hover": "#2f2f3f",
82
- "--sidebar-active": "#3d3e51",
83
- "--filebrowser-bg": "#2e3042",
84
- "--filebrowser-border": "#383a4d",
85
- "--accent-8": "rgba(255, 184, 108, 0.08)",
86
- "--accent-12": "rgba(255, 184, 108, 0.12)",
87
- "--accent-15": "rgba(255, 184, 108, 0.15)",
88
- "--accent-20": "rgba(255, 184, 108, 0.20)",
89
- "--accent-25": "rgba(255, 184, 108, 0.25)",
90
- "--accent-30": "rgba(255, 184, 108, 0.30)",
91
- "--accent2": "#80bfff",
92
- "--accent2-hover": "#8fc7ff",
93
- "--accent2-bg": "rgba(128, 191, 255, 0.12)",
94
- "--accent2-8": "rgba(128, 191, 255, 0.08)",
95
- "--accent2-12": "rgba(128, 191, 255, 0.12)",
96
- "--accent2-15": "rgba(128, 191, 255, 0.15)",
97
- "--accent2-20": "rgba(128, 191, 255, 0.20)",
98
- "--accent2-25": "rgba(128, 191, 255, 0.25)",
99
- "--accent2-30": "rgba(128, 191, 255, 0.30)",
100
- "--error-8": "rgba(255, 85, 85, 0.08)",
101
- "--error-12": "rgba(255, 85, 85, 0.12)",
102
- "--error-15": "rgba(255, 85, 85, 0.15)",
103
- "--error-25": "rgba(255, 85, 85, 0.25)",
104
- "--success-8": "rgba(80, 250, 123, 0.08)",
105
- "--success-12": "rgba(80, 250, 123, 0.12)",
106
- "--success-15": "rgba(80, 250, 123, 0.15)",
107
- "--success-25": "rgba(80, 250, 123, 0.25)",
108
- "--warning-bg": "rgba(241, 250, 140, 0.12)",
109
- "--overlay-rgb": "255,255,255",
110
- "--shadow-rgb": "0,0,0",
111
- "--hl-comment": "#6272a4",
112
- "--hl-keyword": "#ff79c6",
113
- "--hl-string": "#50fa7b",
114
- "--hl-number": "#ffb86c",
115
- "--hl-function": "#80bfff",
116
- "--hl-variable": "#ff5555",
117
- "--hl-type": "#f1fa8c",
118
- "--hl-constant": "#ffb86c",
119
- "--hl-tag": "#ff5555",
120
- "--hl-attr": "#80bfff",
121
- "--hl-regexp": "#8be9fd",
122
- "--hl-meta": "#bd93f9",
123
- "--hl-builtin": "#ffb86c",
124
- "--hl-symbol": "#bd93f9",
125
- "--hl-addition": "#50fa7b",
126
- "--hl-deletion": "#ff5555"
61
+ var DEFAULT_DARK_THEME_ID = "clay-dark";
62
+ var DEFAULT_LIGHT_THEME_ID = "clay-light";
63
+
64
+ // Embedded defaults keep the first render branded before /api/themes loads.
65
+ var defaultDarkFallback = {
66
+ name: "Clay Studio Dark", variant: "dark",
67
+ base00: "171715", base01: "20201D", base02: "393934", base03: "686861",
68
+ base04: "A0A098", base05: "D1D1CA", base06: "E8E8E2", base07: "FAFAF5",
69
+ base08: "FF7479", base09: "7776FF", base0A: "E0B45B", base0B: "07E5A3",
70
+ base0C: "5FD2C0", base0D: "8AA7FF", base0E: "C39AF3", base0F: "D69A7D",
71
+ accent2: "07E5A3", link: "7F7EC8"
127
72
  };
128
73
 
129
- // Minimal Dracula palette for getThemeColor before API loads
130
- var defaultFallback = {
131
- name: "Dracula", variant: "dark",
132
- base00: "282a36", base01: "363447", base02: "44475a", base03: "6272a4",
133
- base04: "9ea8c7", base05: "f8f8f2", base06: "f0f1f4", base07: "ffffff",
134
- base08: "ff5555", base09: "ffb86c", base0A: "f1fa8c", base0B: "50fa7b",
135
- base0C: "8be9fd", base0D: "80bfff", base0E: "ff79c6", base0F: "bd93f9"
74
+ var defaultLightFallback = {
75
+ name: "Clay Studio Light", variant: "light",
76
+ base00: "F7F7F3", base01: "EEEEEA", base02: "D7D7D0", base03: "A2A29B",
77
+ base04: "73736C", base05: "44443F", base06: "252522", base07: "151513",
78
+ base08: "C84B50", base09: "5857FC", base0A: "956A19", base0B: "087B59",
79
+ base0C: "187A7C", base0D: "3F62B4", base0E: "7A52A8", base0F: "9A5A46",
80
+ accent2: "07E5A3", link: "4948B8"
136
81
  };
137
82
 
138
83
  // --- Compute CSS variables from a base16 palette ---
@@ -146,6 +91,7 @@ function computeVars(theme) {
146
91
 
147
92
  var isLight = theme.variant === "light";
148
93
  var accent2 = theme.accent2 ? "#" + theme.accent2 : b.base0D;
94
+ var link = theme.link ? "#" + theme.link : b.base0D;
149
95
 
150
96
  return {
151
97
  "--bg": b.base00,
@@ -157,6 +103,8 @@ function computeVars(theme) {
157
103
  "--accent": b.base09,
158
104
  "--accent-hover": isLight ? darken(b.base09, 0.12) : lighten(b.base09, 0.12),
159
105
  "--accent-bg": hexToRgba(b.base09, 0.12),
106
+ "--link": link,
107
+ "--link-hover": isLight ? darken(link, 0.10) : lighten(link, 0.10),
160
108
  "--code-bg": isLight ? darken(b.base00, 0.03) : darken(b.base00, 0.15),
161
109
  "--border": b.base02,
162
110
  "--border-subtle": mixColors(b.base00, b.base02, 0.6),
@@ -216,6 +164,8 @@ function computeVars(theme) {
216
164
  };
217
165
  }
218
166
 
167
+ var defaultExactVars = computeVars(defaultDarkFallback);
168
+
219
169
  function computeTerminalTheme(theme) {
220
170
  var b = {};
221
171
  var keys = ["base00","base01","base02","base03","base04","base05","base06","base07",
@@ -250,7 +200,7 @@ function computeTerminalTheme(theme) {
250
200
  }
251
201
 
252
202
  function computeMermaidVars(theme) {
253
- var vars = currentThemeId === "dracula" ? defaultExactVars : computeVars(theme);
203
+ var vars = currentThemeId === DEFAULT_DARK_THEME_ID ? defaultExactVars : computeVars(theme);
254
204
  var isLight = theme.variant === "light";
255
205
  return {
256
206
  darkMode: !isLight,
@@ -269,7 +219,7 @@ function computeMermaidVars(theme) {
269
219
  var themes = {};
270
220
  var customSet = {}; // ids that came from ~/.clay/themes/
271
221
  var themesLoaded = false;
272
- var currentThemeId = "dracula";
222
+ var currentThemeId = DEFAULT_DARK_THEME_ID;
273
223
  var changeCallbacks = [];
274
224
  var STORAGE_KEY = "clay-theme";
275
225
  var MODE_KEY = "clay-mode"; // "light" | "dark" | null (system)
@@ -279,7 +229,10 @@ var WIDE_KEY = "clay-wide-view"; // cached from server, "bubble" | "channel"
279
229
  // --- Helpers ---
280
230
 
281
231
  function getTheme(id) {
282
- return themes[id] || (id === "dracula" ? defaultFallback : null);
232
+ if (themes[id]) return themes[id];
233
+ if (id === DEFAULT_DARK_THEME_ID) return defaultDarkFallback;
234
+ if (id === DEFAULT_LIGHT_THEME_ID) return defaultLightFallback;
235
+ return null;
283
236
  }
284
237
 
285
238
  function isCustom(id) {
@@ -289,7 +242,7 @@ function isCustom(id) {
289
242
  // --- Public API ---
290
243
 
291
244
  export function getCurrentTheme() {
292
- return getTheme(currentThemeId) || defaultFallback;
245
+ return getTheme(currentThemeId) || defaultDarkFallback;
293
246
  }
294
247
 
295
248
  export function getThemeId() {
@@ -302,7 +255,7 @@ export function getThemeColor(baseKey) {
302
255
  }
303
256
 
304
257
  export function getComputedVar(varName) {
305
- if (currentThemeId === "dracula" && !themesLoaded) return defaultExactVars[varName] || "";
258
+ if (currentThemeId === DEFAULT_DARK_THEME_ID && !themesLoaded) return defaultExactVars[varName] || "";
306
259
  var theme = getCurrentTheme();
307
260
  var vars = computeVars(theme);
308
261
  return vars[varName] || "";
@@ -330,11 +283,11 @@ export function getThemes() {
330
283
 
331
284
  export function applyTheme(themeId, fromPicker) {
332
285
  var theme = getTheme(themeId);
333
- if (!theme) themeId = "dracula";
286
+ if (!theme) themeId = getEffectiveMode() === "light" ? DEFAULT_LIGHT_THEME_ID : DEFAULT_DARK_THEME_ID;
334
287
  theme = getTheme(themeId);
335
288
  currentThemeId = themeId;
336
289
 
337
- var vars = (themeId === "dracula" && !themesLoaded) ? defaultExactVars : computeVars(theme);
290
+ var vars = (themeId === DEFAULT_DARK_THEME_ID && !themesLoaded) ? defaultExactVars : computeVars(theme);
338
291
  var root = document.documentElement;
339
292
  var varNames = Object.keys(vars);
340
293
  for (var i = 0; i < varNames.length; i++) {
@@ -383,8 +336,8 @@ export function applyTheme(themeId, fromPicker) {
383
336
 
384
337
  // --- Favicon update on theme change ---
385
338
  function updateMascotSvgs(vars, isLight) {
386
- var faviconEl = document.querySelector('link[rel="icon"][type="image/png"]');
387
- if (faviconEl) faviconEl.setAttribute("href", "favicon-banded.png");
339
+ var faviconEl = document.querySelector('link[rel="icon"]');
340
+ if (faviconEl) faviconEl.setAttribute("href", "clay-studio-favicon-32.png");
388
341
  }
389
342
 
390
343
  // --- Theme loading from server ---
@@ -412,8 +365,9 @@ function loadThemes() {
412
365
  }
413
366
  }
414
367
 
415
- // Ensure dracula always exists
416
- if (!themes.dracula) themes.dracula = defaultFallback;
368
+ // Keep both Clay Studio defaults available even if a bundled file is missing.
369
+ if (!themes[DEFAULT_DARK_THEME_ID]) themes[DEFAULT_DARK_THEME_ID] = defaultDarkFallback;
370
+ if (!themes[DEFAULT_LIGHT_THEME_ID]) themes[DEFAULT_LIGHT_THEME_ID] = defaultLightFallback;
417
371
 
418
372
  themesLoaded = true;
419
373
 
@@ -424,9 +378,12 @@ function loadThemes() {
424
378
  // (before this, only defaultExactVars was used as fallback)
425
379
  applyTheme(currentThemeId);
426
380
  }).catch(function () {
427
- // API unavailable — keep dracula fallback
428
- themes.dracula = defaultFallback;
381
+ // API unavailable — keep the embedded Clay Studio pair.
382
+ themes[DEFAULT_DARK_THEME_ID] = defaultDarkFallback;
383
+ themes[DEFAULT_LIGHT_THEME_ID] = defaultLightFallback;
429
384
  themesLoaded = true;
385
+ if (pickerEl) rebuildPicker();
386
+ applyTheme(currentThemeId);
430
387
  });
431
388
  }
432
389
 
@@ -469,14 +426,14 @@ function themeIdForMode(mode) {
469
426
  var skin = null;
470
427
  try { skin = localStorage.getItem(SKIN_KEY); } catch (e) {}
471
428
 
472
- // Default skin pair: clay (dark) / clay-light (light)
429
+ // Default Clay Studio skin pair.
473
430
  if (!skin) {
474
- return mode === "light" ? "ayu-light" : "dracula";
431
+ return mode === "light" ? DEFAULT_LIGHT_THEME_ID : DEFAULT_DARK_THEME_ID;
475
432
  }
476
433
 
477
434
  // Custom skin — try to find the counterpart
478
435
  var current = getTheme(skin);
479
- if (!current) return mode === "light" ? "ayu-light" : "dracula";
436
+ if (!current) return mode === "light" ? DEFAULT_LIGHT_THEME_ID : DEFAULT_DARK_THEME_ID;
480
437
 
481
438
  // Already the right variant?
482
439
  if (current.variant === mode) return skin;
@@ -489,10 +446,10 @@ function themeIdForMode(mode) {
489
446
 
490
447
  if (mode === "light") {
491
448
  if (lightId && themes[lightId]) return lightId;
492
- return "ayu-light";
449
+ return DEFAULT_LIGHT_THEME_ID;
493
450
  } else {
494
451
  if (darkId && themes[darkId]) return darkId;
495
- return "dracula";
452
+ return DEFAULT_DARK_THEME_ID;
496
453
  }
497
454
  }
498
455
 
@@ -616,58 +573,36 @@ function buildPickerContent() {
616
573
  }
617
574
  }
618
575
 
619
- // Clay default themes always first in their section
576
+ // Clay Studio default themes always first in their section.
620
577
  function pinFirst(arr, pinId) {
621
578
  var idx = arr.indexOf(pinId);
622
579
  if (idx > 0) { arr.splice(idx, 1); arr.unshift(pinId); }
623
580
  }
624
- pinFirst(darkIds, "dracula");
625
- pinFirst(lightIds, "ayu-light");
626
-
627
- // Dark section
628
- if (darkIds.length > 0) {
629
- var darkHeader = document.createElement("div");
630
- darkHeader.className = "theme-picker-header";
631
- darkHeader.textContent = "Dark";
632
- pickerEl.appendChild(darkHeader);
633
-
634
- var darkList = document.createElement("div");
635
- darkList.className = "theme-picker-section";
636
- for (var d = 0; d < darkIds.length; d++) {
637
- darkList.appendChild(createThemeItem(darkIds[d], themes[darkIds[d]]));
581
+ pinFirst(darkIds, DEFAULT_DARK_THEME_ID);
582
+ pinFirst(lightIds, DEFAULT_LIGHT_THEME_ID);
583
+
584
+ function appendThemeGroup(label, ids) {
585
+ if (ids.length === 0) return;
586
+ var group = document.createElement("div");
587
+ group.className = "theme-picker-group";
588
+
589
+ var header = document.createElement("div");
590
+ header.className = "theme-picker-header";
591
+ header.textContent = label;
592
+ group.appendChild(header);
593
+
594
+ var list = document.createElement("div");
595
+ list.className = "theme-picker-section";
596
+ for (var j = 0; j < ids.length; j++) {
597
+ list.appendChild(createThemeItem(ids[j], themes[ids[j]]));
638
598
  }
639
- pickerEl.appendChild(darkList);
599
+ group.appendChild(list);
600
+ pickerEl.appendChild(group);
640
601
  }
641
602
 
642
- // Light section
643
- if (lightIds.length > 0) {
644
- var lightHeader = document.createElement("div");
645
- lightHeader.className = "theme-picker-header";
646
- lightHeader.textContent = "Light";
647
- pickerEl.appendChild(lightHeader);
648
-
649
- var lightList = document.createElement("div");
650
- lightList.className = "theme-picker-section";
651
- for (var l = 0; l < lightIds.length; l++) {
652
- lightList.appendChild(createThemeItem(lightIds[l], themes[lightIds[l]]));
653
- }
654
- pickerEl.appendChild(lightList);
655
- }
656
-
657
- // Custom section
658
- if (customIds.length > 0) {
659
- var customHeader = document.createElement("div");
660
- customHeader.className = "theme-picker-header";
661
- customHeader.textContent = "Custom";
662
- pickerEl.appendChild(customHeader);
663
-
664
- var customList = document.createElement("div");
665
- customList.className = "theme-picker-section";
666
- for (var c = 0; c < customIds.length; c++) {
667
- customList.appendChild(createThemeItem(customIds[c], themes[customIds[c]]));
668
- }
669
- pickerEl.appendChild(customList);
670
- }
603
+ appendThemeGroup("Dark", darkIds);
604
+ appendThemeGroup("Light", lightIds);
605
+ appendThemeGroup("Custom", customIds);
671
606
  }
672
607
 
673
608
  function createThemePicker() {
@@ -681,34 +616,34 @@ function createThemePicker() {
681
616
  return pickerEl;
682
617
  }
683
618
 
619
+ export function mountThemePicker(container) {
620
+ if (!container) return;
621
+ var picker = createThemePicker();
622
+ picker.classList.add("theme-picker-embedded");
623
+ if (picker.parentNode !== container) container.appendChild(picker);
624
+ rebuildPicker();
625
+ }
626
+
684
627
  function rebuildPicker() {
685
628
  if (!pickerEl) return;
686
629
  buildPickerContent();
687
630
  }
688
631
 
689
- var pickerVisible = false;
690
-
691
- function togglePicker() {
692
- // Legacy — no longer used as floating popover
693
- // Picker is now embedded in server settings
694
- }
695
-
696
- function closePicker() {
697
- // Legacy — no longer needed
698
- }
699
-
700
632
  // --- Init ---
701
633
  export function initTheme() {
702
- // Determine initial theme from saved mode + skin, or system preference
634
+ // Preserve an explicitly selected skin. Previously auto-applied Ayu Light
635
+ // and Dracula values have no SKIN_KEY, so they migrate to the Clay pair.
703
636
  var saved = null;
637
+ var savedSkin = null;
704
638
  try { saved = localStorage.getItem(STORAGE_KEY); } catch (e) {}
639
+ try { savedSkin = localStorage.getItem(SKIN_KEY); } catch (e) {}
705
640
 
706
- if (saved) {
641
+ if (saved && savedSkin) {
707
642
  currentThemeId = saved;
708
643
  } else {
709
- // No saved theme — use system mode
644
+ // No explicit skin — use the Clay Studio pair in the system mode.
710
645
  var mode = getSystemMode();
711
- currentThemeId = mode === "light" ? "ayu-light" : "dracula";
646
+ currentThemeId = mode === "light" ? DEFAULT_LIGHT_THEME_ID : DEFAULT_DARK_THEME_ID;
712
647
  }
713
648
 
714
649
  // Apply wide view state from localStorage
@@ -717,8 +652,7 @@ export function initTheme() {
717
652
  // Load all themes from server, then apply properly
718
653
  loadThemes();
719
654
 
720
- // Theme toggle is now in User Settings (us-theme-toggle)
721
- // Wired up in user-settings.js initUserSettings()
655
+ // The theme picker is mounted in User Settings by user-settings.js.
722
656
 
723
657
  // Listen for system preference changes (only applies if user has no manual override)
724
658
  if (window.matchMedia) {
@@ -743,99 +677,4 @@ export function initTheme() {
743
677
  // Set initial toggle icon
744
678
  updateToggleIcon();
745
679
 
746
- var islandToggle = document.getElementById("user-theme-toggle-btn");
747
- if (islandToggle && !islandToggle._clayThemeBound) {
748
- islandToggle._clayThemeBound = true;
749
- // Lock down static skin icon (independent of mode) and open the
750
- // theme picker popover anchored to the button.
751
- islandToggle.innerHTML = iconHtml("palette");
752
- islandToggle.title = "Themes";
753
- islandToggle.setAttribute("aria-label", "Themes");
754
- refreshIcons();
755
- islandToggle.addEventListener("click", function (e) {
756
- e.stopPropagation();
757
- toggleIslandThemePicker(islandToggle);
758
- });
759
- }
760
- }
761
-
762
- // --- Floating popover anchored to the user island button ---
763
- //
764
- // Mounts the picker (built by createThemePicker) into document.body as a
765
- // fixed-position overlay aligned under the anchor's right edge. Click
766
- // outside or Escape closes it. The picker DOM is shared with any other
767
- // embed point (settings page); we just reparent and reposition.
768
-
769
- var islandPickerOpen = false;
770
- var islandPickerOutsideHandler = null;
771
- var islandPickerKeyHandler = null;
772
-
773
- function positionPickerNearAnchor(anchorEl) {
774
- if (!pickerEl || !anchorEl) return;
775
- var rect = anchorEl.getBoundingClientRect();
776
- var picker = pickerEl;
777
- // Render hidden first to measure
778
- picker.style.left = "0px";
779
- picker.style.top = "0px";
780
- var pw = picker.offsetWidth || 240;
781
- var ph = picker.offsetHeight || 280;
782
- var vw = window.innerWidth;
783
- var vh = window.innerHeight;
784
- // Right-align to anchor, drop below by 8px. Flip upward if not enough
785
- // room below. Clamp into viewport on both axes.
786
- var left = rect.right - pw;
787
- if (left < 8) left = 8;
788
- if (left + pw > vw - 8) left = vw - pw - 8;
789
- var top = rect.bottom + 8;
790
- if (top + ph > vh - 8) {
791
- var flipped = rect.top - ph - 8;
792
- if (flipped >= 8) top = flipped;
793
- else top = Math.max(8, vh - ph - 8);
794
- }
795
- picker.style.left = left + "px";
796
- picker.style.top = top + "px";
797
- }
798
-
799
- function closeIslandThemePicker() {
800
- if (!islandPickerOpen) return;
801
- islandPickerOpen = false;
802
- if (pickerEl) pickerEl.classList.remove("visible");
803
- if (islandPickerOutsideHandler) {
804
- document.removeEventListener("mousedown", islandPickerOutsideHandler, true);
805
- islandPickerOutsideHandler = null;
806
- }
807
- if (islandPickerKeyHandler) {
808
- document.removeEventListener("keydown", islandPickerKeyHandler, true);
809
- islandPickerKeyHandler = null;
810
- }
811
- }
812
-
813
- export function toggleIslandThemePicker(anchorEl) {
814
- if (islandPickerOpen) {
815
- closeIslandThemePicker();
816
- return;
817
- }
818
- if (!pickerEl) createThemePicker();
819
- if (pickerEl.parentNode !== document.body) {
820
- document.body.appendChild(pickerEl);
821
- }
822
- positionPickerNearAnchor(anchorEl);
823
- pickerEl.classList.add("visible");
824
- islandPickerOpen = true;
825
-
826
- // Click anywhere outside picker (and not the anchor) closes it. Use
827
- // capture phase + mousedown so the click that opened the picker
828
- // doesn't immediately close it.
829
- islandPickerOutsideHandler = function (ev) {
830
- if (!pickerEl) return;
831
- if (pickerEl.contains(ev.target)) return;
832
- if (anchorEl && anchorEl.contains(ev.target)) return;
833
- closeIslandThemePicker();
834
- };
835
- document.addEventListener("mousedown", islandPickerOutsideHandler, true);
836
-
837
- islandPickerKeyHandler = function (ev) {
838
- if (ev.key === "Escape") closeIslandThemePicker();
839
- };
840
- document.addEventListener("keydown", islandPickerKeyHandler, true);
841
680
  }
@@ -3,7 +3,7 @@
3
3
 
4
4
  import { refreshIcons } from './icons.js';
5
5
  import { showToast } from './utils.js';
6
- import { getChatLayout, setChatLayout } from './theme.js';
6
+ import { getChatLayout, mountThemePicker, setChatLayout } from './theme.js';
7
7
  import { applyTerminalFont } from './terminal-prefs.js';
8
8
  import { showEmailSetupModal, getEmailAccountListCache } from './context-sources.js';
9
9
  import { setSTTLang } from './stt.js';
@@ -231,8 +231,7 @@ export function initUserSettings(appCtx) {
231
231
  });
232
232
  }
233
233
 
234
- // Theme picker lives on the user island button now (palette icon).
235
- // No appearance-section mount required.
234
+ mountThemePicker(document.getElementById('us-theme-picker'));
236
235
 
237
236
  // Terminal font picker lives in the title bar (visible only while a
238
237
  // TUI session is active). See lib/public/modules/header-tui-font.js.
@@ -1,16 +1,16 @@
1
- @import url("css/base.css");
2
- @import url("css/icon-strip.css");
3
- @import url("css/title-bar.css?v=20260824");
1
+ @import url("css/base.css?v=20260828-type1");
2
+ @import url("css/icon-strip.css?v=20260828-surfaces4");
3
+ @import url("css/title-bar.css?v=20260828-project-header1");
4
4
  @import url("css/pane.css");
5
5
  @import url("css/worker-proposal.css");
6
- @import url("css/session-actions.css");
7
- @import url("css/sidebar.css");
8
- @import url("css/overlays.css");
6
+ @import url("css/session-actions.css?v=20260828-contrast1");
7
+ @import url("css/sidebar.css?v=20260828-session-list3");
8
+ @import url("css/overlays.css?v=20260827-brand3");
9
9
  @import url("css/menus.css");
10
- @import url("css/messages.css?v=20260824");
10
+ @import url("css/messages.css?v=20260828-delegated-muted1");
11
11
  @import url("css/rewind.css");
12
- @import url("css/input.css");
13
- @import url("css/filebrowser.css");
12
+ @import url("css/input.css?v=20260828-composer-depth1");
13
+ @import url("css/filebrowser.css?v=20260828-workbench4");
14
14
  @import url("css/git-panel.css");
15
15
  @import url("css/diff.css");
16
16
  @import url("css/highlight.css");
@@ -26,7 +26,7 @@
26
26
  @import url("css/playbook.css");
27
27
  @import url("css/stt.css");
28
28
  @import url("css/profile.css");
29
- @import url("css/user-settings.css");
29
+ @import url("css/user-settings.css?v=20260828-type1");
30
30
  @import url("css/admin.css");
31
31
  @import url("css/session-search.css");
32
32
  @import url("css/tooltip.css");
package/lib/public/sw.js CHANGED
@@ -1,4 +1,4 @@
1
- var CACHE_NAME = "clay-offline-v3";
1
+ var CACHE_NAME = "clay-offline-v4";
2
2
 
3
3
  self.addEventListener("install", function (event) {
4
4
  event.waitUntil(self.skipWaiting());
@@ -68,7 +68,7 @@ self.addEventListener("fetch", function (event) {
68
68
  // If even the index page is not cached, show a minimal offline page
69
69
  if (indexCached) return indexCached;
70
70
  return new Response(
71
- "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Clay</title>" +
71
+ "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Clay Studio</title>" +
72
72
  "<style>body{margin:0;background:#000;color:#888;display:flex;" +
73
73
  "align-items:center;justify-content:center;height:100vh;" +
74
74
  "font-family:monospace;font-size:1.2em}</style></head>" +
@@ -97,6 +97,8 @@ self.addEventListener("push", function (event) {
97
97
  body: data.body || "",
98
98
  tag: data.tag || "clay",
99
99
  data: data,
100
+ icon: "/clay-studio-symbol.png",
101
+ badge: "/clay-studio-favicon-32.png",
100
102
  };
101
103
 
102
104
  if (data.type === "permission_request") {
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "Clay Studio Dark",
3
+ "author": "Clay Studio",
4
+ "variant": "dark",
5
+ "base00": "171715", "base01": "20201D", "base02": "393934", "base03": "686861",
6
+ "base04": "A0A098", "base05": "D1D1CA", "base06": "E8E8E2", "base07": "FAFAF5",
7
+ "base08": "FF7479", "base09": "7776FF", "base0A": "E0B45B", "base0B": "07E5A3",
8
+ "base0C": "5FD2C0", "base0D": "8AA7FF", "base0E": "C39AF3", "base0F": "D69A7D",
9
+ "accent2": "07E5A3",
10
+ "link": "7F7EC8"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "Clay Studio Light",
3
+ "author": "Clay Studio",
4
+ "variant": "light",
5
+ "base00": "F7F7F3", "base01": "EEEEEA", "base02": "D7D7D0", "base03": "A2A29B",
6
+ "base04": "73736C", "base05": "44443F", "base06": "252522", "base07": "151513",
7
+ "base08": "C84B50", "base09": "5857FC", "base0A": "956A19", "base0B": "087B59",
8
+ "base0C": "187A7C", "base0D": "3F62B4", "base0E": "7A52A8", "base0F": "9A5A46",
9
+ "accent2": "07E5A3",
10
+ "link": "4948B8"
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "3.6.0-beta.1",
3
+ "version": "3.6.0",
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",
Binary file
Binary file
Binary file