flying-lobster 1.6.2 → 1.6.3

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.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flying-lobster",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
4
  "description": "Always-on-top chat window for OpenClaw gateways 🦞",
5
5
  "author": "Rootlab.ai",
6
6
  "license": "MIT",
@@ -31,15 +31,20 @@
31
31
  "assets/**/*"
32
32
  ],
33
33
  "mac": {
34
+ "icon": "assets/icon.icns",
34
35
  "category": "public.app-category.productivity",
35
36
  "target": [
36
37
  {
37
38
  "target": "dmg",
38
- "arch": ["universal"]
39
+ "arch": [
40
+ "universal"
41
+ ]
39
42
  },
40
43
  {
41
44
  "target": "dir",
42
- "arch": ["universal"]
45
+ "arch": [
46
+ "universal"
47
+ ]
43
48
  }
44
49
  ]
45
50
  },
package/src/main/index.js CHANGED
@@ -36,6 +36,8 @@ const OPENCLAW_CSS = `
36
36
  .chat-compose__actions .btn { padding: 0 10px !important; }
37
37
  .chat-group-messages { max-width: 100% !important; }
38
38
  .chat-group { margin-right: 4px !important; margin-left: 4px !important; }
39
+ /* Hide tool call/result cards */
40
+ .chat-tool-card { display: none !important; }
39
41
  `;
40
42
 
