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
@@ -1,3 +1,5 @@
1
+ import { selectIntegrationEditor, clearIntegrationEditor, bindIntegrationEditorFocus } from './personal-integration-editor.js';
2
+ import { initGithubIntegration } from './user-settings-github.js';
1
3
  import { linearIconMarkup } from './session-github.js';
2
4
  import { iconHtml } from './icons.js';
3
5
  import { store } from './store.js';
@@ -5,11 +7,13 @@ import { confirmSettingsAction } from './server-settings-state.js';
5
7
 
6
8
  export function initPersonalIntegrations() {
7
9
  var root = document.querySelector('[data-section="us-integrations"].us-section');
8
- root.innerHTML = '<h2>Integrations</h2><p class="settings-hint">Your private accounts and API keys.</p><h3>Your connections</h3><div id="us-email-list"></div><div id="us-linear-connections"></div><p id="us-connections-empty" class="settings-hint">No connections yet. Choose a service below to get started.</p><p id="us-linear-collection-status" class="settings-status" role="status" aria-live="polite"></p><button id="us-linear-retry" type="button" class="settings-btn-sm" hidden>Retry Linear</button><section class="us-integration-catalog"><h3>Available integrations</h3><div class="us-integration-tiles"><button type="button" class="us-integration-tile" data-email-provider="gmail"><img src="/icons/email/gmail.svg" alt=""><span>Gmail</span></button><button type="button" class="us-integration-tile" data-email-provider="outlook"><img src="/icons/email/outlook.svg" alt=""><span>Outlook Mail</span></button><button type="button" class="us-integration-tile" data-email-provider="yahoo"><img src="/icons/email/yahoo.svg" alt=""><span>Yahoo Mail</span></button><button type="button" class="us-integration-tile" data-email-provider="custom">' + iconHtml('mail') + '<span>Custom email</span></button><button type="button" class="us-integration-tile" id="us-linear-add" aria-label="Add Linear connection">' + linearIconMarkup() + '<span>Linear</span></button></div></section><div id="us-integration-setup"></div><form id="us-linear-editor" class="settings-form" hidden><div class="settings-field"><label class="settings-label" for="us-linear-name">Connection name</label><input id="us-linear-name" class="us-text-input" maxlength="80" required placeholder="Work, personal, or client name"></div><div class="settings-field"><label class="settings-label" for="us-linear-key">Personal API key</label><input id="us-linear-key" class="us-text-input" type="password" autocomplete="off" spellcheck="false" maxlength="4096"><p id="us-linear-key-help" class="settings-hint">Existing keys stay masked.</p></div><div class="settings-form-actions"><button type="submit" class="settings-btn-sm">Save connection</button><button type="button" class="settings-btn-sm" id="us-linear-cancel">Cancel</button><button type="button" class="settings-btn-sm" id="us-linear-remove">Disconnect</button></div></form>';
10
+ root.innerHTML = '<h2>Integrations</h2><p class="settings-hint">Connect accounts and services used by Clay Studio.</p><h3>Your connections</h3><div id="us-email-list"></div><div id="us-linear-connections"></div><p id="us-connections-empty" class="settings-hint">No connections yet. Choose a service below to get started.</p><p id="us-linear-collection-status" class="settings-status" role="status" aria-live="polite"></p><button id="us-linear-retry" type="button" class="settings-btn-sm" hidden>Retry Linear</button><section class="us-integration-catalog"><h3>Available integrations</h3><div class="us-integration-tiles"><button type="button" class="us-integration-tile" data-email-provider="gmail"><img src="/icons/email/gmail.svg" alt=""><span>Gmail</span></button><button type="button" class="us-integration-tile" data-email-provider="outlook"><img src="/icons/email/outlook.svg" alt=""><span>Outlook Mail</span></button><button type="button" class="us-integration-tile" data-email-provider="yahoo"><img src="/icons/email/yahoo.svg" alt=""><span>Yahoo Mail</span></button><button type="button" class="us-integration-tile" data-email-provider="custom">' + iconHtml('mail') + '<span>Custom email</span></button><button type="button" class="us-integration-tile" id="us-linear-add" aria-label="Add Linear connection">' + linearIconMarkup() + '<span>Linear</span></button></div></section><div id="us-integration-setup"></div><form id="us-linear-editor" class="settings-form" hidden><div class="settings-field"><label class="settings-label" for="us-linear-name">Connection name</label><input id="us-linear-name" class="us-text-input" maxlength="80" required placeholder="Work, personal, or client name"></div><div class="settings-field"><label class="settings-label" for="us-linear-key">Personal API key</label><input id="us-linear-key" class="us-text-input" type="password" autocomplete="off" spellcheck="false" maxlength="4096"><p id="us-linear-key-help" class="settings-hint">Existing keys stay masked.</p></div><div class="settings-form-actions"><button type="submit" class="settings-btn-sm">Save connection</button><button type="button" class="settings-btn-sm" id="us-linear-cancel">Cancel</button><button type="button" class="settings-btn-sm" id="us-linear-remove">Disconnect</button></div></form>';
11
+ bindIntegrationEditorFocus(root);
12
+ initGithubIntegration(root);
9
13
  var list = root.querySelector('#us-linear-connections'), form = root.querySelector('form');
10
14
  var name = root.querySelector('#us-linear-name'), key = root.querySelector('#us-linear-key');
11
15
  var add = root.querySelector('#us-linear-add'), remove = root.querySelector('#us-linear-remove');
12
- var status = root.querySelector('[role=status]'), retry = root.querySelector('#us-linear-retry');
16
+ var status = root.querySelector('#us-linear-collection-status'), retry = root.querySelector('#us-linear-retry');
13
17
  function message(text, error) { status.textContent = text; status.dataset.state = error ? 'error' : ''; }
14
18
  function busy(value) {
15
19
  store.set({ personalLinearSaving: value });
@@ -29,7 +33,7 @@ export function initPersonalIntegrations() {
29
33
  var button = document.createElement('button'); button.type = 'button'; button.className = 'settings-btn-sm'; button.textContent = 'Manage';
30
34
  button.setAttribute('aria-label', 'Manage ' + connection.name); button.setAttribute('aria-controls', 'us-linear-editor');
31
35
  button.setAttribute('aria-expanded', String(store.get('personalLinearEditing') === connection.id));
32
- button.onclick = function () { discardThen(function () { edit(connection); }); };
36
+ button.onclick = function () { selectIntegrationEditor('linear:' + connection.id, '#us-linear-editor', function () { edit(connection); }); };
33
37
  var brand = document.createElement('span'); brand.className = 'us-integration-brand'; brand.innerHTML = linearIconMarkup();
34
38
  var saved = document.createElement('span'); saved.className = 'us-integration-state'; saved.textContent = 'Key saved';
35
39
  controls.append(brand, copy, saved, button); row.appendChild(controls); list.appendChild(row);
@@ -46,14 +50,10 @@ export function initPersonalIntegrations() {
46
50
  }
47
51
  function close() {
48
52
  var id = store.get('personalLinearEditing');
49
- store.set({personalLinearEditing:null,personalLinearDirty:false}); form.reset(); form.hidden = true; render();
53
+ closePersonalLinearEditor(); render();
50
54
  var row = Array.from(list.children).find(function (el) { return el.dataset.connectionId === id; });
51
55
  (row ? row.querySelector('button') : add).focus();
52
56
  }
53
- function discardThen(action) {
54
- if (!store.get('personalLinearDirty')) { action(); return; }
55
- confirmSettingsAction('Discard this unsaved connection draft?', 'Discard', action);
56
- }
57
57
  // Only an open editor holds a draft; autofill or restore into the hidden form is not an edit.
58
58
  form.oninput = function () { if (!store.get('personalLinearEditing')) return; store.set({personalLinearDirty: !!key.value || name.value !== store.get('personalLinearOriginalName')}); };
59
59
  async function save(disconnect) {
@@ -69,20 +69,20 @@ export function initPersonalIntegrations() {
69
69
  if (!response.ok) throw new Error(data.error || 'Could not save connection.');
70
70
  var entries = (store.get('personalLinearConnections') || []).filter(function (entry) { return entry.id !== id; });
71
71
  if (!disconnect) entries.push(Object.assign({id:id,name:name.value.trim(),legacy:id === 'default'},data));
72
- store.set({personalLinearConnections:entries, githubWorkRefresh:null}); close(); message(disconnect ? 'Connection removed.' : 'Connection saved.');
72
+ store.set({personalLinearConnections:entries, githubWorkRefresh:null}); busy(false); close(); message(disconnect ? 'Connection removed.' : 'Connection saved.');
73
73
  } catch (error) { if (actor === store.get('myUserId') && generation === store.get('personalLinearGeneration')) message(error.message,true); }
74
74
  finally { if (generation === store.get('personalLinearGeneration')) busy(false); }
75
75
  }
76
76
  form.onsubmit = function (event) { event.preventDefault(); save(false); };
77
- add.onclick = function () { discardThen(function () { edit(null); }); };
78
- root.querySelector('#us-linear-cancel').onclick = function () { discardThen(close); };
77
+ add.onclick = function () { selectIntegrationEditor('linear:new', '#us-linear-editor', function () { edit(null); }); };
78
+ root.querySelector('#us-linear-cancel').onclick = function () { selectIntegrationEditor(null, null); };
79
79
  remove.onclick = function () { confirmSettingsAction('Disconnect this Linear connection? Work using this key will need to be reconnected. Existing issue links are retained.', 'Disconnect', function () { save(true); }); };
80
80
  retry.onclick = loadPersonalIntegrations;
81
81
  store.subscribe(function (state, previous) {
82
82
  if (state.myUserId !== previous.myUserId) {
83
83
  store.set({personalLinearConnections:null,personalLinearEditing:null,personalLinearDirty:false,personalLinearSaving:false,personalLinearGeneration:(state.personalLinearGeneration || 0)+1});
84
84
  root.dataset.connectionsLoaded = 'false'; form.reset(); form.hidden = true; busy(false); render(); message('');
85
- } else if (state.personalLinearConnections !== previous.personalLinearConnections) render();
85
+ } else if (state.personalLinearConnections !== previous.personalLinearConnections || state.personalLinearEditing !== previous.personalLinearEditing) render();
86
86
  });
87
87
  }
88
88
 
@@ -103,3 +103,14 @@ export async function loadPersonalIntegrations() {
103
103
  status.textContent = error.message; status.dataset.state = 'error'; retry.hidden = false;
104
104
  }
105
105
  }
