clay-server 5.5.0-beta.4 → 5.5.0-beta.6

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 (107) hide show
  1. package/lib/github-connection-store.js +39 -0
  2. package/lib/github-runtime.js +43 -0
  3. package/lib/project-github-connection.js +164 -0
  4. package/lib/project-split-views.js +3 -1
  5. package/lib/project-view-workspace.js +10 -1
  6. package/lib/project.js +8 -0
  7. package/lib/public/appearance-bootstrap.js +6 -0
  8. package/lib/public/css/chrome-material.css +133 -0
  9. package/lib/public/css/menus.css +1 -17
  10. package/lib/public/css/navigation-island.css +261 -0
  11. package/lib/public/css/notifications-center.css +1 -17
  12. package/lib/public/css/settings-ui.css +8 -2
  13. package/lib/public/css/shared-browser.css +1 -18
  14. package/lib/public/css/split-view-layout.css +9 -5
  15. package/lib/public/css/sticky-notes.css +1 -18
  16. package/lib/public/css/title-bar.css +1 -1
  17. package/lib/public/css/workspace-background-gallery.css +43 -0
  18. package/lib/public/css/you.css +13 -10
  19. package/lib/public/index.html +53 -46
  20. package/lib/public/modules/app-messages.js +5 -0
  21. package/lib/public/modules/appearance-preferences.js +81 -0
  22. package/lib/public/modules/context-sources.js +2 -282
  23. package/lib/public/modules/github-mark.js +2 -0
  24. package/lib/public/modules/pane-appearance-bridge.js +9 -1
  25. package/lib/public/modules/pane-header-tooltips.js +36 -9
  26. package/lib/public/modules/personal-email-editor.js +305 -0
  27. package/lib/public/modules/personal-integration-editor.js +70 -0
  28. package/lib/public/modules/personal-settings-dialog.js +15 -1
  29. package/lib/public/modules/session-list-material.js +25 -0
  30. package/lib/public/modules/sidebar-collapse-focus.js +22 -0
  31. package/lib/public/modules/sidebar-sessions.js +2 -0
  32. package/lib/public/modules/sidebar.js +9 -10
  33. package/lib/public/modules/split-pane-renderer.js +5 -5
  34. package/lib/public/modules/split-view-shell-controls.js +9 -5
  35. package/lib/public/modules/split-view.js +2 -2
  36. package/lib/public/modules/theme.js +10 -28
  37. package/lib/public/modules/tool-pane-chrome.js +4 -1
  38. package/lib/public/modules/tool-pane-material.js +23 -0
  39. package/lib/public/modules/tooltip.js +7 -3
  40. package/lib/public/modules/tui-attention.js +9 -4
  41. package/lib/public/modules/user-settings-account.js +10 -1
  42. package/lib/public/modules/user-settings-github.js +176 -0
  43. package/lib/public/modules/user-settings-integrations.js +23 -12
  44. package/lib/public/modules/user-settings-layout.js +5 -3
  45. package/lib/public/modules/user-settings-themes.js +6 -2
  46. package/lib/public/modules/user-settings.js +2 -1
  47. package/lib/public/modules/view-presentation-state.js +2 -0
  48. package/lib/public/modules/workspace-background-gallery.js +92 -0
  49. package/lib/public/modules/workspace-custom-colors.js +132 -0
  50. package/lib/public/modules/you-activity.js +4 -2
  51. package/lib/public/pane-assets/app-BNMUDWD5.js +1 -0
  52. package/lib/public/pane-assets/chunk-3EA7GMQB.js +1 -0
  53. package/lib/public/pane-assets/chunk-4FKXTJ6O.js +1 -0
  54. package/lib/public/pane-assets/chunk-4WIT6JEU.js +1 -0
  55. package/lib/public/pane-assets/chunk-A5YYSG4B.js +1 -0
  56. package/lib/public/pane-assets/{chunk-SGMEO3P7.js → chunk-AF5TDQTT.js} +1 -1
  57. package/lib/public/pane-assets/{chunk-4WYURTPH.js → chunk-B4JTSONS.js} +1 -1
  58. package/lib/public/pane-assets/chunk-D2BDBUG5.js +2 -0
  59. package/lib/public/pane-assets/chunk-EGCR2MH4.js +1 -0
  60. package/lib/public/pane-assets/{chunk-FB5B2WYF.js → chunk-ENPPQJSX.js} +1 -1
  61. package/lib/public/pane-assets/chunk-ENWMPEO2.js +1 -0
  62. package/lib/public/pane-assets/{chunk-53KRXJDL.js → chunk-FA454EFE.js} +1 -1
  63. package/lib/public/pane-assets/chunk-FDZ37JGF.js +174 -0
  64. package/lib/public/pane-assets/{chunk-TU3AHPSN.js → chunk-FWEHZLDA.js} +1 -1
  65. package/lib/public/pane-assets/chunk-HY74JLC5.js +1 -0
  66. package/lib/public/pane-assets/{chunk-AMZ7P526.js → chunk-LH46GC2K.js} +1 -1
  67. package/lib/public/pane-assets/chunk-PDHV6Q3E.js +1 -0
  68. package/lib/public/pane-assets/chunk-Q2HJ5CWT.js +1 -0
  69. package/lib/public/pane-assets/chunk-SQC7XIGG.js +1 -0
  70. package/lib/public/pane-assets/chunk-TIEOZRO2.js +1 -0
  71. package/lib/public/pane-assets/chunk-U3KC4P2F.js +1 -0
  72. package/lib/public/pane-assets/{chunk-X45WI2U7.js → chunk-ULGL2YFP.js} +1 -1
  73. package/lib/public/pane-assets/chunk-W3KJANR2.js +1 -0
  74. package/lib/public/pane-assets/{chunk-UVTS6GUM.js → chunk-WI3BTTUY.js} +1 -1
  75. package/lib/public/pane-assets/{chunk-JIV7O5GN.js → chunk-YHBTK56T.js} +1 -1
  76. package/lib/public/pane-assets/{chunk-TK446XFF.js → chunk-YXTZSYUM.js} +1 -1
  77. package/lib/public/pane-assets/chunk-ZY4WB3PH.js +1 -0
  78. package/lib/public/pane-assets/manifest.json +424 -410
  79. package/lib/public/pane-assets/style-F4MQRRX2.css +1 -0
  80. package/lib/public/style.css +4 -0
  81. package/lib/public/workspace-art/workshop-dark.webp +0 -0
  82. package/lib/public/workspace-art/workshop-light.webp +0 -0
  83. package/lib/public/workspace-background-catalog.js +40 -0
  84. package/lib/server-appearance-settings.js +16 -5
  85. package/lib/server.js +6 -0
  86. package/lib/session-github.js +1 -7
  87. package/lib/terminal-manager.js +5 -1
  88. package/lib/terminal.js +10 -1
  89. package/package.json +1 -1
  90. package/lib/public/pane-assets/app-PV3SEOTX.js +0 -1
  91. package/lib/public/pane-assets/chunk-2G6DB3ES.js +0 -1
  92. package/lib/public/pane-assets/chunk-6QFLYOVY.js +0 -1
  93. package/lib/public/pane-assets/chunk-BK2NRZFM.js +0 -1
  94. package/lib/public/pane-assets/chunk-D3WR2TYZ.js +0 -2
  95. package/lib/public/pane-assets/chunk-E7D5A27I.js +0 -1
  96. package/lib/public/pane-assets/chunk-FDBSE7BE.js +0 -174
  97. package/lib/public/pane-assets/chunk-FHB76KH2.js +0 -1
  98. package/lib/public/pane-assets/chunk-FNGO6432.js +0 -1
  99. package/lib/public/pane-assets/chunk-HF7AK56Q.js +0 -1
  100. package/lib/public/pane-assets/chunk-ILVRGTDF.js +0 -1
  101. package/lib/public/pane-assets/chunk-L3SFMSLP.js +0 -1
  102. package/lib/public/pane-assets/chunk-PAY3A4JS.js +0 -1
  103. package/lib/public/pane-assets/chunk-QVHC324H.js +0 -1
  104. package/lib/public/pane-assets/chunk-SFANEWZA.js +0 -1
  105. package/lib/public/pane-assets/chunk-US5VELGB.js +0 -1
  106. package/lib/public/pane-assets/chunk-VZ2OKDAO.js +0 -1
  107. package/lib/public/pane-assets/style-563D4UN6.css +0 -1