41
43
  const OPENCLAW_JS = `
@@ -107,9 +109,17 @@ ipcMain.handle('delete-gateway', (_e, id) => {
107
109
  return gateways;
108
110
  });
109
111
 
110
- ipcMain.handle('get-active-gateway', () => store.get('activeGateway'));
112
+ ipcMain.handle('get-active-gateway', () => {
113
+ const id = store.get('activeGateway');
114
+ console.log('[Main] get-active-gateway:', id);
115
+ return id;
116
+ });
111
117
 
112
118
  ipcMain.handle('set-active-gateway', (_e, id) => {
119
+ console.log('[Main] set-active-gateway:', id);
120
+ const gateways = store.get('gateways');
121
+ const gw = gateways.find(g => g.id === id);
122
+ console.log('[Main] Gateway details:', gw?.name, gw?.url);
113
123
  store.set('activeGateway', id);
114
124
  return id;
115
125
  });
@@ -148,6 +158,19 @@ ipcMain.handle('get-theme', () => {
148
158
  return nativeTheme.shouldUseDarkColors ? 'dark' : 'light';
149
159
  });
150
160
 
161
+ // Toggle between dark and light theme
162
+ function toggleTheme() {
163
+ // Cycle: system -> dark -> light -> system
164
+ // Or simply toggle: dark <-> light
165
+ if (nativeTheme.themeSource === 'system') {
166
+ nativeTheme.themeSource = nativeTheme.shouldUseDarkColors ? 'light' : 'dark';
167
+ } else if (nativeTheme.themeSource === 'dark') {
168
+ nativeTheme.themeSource = 'light';
169
+ } else {
170
+ nativeTheme.themeSource = 'dark';
171
+ }
172
+ }
173
+
151
174
  // Notify all windows when theme changes
152
175
  nativeTheme.on('updated', () => {
153
176
  const theme = nativeTheme.shouldUseDarkColors ? 'dark' : 'light';
@@ -193,11 +216,29 @@ function createWindow() {
193
216
 
194
217
  mainWindow.loadFile(path.join(__dirname, '..', 'renderer', 'index.html'));
195
218
 
219
+ // Open DevTools with Cmd+Option+I
220
+ mainWindow.webContents.on('before-input-event', (event, input) => {
221
+ if (input.meta && input.alt && input.key === 'i') {
222
+ mainWindow.webContents.openDevTools({ mode: 'detach' });
223
+ }
224
+ });
225
+
196
226
  // Inject CSS/JS into any webview that loads inside this window
197
227
  mainWindow.webContents.on('did-attach-webview', (event, wvWebContents) => {
228
+ console.log('[Main] Webview attached, URL:', wvWebContents.getURL());
229
+
230
+ wvWebContents.on('did-start-loading', () => console.log('[Main] Webview did-start-loading'));
231
+ wvWebContents.on('did-stop-loading', () => console.log('[Main] Webview did-stop-loading'));
232
+ wvWebContents.on('did-fail-load', (e, code, desc) => console.log('[Main] Webview did-fail-load:', code, desc));
233
+
198
234
  wvWebContents.on('dom-ready', () => {
199
- wvWebContents.insertCSS(OPENCLAW_CSS).catch(e => console.error('Main insertCSS failed:', e));
200
- wvWebContents.executeJavaScript(OPENCLAW_JS).catch(e => console.error('Main executeJS failed:', e));
235
+ const url = wvWebContents.getURL();
236
+ console.log('[Main] Webview dom-ready, URL:', url);
237
+
238
+ // TEMPORARILY DISABLE INJECTION TO DEBUG
239
+ // wvWebContents.insertCSS(OPENCLAW_CSS).catch(e => console.error('Main insertCSS failed:', e));
240
+ // wvWebContents.executeJavaScript(OPENCLAW_JS).catch(e => console.error('Main executeJS failed:', e));
241
+ console.log('[Main] CSS/JS injection DISABLED for debugging');
201
242
  });
202
243
  });
203
244
 
@@ -223,6 +264,19 @@ function createWindow() {
223
264
  if (input.key === 'Escape') {
224
265
  mainWindow.hide();
225
266
  }
267
+ // Catch agent switching shortcuts even when webview has focus
268
+ if (input.type === 'keyDown' && input.shift && (input.meta || input.control)) {
269
+ if (input.key === 'ArrowRight') {
270
+ event.preventDefault();
271
+ cycleGateway('next');
272
+ } else if (input.key === 'ArrowLeft') {
273
+ event.preventDefault();
274
+ cycleGateway('prev');
275
+ } else if (input.key === 'k' || input.key === 'K') {
276
+ event.preventDefault();
277
+ toggleTheme();
278
+ }
279
+ }
226
280
  });
227
281
 
228
282
  mainWindow.on('closed', () => {
@@ -337,6 +391,10 @@ function registerHotkey() {
337
391
 
338
392
  if (!nextRegistered) console.error('Failed to register Cmd+Shift+Right');
339
393
  if (!prevRegistered) console.error('Failed to register Cmd+Shift+Left');
394
+
395
+ // Theme toggle shortcut (Cmd+Shift+K)
396
+ const themeRegistered = globalShortcut.register('CommandOrControl+Shift+K', toggleTheme);
397
+ if (!themeRegistered) console.error('Failed to register Cmd+Shift+K for theme toggle');
340
398
  }
341
399
 
342
400
  if (process.platform === 'darwin') {
@@ -353,6 +353,9 @@
353
353
  }
354
354
 
355
355
  function loadGatewayUrl(url, token) {
356
+ console.log('[FL Debug] loadGatewayUrl called:', url, 'token:', token ? 'yes' : 'no');
357
+ console.log('[FL Debug] currentGatewayUrl:', currentGatewayUrl, 'currentWebview:', !!currentWebview);
358
+
356
359
  if (!url) {
357
360
  destroyWebview();
358
361
  showOverlay('empty');
@@ -361,9 +364,13 @@
361
364
  }
362
365
 
363
366
  // Don't reload if same URL
364
- if (currentGatewayUrl === url && currentWebview) return;
367
+ if (currentGatewayUrl === url && currentWebview) {
368
+ console.log('[FL Debug] Skipping reload - same URL');
369
+ return;
370
+ }
365
371
 
366
372
  destroyWebview();
373
+ console.log('[FL Debug] Showing loading overlay');
367
374
  showOverlay('loading');
368
375
  dot.className = 'status-dot loading';
369
376
 
@@ -375,11 +382,19 @@
375
382
  }
376
383
 
377
384
  const wv = document.createElement('webview');
385
+ console.log('[FL Debug] Loading URL:', loadUrl);
378
386
  wv.src = loadUrl;
379
387
  wv.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;border:none;';
380
388
  wv.setAttribute('allowpopups', '');
381
389
  wv.setAttribute('partition', 'persist:gateway');
390
+
391
+ wv.addEventListener('did-start-loading', () => console.log('[FL Debug] did-start-loading'));
392
+ wv.addEventListener('did-stop-loading', () => console.log('[FL Debug] did-stop-loading'));
393
+ wv.addEventListener('did-finish-load', () => console.log('[FL Debug] did-finish-load'));
394
+ wv.addEventListener('console-message', (e) => console.log('[FL Webview]', e.message));
395
+
382
396
  wv.addEventListener('dom-ready', () => {
397
+ console.log('[FL Debug] dom-ready');
383
398
  showOverlay(null); // hide all
384
399
  dot.className = 'status-dot online';
385
400
  // Inject CSS at document level — CSS custom properties inherit into shadow DOM
@@ -505,6 +520,18 @@
505
520
  window.api.onGatewaysUpdated(loadGateways);
506
521
  }
507
522
 
523
+ // Listen for agent switch from main process (Cmd+Shift+Arrow shortcuts)
524
+ if (window.api && window.api.onSwitchGateway) {
525
+ window.api.onSwitchGateway(async (gatewayId) => {
526
+ const gw = gatewaysList.find(g => g.id === gatewayId);
527
+ if (gw) {
528
+ select.value = gw.id;
529
+ showAgentIndicator(gw.name);
530
+ loadGatewayUrl(gw.url, gw.token);
531
+ }
532
+ });
533
+ }
534
+
508
535
  // Add Gateway button (empty state)
509
536
  document.getElementById('add-gw-btn').addEventListener('click', () => {
510
537
  window.api.openSettings();
@@ -130,50 +130,92 @@
130
130
  :root.light .btn-secondary { background: #c7c7cc; color: #1d1d1f; }
131
131
  :root.light .btn-secondary:hover { background: #aeaeb2; }
132
132
 
133
- /* Hotkey section */
134
- .hotkey-section { margin-top: 8px; }
135
- .hotkey-row { display: flex; gap: 8px; align-items: center; }
136
- .hotkey-row input { max-width: 260px; }
137
-
138
- .toast {
139
- position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
140
- background: var(--success); color: #fff; padding: 8px 20px; border-radius: 6px;
141
- font-size: 13px; opacity: 0; transition: opacity 0.3s; pointer-events: none;
142
- }
143
- .toast.show { opacity: 1; }
144
- .toast.error { background: var(--danger); }
145
-
146
- /* Keyboard shortcuts hint */
147
- .shortcuts-hint {
148
- margin-top: 24px;
149
- padding: 12px;
133
+ /* Keyboard Shortcuts section */
134
+ .shortcuts-section { margin-top: 8px; }
135
+ .shortcuts-list {
136
+ display: flex;
137
+ flex-direction: column;
150
138
  background: var(--bg-secondary);
151
139
  border-radius: 8px;
152
- font-size: 12px;
153
- color: var(--text-secondary);
154
- transition: background 0.2s, color 0.2s;
155
- }
156
- .shortcuts-hint h3 {
157
- font-size: 12px;
158
- text-transform: uppercase;
159
- letter-spacing: 0.5px;
160
- margin-bottom: 8px;
161
- color: var(--text-muted);
140
+ overflow: hidden;
162
141
  }
163
- .shortcuts-hint .shortcut {
142
+ .shortcut-item {
164
143
  display: flex;
144
+ align-items: center;
165
145
  justify-content: space-between;
166
- padding: 4px 0;
146
+ padding: 12px 14px;
147
+ border-bottom: 1px solid var(--border-color);
148
+ transition: background 0.2s;
167
149
  }
168
- .shortcuts-hint kbd {
150
+ .shortcut-item:last-child { border-bottom: none; }
151
+ .shortcut-item .label {
152
+ font-size: 13px;
153
+ font-weight: 500;
154
+ color: var(--text-primary);
155
+ display: flex;
156
+ align-items: center;
157
+ gap: 8px;
158
+ }
159
+ .shortcut-item .keys {
160
+ display: flex;
161
+ align-items: center;
162
+ gap: 4px;
163
+ }
164
+ .shortcut-item kbd {
169
165
  background: var(--bg-input);
170
166
  border: 1px solid var(--border-color);
171
167
  border-radius: 4px;
172
- padding: 2px 6px;
168
+ padding: 4px 8px;
173
169
  font-family: -apple-system, BlinkMacSystemFont, monospace;
174
- font-size: 11px;
170
+ font-size: 12px;
175
171
  color: var(--text-primary);
172
+ min-width: 24px;
173
+ text-align: center;
174
+ transition: background 0.2s, border-color 0.2s, color 0.2s;
175
+ }
176
+ .shortcut-item.editable {
177
+ background: rgba(93,173,226,0.05);
178
+ }
179
+ :root.light .shortcut-item.editable {
180
+ background: rgba(0,113,227,0.05);
181
+ }
182
+ .shortcut-item .hotkey-input-wrapper {
183
+ display: flex;
184
+ align-items: center;
185
+ gap: 8px;
186
+ }
187
+ .shortcut-item #hotkey-input {
188
+ width: 180px;
189
+ font-size: 12px;
190
+ padding: 6px 10px;
191
+ text-align: center;
192
+ font-family: -apple-system, BlinkMacSystemFont, monospace;
193
+ }
194
+ .shortcut-item .btn {
195
+ padding: 6px 12px;
196
+ font-size: 12px;
176
197
  }
198
+ .editable-badge {
199
+ font-size: 9px;
200
+ text-transform: uppercase;
201
+ letter-spacing: 0.5px;
202
+ color: var(--accent);
203
+ font-weight: 600;
204
+ background: rgba(93,173,226,0.15);
205
+ padding: 2px 6px;
206
+ border-radius: 4px;
207
+ }
208
+ :root.light .editable-badge {
209
+ background: rgba(0,113,227,0.1);
210
+ }
211
+
212
+ .toast {
213
+ position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
214
+ background: var(--success); color: #fff; padding: 8px 20px; border-radius: 6px;
215
+ font-size: 13px; opacity: 0; transition: opacity 0.3s; pointer-events: none;
216
+ }
217
+ .toast.show { opacity: 1; }
218
+ .toast.error { background: var(--danger); }
177
219
  </style>
178
220
  </head>
179
221
  <body>
@@ -186,7 +228,8 @@
186
228
  <h2>Gateways</h2>
187
229
  <div class="gw-list" id="gw-list"></div>
188
230
 
189
- <div class="form" id="gw-form">
231
+ <button class="btn btn-secondary" id="gw-add-btn" style="margin-bottom: 16px;">+ Add Gateway</button>
232
+ <div class="form" id="gw-form" style="display: none;">
190
233
  <div class="form-row">
191
234
  <input id="gw-name" placeholder="Name (e.g. Rooty)" />
192
235
  <input id="gw-url" placeholder="URL (e.g. http://localhost:19002)" />
@@ -196,31 +239,35 @@
196
239
  </div>
197
240
  <div class="form-row">
198
241
  <button class="btn" id="gw-save">Add Gateway</button>
199
- <button class="btn btn-secondary" id="gw-cancel" style="display:none">Cancel</button>
242
+ <button class="btn btn-secondary" id="gw-cancel">Cancel</button>
200
243
  </div>
201
244
  </div>
202
245
 
203
- <h2>Hotkey</h2>
204
- <div class="hotkey-section">
205
- <div class="hotkey-row">
206
- <input id="hotkey-input" placeholder="CommandOrControl+Shift+L" />
207
- <button class="btn" id="hotkey-save">Save</button>
208
- </div>
209
- </div>
210
-
211
- <div class="shortcuts-hint">
212
- <h3>Keyboard Shortcuts</h3>
213
- <div class="shortcut">
214
- <span>Next agent</span>
215
- <span><kbd>⌘</kbd> <kbd>⇧</kbd> <kbd>→</kbd></span>
216
- </div>
217
- <div class="shortcut">
218
- <span>Previous agent</span>
219
- <span><kbd>⌘</kbd> <kbd>⇧</kbd> <kbd>←</kbd></span>
220
- </div>
221
- <div class="shortcut">
222
- <span>Hide window</span>
223
- <span><kbd>Esc</kbd></span>
246
+ <h2>Keyboard Shortcuts</h2>
247
+ <div class="shortcuts-section">
248
+ <div class="shortcuts-list">
249
+ <div class="shortcut-item editable">
250
+ <span class="label">
251
+ Show/Hide Window
252
+ <span class="editable-badge">Editable</span>
253
+ </span>
254
+ <div class="hotkey-input-wrapper">
255
+ <input id="hotkey-input" placeholder="⌘⇧L" />
256
+ <button class="btn" id="hotkey-save">Save</button>
257
+ </div>
258
+ </div>
259
+ <div class="shortcut-item">
260
+ <span class="label">Next Agent</span>
261
+ <span class="keys"><kbd>⌘</kbd><kbd>⇧</kbd><kbd>→</kbd></span>
262
+ </div>
263
+ <div class="shortcut-item">
264
+ <span class="label">Previous Agent</span>
265
+ <span class="keys"><kbd>⌘</kbd><kbd>⇧</kbd><kbd>←</kbd></span>
266
+ </div>
267
+ <div class="shortcut-item">
268
+ <span class="label">Toggle Theme</span>
269
+ <span class="keys"><kbd>⌘</kbd><kbd>⇧</kbd><kbd>K</kbd></span>
270
+ </div>
224
271
  </div>
225
272
  </div>
226
273
  </div>
@@ -58,6 +58,25 @@ function esc(s) {
58
58
  return d.innerHTML;
59
59
  }
60
60
 
61
+ // ── Form visibility ───────────────────────────────────────────
62
+ function showForm() {
63
+ $('#gw-form').style.display = '';
64
+ $('#gw-add-btn').style.display = 'none';
65
+ $('#gw-name').focus();
66
+ }
67
+
68
+ function hideForm() {
69
+ $('#gw-form').style.display = 'none';
70
+ $('#gw-add-btn').style.display = '';
71
+ editingId = null;
72
+ $('#gw-name').value = '';
73
+ $('#gw-url').value = '';
74
+ $('#gw-token').value = '';
75
+ $('#gw-save').textContent = 'Add Gateway';
76
+ }
77
+
78
+ $('#gw-add-btn').addEventListener('click', showForm);
79
+
61
80
  // ── Add / Edit ────────────────────────────────────────────────
62
81
  function startEdit(gw) {
63
82
  editingId = gw.id;
@@ -65,16 +84,11 @@ function startEdit(gw) {
65
84
  $('#gw-url').value = gw.url;
66
85
  $('#gw-token').value = gw.token || '';
67
86
  $('#gw-save').textContent = 'Update Gateway';
68
- $('#gw-cancel').style.display = '';
87
+ showForm();
69
88
  }
70
89
 
71
90
  function cancelEdit() {
72
- editingId = null;
73
- $('#gw-name').value = '';
74
- $('#gw-url').value = '';
75
- $('#gw-token').value = '';
76
- $('#gw-save').textContent = 'Add Gateway';
77
- $('#gw-cancel').style.display = 'none';
91
+ hideForm();
78
92
  }
79
93
 
80
94
  $('#gw-save').addEventListener('click', async () => {
@@ -90,7 +104,7 @@ $('#gw-save').addEventListener('click', async () => {
90
104
  await window.api.addGateway({ name, url, token });
91
105
  toast('Gateway added');
92
106
  }
93
- cancelEdit();
107
+ hideForm();
94
108
  renderGateways();
95
109
  });
96
110
 
@@ -103,11 +117,148 @@ async function deleteGateway(id) {
103
117
  }
104
118
 
105
119
  // ── Hotkey ────────────────────────────────────────────────────
120
+ // Stores the Electron-format hotkey string
121
+ let currentHotkeyElectron = '';
122
+
123
+ // Map keys to display symbols (macOS style)
124
+ const MODIFIER_SYMBOLS = {
125
+ meta: '⌘', // Cmd
126
+ ctrl: '⌃', // Control
127
+ alt: '⌥', // Option
128
+ shift: '⇧', // Shift
129
+ };
130
+
131
+ // Special key display names
132
+ const KEY_DISPLAY = {
133
+ 'ArrowUp': '↑',
134
+ 'ArrowDown': '↓',
135
+ 'ArrowLeft': '←',
136
+ 'ArrowRight': '→',
137
+ 'Escape': 'Esc',
138
+ 'Backspace': '⌫',
139
+ 'Delete': '⌦',
140
+ 'Enter': '↵',
141
+ 'Tab': '⇥',
142
+ 'Space': '␣',
143
+ ' ': '␣',
144
+ };
145
+
146
+ // Convert keyboard event to display string and Electron format
147
+ function parseHotkey(e) {
148
+ const modifiers = [];
149
+ const electronParts = [];
150
+
151
+ // Order: Ctrl, Alt, Shift, Meta (standard order for display)
152
+ // But for macOS feel, we show: ⌃⌥⇧⌘
153
+ if (e.ctrlKey && !e.metaKey) {
154
+ modifiers.push(MODIFIER_SYMBOLS.ctrl);
155
+ electronParts.push('Control');
156
+ }
157
+ if (e.altKey) {
158
+ modifiers.push(MODIFIER_SYMBOLS.alt);
159
+ electronParts.push('Alt');
160
+ }
161
+ if (e.shiftKey) {
162
+ modifiers.push(MODIFIER_SYMBOLS.shift);
163
+ electronParts.push('Shift');
164
+ }
165
+ if (e.metaKey) {
166
+ modifiers.push(MODIFIER_SYMBOLS.meta);
167
+ electronParts.push('CommandOrControl');
168
+ }
169
+
170
+ // Get the main key (ignore standalone modifier keys)
171
+ const key = e.key;
172
+ const code = e.code;
173
+
174
+ // Skip if only modifier pressed
175
+ if (['Meta', 'Control', 'Alt', 'Shift'].includes(key)) {
176
+ return null;
177
+ }
178
+
179
+ // Determine display and Electron key
180
+ let displayKey = KEY_DISPLAY[key] || KEY_DISPLAY[code] || key.toUpperCase();
181
+ let electronKey = key.length === 1 ? key.toUpperCase() : key;
182
+
183
+ // Handle special cases for Electron
184
+ if (code.startsWith('Arrow')) {
185
+ electronKey = code.replace('Arrow', '');
186
+ } else if (key === ' ') {
187
+ electronKey = 'Space';
188
+ }
189
+
190
+ // Must have at least one modifier for a global hotkey
191
+ if (modifiers.length === 0) {
192
+ return null;
193
+ }
194
+
195
+ return {
196
+ display: modifiers.join('') + displayKey,
197
+ electron: [...electronParts, electronKey].join('+'),
198
+ };
199
+ }
200
+
201
+ // Convert Electron format back to display symbols
202
+ function electronToDisplay(electronStr) {
203
+ if (!electronStr) return '';
204
+
205
+ const parts = electronStr.split('+');
206
+ const display = [];
207
+ let mainKey = '';
208
+
209
+ for (const part of parts) {
210
+ switch (part) {
211
+ case 'CommandOrControl':
212
+ case 'Command':
213
+ case 'Cmd':
214
+ display.push('⌘');
215
+ break;
216
+ case 'Control':
217
+ case 'Ctrl':
218
+ display.push('⌃');
219
+ break;
220
+ case 'Alt':
221
+ case 'Option':
222
+ display.push('⌥');
223
+ break;
224
+ case 'Shift':
225
+ display.push('⇧');
226
+ break;
227
+ default:
228
+ // Main key
229
+ mainKey = KEY_DISPLAY[part] || KEY_DISPLAY['Arrow' + part] || part;
230
+ }
231
+ }
232
+
233
+ return display.join('') + mainKey;
234
+ }
235
+
236
+ // Hotkey input capture
237
+ const hotkeyInput = $('#hotkey-input');
238
+
239
+ hotkeyInput.addEventListener('focus', () => {
240
+ hotkeyInput.classList.add('capturing');
241
+ });
242
+
243
+ hotkeyInput.addEventListener('blur', () => {
244
+ hotkeyInput.classList.remove('capturing');
245
+ });
246
+
247
+ hotkeyInput.addEventListener('keydown', (e) => {
248
+ e.preventDefault();
249
+ e.stopPropagation();
250
+
251
+ const result = parseHotkey(e);
252
+ if (result) {
253
+ hotkeyInput.value = result.display;
254
+ currentHotkeyElectron = result.electron;
255
+ }
256
+ });
257
+
106
258
  $('#hotkey-save').addEventListener('click', async () => {
107
- const hotkey = $('#hotkey-input').value.trim();
108
- if (!hotkey) return toast('Enter a hotkey', true);
259
+ if (!currentHotkeyElectron) return toast('Press a hotkey combination first', true);
109
260
  try {
110
- await window.api.updateSettings({ hotkey });
261
+ await window.api.updateSettings({ hotkey: currentHotkeyElectron });
111
262
  toast('Hotkey saved');
112
263
  } catch (e) {
113
264
  toast('Failed to save hotkey', true);
@@ -121,5 +272,9 @@ $('#close-btn').addEventListener('click', () => window.close());
121
272
  (async () => {
122
273
  renderGateways();
123
274
  const settings = await window.api.getSettings();
124
- $('#hotkey-input').value = settings.hotkey || '';
275
+ // Load existing hotkey and display as symbols
276
+ if (settings.hotkey) {
277
+ currentHotkeyElectron = settings.hotkey;
278
+ $('#hotkey-input').value = electronToDisplay(settings.hotkey);
279
+ }
125
280
  })();