106
+
107
+ export function closePersonalLinearEditor() {
108
+ var id = store.get('personalLinearEditing');
109
+ var form = document.getElementById('us-linear-editor');
110
+ store.set({personalLinearEditing:null,personalLinearDirty:false,personalLinearNewId:null});
111
+ if (form) { form.reset(); form.hidden = true; }
112
+ clearIntegrationEditor('linear:' + id);
113
+ var row = Array.from(document.querySelectorAll('#us-linear-connections [data-connection-id]')).find(function (el) { return el.dataset.connectionId === id; });
114
+ var trigger = row ? row.querySelector('button') : document.getElementById('us-linear-add');
115
+ if (trigger) trigger.focus();
116
+ }
@@ -1,3 +1,4 @@
1
+ import { loadGithubIntegration, leaveGithubIntegration } from './user-settings-github.js';
1
2
  import { initPersonalProviders, loadPersonalProviders, leavePersonalProviders } from './user-settings-providers.js';
2
3
  import { initPersonalIntegrations, loadPersonalIntegrations } from './user-settings-integrations.js';
3
4
  import { getWs } from './ws-ref.js';
@@ -46,7 +47,8 @@ export function buildUserSettingsLayout() {
46
47
  root.querySelectorAll('.layout-option-icon').forEach(function (el) {el.remove();});
47
48
  }