@@ -27,6 +27,7 @@
27
27
  is lazy-loaded by terminal-prefs.js the first time the user selects
28
28
  it (or boots with it already saved). See ensureD2CodingWebfont(). -->
29
29
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5/css/xterm.min.css">
30
+ <script src="workspace-background-catalog.js"></script>
30
31
  <script src="appearance-bootstrap.js"></script>
31
32
  <style>
32
33
  html { background: #171715; color: #e8e8e2; }
@@ -56,7 +57,7 @@ body { background: transparent; color: inherit; }
56
57
  </head>
57
58
  <body class="capsules-disabled">
58
59
  <div id="layout">
59
- <!-- === Top Bar (full width, forms reverse-ㄱ with icon strip) === -->
60
+ <!-- === Top Bar (independent full-width upper layer) === -->
60
61
  <div id="top-bar">
61
62
  <div class="top-bar-left-pills">
62
63
  <a class="top-bar-brand-link" href="https://clay.studio" target="_blank" rel="noopener noreferrer" aria-label="Clay Studio website"><span class="top-bar-brand-wordmark" aria-hidden="true"></span></a>
@@ -89,6 +90,8 @@ body { background: transparent; color: inherit; }
89
90
  <!-- === Bottom row === -->
90
91
  <div id="layout-body">
91
92
 
93
+ <!-- Layout wrapper for the independent rail and sidebar islands. -->
94
+ <div id="navigation-island">
92
95
  <!-- === Icon Strip === -->
93
96
  <div id="icon-strip">
94
97
  <button type="button" class="icon-strip-home icon-strip-brand" aria-label="Open Clay" disabled>
@@ -98,6 +101,7 @@ body { background: transparent; color: inherit; }
98
101
  </span>
99
102
  </button>
100
103
  <div class="icon-strip-separator"></div>
104
+ <div class="icon-strip-navigation" id="icon-strip-navigation">
101
105
  <button type="button" class="icon-strip-hint" id="icon-strip-hint-project" data-switcher-mode="project" aria-label="Switch project"></button>
102
106
  <div class="icon-strip-filter" id="icon-strip-filter">
103
107
  <button type="button" class="icon-strip-filter-knob" id="icon-strip-filter-knob" data-mode="all" aria-label="Showing all. Activate to show projects only">
@@ -119,30 +123,9 @@ body { background: transparent; color: inherit; }
119
123
  <button type="button" class="icon-strip-hint hidden" id="icon-strip-hint-mate" data-switcher-mode="mate" aria-label="Switch mate"></button>
120
124
  <div class="icon-strip-users hidden" id="icon-strip-users"></div>
121
125
  </div>
126
+ </div>
122
127
  </div>
123
128
 
124
- <!-- === Main Area (sidebar + resize-handle + main-column) === -->
125
- <div id="main-area">
126
- <section id="projects-hub" class="hidden" aria-label="Projects"></section>
127
- <!-- Retained inert Capsule host markup. Capsules and their stored data are
128
- preserved for a future placement without keeping the Home board live. -->
129
- <template id="retained-capsule-host-template">
130
- <section id="retained-capsule-host" aria-label="Retained Capsules Workbench">
131
- <section id="home-tool-workbench" class="home-tool-workbench" aria-label="Capsules Workbench">
132
- <div class="home-dock-header">
133
- <button id="home-dock-return" class="home-dock-return" type="button"><i data-lucide="arrow-left"></i><span>Return to conversation</span></button>
134
- <div class="home-dock-switcher" id="home-dock-switcher"></div>
135
- <div class="home-dock-context" id="home-dock-context" aria-label="Active Capsule actions" hidden></div>
136
- <div class="home-dock-actions">
137
- <button id="home-dock-focus" type="button" title="Focus Capsules Workbench" aria-label="Focus Capsules Workbench"><i data-lucide="maximize-2"></i></button>
138
- <button id="home-dock-collapse" type="button" title="Collapse Capsules Workbench" aria-label="Collapse Capsules Workbench"><i data-lucide="panel-right-close"></i></button>
139
- </div>
140
- </div>
141
- <div class="home-dock-content" id="home-dock-content"></div>
142
- </section>
143
- </section>
144
- </template>
145
-
146
129
  <div id="sidebar-column">
147
130
  <div class="title-bar-sidebar">
148
131
  <button class="title-bar-project-dropdown" id="title-bar-project-dropdown">
@@ -259,6 +242,52 @@ body { background: transparent; color: inherit; }
259
242
  </div>
260
243
  <div id="mate-sidebar-resize-handle" class="hidden"></div>
261
244
 
245
+ <!-- === User Island === -->
246
+ <div id="user-island">
247
+ <button class="user-island-profile" id="user-account-trigger" type="button" aria-label="Open account menu" aria-controls="account-popover" aria-expanded="false">
248
+ <div class="user-island-avatar"><span class="user-island-avatar-letter">?</span></div>
249
+ <div class="user-island-info">
250
+ <span class="user-island-name"></span>
251
+ <span class="user-island-cta hidden">Personalize your name</span>
252
+ </div>
253
+ </button>
254
+ <div class="user-island-quick-controls">
255
+ <button id="cursor-share-toggle" class="cursor-share-toggle" type="button" aria-pressed="false" aria-label="Cursor sharing unavailable" disabled>
256
+ <i data-lucide="mouse-pointer-2" class="cursor-share-icon" aria-hidden="true"></i>
257
+ </button>
258
+ <button id="appearance-mode-toggle" class="cursor-share-toggle" type="button" aria-label="Switch to light mode" title="Switch to light mode">
259
+ <i data-lucide="sun" class="cursor-share-icon" aria-hidden="true"></i>
260
+ </button>
261
+ </div>
262
+ <div class="user-island-actions">
263
+ <button id="user-settings-btn" title="Personal settings" aria-label="Personal settings" aria-expanded="false"><i data-lucide="settings"></i></button>
264
+ </div>
265
+ </div>
266
+
267
+ </div><!-- /navigation-island -->
268
+
269
+ <!-- === Main Area (workspace) === -->
270
+ <div id="main-area">
271
+ <section id="projects-hub" class="hidden" aria-label="Projects"></section>
272
+ <!-- Retained inert Capsule host markup. Capsules and their stored data are
273
+ preserved for a future placement without keeping the Home board live. -->
274
+ <template id="retained-capsule-host-template">
275
+ <section id="retained-capsule-host" aria-label="Retained Capsules Workbench">
276
+ <section id="home-tool-workbench" class="home-tool-workbench" aria-label="Capsules Workbench">
277
+ <div class="home-dock-header">
278
+ <button id="home-dock-return" class="home-dock-return" type="button"><i data-lucide="arrow-left"></i><span>Return to conversation</span></button>
279
+ <div class="home-dock-switcher" id="home-dock-switcher"></div>
280
+ <div class="home-dock-context" id="home-dock-context" aria-label="Active Capsule actions" hidden></div>
281
+ <div class="home-dock-actions">
282
+ <button id="home-dock-focus" type="button" title="Focus Capsules Workbench" aria-label="Focus Capsules Workbench"><i data-lucide="maximize-2"></i></button>
283
+ <button id="home-dock-collapse" type="button" title="Collapse Capsules Workbench" aria-label="Collapse Capsules Workbench"><i data-lucide="panel-right-close"></i></button>
284
+ </div>
285
+ </div>
286
+ <div class="home-dock-content" id="home-dock-content"></div>
287
+ </section>
288
+ </section>
289
+ </template>
290
+
262
291
  <div id="main-column">
263
292
  <!-- Knowledge editor (full overlay, shown on Edit) -->
264
293
  <div id="mate-knowledge-editor" class="mate-knowledge-editor hidden">
@@ -303,8 +332,8 @@ body { background: transparent; color: inherit; }
303
332
  </div>
304
333
  <button id="hamburger-btn" aria-label="Toggle sidebar" title="Toggle sidebar"><i data-lucide="menu"></i></button>
305
334
  <span class="header-title" id="header-title">Connecting...</span>
306
- <button id="header-rename-btn" type="button" title="Rename session"><i data-lucide="pencil"></i></button>
307
335
  <button id="header-info-btn" type="button" title="Session info"><i data-lucide="info"></i></button>
336
+ <button id="header-rename-btn" type="button" title="Rename session"><i data-lucide="pencil"></i></button>
308
337
  <div id="header-full-access-btn" class="session-permission-control hidden" role="group" aria-label="Session permission mode">
309
338
  <div class="session-permission-segmented">
310
339
  <button type="button" data-permission-mode="default" aria-label="Ask permissions" data-tip="Ask before tool actions that need permission. Previously allowed actions can run without asking again."><span class="permission-label">Ask</span><span class="permission-spinner" aria-hidden="true"></span></button>
@@ -728,28 +757,6 @@ body { background: transparent; color: inherit; }
728
757
  </div><!-- /main-column -->
729
758
  </div><!-- /main-area -->
730
759
 
731
- <!-- === User Island === -->
732
- <div id="user-island">
733
- <button class="user-island-profile" id="user-account-trigger" type="button" aria-label="Open account menu" aria-controls="account-popover" aria-expanded="false">
734
- <div class="user-island-avatar"><span class="user-island-avatar-letter">?</span></div>
735
- <div class="user-island-info">
736
- <span class="user-island-name"></span>
737
- <span class="user-island-cta hidden">Personalize your name</span>
738
- </div>
739
- </button>
740
- <div class="user-island-quick-controls">
741
- <button id="cursor-share-toggle" class="cursor-share-toggle" type="button" aria-pressed="false" aria-label="Cursor sharing unavailable" disabled>
742
- <i data-lucide="mouse-pointer-2" class="cursor-share-icon" aria-hidden="true"></i>
743
- </button>
744
- <button id="appearance-mode-toggle" class="cursor-share-toggle" type="button" aria-label="Switch to light mode" title="Switch to light mode">
745
- <i data-lucide="sun" class="cursor-share-icon" aria-hidden="true"></i>
746
- </button>
747
- </div>
748
- <div class="user-island-actions">
749
- <button id="user-settings-btn" title="Personal settings" aria-label="Personal settings" aria-expanded="false"><i data-lucide="settings"></i></button>
750
- </div>
751
- </div>
752
-
753
760
  </div><!-- /layout-body -->
754
761
  </div><!-- /layout -->
755
762
 
@@ -1,3 +1,4 @@
1
+ import { handleGithubIntegration } from './user-settings-github.js';
1
2
  import { handlePersonalProviderMessage } from './user-settings-providers.js';
2
3
  import { handleSplitSessionCreated, finishPickerCreation } from './split-view-picker-actions.js';
3
4
  import { handleWorkerMessagePreview } from './worker-message-previews.js';
@@ -1639,6 +1640,10 @@ export function processMessage(msg) {
1639
1640
  if (!store.get('replayingHistory')) autoStartLoginIfNeeded(msg);
1640
1641
  break;
1641
1642
 
1643
+ case "personal_github":
1644
+ handleGithubIntegration(msg);
1645
+ break;
1646
+
1642
1647
  case "vendor_login_ready":
1643
1648
  handleVendorLoginReady(msg);
1644
1649
  break;
@@ -0,0 +1,81 @@
1
+ import { store } from './store.js';
2
+ import '../workspace-background-catalog.js';
3
+ export var workspaceBackgrounds = globalThis.ClayWorkspaceBackgrounds;
4
+
5
+ export function effectiveAppearance(state) {
6
+ state = state || store.snap();
7
+ var pref = state.appearanceDraft || state.appearancePreference || {};
8
+ var preview = state.appearanceColorPreview;
9
+ if (!preview || !workspaceBackgrounds.normalizeCustom([preview])) return pref;
10
+ return Object.assign({}, pref, { background: 'plain', backgroundColor: preview.id,
11
+ customBackgroundColors: (pref.customBackgroundColors || []).filter(function (color) { return color.id !== preview.id; }).concat([preview]) });
12
+ }
13
+
14
+ export function appearanceStatus(text, error) {
15
+ store.set({ appearanceSaveStatus: text, appearanceSaveError: !!error });
16
+ var status = document.getElementById('us-appearance-status');
17
+ if (status) { status.textContent = text; status.dataset.state = error ? 'error' : ''; }
18
+ }
19
+ export function initWorkspaceBackground() {
20
+ function sync(state) {
21
+ var pref = effectiveAppearance(state);
22
+ var color = workspaceBackgrounds.getColor(pref.backgroundColor, pref.customBackgroundColors);
23
+ document.documentElement.dataset.workspaceColor = color.id;
24
+ document.documentElement.style.setProperty('--workspace-solid-light', color.light);
25
+ document.documentElement.style.setProperty('--workspace-solid-dark', color.dark);
26
+ document.documentElement.dataset.workspaceBackground = pref.background === 'workshop' ? 'workshop' : 'plain';
27
+ }
28
+ store.subscribe(function (state, previous) {
29
+ if (previous.myUserId && state.myUserId !== previous.myUserId) {
30
+ store.set({ appearanceColorEditor: null, appearanceColorPreview: null, appearanceDraft: null, appearancePreference: null });
31
+ return;
32
+ }
33
+ if (state.appearanceDraft !== previous.appearanceDraft || state.appearancePreference !== previous.appearancePreference || state.appearanceColorPreview !== previous.appearanceColorPreview) sync(state);
34
+ });
35
+ }
36
+
37
+ export function selectAppearanceBackground(background, backgroundColor) {
38
+ if (['plain', 'workshop'].indexOf(background) < 0 || (store.get('appearanceColorEditor') || {}).saving) return;
39
+ var current = store.get('appearanceDraft') || store.get('appearancePreference') || {};
40
+ if (backgroundColor !== undefined && !workspaceBackgrounds.isColor(backgroundColor, current.customBackgroundColors)) return;
41
+ store.set({ appearanceColorPreview: null });
42
+ var values = Object.assign({}, current, { background: background });
43
+ values.backgroundColor = workspaceBackgrounds.getColor(backgroundColor === undefined ? current.backgroundColor : backgroundColor, current.customBackgroundColors).id;
44
+ delete values.configured;
45
+ ['lightTheme', 'darkTheme'].forEach(function (key) { if (!values[key]) delete values[key]; });
46
+ return queueAppearanceSave(values);
47
+ }
48
+
49
+ export function queueAppearanceSave(values) {
50
+ delete values.configured;
51
+ ['lightTheme', 'darkTheme'].forEach(function (key) { if (!values[key]) delete values[key]; });
52
+ store.set({ appearanceDraft: values, appearancePendingSaves: (store.get('appearancePendingSaves') || 0) + 1 });
53
+ var owner = store.get('myUserId') || null;
54
+ appearanceStatus('Saving appearance…');
55
+ var queue = store.get('appearanceSaveQueue') || Promise.resolve();
56
+ var next = queue.catch(function () {}).then(function () { return persistAppearance(values, owner); });
57
+ store.set({ appearanceSaveQueue: next });
58
+ return next;
59
+ }
60
+ async function persistAppearance(values, owner) {
61
+ var request = (store.get('appearanceSaveRequest') || 0) + 1;
62
+ store.set({ appearanceSaveRequest: request });
63
+ appearanceStatus('Saving appearance…');
64
+ var controller = new AbortController();
65
+ var timeout = setTimeout(function () { controller.abort(); }, 12000);
66
+ try {
67
+ if ((store.get('myUserId') || null) !== owner) throw new Error('Account changed. Reload before saving appearance.');
68
+ var custom = workspaceBackgrounds.normalizeCustom(values.customBackgroundColors || []);
69
+ if (!custom || !workspaceBackgrounds.isColor(values.backgroundColor || workspaceBackgrounds.defaultColorId, custom)) throw new Error('Invalid workspace color.');
70
+ var response = await fetch('/api/user/appearance', {method:'PUT',headers:{'Content-Type':'application/json','X-Clay-Settings':'1'},body:JSON.stringify(values),signal:controller.signal});
71
+ if (!response.ok) throw new Error('Could not save appearance. Your preview remains. Retry to save it.');
72
+ if (store.get('appearanceSaveRequest') !== request || (store.get('myUserId') || null) !== owner) return false;
73
+ var saved = await response.json();
74
+ store.set({ appearancePreference: saved, appearanceDraft: store.get('appearanceDraft') === values ? null : store.get('appearanceDraft') });
75
+ ['clay-theme','clay-skin','clay-mode','clay-theme-vars','clay-theme-variant'].forEach(function (key) {try {localStorage.removeItem(key);}catch(error){}});
76
+ if (store.get('appearanceDraft')) { appearanceStatus('Saving appearance…'); return true; }
77
+ appearanceStatus('Saved. Applied to this view; other devices use it when they reload.');
78
+ return true;
79
+ } catch(error) {if(store.get('appearanceSaveRequest') === request) appearanceStatus(store.get('appearanceDraft') === values ? (error.name === 'AbortError' ? 'Save acknowledgement timed out. Retry to save your preview.' : error.message) : 'Saving appearance…', store.get('appearanceDraft') === values); return false;}
80
+ finally { clearTimeout(timeout); store.set({ appearancePendingSaves: Math.max(0, (store.get('appearancePendingSaves') || 1) - 1) }); }
81
+ }
@@ -1,5 +1,6 @@
1
1
  import { showToast } from './utils.js';
2
- import { preparePersonalConnectionDialog } from './personal-settings-dialog.js';
2
+ import { showEmailSetupModal } from './personal-email-editor.js';
3
+ export { showEmailSetupModal, handleEmailTestResult, handleEmailAddResult, handleEmailRemoveResult } from './personal-email-editor.js';
3
4
  // Context Sources — attach terminal output, browser tabs, and email accounts as context for Claude
4
5
 
5
6
  var ctx = null;
@@ -9,9 +10,6 @@ var browserTabList = []; // synced from Chrome extension via postMessage
9
10
  var emailAccountList = []; // synced from server via email_accounts_list
10
11
  var emailProviders = {}; // provider presets from server
11
12
  var emailUnreadCounts = {}; // accountId -> unread count
12
- var _emailTestPassed = false;
13
- var _emailPendingSave = false;
14
- var _emailDoSave = null; // set by showEmailSetupModal
15
13
 
16
14
  export function initContextSources(_ctx) {
17
15
  ctx = _ctx;
@@ -435,284 +433,6 @@ export function getEmailAccountListCache() {
435
433
  return emailAccountList;
436
434
  }
437
435
 
438
- export function showEmailSetupModal(provider, host) {
439
- var providerNames = { gmail: "Gmail", outlook: "Outlook Mail", yahoo: "Yahoo Mail", custom: "Custom email" };
440
- if (!Object.hasOwn(providerNames, provider)) provider = null;
441
- // Remove any existing modal
442
- var existing = document.getElementById("email-setup-modal");
443
- if (existing) {existing.querySelector("input:not([type=hidden]),button").focus();return;}
444
-
445
- var overlay = document.createElement("div");
446
- overlay.id = "email-setup-modal";
447
- overlay.className = host ? "email-setup-inline" : "email-setup-overlay";
448
-
449
- if (!provider) {
450
- var picker = document.createElement('div'); picker.className = 'email-setup-dialog';
451
- picker.innerHTML = '<h3>Choose an email provider</h3><div class="us-integration-tiles"></div><button id="email-setup-cancel" type="button" class="email-setup-btn">Cancel</button>';
452
- Object.keys(providerNames).forEach(function (id) {
453
- var button = document.createElement('button'); button.type = 'button'; button.className = 'email-setup-btn'; button.textContent = providerNames[id];
454
- button.onclick = function () { closePicker(); showEmailSetupModal(id, host); };
455
- picker.querySelector('.us-integration-tiles').appendChild(button);
456
- });
457
- overlay.appendChild(picker); (host || document.body).appendChild(overlay); var closePicker = preparePersonalConnectionDialog(overlay, !!host); return;
458
- }
459
- var modal = document.createElement("div");
460
- modal.className = "email-setup-dialog";
461
- modal.innerHTML =
462
- '<h3 class="email-setup-title">Add ' + providerNames[provider] + ' account</h3>' +
463
- '<input type="hidden" id="email-setup-provider" value="' + provider + '">' +
464
- '<div class="email-setup-field">' +
465
- '<label class="email-setup-label">Email Address</label>' +
466
- '<input id="email-setup-address" type="email" placeholder="you@example.com" class="email-setup-input" />' +
467
- '</div>' +
468
- '<div class="email-setup-field">' +
469
- '<label class="email-setup-label">App Password</label>' +
470
- '<div class="email-setup-password-wrap">' +
471
- '<input id="email-setup-password" type="password" placeholder="xxxx-xxxx-xxxx-xxxx" class="email-setup-input" />' +
472
- '<button id="email-setup-password-toggle" type="button" class="email-setup-password-eye" title="Show password"><i data-lucide="eye"></i></button>' +
473
- '</div>' +
474
- '<a id="email-setup-help" href="https://support.google.com/accounts/answer/185833" target="_blank" rel="noopener" class="email-setup-help">How to create an App Password</a>' +
475
- '</div>' +
476
- '<div id="email-setup-custom-fields" style="display:none;">' +
477
- '<div class="email-setup-field">' +
478
- '<label class="email-setup-label">IMAP Host</label>' +
479
- '<div class="email-setup-row">' +
480
- '<input id="email-setup-imap-host" type="text" placeholder="imap.example.com" class="email-setup-input" style="flex:1;" />' +
481
- '<input id="email-setup-imap-port" type="number" value="993" class="email-setup-input email-setup-port" />' +
482
- '</div>' +
483
- '</div>' +
484
- '<div class="email-setup-field">' +
485
- '<label class="email-setup-label">SMTP Host</label>' +
486
- '<div class="email-setup-row">' +
487
- '<input id="email-setup-smtp-host" type="text" placeholder="smtp.example.com" class="email-setup-input" style="flex:1;" />' +
488
- '<input id="email-setup-smtp-port" type="number" value="587" class="email-setup-input email-setup-port" />' +
489
- '</div>' +
490
- '</div>' +
491
- '</div>' +
492
- '<div id="email-setup-status" class="email-setup-status"></div>' +
493
- '<div class="email-setup-actions">' +
494
- '<button id="email-setup-test" type="button" class="email-setup-btn email-setup-btn-secondary">Test Connection</button>' +
495
- '<button id="email-setup-cancel" type="button" class="email-setup-btn email-setup-btn-ghost">Cancel</button>' +
496
- '<button id="email-setup-save" type="button" class="email-setup-btn email-setup-btn-primary">Add Account</button>' +
497
- '</div>';
498
-
499
- overlay.appendChild(modal);
500
- (host || document.body).appendChild(overlay);
501
- preparePersonalConnectionDialog(overlay, !!host);
502
-
503
- // Event: close on overlay click
504
- overlay.addEventListener("click", function (e) {
505
- if (!host && e.target === overlay) overlay.remove();
506
- });
507
-
508
- // Event: cancel button
509
- document.getElementById("email-setup-cancel").addEventListener("click", function () {
510
- overlay.remove();
511
- });
512
-
513
- // Event: password visibility toggle
514
- var pwInput = document.getElementById("email-setup-password");
515
- var pwToggle = document.getElementById("email-setup-password-toggle");
516
- pwToggle.addEventListener("click", function () {
517
- var isHidden = pwInput.type === "password";
518
- pwInput.type = isHidden ? "text" : "password";
519
- pwToggle.innerHTML = isHidden ? '<i data-lucide="eye-off"></i>' : '<i data-lucide="eye"></i>';
520
- pwToggle.title = isHidden ? "Hide password" : "Show password";
521
- if (typeof lucide !== "undefined") lucide.createIcons();
522
- });
523
-
524
- // Event: provider change
525
- var providerSelect = document.getElementById("email-setup-provider");
526
- var helpLink = document.getElementById("email-setup-help");
527
- var customFields = document.getElementById("email-setup-custom-fields");
528
-
529
- // Provider presets only configure IMAP/SMTP infra and the help link.
530
- // The user always enters their full email address (e.g. user@company.com),
531
- // so Google Workspace / Microsoft 365 accounts on custom domains work too.
532
- var providerMeta = {
533
- gmail: {
534
- helpUrl: "https://support.google.com/accounts/answer/185833",
535
- },
536
- outlook: {
537
- helpUrl: "https://support.microsoft.com/en-us/account-billing/using-app-passwords-with-apps-that-don-t-support-two-step-verification-5896ed9b-4263-e681-128a-a6f2979a7944",
538
- },
539
- yahoo: {
540
- helpUrl: "https://help.yahoo.com/kb/generate-manage-third-party-passwords-sln15241.html",
541
- },
542
- custom: {
543
- helpUrl: null,
544
- },
545
- };
546
-
547
- var emailInput = document.getElementById("email-setup-address");
548
-
549
- function applyProviderMeta(val) {
550
- var meta = providerMeta[val] || providerMeta.custom;
551
- customFields.style.display = val === "custom" ? "block" : "none";
552
- if (meta.helpUrl) {
553
- helpLink.href = meta.helpUrl;
554
- helpLink.style.display = "";
555
- } else {
556
- helpLink.style.display = "none";
557
- }
558
- }
559
-
560
- applyProviderMeta(provider);
561
-
562
- // Reset test state
563
- _emailTestPassed = false;
564
- _emailPendingSave = false;
565
-
566
- // Reset test status when inputs change
567
- function onInputChange() {
568
- _emailTestPassed = false;
569
- _emailPendingSave = false;
570
- var saveBtn = document.getElementById("email-setup-save");
571
- if (saveBtn) saveBtn.textContent = "Add Account";
572
- }
573
- document.getElementById("email-setup-address").addEventListener("input", onInputChange);
574
- document.getElementById("email-setup-password").addEventListener("input", onInputChange);
575
-
576
-
577
- function runTest() {
578
- var statusEl = document.getElementById("email-setup-status");
579
- var emailAddr = getFullEmail();
580
- var appPass = document.getElementById("email-setup-password").value;
581
-
582
- if (!emailAddr || emailAddr.indexOf("@") === -1 || !appPass) {
583
- statusEl.style.display = "block";
584
- statusEl.style.color = "var(--error, #e74c3c)";
585
- statusEl.textContent = "Email and app password are required.";
586
- return;
587
- }
588
-
589
- statusEl.style.display = "block";
590
- statusEl.style.color = "var(--text-muted)";
591
- statusEl.textContent = "Testing connection...";
592
-
593
- var msgData = buildEmailSetupData();
594
- msgData.type = "email_account_test";
595
-
596
- if (ctx && ctx.ws && ctx.connected) {
597
- ctx.ws.send(JSON.stringify(msgData));
598
- }
599
- }
600
-
601
- // Event: test connection
602
- document.getElementById("email-setup-test").addEventListener("click", function () {
603
- _emailPendingSave = false;
604
- runTest();
605
- });
606
-
607
- // Event: save (requires test to pass first)
608
- document.getElementById("email-setup-save").addEventListener("click", function () {
609
- if (_emailTestPassed) {
610
- // Test already passed, proceed with save
611
- doSave();
612
- return;
613
- }
614
- // Run test first, then auto-save on success
615
- _emailPendingSave = true;
616
- var saveBtn = document.getElementById("email-setup-save");
617
- if (saveBtn) saveBtn.textContent = "Testing...";
618
- runTest();
619
- });
620
-
621
- _emailDoSave = doSave;
622
- function doSave() {
623
- var statusEl = document.getElementById("email-setup-status");
624
- statusEl.style.display = "block";
625
- statusEl.style.color = "var(--text-muted)";
626
- statusEl.textContent = "Adding account...";
627
-
628
- var msgData = buildEmailSetupData();
629
- msgData.type = "email_account_add";
630
-
631
- if (ctx && ctx.ws && ctx.connected) {
632
- ctx.ws.send(JSON.stringify(msgData));
633
- }
634
- }
635
- }
636
-
637
- function getFullEmail() {
638
- return document.getElementById("email-setup-address").value.trim();
639
- }
640
-
641
- function buildEmailSetupData() {
642
- var provider = document.getElementById("email-setup-provider").value;
643
- var email = getFullEmail();
644
- var appPassword = document.getElementById("email-setup-password").value;
645
- var data = { provider: provider, email: email, appPassword: appPassword };
646
-
647
- if (provider === "custom") {
648
- data.imap = {
649
- host: document.getElementById("email-setup-imap-host").value.trim(),
650
- port: parseInt(document.getElementById("email-setup-imap-port").value, 10) || 993,
651
- tls: true,
652
- };
653
- data.smtp = {
654
- host: document.getElementById("email-setup-smtp-host").value.trim(),
655
- port: parseInt(document.getElementById("email-setup-smtp-port").value, 10) || 587,
656
- };
657
- }
658
- return data;
659
- }
660
-
661
- // Handle email_account_test_result from server
662
- export function handleEmailTestResult(msg) {
663
- var statusEl = document.getElementById("email-setup-status");
664
- if (!statusEl) return;
665
- var saveBtn = document.getElementById("email-setup-save");
666
-
667
- statusEl.style.display = "block";
668
- if (msg.ok) {
669
- _emailTestPassed = true;
670
- if (_emailPendingSave && _emailDoSave) {
671
- // Test passed from Add Account click, proceed to save
672
- _emailPendingSave = false;
673
- _emailDoSave();
674
- return;
675
- }
676
- statusEl.style.color = "var(--success, #2ecc71)";
677
- statusEl.textContent = "Connection successful! IMAP and SMTP both working.";
678
- } else {
679
- _emailTestPassed = false;
680
- _emailPendingSave = false;
681
- if (saveBtn) saveBtn.textContent = "Add Account";
682
- statusEl.style.color = "var(--error, #e74c3c)";
683
- var parts = [];
684
- if (msg.imap && !msg.imap.ok) parts.push("IMAP: " + (msg.imap.error || "failed"));
685
- if (msg.smtp && !msg.smtp.ok) parts.push("SMTP: " + (msg.smtp.error || "failed"));
686
- statusEl.textContent = parts.length > 0 ? parts.join("; ") : (msg.error || "Connection failed");
687
- }
688
- }
689
-
690
- // Handle email_account_add_result from server
691
- export function handleEmailAddResult(msg) {
692
- var statusEl = document.getElementById("email-setup-status");
693
- if (!statusEl) return;
694
-
695
- statusEl.style.display = "block";
696
- if (msg.ok) {
697
- statusEl.style.color = "#2ecc71";
698
- statusEl.textContent = "Account added successfully!";
699
- // Close modal after short delay
700
- setTimeout(function () {
701
- var modal = document.getElementById("email-setup-modal");
702
- if (modal) modal.remove();
703
- }, 800);
704
- } else {
705
- statusEl.style.color = "#e74c3c";
706
- statusEl.textContent = msg.error || "Failed to add account.";
707
- }
708
- }
709
-
710
- // Handle email_account_remove_result from server
711
- export function handleEmailRemoveResult(msg) {
712
- showToast(msg.ok ? 'Personal mailbox disconnected.' : msg.error || 'Could not disconnect mailbox. Retry from Email settings.');
713
- // The acknowledged list arrives separately through email_accounts_list.
714
- }
715
-
716
436
  function getSourceLabel(id) {
717
437
  if (id.startsWith("term:")) {
718
438
  var termId = parseInt(id.split(":")[1], 10);
@@ -0,0 +1,2 @@
1
+ // Reuse the GitHub mark already shown in the application header.
2
+ export function githubMark() { return '<svg width="20" height="20" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>'; }
@@ -4,6 +4,7 @@
4
4
  // pushes the applied appearance down on every change and when a frame loads.
5
5
  // Same-origin only, shell to child only; nothing is saved from either side.
6
6
 
7
+ import { workspaceBackgrounds, effectiveAppearance } from './appearance-preferences.js';
7
8
  import { store } from './store.js';
8
9
  import { onThemeChange, getAppearanceSnapshot, applyExternalAppearance } from './theme.js';
9
10
 
@@ -31,6 +32,9 @@ function validSnapshot(value) {
31
32
  if (value.userId !== null && (typeof value.userId !== 'string' || !value.userId || value.userId.length > 200)) return false;
32
33
  if (typeof pref.configured !== 'boolean') return false;
33
34
  if (pref.mode !== null && pref.mode !== 'light' && pref.mode !== 'dark') return false;
35
+ if (pref.background !== undefined && ['plain', 'workshop'].indexOf(pref.background) < 0) return false;
36
+ var custom = workspaceBackgrounds.normalizeCustom(pref.customBackgroundColors || []);
37
+ if (!custom || pref.backgroundColor !== undefined && !workspaceBackgrounds.isColor(pref.backgroundColor, custom)) return false;
34
38
  return isIdOrNull(pref.theme) && isIdOrNull(pref.lightTheme) && isIdOrNull(pref.darkTheme);
35
39
  }
36
40
 
@@ -51,7 +55,11 @@ export function initPaneAppearanceBridge() {
51
55
  }
52
56
  onThemeChange(function () { paneFrames().forEach(push); });
53
57
  // Frames that rejected an early snapshot (identity not yet known here) get it again once it is.
54
- store.subscribe(function (state, previous) { if (state.myUserId !== previous.myUserId) paneFrames().forEach(push); });
58
+ store.subscribe(function (state, previous) {
59
+ var current = effectiveAppearance(state);
60
+ var before = effectiveAppearance(previous);
61
+ if (state.myUserId !== previous.myUserId || current.background !== before.background || current.backgroundColor !== before.backgroundColor || JSON.stringify(current.customBackgroundColors) !== JSON.stringify(before.customBackgroundColors)) paneFrames().forEach(push);
62
+ });
55
63
  // A frame that finished loading may have been seeded before a pending save.
56
64
  document.addEventListener('load', function (event) {
57
65
  if (event.target && event.target.tagName === 'IFRAME' && paneFrames().indexOf(event.target) >= 0) push(event.target);