48
49
  export function loadUserSettingsExtras(page) {
49
- if (page === 'us-integrations') loadPersonalIntegrations();
50
+ if (page === 'us-integrations') { loadPersonalIntegrations(); loadGithubIntegration(); }
51
+ else leaveGithubIntegration();
50
52
  if (page === 'us-providers' && !store.get('personalAiSettingsMounted')) {
51
53
  store.set({ personalAiSettingsMounted: true });
52
54
  loadSpeechSettings(); loadPersonalProviders();
@@ -57,7 +59,7 @@ export function loadUserSettingsExtras(page) {
57
59
  }
58
60
  export function leaveAgentBehaviorPage() {unmountAgentBehavior();resetAgentBehavior();}
59
61
  function leavePersonalAiPage() {leavePersonalProviders();closeSpeechPicker();unmountDefaultAi(document.getElementById('us-default-ai'));store.set({personalAiSettingsMounted:false});}
60
- export function finishUserSettingsExtras() {leavePersonalAiPage();leaveAgentBehaviorPage();}
62
+ export function finishUserSettingsExtras() {leaveGithubIntegration();leavePersonalAiPage();leaveAgentBehaviorPage();}
61
63
  // Returns the draft prompt for leaving one personal page (or every page when
62
64
  // pageId is omitted); the shell shows it with the shared confirmation.
63
65
  export function personalLeavePrompt(pageId) {
@@ -71,7 +73,7 @@ export function personalLeavePrompt(pageId) {
71
73
  var dirty = leaving('us-integrations') && (store.get('personalEmailDirty') || store.get('personalLinearDirty') || keyDraft) ||
72
74
  leaving('us-providers') && (store.get('userVoiceDirty') || store.get('defaultAiDraftDirty') || store.get('userAllowDirty')) ||
73
75
  leaving('us-notifications') && store.get('notificationDraft') ||
74
- leaving('us-appearance') && (store.get('appearanceDraft') || store.get('userTerminalDirty')) ||
76
+ leaving('us-appearance') && (store.get('appearanceDraft') || store.get('appearanceColorEditor') || store.get('userTerminalDirty')) ||
75
77
  leaving('us-account') && (store.get('userProfileDirty') || pinDirty);
76
78
  if (!dirty) return null;
77
79
  return { message: 'Your unsaved changes will remain in this tab. Leave this editor and keep the draft?', action: 'Keep draft and leave' };
@@ -1,9 +1,12 @@
1
+ import { createWorkspaceBackgroundGallery } from './workspace-background-gallery.js';
1
2
  import { store } from './store.js';
2
3
  import { getThemes, retryAppearanceSave, getAppearanceChoices, selectAppearanceMode, selectAppearanceTheme, onThemeChange, importLegacyAppearance } from './theme.js';
3
4
  import { enhanceSettingsSelects } from './settings-select.js';
4
5
 
5
6
  export function initPersonalThemes(root) {
6
7
  root.innerHTML = '<h3>Theme</h3><div class="us-preference-row"><label class="settings-label" for="us-theme-mode">Appearance</label><select id="us-theme-mode" class="settings-select"><option value="system">Follow system</option><option value="light">Light</option><option value="dark">Dark</option></select></div><p class="settings-hint">Choose a palette for each appearance. Switching light and dark keeps both choices.</p><div class="us-theme-pair"></div><p id="us-appearance-status" class="settings-status" role="status"></p>';
8
+ var syncBackground = createWorkspaceBackgroundGallery(root);
9
+ root.appendChild(root.querySelector('#us-appearance-status'));
7
10
  var mode = root.querySelector('select');
8
11
  mode.onchange = function () { selectAppearanceMode(mode.value); };
9
12
  ['light', 'dark'].forEach(function (variant) {
@@ -13,6 +16,7 @@ export function initPersonalThemes(root) {
13
16
  root.querySelector('.us-theme-pair').appendChild(group);
14
17
  });
15
18
  function sync() {
19
+ syncBackground();
16
20
  var themes = getThemes(); var choices = getAppearanceChoices(); mode.value = choices.mode; mode.dispatchEvent(new Event('settings-sync'));
17
21
  ['light', 'dark'].forEach(function (variant) {
18
22
  var select = root.querySelector('#us-theme-' + variant); select.replaceChildren();
@@ -31,8 +35,8 @@ export function initPersonalThemes(root) {
31
35
  var retry = document.createElement('button'); retry.type = 'button'; retry.className = 'settings-btn-sm'; retry.textContent = 'Retry save'; retry.hidden = !store.get('appearanceSaveError'); retry.onclick = retryAppearanceSave; root.appendChild(retry);
32
36
  var legacy = document.createElement('button'); legacy.type = 'button'; legacy.className = 'settings-btn-sm'; legacy.textContent = "Import this browser's previous theme"; legacy.onclick = importLegacyAppearance; root.appendChild(legacy);
33
37
  store.subscribe(function (state, previous) {
34
- legacy.hidden = !state.legacyAppearanceAvailable; retry.hidden = !state.appearanceSaveError;
35
- if (state.appearanceDraft !== previous.appearanceDraft || state.appearancePreference !== previous.appearancePreference) sync();
38
+ legacy.hidden = !state.legacyAppearanceAvailable; retry.hidden = !state.appearanceSaveError || !!state.appearanceColorEditor;
39
+ if (state.appearanceDraft !== previous.appearanceDraft || state.appearancePreference !== previous.appearancePreference || state.appearanceColorEditor !== previous.appearanceColorEditor || state.appearanceColorPreview !== previous.appearanceColorPreview || state.appearancePendingSaves !== previous.appearancePendingSaves) sync();
36
40
  });
37
41
  legacy.hidden = !store.get('legacyAppearanceAvailable'); onThemeChange(sync); sync(); enhanceSettingsSelects(root);
38
42
  }
@@ -1,3 +1,4 @@
1
+ import { integrationEditorBusy } from './personal-integration-editor.js';
1
2
  import { initPersonalAppearance } from './user-settings-appearance.js';
2
3
  import { initAccountSessions, resetAccountSessions } from './user-settings-sessions.js';
3
4
  import { initPersonalVoice } from './user-settings-voice.js';
@@ -38,7 +39,7 @@ export function initUserSettings() {
38
39
  enter: function () { populateAccount(); },
39
40
  activate: function (page) { switchSection(page); },
40
41
  leave: function () { currentPage = null; resetAccountSessions(); finishUserSettingsExtras(); },
41
- busy: function () { return store.get('speechPopup') ? 'Finish or close the open voice settings editor first.' : ''; },
42
+ busy: function () { if (integrationEditorBusy()) return 'Wait for the current connection operation to finish.'; return store.get('speechPopup') ? 'Finish or close the open voice settings editor first.' : ''; },
42
43
  leavePrompt: function (kind, pageId) { return personalLeavePrompt(kind === 'page' ? pageId : undefined); }
43
44
  });
44
45
  pinForm = document.getElementById('us-pin-form');
@@ -21,6 +21,8 @@ export async function restoreViewPresentation(view, key) {
21
21
  var run = store.get('savedWorkspaceRuntime');
22
22
  if (run && run.key === key) run.presentationApplied = true;
23
23
  var state = presentation(view.workspace), pane = view.workspace.panes.find(function (p) { return p.instanceId === state.expandedPaneId && p.open; });
24
+ // An expanded tool that failed to restore must not stay fullscreen over the sessions.
25
+ if (pane && run && run.key === key && run.unavailable && run.unavailable[paneOwner(pane)]) pane = null;
24
26
  store.set({ savedWorkspaceApplying: true });
25
27
  try {
26
28
  store.set({ projectSplitViewFocusedOrigin: state.focusedSessionOriginId, savedViewExpandedOwner: pane ? paneOwner(pane) : null });
@@ -0,0 +1,92 @@
1
+ import { store } from './store.js';
2
+ import { workspaceBackgrounds, selectAppearanceBackground } from './appearance-preferences.js';
3
+ import { createCustomColorEditor, openCustomColor, cancelCustomColor } from './workspace-custom-colors.js';
4
+ import { registerTooltip } from './tooltip.js';
5
+
6
+ function chevron(right) { return '<svg viewBox="0 0 20 20" fill="none" aria-hidden="true"><path d="' + (right ? 'm8 5 5 5-5 5' : 'm12 5-5 5 5 5') + '" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>'; }
7
+ export function createWorkspaceBackgroundGallery(root) {
8
+ var gallery = document.createElement('div');
9
+ gallery.id = 'us-workspace-background'; gallery.className = 'us-background-gallery';
10
+ gallery.setAttribute('role', 'radiogroup'); gallery.setAttribute('aria-label', 'Workspace background');
11
+ gallery.setAttribute('aria-describedby', 'us-background-help');
12
+ gallery.innerHTML = '<h3>Workspace background</h3><p id="us-background-help" class="settings-hint">Choose a color or picture. Both follow your light or dark appearance.</p><div class="us-background-palette"><button type="button" class="us-palette-arrow" data-step="-1" aria-label="Previous colors" hidden>' + chevron(false) + '</button><div class="us-background-colors"></div><button type="button" class="us-palette-arrow" data-step="1" aria-label="Next colors" hidden>' + chevron(true) + '</button><button type="button" id="us-add-color" class="us-palette-add" aria-label="Add custom color" aria-haspopup="dialog" aria-expanded="false" aria-controls="us-custom-color-editor"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true"><path d="M10 4v12M4 10h12" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg></button></div><h4>Pictures</h4><div class="us-background-pictures"></div>';
13
+ var strip = gallery.querySelector('.us-background-colors');
14
+ var add = gallery.querySelector('#us-add-color'); registerTooltip(add, 'Add custom color');
15
+ add.dataset.tipSuppressWhenOpen = '#us-custom-color-editor';
16
+ function addChoice(color, container) {
17
+ var button = document.createElement('button'); button.type = 'button'; button.className = 'us-background-choice';
18
+ button.dataset.backgroundChoice = color ? color.id : 'workshop';
19
+ button.setAttribute('role', 'radio'); button.setAttribute('aria-label', color ? color.name : 'Workshop');
20
+ var preview = document.createElement('span'); preview.className = 'us-background-sample'; preview.setAttribute('aria-hidden', 'true');
21
+ if (color) { preview.style.setProperty('--swatch-light', color.light); preview.style.setProperty('--swatch-dark', color.dark); registerTooltip(button, color.name); }
22
+ else { preview.classList.add('us-background-workshop'); }
23
+ button.appendChild(preview);
24
+ if (!color) { var label = document.createElement('span'); label.textContent = 'Workshop'; button.appendChild(label); }
25
+ container.appendChild(button);
26
+ button.onclick = function () { selectAppearanceBackground(color ? 'plain' : 'workshop', color ? color.id : undefined); };
27
+ }
28
+ function overflow() {
29
+ var overflowing = strip.scrollWidth > strip.clientWidth + 1;
30
+ gallery.querySelectorAll('[data-step]').forEach(function (button) {
31
+ button.hidden = !overflowing;
32
+ button.disabled = button.dataset.step === '-1' ? strip.scrollLeft < 1 : strip.scrollLeft + strip.clientWidth >= strip.scrollWidth - 1;
33
+ });
34
+ }
35
+ gallery.querySelectorAll('[data-step]').forEach(function (button) {
36
+ registerTooltip(button, button.getAttribute('aria-label'));
37
+ button.onclick = function () { strip.scrollBy({ left: Number(button.dataset.step) * strip.clientWidth * 0.8 }); };
38
+ });
39
+ strip.onscroll = overflow;
40
+ function reveal(button) {
41
+ if (!button || !strip.contains(button) || !strip.clientWidth) return;
42
+ var box = button.getBoundingClientRect(), bounds = strip.getBoundingClientRect();
43
+ if (box.left < bounds.left) strip.scrollLeft -= bounds.left - box.left;
44
+ if (box.right > bounds.right) strip.scrollLeft += box.right - bounds.right;
45
+ }
46
+ function layout() {
47
+ overflow();
48
+ var visible = strip.clientWidth > 0;
49
+ if (visible && (strip.dataset.visible !== 'true' || strip.dataset.revealPending === 'true')) {
50
+ reveal(strip.querySelector('[aria-checked="true"]'));
51
+ delete strip.dataset.revealPending;
52
+ }
53
+ strip.dataset.visible = String(visible);
54
+ }
55
+ strip.addEventListener('focusin', function (event) { reveal(event.target); });
56
+ addChoice(null, gallery.querySelector('.us-background-pictures'));
57
+ add.onclick = function () { if (store.get('appearanceColorEditor')) cancelCustomColor(); else openCustomColor(); };
58
+ var syncEditor = createCustomColorEditor(gallery, add);
59
+ var buttons = [], renderedColors = null;
60
+ gallery.onkeydown = function (event) {
61
+ var index = buttons.indexOf(event.target); if (index < 0) return;
62
+ var target;
63
+ if (event.key === 'ArrowRight' || event.key === 'ArrowDown') target = (index + 1) % buttons.length;
64
+ if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') target = (index + buttons.length - 1) % buttons.length;
65
+ if (event.key === 'Home') target = 0;
66
+ if (event.key === 'End') target = buttons.length - 1;
67
+ if (target === undefined) return;
68
+ event.preventDefault(); buttons[target].focus({ preventScroll: strip.contains(buttons[target]) }); buttons[target].click();
69
+ };
70
+ root.appendChild(gallery);
71
+ new ResizeObserver(layout).observe(strip);
72
+ return function () {
73
+ var current = store.get('appearanceDraft') || store.get('appearancePreference') || {};
74
+ var colors = workspaceBackgrounds.colors.concat(current.customBackgroundColors || []);
75
+ if (renderedColors !== JSON.stringify(colors)) {
76
+ var focused = document.activeElement && document.activeElement.dataset.backgroundChoice;
77
+ renderedColors = JSON.stringify(colors); strip.replaceChildren();
78
+ colors.forEach(function (color) { addChoice(color, strip); });
79
+ if (focused) { var restored = Array.from(strip.children).find(function (button) { return button.dataset.backgroundChoice === focused; }); if (restored) restored.focus({ preventScroll: true }); }
80
+ }
81
+ buttons = Array.from(gallery.querySelectorAll('[role="radio"]'));
82
+ var editor = store.get('appearanceColorEditor'); add.disabled = !!store.get('appearancePendingSaves');
83
+ add.setAttribute('aria-expanded', String(!!editor)); syncEditor();
84
+ var selected = current.background === 'workshop' ? 'workshop' : workspaceBackgrounds.getColor(current.backgroundColor, current.customBackgroundColors).id;
85
+ if (strip.dataset.selection !== selected) { strip.dataset.selection = selected; strip.dataset.revealPending = 'true'; }
86
+ buttons.forEach(function (button) {
87
+ var checked = button.dataset.backgroundChoice === selected;
88
+ button.setAttribute('aria-checked', String(checked)); button.tabIndex = checked ? 0 : -1; button.disabled = !!(editor && editor.saving);
89
+ });
90
+ layout();
91
+ };
92
+ }
@@ -0,0 +1,132 @@
1
+ import { store } from './store.js';
2
+ import { workspaceBackgrounds, queueAppearanceSave, appearanceStatus } from './appearance-preferences.js';
3
+ import { registerTooltip, dismissTooltip } from './tooltip.js';
4
+
5
+ function preference() { return store.get('appearanceDraft') || store.get('appearancePreference') || {}; }
6
+ function clamp(value, max) { return Math.max(0, Math.min(max, value)); }
7
+ function hsv(hex) {
8
+ var rgb = hex.slice(1).match(/../g).map(function (part) { return parseInt(part, 16) / 255; });
9
+ var max = Math.max.apply(null, rgb), min = Math.min.apply(null, rgb), delta = max - min, hue = 0;
10
+ if (delta) {
11
+ if (max === rgb[0]) hue = ((rgb[1] - rgb[2]) / delta) % 6;
12
+ else if (max === rgb[1]) hue = (rgb[2] - rgb[0]) / delta + 2;
13
+ else hue = (rgb[0] - rgb[1]) / delta + 4;
14
+ }
15
+ return { hue: (hue * 60 + 360) % 360, saturation: max ? delta / max * 100 : 0, value: max * 100 };
16
+ }
17
+ function hex(color) {
18
+ var chroma = color.value / 100 * color.saturation / 100;
19
+ var x = chroma * (1 - Math.abs((color.hue / 60) % 2 - 1)), m = color.value / 100 - chroma;
20
+ var rgb = color.hue < 60 ? [chroma,x,0] : color.hue < 120 ? [x,chroma,0] : color.hue < 180 ? [0,chroma,x] : color.hue < 240 ? [0,x,chroma] : color.hue < 300 ? [x,0,chroma] : [chroma,0,x];
21
+ return '#' + rgb.map(function (value) { return Math.round((value + m) * 255).toString(16).padStart(2, '0'); }).join('').toUpperCase();
22
+ }
23
+ export function openCustomColor() {
24
+ if (store.get('appearancePendingSaves') || store.get('appearanceColorEditor')) return;
25
+ var current = preference(), source = workspaceBackgrounds.getColor(current.backgroundColor, current.customBackgroundColors);
26
+ var variant = document.documentElement.classList.contains('light-theme') ? 'light' : 'dark';
27
+ var draft = Object.assign({ id: 'custom-' + crypto.randomUUID(), light: source.light.toUpperCase(), dark: source.dark.toUpperCase(), variant: variant, saving: false, error: '', previousStatus: store.get('appearanceSaveStatus') || '', previousError: !!store.get('appearanceSaveError') }, hsv(source[variant]));
28
+ store.set({ appearanceColorEditor: draft }); dismissTooltip();
29
+ document.getElementById((current.customBackgroundColors || []).length >= workspaceBackgrounds.maxCustom ? 'us-color-cancel' : 'us-color-area').focus();
30
+ }
31
+ export function cancelCustomColor(restoreFocus) {
32
+ var editor = store.get('appearanceColorEditor'); if (!editor || editor.saving) return;
33
+ store.set({ appearanceColorEditor: null, appearanceColorPreview: null });
34
+ appearanceStatus(editor.previousStatus, editor.previousError);
35
+ if (restoreFocus !== false) document.getElementById('us-add-color').focus();
36
+ }
37
+ function updateEditor(change, visual) {
38
+ var editor = store.get('appearanceColorEditor'); if (!editor || editor.saving) return;
39
+ var next = Object.assign({}, editor, change, { error: '' });
40
+ if (visual) next[next.variant] = hex(next);
41
+ var custom = workspaceBackgrounds.normalizeCustom([{ id: next.id, name: next.light + ' / ' + next.dark, light: next.light, dark: next.dark }]);
42
+ store.set({ appearanceColorEditor: next, appearanceColorPreview: custom ? custom[0] : store.get('appearanceColorPreview') });
43
+ }
44
+ async function saveEditor() {
45
+ var editor = store.get('appearanceColorEditor'); if (!editor || editor.saving) return;
46
+ var custom = workspaceBackgrounds.normalizeCustom([{ id: editor.id, name: editor.light + ' / ' + editor.dark, light: editor.light, dark: editor.dark }]);
47
+ if (!custom) { store.set({ appearanceColorEditor: Object.assign({}, editor, { error: 'Use six-digit HEX colors, such as #E9CBD4.' }) }); document.getElementById('us-color-hex').focus(); return; }
48
+ var current = preference(), colors = (current.customBackgroundColors || []).concat(custom);
49
+ if (!workspaceBackgrounds.normalizeCustom(colors)) return;
50
+ var values = Object.assign({}, current, { customBackgroundColors: colors, background: 'plain', backgroundColor: editor.id });
51
+ store.set({ appearanceColorEditor: Object.assign({}, editor, { saving: true, error: '' }) });
52
+ var saved = await queueAppearanceSave(values), active = store.get('appearanceColorEditor');
53
+ if (!active || active.id !== editor.id) return;
54
+ if (saved) {
55
+ store.set({ appearanceColorEditor: null, appearanceColorPreview: null });
56
+ var button = Array.from(document.querySelectorAll('[data-background-choice]')).find(function (node) { return node.dataset.backgroundChoice === editor.id; });
57
+ if (button) button.focus({ preventScroll: true });
58
+ } else {
59
+ if (store.get('appearanceDraft') === values) {
60
+ var acknowledged = store.get('appearancePreference') || {};
61
+ var unchanged = ['theme','mode','lightTheme','darkTheme','background','backgroundColor','customBackgroundColors'].every(function (key) { return JSON.stringify(current[key] || null) === JSON.stringify(acknowledged[key] || null); });
62
+ store.set({ appearanceDraft: unchanged ? null : current });
63
+ }
64
+ store.set({ appearanceColorEditor: Object.assign({}, active, { saving: false, error: 'Could not save. Your preview remains. Try Add again.' }) });
65
+ document.getElementById('us-color-save').focus();
66
+ }
67
+ }
68
+ export function createCustomColorEditor(host, anchor) {
69
+ var form = document.createElement('form'); form.id = 'us-custom-color-editor'; form.className = 'us-color-popover'; form.hidden = true;
70
+ form.setAttribute('role', 'dialog'); form.setAttribute('aria-label', 'Add color');
71
+ form.innerHTML = '<div class="us-color-top"><div class="us-color-modes" role="group" aria-label="Color appearance"><button type="button" data-variant="light" aria-label="Light color"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true"><circle cx="10" cy="10" r="3"/><path d="M10 1v2m0 14v2M1 10h2m14 0h2M4 4l1.5 1.5m9 9L16 16M4 16l1.5-1.5m9-9L16 4"/></svg></button><button type="button" data-variant="dark" aria-label="Dark color"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true"><path d="M16.7 12A7 7 0 0 1 8 3.3 7 7 0 1 0 16.7 12Z"/></svg></button></div><span class="us-color-preview" aria-hidden="true"></span></div><div class="us-color-controls"><div id="us-color-area" class="us-color-area" role="slider" tabindex="0" aria-label="Saturation and brightness" aria-valuemin="0" aria-valuemax="100" aria-describedby="us-color-help"><span></span></div><span id="us-color-help" class="sr-only">Left and right adjust saturation. Up and down adjust brightness.</span><input id="us-color-hue" type="range" min="0" max="359" step="1" aria-label="Hue"><label for="us-color-hex" class="sr-only">HEX color</label><input id="us-color-hex" class="us-text-input" maxlength="7" spellcheck="false" autocomplete="off" aria-describedby="us-color-error"></div><p id="us-color-error" class="settings-status" role="status"></p><div class="settings-form-actions"><button id="us-color-save" class="us-btn" type="submit">Add</button><button id="us-color-cancel" class="settings-btn-sm" type="button">Cancel</button></div>';
72
+ var area = form.querySelector('#us-color-area'), hue = form.querySelector('#us-color-hue'), input = form.querySelector('#us-color-hex');
73
+ function position() {
74
+ if (form.hidden) return;
75
+ var box = anchor.getBoundingClientRect();
76
+ var left = clamp(box.right - form.offsetWidth, window.innerWidth - form.offsetWidth - 8);
77
+ var top = box.bottom + 6;
78
+ if (top + form.offsetHeight > window.innerHeight - 8) top = box.top - form.offsetHeight - 6;
79
+ form.style.left = Math.max(8, left) + 'px'; form.style.top = Math.max(8, top) + 'px';
80
+ }
81
+ form.querySelectorAll('[data-variant]').forEach(function (button) {
82
+ registerTooltip(button, button.getAttribute('aria-label'));
83
+ button.onclick = function () { var editor = store.get('appearanceColorEditor'); if (editor && !editor.saving) updateEditor(Object.assign({ variant: button.dataset.variant }, hsv(workspaceBackgrounds.normalizeHex(editor[button.dataset.variant]) || '#808080'))); };
84
+ });
85
+ input.oninput = function () {
86
+ var editor = store.get('appearanceColorEditor'), change = {}; if (!editor) return;
87
+ change[editor.variant] = input.value; var valid = workspaceBackgrounds.normalizeHex(input.value);
88
+ if (valid) Object.assign(change, hsv(valid)); updateEditor(change);
89
+ };
90
+ hue.oninput = function () { updateEditor({ hue: Number(hue.value) }, true); };
91
+ function point(event) {
92
+ var editor = store.get('appearanceColorEditor'); if (!editor || editor.saving) return;
93
+ var box = area.getBoundingClientRect();
94
+ updateEditor({ saturation: clamp((event.clientX - box.left) / box.width * 100, 100), value: clamp(100 - (event.clientY - box.top) / box.height * 100, 100) }, true);
95
+ }
96
+ area.onpointerdown = function (event) { if (event.button !== 0) return; event.preventDefault(); area.focus(); area.setPointerCapture(event.pointerId); point(event); };
97
+ area.onpointermove = function (event) { if (area.hasPointerCapture(event.pointerId)) point(event); };
98
+ area.onkeydown = function (event) {
99
+ var editor = store.get('appearanceColorEditor'); if (!editor) return;
100
+ var change = {}, amount = event.shiftKey ? 10 : 1;
101
+ if (event.key === 'ArrowLeft') change.saturation = clamp(editor.saturation - amount, 100);
102
+ if (event.key === 'ArrowRight') change.saturation = clamp(editor.saturation + amount, 100);
103
+ if (event.key === 'ArrowUp') change.value = clamp(editor.value + amount, 100);
104
+ if (event.key === 'ArrowDown') change.value = clamp(editor.value - amount, 100);
105
+ if (!Object.keys(change).length) return;
106
+ event.preventDefault(); updateEditor(change, true);
107
+ };
108
+ form.querySelector('#us-color-cancel').onclick = function () { cancelCustomColor(); };
109
+ form.onsubmit = function (event) { event.preventDefault(); saveEditor(); };
110
+ form.onkeydown = function (event) { if (event.key === 'Escape') { event.preventDefault(); event.stopPropagation(); cancelCustomColor(); } };
111
+ document.addEventListener('pointerdown', function (event) { if (!form.hidden && !form.contains(event.target) && !anchor.contains(event.target)) cancelCustomColor(false); }, true);
112
+ form.addEventListener('focusout', function () { setTimeout(function () { if (!form.hidden && !form.contains(document.activeElement) && document.activeElement !== anchor) cancelCustomColor(false); }, 0); });
113
+ window.addEventListener('resize', position); document.addEventListener('scroll', position, true);
114
+ host.appendChild(form);
115
+ return function () {
116
+ var editor = store.get('appearanceColorEditor'); form.hidden = !editor; if (!editor) return;
117
+ var limit = (preference().customBackgroundColors || []).length >= workspaceBackgrounds.maxCustom && !editor.saving;
118
+ form.querySelector('.us-color-controls').hidden = limit; form.querySelector('.us-color-top').hidden = limit;
119
+ form.querySelectorAll('[data-variant]').forEach(function (button) { button.setAttribute('aria-pressed', String(editor.variant === button.dataset.variant)); });
120
+ input.value = editor[editor.variant]; input.setAttribute('aria-label', (editor.variant === 'light' ? 'Light' : 'Dark') + ' HEX'); input.setAttribute('aria-invalid', String(!workspaceBackgrounds.normalizeHex(input.value)));
121
+ form.querySelector('.us-color-preview').style.backgroundColor = workspaceBackgrounds.normalizeHex(input.value) || 'transparent';
122
+ area.style.setProperty('--picker-hue', String(editor.hue));
123
+ area.querySelector('span').style.left = editor.saturation + '%'; area.querySelector('span').style.top = (100 - editor.value) + '%';
124
+ area.setAttribute('aria-valuenow', String(Math.round(editor.saturation)));
125
+ area.setAttribute('aria-valuetext', 'Saturation ' + Math.round(editor.saturation) + '%, brightness ' + Math.round(editor.value) + '%');
126
+ hue.value = Math.round(editor.hue); hue.setAttribute('aria-valuetext', hue.value + ' degrees');
127
+ form.querySelector('#us-color-error').textContent = limit ? '12 custom colors saved.' : editor.error || (editor.saving ? 'Saving…' : '');
128
+ form.querySelectorAll('input,button').forEach(function (control) { control.disabled = editor.saving; });
129
+ form.querySelector('#us-color-save').hidden = limit;
130
+ position();
131
+ };
132
+ }
@@ -2,6 +2,7 @@
2
2
  import { store } from './store.js';
3
3
  import { sendYou, openYou } from './you.js';
4
4
  import { relativeTime } from './project-logs-render.js';
5
+ import { registerTooltip } from './tooltip.js';
5
6
 
6
7
  function state() { return store.get('youActivity') || {}; }
7
8
  function put(values) { store.set({ youActivity: Object.assign({}, state(), values) }); }
@@ -24,7 +25,7 @@ function announce(message) {
24
25
  put({ active: true });
25
26
  el('you-activity').classList.add('announcing');
26
27
  el('you-activity-toggle').setAttribute('aria-label', message);
27
- el('you-activity-toggle').title = message;
28
+ registerTooltip(el('you-activity-toggle'), message);
28
29
  var characters = Array.from(message);
29
30
  var index = 0;
30
31
  var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
@@ -94,7 +95,8 @@ export function handleYouActivity(msg) {
94
95
  }
95
96
  export function initYouActivity() {
96
97
  var host = document.createElement('div'); host.id = 'you-activity';
97
- host.innerHTML = '<button id="you-activity-toggle" type="button" aria-expanded="false" aria-controls="you-activity-history"><span id="you-activity-text" aria-hidden="true"></span></button>' +
98
+ host.innerHTML = '<button id="you-activity-toggle" type="button" aria-expanded="false" aria-controls="you-activity-history">' +
99
+ '<span id="you-activity-text" aria-hidden="true"></span><svg class="you-activity-chevron" viewBox="0 0 16 16" aria-hidden="true"><path d="m4 6 4 4 4-4"/></svg></button>' +
98
100
  '<section id="you-activity-history" class="hidden" aria-label="Clay’s memory activity"><header><strong>Clay’s memory</strong><button id="you-activity-close" type="button" aria-label="Close memory activity">Close</button></header>' +
99
101
  '<div id="you-activity-list"></div><button id="you-activity-more" class="hidden" type="button">Load earlier activity</button><button id="you-activity-open" type="button">Open Me</button></section>';
100
102
  document.querySelector('.top-bar-left-pills').prepend(host);