glad-web 1.0.43 → 1.0.44

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.
@@ -475,7 +475,9 @@ async function webCommand(options) {
475
475
  const webAssets = [
476
476
  'gitgraph.js',
477
477
  'styles.css',
478
+ 'theme.js',
478
479
  'core.js',
480
+ 'layout.js',
479
481
  'notifications.js',
480
482
  'claude.js',
481
483
  'schedules.js',
package/lib/web/claude.js CHANGED
@@ -9,13 +9,23 @@
9
9
  function setClaudeModeEnabled(enabled) {
10
10
  const codexChat = isCodexSession() && codexState.presentation === 'structured';
11
11
  const structured = enabled || codexChat;
12
+ const actionRail = enabled
13
+ ? document.querySelector('.claude-control-rail')
14
+ : codexChat
15
+ ? document.getElementById('codex-control-rail')
16
+ : document.getElementById('shortcut-rail');
17
+ const attachButton = document.getElementById('attach-image-btn');
18
+ const scheduleButton = document.getElementById('schedule-send-btn');
19
+ if (actionRail && attachButton && scheduleButton) {
20
+ actionRail.prepend(scheduleButton);
21
+ actionRail.prepend(attachButton);
22
+ }
12
23
  document.getElementById('terminal-container').style.display = structured ? 'none' : '';
13
24
  document.getElementById('claude-chat-container').style.display = enabled ? 'block' : 'none';
14
25
  document.getElementById('codex-chat-container').style.display = codexChat ? 'block' : 'none';
15
- document.getElementById('claude-control-panel').style.display = enabled ? 'block' : 'none';
16
- document.getElementById('codex-control-panel').style.display = codexChat ? 'block' : 'none';
26
+ document.getElementById('claude-control-panel').style.display = enabled ? 'flex' : 'none';
27
+ document.getElementById('codex-control-panel').style.display = codexChat ? 'flex' : 'none';
17
28
  document.getElementById('codex-terminal-switch').style.display = isCodexSession() ? '' : 'none';
18
- document.getElementById('timer-btn').style.display = '';
19
29
  document.getElementById('attach-image-btn').style.display = structured ? '' : 'none';
20
30
  document.getElementById('shortcut-rail').style.display = structured ? 'none' : '';
21
31
  document.getElementById('scroll-controls').style.display = structured ? 'none' : '';
@@ -94,7 +104,7 @@
94
104
  const option = select.selectedOptions && select.selectedOptions[0];
95
105
  const label = pickerLabelFromOption(option);
96
106
  const fullValue = pickerFullValue(option);
97
- button.textContent = prefix;
107
+ setActionButtonLabel(button, prefix);
98
108
  button.title = type === 'model'
99
109
  ? `${fullValue || label || prefix} · ${effortLabel(claudeState.effort)}`
100
110
  : (fullValue || label || prefix);
@@ -252,12 +262,12 @@
252
262
  const usageBtn = document.getElementById('claude-usage-btn');
253
263
  if (usageBtn) {
254
264
  usageBtn.disabled = claudeUsagePending || claudeStatus === 'thinking';
255
- usageBtn.textContent = claudeUsagePending ? 'Loading' : 'Usage';
265
+ setActionButtonLabel(usageBtn, claudeUsagePending ? 'Loading' : 'Usage');
256
266
  }
257
267
  const contextBtn = document.getElementById('claude-context-btn');
258
268
  if (contextBtn) {
259
269
  contextBtn.disabled = claudeContextPending || claudeStatus === 'thinking';
260
- contextBtn.textContent = claudeContextPending ? 'Loading' : 'Context';
270
+ setActionButtonLabel(contextBtn, claudeContextPending ? 'Loading' : 'Context');
261
271
  }
262
272
  renderClaudeStateBar();
263
273
  renderClaudeChat();
package/lib/web/codex.js CHANGED
@@ -445,14 +445,14 @@
445
445
  sandbox.value = codexState.sandboxMode || 'default';
446
446
  }
447
447
  const modelButton = document.getElementById('codex-model-btn');
448
- if (modelButton) modelButton.textContent = 'Model';
448
+ if (modelButton) setActionButtonLabel(modelButton, 'Model');
449
449
  const abort = document.getElementById('codex-abort-btn');
450
450
  if (abort) {
451
451
  abort.disabled = !codexState.canAbort;
452
- abort.textContent = codexState.aborting ? 'Aborting…' : 'Abort';
452
+ setActionButtonLabel(abort, codexState.aborting ? 'Aborting…' : 'Abort');
453
453
  }
454
454
  const compact = document.getElementById('codex-compact-btn');
455
- if (compact) { compact.disabled = !codexState.canCompact; compact.textContent = codexState.compacting ? 'Compacting' : 'Compact'; }
455
+ if (compact) { compact.disabled = !codexState.canCompact; setActionButtonLabel(compact, codexState.compacting ? 'Compacting' : 'Compact'); }
456
456
  const skills = document.getElementById('codex-skills-btn');
457
457
  if (skills) { skills.disabled = !codexReadyForInput(); skills.classList.toggle('primary', Boolean(selectedCodexSkill)); }
458
458
  const resume = document.getElementById('codex-resume-btn');
@@ -9,9 +9,9 @@
9
9
  }
10
10
 
11
11
  function syncComposerButtonState() {
12
- const menuOpen = document.getElementById('composer-menu').classList.contains('active');
13
12
  const timerOpen = document.getElementById('timed-send-panel').classList.contains('active');
14
- document.getElementById('timer-btn').classList.toggle('active', menuOpen || timerOpen);
13
+ document.getElementById('schedule-send-btn').classList.toggle('active', timerOpen);
14
+ document.getElementById('attach-image-btn').classList.toggle('active', selectedImageAttachments.length > 0);
15
15
  }
16
16
 
17
17
  function renderImageAttachments() {
@@ -19,6 +19,7 @@
19
19
  `<div class="attachment-chip${item.uploading ? ' uploading' : ''}"><span aria-hidden="true">▧</span><span class="attachment-chip-content"><span class="attachment-chip-name" title="${escapeHtml(item.name)}">${escapeHtml(item.name)}</span>${item.uploading ? `<span class="attachment-progress${item.progressKnown ? '' : ' estimated'}"><span style="width:${Math.max(0, Math.min(100, item.progress || 0))}%"></span></span><span class="attachment-status">${escapeHtml(item.status || (item.progressKnown ? `Uploading ${Math.round(item.progress || 0)}%` : 'Uploading original image…'))}</span>` : ''}</span><button class="attachment-remove" type="button" title="Remove image" aria-label="Remove ${escapeHtml(item.name)}" onclick="removeImageAttachment('${item.id}')">×</button></div>`
20
20
  )).join('');
21
21
  attachmentStrip.classList.toggle('active', selectedImageAttachments.length > 0);
22
+ syncComposerButtonState();
22
23
  updateTerminalControlsHeight();
23
24
  }
24
25
 
@@ -147,14 +148,7 @@
147
148
  }
148
149
  }
149
150
 
150
- function closeComposerMenu() {
151
- document.getElementById('composer-menu').classList.remove('active');
152
- syncComposerButtonState();
153
- updateTerminalControlsHeight();
154
- }
155
-
156
151
  function openTimedSendPanel() {
157
- closeComposerMenu();
158
152
  if (isClaudeSession()) {
159
153
  closeClaudePicker();
160
154
  claudeResumePanelOpen = false;
package/lib/web/core.js CHANGED
@@ -9,6 +9,15 @@
9
9
  let timedSendRefreshTimer = null;
10
10
  let timedTagTimer = null;
11
11
  let editingTimedInputId = null;
12
+ function setActionButtonLabel(button, label) {
13
+ if (!button) return;
14
+ const labelNode = button.querySelector('.action-label');
15
+ if (labelNode) {
16
+ labelNode.textContent = label;
17
+ return;
18
+ }
19
+ button.textContent = label;
20
+ }
12
21
  let claudeMessages = [];
13
22
  let claudePendingPermissions = [];
14
23
  let claudeStatus = 'idle';
@@ -235,7 +244,7 @@
235
244
  ${timedInputCount}
236
245
  </span>`
237
246
  : '';
238
- html += `<div class="session-card">
247
+ html += `<div class="session-card${s.id === activeSessionId ? ' selected' : ''}" data-session-id="${escapeHtml(s.id)}">
239
248
  <div class="session-info">
240
249
  <h3>${escapeHtml(s.name)}${s.hasUnreadCompletion ? '<span class="completion-dot" title="Completed"></span>' : ''}${timerBadge} <button type="button" class="icon-btn session-edit-btn" title="Rename session" aria-label="Rename session" onclick="renameSession('${s.id}', decodePathValue('${encodedName}'), event)"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg></button></h3>
241
250
  <p>${escapeHtml(s.tool)}</p>
@@ -320,7 +329,8 @@
320
329
  }
321
330
 
322
331
  function isLobbyVisible() {
323
- return document.getElementById('lobby-view').classList.contains('active');
332
+ return document.getElementById('lobby-view').classList.contains('active')
333
+ || (typeof isSplitLayout === 'function' && isSplitLayout());
324
334
  }
325
335
 
326
336
  function scheduleSessionPolling() {
@@ -5,6 +5,17 @@
5
5
  <title>Glad - AI Sessions</title>
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
7
7
  <meta name="apple-mobile-web-app-capable" content="yes">
8
+ <meta name="theme-color" content="#000000">
9
+ <script>
10
+ (() => {
11
+ const storedTheme = localStorage.getItem('glad-theme');
12
+ const theme = storedTheme === 'light' || storedTheme === 'dark'
13
+ ? storedTheme
14
+ : (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
15
+ document.documentElement.dataset.theme = theme;
16
+ document.documentElement.style.backgroundColor = theme === 'dark' ? '#000000' : '#f5f6f8';
17
+ })();
18
+ </script>
8
19
  <link rel="manifest" href="manifest.json">
9
20
  <link rel="icon" type="image/svg+xml" href="logo.svg">
10
21
  <link rel="apple-touch-icon" href="logo.svg">
@@ -13,6 +24,25 @@
13
24
  </head>
14
25
  <body>
15
26
  <div id="debug-log"></div>
27
+ <svg class="app-icon-sprite" aria-hidden="true">
28
+ <symbol id="icon-model" viewBox="0 0 24 24"><path d="M4 7h10M18 7h2M4 17h2M10 17h10M14 4v6M6 14v6"/></symbol>
29
+ <symbol id="icon-usage" viewBox="0 0 24 24"><path d="M5 20V10M12 20V4M19 20v-7"/></symbol>
30
+ <symbol id="icon-context" viewBox="0 0 24 24"><path d="M12 3a9 9 0 1 0 9 9h-9V3Z"/><path d="M15 3.5A9 9 0 0 1 20.5 9H15V3.5Z"/></symbol>
31
+ <symbol id="icon-stop" viewBox="0 0 24 24"><rect x="6" y="6" width="12" height="12" rx="2"/></symbol>
32
+ <symbol id="icon-resume" viewBox="0 0 24 24"><path d="M4 12a8 8 0 1 0 2.3-5.7L4 8.6"/><path d="M4 4v4.6h4.6"/><path d="M12 8v4l3 2"/></symbol>
33
+ <symbol id="icon-fork" viewBox="0 0 24 24"><circle cx="6" cy="5" r="2"/><circle cx="18" cy="5" r="2"/><circle cx="12" cy="19" r="2"/><path d="M6 7v2c0 4 6 3 6 8M18 7v2c0 4-6 3-6 8"/></symbol>
34
+ <symbol id="icon-shield" viewBox="0 0 24 24"><path d="M12 3 20 6v5c0 5-3.4 8.2-8 10-4.6-1.8-8-5-8-10V6l8-3Z"/><path d="m9 12 2 2 4-4"/></symbol>
35
+ <symbol id="icon-status" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 11v6M12 7h.01"/></symbol>
36
+ <symbol id="icon-prompts" viewBox="0 0 24 24"><path d="M4 5h16v11H8l-4 4V5Z"/><path d="M8 9h8M8 12h5"/></symbol>
37
+ <symbol id="icon-compact" viewBox="0 0 24 24"><path d="m4 9 5 0 0-5M9 9 4 4M20 9h-5V4M15 9l5-5M4 15h5v5M9 15l-5 5M20 15h-5v5M15 15l5 5"/></symbol>
38
+ <symbol id="icon-sandbox" viewBox="0 0 24 24"><path d="m12 3 8 4.5v9L12 21l-8-4.5v-9L12 3Z"/><path d="m4.5 7.8 7.5 4.3 7.5-4.3M12 12v9"/></symbol>
39
+ <symbol id="icon-lock" viewBox="0 0 24 24"><rect x="5" y="10" width="14" height="11" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></symbol>
40
+ <symbol id="icon-skills" viewBox="0 0 24 24"><path d="M9 3H4a1 1 0 0 0-1 1v5h2.5a2.5 2.5 0 1 1 0 5H3v5a1 1 0 0 0 1 1h5v-2.5a2.5 2.5 0 1 1 5 0V20h5a1 1 0 0 0 1-1v-5h-2.5a2.5 2.5 0 1 1 0-5H20V4a1 1 0 0 0-1-1h-5v2.5a2.5 2.5 0 1 1-5 0V3Z"/></symbol>
41
+ <symbol id="icon-image" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="9" cy="10" r="2"/><path d="m4 17 4.5-4 3.5 3 2.5-2 5.5 4"/></symbol>
42
+ <symbol id="icon-schedule" viewBox="0 0 24 24"><circle cx="10.5" cy="13" r="7.5"/><path d="M10.5 9v4l2.5 1.5M18 3v6M15 6h6"/></symbol>
43
+ </svg>
44
+
45
+ <div id="app-shell">
16
46
 
17
47
  <!-- Lobby View -->
18
48
  <div id="lobby-view" class="view active">
@@ -55,6 +85,14 @@
55
85
  </div>
56
86
  </div>
57
87
 
88
+ <div id="sidebar-resizer" role="separator" aria-label="Resize session sidebar" aria-orientation="vertical"></div>
89
+ <main id="detail-pane">
90
+ <div id="detail-empty">
91
+ <img src="logo.svg" alt="" aria-hidden="true">
92
+ <h2>Select a session</h2>
93
+ <p>Choose an active session from the sidebar or create a new one.</p>
94
+ </div>
95
+
58
96
  <!-- Usage Dashboard View -->
59
97
  <div id="usage-view" class="view">
60
98
  <div class="usage-nav">
@@ -141,16 +179,13 @@
141
179
  <div id="timed-tag-rail"></div>
142
180
  <div id="input-row">
143
181
  <textarea id="cmd-input" rows="1" placeholder="Type a message..."></textarea>
144
- <button id="timer-btn" title="Add image or schedule send" aria-label="Add image or schedule send">+</button>
145
- <button id="send-btn">
182
+ <button id="attach-image-btn" class="composer-action-btn" type="button" title="Add image" aria-label="Add image"><svg class="action-icon" aria-hidden="true"><use href="#icon-image"></use></svg></button>
183
+ <button id="schedule-send-btn" class="composer-action-btn" type="button" title="Schedule send" aria-label="Schedule send"><svg class="action-icon" aria-hidden="true"><use href="#icon-schedule"></use></svg></button>
184
+ <button id="send-btn" type="button" title="Send" aria-label="Send">
146
185
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
147
186
  </button>
148
187
  </div>
149
188
  <div id="attachment-strip" aria-live="polite"></div>
150
- <div id="composer-menu" role="menu">
151
- <button id="attach-image-btn" class="composer-menu-btn" type="button" role="menuitem">Add image</button>
152
- <button id="schedule-send-btn" class="composer-menu-btn" type="button" role="menuitem">Schedule send</button>
153
- </div>
154
189
  <input id="image-file-input" type="file" accept="image/png,image/jpeg,image/gif,image/webp" multiple hidden>
155
190
  <div id="claude-control-panel">
156
191
  <div class="claude-control-rail">
@@ -166,13 +201,13 @@
166
201
  <option value="xhigh">Extra high</option>
167
202
  <option value="max">Max</option>
168
203
  </select>
169
- <button id="claude-model-picker-btn" class="claude-select claude-picker-btn" onclick="toggleClaudePicker('model')" title="Model and effort">Model</button>
204
+ <button id="claude-model-picker-btn" class="claude-select claude-picker-btn" onclick="toggleClaudePicker('model')" title="Model and effort" aria-label="Model and effort"><svg class="action-icon" aria-hidden="true"><use href="#icon-model"></use></svg><span class="action-label">Model</span></button>
170
205
  </div>
171
- <button id="claude-usage-btn" class="claude-ctrl-btn" onclick="requestClaudeUsage()" title="Show Claude usage">Usage</button>
172
- <button id="claude-context-btn" class="claude-ctrl-btn" onclick="requestClaudeContext()" title="Show Claude context">Context</button>
173
- <button id="claude-abort-btn" class="claude-ctrl-btn danger" onclick="abortClaudeSession()" title="Abort current Claude turn">Abort</button>
174
- <button id="claude-resume-btn" class="claude-ctrl-btn primary" onclick="toggleClaudeResumePanel()" title="Choose a Claude session to resume">Resume</button>
175
- <button id="claude-fork-btn" class="claude-ctrl-btn primary" onclick="toggleClaudeForkPanel()" title="Fork a Claude session">Fork</button>
206
+ <button id="claude-usage-btn" class="claude-ctrl-btn" onclick="requestClaudeUsage()" title="Show Claude usage" aria-label="Show Claude usage"><svg class="action-icon" aria-hidden="true"><use href="#icon-usage"></use></svg><span class="action-label">Usage</span></button>
207
+ <button id="claude-context-btn" class="claude-ctrl-btn" onclick="requestClaudeContext()" title="Show Claude context" aria-label="Show Claude context"><svg class="action-icon" aria-hidden="true"><use href="#icon-context"></use></svg><span class="action-label">Context</span></button>
208
+ <button id="claude-abort-btn" class="claude-ctrl-btn danger" onclick="abortClaudeSession()" title="Abort current Claude turn" aria-label="Abort current Claude turn"><svg class="action-icon" aria-hidden="true"><use href="#icon-stop"></use></svg><span class="action-label">Abort</span></button>
209
+ <button id="claude-resume-btn" class="claude-ctrl-btn primary" onclick="toggleClaudeResumePanel()" title="Choose a Claude session to resume" aria-label="Choose a Claude session to resume"><svg class="action-icon" aria-hidden="true"><use href="#icon-resume"></use></svg><span class="action-label">Resume</span></button>
210
+ <button id="claude-fork-btn" class="claude-ctrl-btn primary" onclick="toggleClaudeForkPanel()" title="Fork a Claude session" aria-label="Fork a Claude session"><svg class="action-icon" aria-hidden="true"><use href="#icon-fork"></use></svg><span class="action-label">Fork</span></button>
176
211
  </div>
177
212
  <div class="claude-control-page secondary">
178
213
  <div class="claude-picker-wrap">
@@ -182,7 +217,7 @@
182
217
  <option value="plan">Plan mode</option>
183
218
  <option value="bypassPermissions">Bypass</option>
184
219
  </select>
185
- <button id="claude-permission-picker-btn" class="claude-select claude-picker-btn" onclick="toggleClaudePicker('permission')" title="Permission mode">Permission</button>
220
+ <button id="claude-permission-picker-btn" class="claude-select claude-picker-btn" onclick="toggleClaudePicker('permission')" title="Permission mode" aria-label="Permission mode"><svg class="action-icon" aria-hidden="true"><use href="#icon-shield"></use></svg><span class="action-label">Permission</span></button>
186
221
  </div>
187
222
  </div>
188
223
  </div>
@@ -194,28 +229,28 @@
194
229
  <div id="codex-control-panel">
195
230
  <div id="codex-control-rail" class="codex-control-rail">
196
231
  <div class="codex-control-page">
197
- <button id="codex-model-btn" class="claude-ctrl-btn" onclick="toggleCodexModelPanel()" title="Model and effort">Model</button>
198
- <button id="codex-status-btn" class="claude-ctrl-btn" onclick="requestCodexStatus()" title="Show Codex account, limits, and context status">Status</button>
199
- <button id="codex-abort-btn" class="claude-ctrl-btn danger" onclick="abortCodexSession()" title="Abort current Codex turn">Abort</button>
200
- <button id="codex-resume-btn" class="claude-ctrl-btn primary" onclick="toggleCodexResumePanel()" title="Resume a Codex thread">Resume</button>
201
- <button id="codex-fork-btn" class="claude-ctrl-btn primary" onclick="toggleCodexForkPanel()" title="Fork a Codex thread in this conversation">Fork</button>
232
+ <button id="codex-model-btn" class="claude-ctrl-btn" onclick="toggleCodexModelPanel()" title="Model and effort" aria-label="Model and effort"><svg class="action-icon" aria-hidden="true"><use href="#icon-model"></use></svg><span class="action-label">Model</span></button>
233
+ <button id="codex-status-btn" class="claude-ctrl-btn" onclick="requestCodexStatus()" title="Show Codex account, limits, and context status" aria-label="Show Codex status"><svg class="action-icon" aria-hidden="true"><use href="#icon-status"></use></svg><span class="action-label">Status</span></button>
234
+ <button id="codex-abort-btn" class="claude-ctrl-btn danger" onclick="abortCodexSession()" title="Abort current Codex turn" aria-label="Abort current Codex turn"><svg class="action-icon" aria-hidden="true"><use href="#icon-stop"></use></svg><span class="action-label">Abort</span></button>
235
+ <button id="codex-resume-btn" class="claude-ctrl-btn primary" onclick="toggleCodexResumePanel()" title="Resume a Codex thread" aria-label="Resume a Codex thread"><svg class="action-icon" aria-hidden="true"><use href="#icon-resume"></use></svg><span class="action-label">Resume</span></button>
236
+ <button id="codex-fork-btn" class="claude-ctrl-btn primary" onclick="toggleCodexForkPanel()" title="Fork a Codex thread in this conversation" aria-label="Fork this Codex thread"><svg class="action-icon" aria-hidden="true"><use href="#icon-fork"></use></svg><span class="action-label">Fork</span></button>
202
237
  </div>
203
238
  <div class="codex-control-page">
204
- <button id="codex-prompts-btn" class="claude-ctrl-btn" onclick="toggleCodexPromptPanel()" title="Browse and copy recent prompts">Prompts</button>
205
- <button id="codex-compact-btn" class="claude-ctrl-btn" onclick="compactCodexContext()" title="Compact the current Codex context">Compact</button>
239
+ <button id="codex-prompts-btn" class="claude-ctrl-btn" onclick="toggleCodexPromptPanel()" title="Browse and copy recent prompts" aria-label="Browse recent prompts"><svg class="action-icon" aria-hidden="true"><use href="#icon-prompts"></use></svg><span class="action-label">Prompts</span></button>
240
+ <button id="codex-compact-btn" class="claude-ctrl-btn" onclick="compactCodexContext()" title="Compact the current Codex context" aria-label="Compact Codex context"><svg class="action-icon" aria-hidden="true"><use href="#icon-compact"></use></svg><span class="action-label">Compact</span></button>
206
241
  <label class="codex-select-control" title="Sandbox mode">
207
- <span class="codex-select-label" aria-hidden="true">Sandbox</span>
242
+ <span class="codex-select-label" aria-hidden="true"><svg class="action-icon"><use href="#icon-sandbox"></use></svg><span class="action-label">Sandbox</span></span>
208
243
  <select id="codex-sandbox-select" class="codex-select" aria-label="Sandbox mode" onchange="updateCodexSettingsFromControls()">
209
244
  <option value="default">Default</option><option value="read-only">Read only</option><option value="workspace-write">Workspace write</option><option value="danger-full-access">Full access</option>
210
245
  </select>
211
246
  </label>
212
247
  <label class="codex-select-control" title="Approval policy">
213
- <span class="codex-select-label" aria-hidden="true">Ask</span>
248
+ <span class="codex-select-label" aria-hidden="true"><svg class="action-icon"><use href="#icon-lock"></use></svg><span class="action-label">Ask</span></span>
214
249
  <select id="codex-permission-select" class="codex-select" aria-label="Approval policy" onchange="updateCodexSettingsFromControls()">
215
250
  <option value="default">Default</option><option value="untrusted">Untrusted</option><option value="on-request">On request</option><option value="never">Never ask</option>
216
251
  </select>
217
252
  </label>
218
- <button id="codex-skills-btn" class="claude-ctrl-btn" onclick="toggleCodexSkillPanel()" title="Choose a skill for the next message">Skills</button>
253
+ <button id="codex-skills-btn" class="claude-ctrl-btn" onclick="toggleCodexSkillPanel()" title="Choose a skill for the next message" aria-label="Skills"><svg class="action-icon" aria-hidden="true"><use href="#icon-skills"></use></svg><span class="action-label">Skills</span></button>
219
254
  </div>
220
255
  </div>
221
256
  <div id="codex-state-bar"></div>
@@ -314,6 +349,9 @@
314
349
  </div>
315
350
  </div>
316
351
 
352
+ </main>
353
+ </div>
354
+
317
355
  <!-- Tool Modal -->
318
356
  <div id="modal-overlay" onclick="closeToolModal(event)">
319
357
  <div id="tool-modal" onclick="event.stopPropagation()">
@@ -355,6 +393,20 @@
355
393
  </div>
356
394
  <button class="icon-btn settings-close" type="button" onclick="closeSettings()" aria-label="Close">×</button>
357
395
  </div>
396
+ <section class="settings-section" aria-labelledby="appearance-settings-title">
397
+ <div class="settings-section-label" id="appearance-settings-title">Appearance</div>
398
+ <div class="theme-choice" role="group" aria-label="Color theme">
399
+ <button id="theme-light-btn" type="button" onclick="setGladTheme('light')" aria-pressed="false">
400
+ <span class="theme-swatch light" aria-hidden="true"></span>
401
+ Light
402
+ </button>
403
+ <button id="theme-dark-btn" type="button" onclick="setGladTheme('dark')" aria-pressed="false">
404
+ <span class="theme-swatch dark" aria-hidden="true"></span>
405
+ Dark
406
+ </button>
407
+ </div>
408
+ <p class="settings-theme-help">Glad follows your system until you choose a theme.</p>
409
+ </section>
358
410
  <section class="settings-section" aria-labelledby="notifications-settings-title">
359
411
  <div class="settings-section-label" id="notifications-settings-title">Notifications</div>
360
412
  <div class="settings-provider-header">
@@ -441,8 +493,10 @@
441
493
 
442
494
  <script defer src="vendor/xterm.js"></script>
443
495
  <script defer src="vendor/xterm-addon-fit.js"></script>
496
+ <script src="theme.js"></script>
444
497
  <script src="gitgraph.js"></script>
445
498
  <script src="core.js"></script>
499
+ <script src="layout.js"></script>
446
500
  <script src="notifications.js"></script>
447
501
  <script src="claude.js"></script>
448
502
  <script src="schedules.js"></script>
@@ -0,0 +1,72 @@
1
+ const GLAD_SPLIT_QUERY = '(min-width: 920px)';
2
+ const GLAD_SIDEBAR_KEY = 'glad-sidebar-width';
3
+
4
+ function isSplitLayout() {
5
+ return window.matchMedia(GLAD_SPLIT_QUERY).matches;
6
+ }
7
+
8
+ function clampSidebarWidth(value) {
9
+ const max = Math.max(300, Math.min(480, window.innerWidth - 484));
10
+ return Math.min(max, Math.max(300, Number(value) || 348));
11
+ }
12
+
13
+ function applySidebarWidth(value) {
14
+ const width = clampSidebarWidth(value);
15
+ document.documentElement.style.setProperty('--sidebar-w', `${width}px`);
16
+ return width;
17
+ }
18
+
19
+ function initializeResponsiveLayout() {
20
+ const storedWidth = Number(localStorage.getItem(GLAD_SIDEBAR_KEY));
21
+ applySidebarWidth(storedWidth || 348);
22
+
23
+ const handle = document.getElementById('sidebar-resizer');
24
+ if (handle) {
25
+ let dragging = false;
26
+ const onPointerMove = event => {
27
+ if (!dragging) return;
28
+ applySidebarWidth(event.clientX);
29
+ if (typeof syncLayout === 'function') syncLayout({ keepAtBottom: false });
30
+ };
31
+ const stopDragging = () => {
32
+ if (!dragging) return;
33
+ dragging = false;
34
+ handle.classList.remove('dragging');
35
+ document.body.style.removeProperty('cursor');
36
+ document.body.style.removeProperty('user-select');
37
+ const width = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-w'));
38
+ localStorage.setItem(GLAD_SIDEBAR_KEY, String(clampSidebarWidth(width)));
39
+ };
40
+ handle.addEventListener('pointerdown', event => {
41
+ if (!isSplitLayout()) return;
42
+ dragging = true;
43
+ handle.classList.add('dragging');
44
+ handle.setPointerCapture?.(event.pointerId);
45
+ document.body.style.cursor = 'col-resize';
46
+ document.body.style.userSelect = 'none';
47
+ event.preventDefault();
48
+ });
49
+ window.addEventListener('pointermove', onPointerMove);
50
+ window.addEventListener('pointerup', stopDragging);
51
+ window.addEventListener('pointercancel', stopDragging);
52
+ }
53
+
54
+ const media = window.matchMedia(GLAD_SPLIT_QUERY);
55
+ media.addEventListener('change', () => {
56
+ applySidebarWidth(parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-w')));
57
+ if (isSplitLayout()) scheduleSessionPolling();
58
+ if (typeof syncLayout === 'function') requestAnimationFrame(() => syncLayout({ keepAtBottom: false }));
59
+ });
60
+
61
+ const controls = document.getElementById('terminal-controls');
62
+ if (controls && typeof ResizeObserver !== 'undefined') {
63
+ new ResizeObserver(() => {
64
+ if (typeof syncLayout === 'function') syncLayout();
65
+ }).observe(controls);
66
+ }
67
+ }
68
+
69
+ window.addEventListener('resize', () => {
70
+ if (isSplitLayout()) applySidebarWidth(parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-w')));
71
+ });
72
+ document.addEventListener('DOMContentLoaded', initializeResponsiveLayout);
@@ -49,6 +49,7 @@
49
49
  async function openSettings(event = null) {
50
50
  event?.stopPropagation();
51
51
  document.getElementById('settings-modal-overlay').style.display = 'flex';
52
+ if (typeof syncGladThemeControls === 'function') syncGladThemeControls();
52
53
  setServerChanStatus('Loading configuration…');
53
54
  try {
54
55
  const response = await fetchWithTimeout('/api/notifications/serverchan', {}, 10000);
@@ -17,6 +17,7 @@
17
17
  setClaudeModeEnabled(isClaudeSession());
18
18
  if (currentSocket) { currentSocket.close(); currentSocket = null; }
19
19
  initTerminal(id);
20
+ if (typeof isSplitLayout === 'function' && isSplitLayout()) refreshSessionsNow();
20
21
  }
21
22
 
22
23
  function initTerminal(sessionId) {
@@ -37,7 +38,7 @@
37
38
  return /^\x1b\[\?[\d;]*c$/.test(data) || /^\x1b\[>[\d;]*c$/.test(data);
38
39
  }
39
40
  if (!term) {
40
- term = new Terminal({ theme: { background: '#000', foreground: '#fff', cursor: '#0f0' }, cursorBlink: true, fontSize: 14, cursorStyle: 'bar', scrollback: 10000 });
41
+ term = new Terminal({ theme: typeof getGladTerminalTheme === 'function' ? getGladTerminalTheme() : { background: '#000', foreground: '#fff', cursor: '#0f0' }, cursorBlink: true, fontSize: 14, cursorStyle: 'bar', scrollback: 10000 });
41
42
  fitAddon = new FitAddon.FitAddon();
42
43
  term.loadAddon(fitAddon);
43
44
  term.open(document.getElementById('terminal'));
package/lib/web/shell.js CHANGED
@@ -7,6 +7,9 @@
7
7
  if (currentSocket) { currentSocket.close(); currentSocket = null; }
8
8
  closeTimedSendPanel();
9
9
  stopTimedInputTimers();
10
+ activeSessionId = null;
11
+ window.activeSessionId = null;
12
+ activeToolKey = null;
10
13
  document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
11
14
  document.getElementById('lobby-view').classList.add('active');
12
15
  refreshSessionsNow();
@@ -142,13 +142,7 @@
142
142
  #cmd-input { flex: 1; min-height: 38px; max-height: 150px; background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.1); border-radius: 19px; color: #fff; padding: 9px 16px; font-size: 16px; outline: none; resize: none; overflow-y: auto; line-height: 20px; box-sizing: border-box; transition: background 0.18s ease, border-color 0.18s ease; }
143
143
  #cmd-input::placeholder { color: rgba(255,255,255,0.45); }
144
144
  #cmd-input:focus { background: rgba(255,255,255,0.1); border-color: rgba(0,122,255,0.42); color: #fff; }
145
- #timer-btn { width: 38px; height: 38px; margin-left: 8px; background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.1); border-radius: 19px; color: #d1d5db; display: flex; align-items: center; justify-content: center; flex-shrink: 0; cursor: pointer; font-size: 24px; line-height: 1; }
146
- #timer-btn.active { color: #fff; border-color: rgba(0,122,255,0.45); background: rgba(0,122,255,0.22); }
147
145
  #send-btn { width: 44px; height: 38px; margin-left: 10px; background: #007aff; border: none; border-radius: 19px; color: #fff; display: flex; align-items: center; justify-content: center; flex-shrink: 0; cursor: pointer; }
148
- #composer-menu { display: none; width: min(calc(100% - 28px), var(--control-content-max)); margin: -2px auto 8px auto; padding: 6px; border: 1px solid rgba(255,255,255,0.12); border-radius: 12px; background: rgba(38,38,42,0.98); box-shadow: 0 12px 28px rgba(0,0,0,0.28); box-sizing: border-box; }
149
- #composer-menu.active { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }
150
- .composer-menu-btn { min-height: 42px; border: 0; border-radius: 9px; background: rgba(255,255,255,0.08); color: #fff; font-size: 14px; font-weight: 650; cursor: pointer; }
151
- .composer-menu-btn:hover { background: rgba(255,255,255,0.14); }
152
146
  #attachment-strip { display: none; width: min(calc(100% - 28px), var(--control-content-max)); margin: -3px auto 2px auto; gap: 7px; overflow-x: auto; padding: 0 0 4px 0; box-sizing: border-box; scrollbar-width: none; }
153
147
  #attachment-strip.active { display: flex; }
154
148
  #attachment-strip::-webkit-scrollbar { display: none; }
@@ -539,3 +533,278 @@
539
533
  :root { --chat-content-max: 960px; --control-content-max: 1100px; }
540
534
  #codex-chat-container { padding: 18px 22px 34px; }
541
535
  }
536
+
537
+ /* Responsive application shell, bottom composer, and color themes. */
538
+ :root {
539
+ --sidebar-w: 348px;
540
+ --surface: #121212;
541
+ --surface-raised: #1c1c1e;
542
+ --surface-soft: rgba(255,255,255,.08);
543
+ --line: rgba(255,255,255,.10);
544
+ --chat-bg: #050505;
545
+ --input-bg: rgba(255,255,255,.08);
546
+ --input-fg: #ffffff;
547
+ --input-placeholder: rgba(255,255,255,.45);
548
+ --overlay-bg: rgba(0,0,0,.80);
549
+ color-scheme: dark;
550
+ }
551
+
552
+ html[data-theme="light"] {
553
+ --bg: #f5f6f8;
554
+ --card-bg: #ffffff;
555
+ --text: #172033;
556
+ --text-dim: #667085;
557
+ --surface: #ffffff;
558
+ --surface-raised: #ffffff;
559
+ --surface-soft: rgba(15,23,42,.055);
560
+ --line: rgba(15,23,42,.11);
561
+ --chat-bg: #ffffff;
562
+ --input-bg: #f2f4f7;
563
+ --input-fg: #172033;
564
+ --input-placeholder: #8a94a6;
565
+ --overlay-bg: rgba(15,23,42,.45);
566
+ color-scheme: light;
567
+ }
568
+
569
+ html[data-theme="dark"] { color-scheme: dark; }
570
+
571
+ .app-icon-sprite { position: fixed; width: 0; height: 0; overflow: hidden; pointer-events: none; }
572
+ .action-icon { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; pointer-events: none; }
573
+ .action-label { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
574
+
575
+ #app-shell { display: flex; width: 100%; height: 100%; min-height: 0; overflow: hidden; background: var(--bg); }
576
+ #detail-pane { position: relative; display: flex; min-width: 0; min-height: 0; flex: 1; overflow: hidden; background: var(--bg); }
577
+ #detail-pane > .view { min-width: 0; min-height: 0; }
578
+ #detail-empty { display: none; flex: 1; flex-direction: column; align-items: center; justify-content: center; padding: 32px; color: var(--text-dim); text-align: center; }
579
+ #detail-empty img { width: 72px; height: 72px; margin-bottom: 14px; opacity: .72; }
580
+ #detail-empty h2 { margin: 0; color: var(--text); font-size: 20px; }
581
+ #detail-empty p { max-width: 360px; margin: 8px 0 0; font-size: 13px; line-height: 1.5; }
582
+ #sidebar-resizer { display: none; }
583
+
584
+ #terminal-view.active { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; }
585
+ #top-ui { display: contents; }
586
+ #nav-bar { grid-row: 1; z-index: 20; padding-top: max(8px, env(safe-area-inset-top)); background: var(--surface); border-color: var(--line); }
587
+ #terminal-controls { position: relative; inset: auto; grid-row: 3; z-index: 30; max-height: min(62dvh, 620px); overflow-y: auto; padding-bottom: env(safe-area-inset-bottom); border-top: 1px solid var(--line); border-bottom: 0; background: color-mix(in srgb, var(--surface) 96%, transparent); box-shadow: 0 -10px 28px rgba(0,0,0,.12); }
588
+ #terminal-controls-spacer { display: none; }
589
+ #terminal-container, #claude-chat-container, #codex-chat-container { grid-row: 2; min-width: 0; min-height: 0; background: var(--chat-bg); }
590
+ #input-row { padding-top: 10px; padding-bottom: 7px; }
591
+ #cmd-input { width: 100%; min-height: 42px; border-color: var(--line); background: var(--input-bg); color: var(--input-fg); }
592
+ #cmd-input::placeholder { color: var(--input-placeholder); }
593
+ #cmd-input:focus { color: var(--input-fg); }
594
+ #send-btn { position: absolute; z-index: 5; bottom: calc(10px + env(safe-area-inset-bottom)); margin: 0; }
595
+ .composer-action-btn { position: relative; width: 40px; min-width: 40px; height: 40px; flex: 0 0 40px; padding: 0; margin: 0; border: 1px solid var(--line); border-radius: 12px; background: var(--surface-soft); color: var(--text-dim); display: flex; align-items: center; justify-content: center; cursor: pointer; }
596
+ .composer-action-btn:hover { color: var(--text); }
597
+ .composer-action-btn.active { color: var(--primary); border-color: color-mix(in srgb, var(--primary) 48%, transparent); background: color-mix(in srgb, var(--primary) 14%, var(--surface)); }
598
+ #send-btn { right: max(12px, calc((100% - var(--control-content-max)) / 2 + 14px)); width: 44px; height: 40px; }
599
+ #claude-control-panel, #codex-control-panel { position: relative; flex-direction: column; width: min(100%, var(--control-content-max)); padding: 0 14px 10px; border: 0; background: transparent; }
600
+ .claude-control-rail, .codex-control-rail { order: 100; min-height: 40px; gap: 7px; margin: 0 58px 0 0; padding: 0; overflow-x: auto; overflow-y: hidden; scroll-snap-type: none; scroll-padding: 0; }
601
+ .claude-control-page, .claude-control-page.secondary, .codex-control-page { display: flex; width: auto; flex: 0 0 auto; gap: 7px; justify-content: flex-start; scroll-snap-align: none; }
602
+ .claude-control-page > *, .codex-control-page > * { width: 40px; min-width: 40px; height: 40px; flex: 0 0 40px; }
603
+ .claude-control-page .claude-ctrl-btn, .claude-control-page .claude-select, .codex-control-page .claude-ctrl-btn, .codex-control-page .codex-select-label,
604
+ .claude-select, .claude-ctrl-btn, .codex-select-control, .codex-select-label { width: 40px; height: 40px; padding: 0; border-radius: 12px; font-size: 0; }
605
+ .claude-picker-btn, .claude-ctrl-btn, .codex-select-label { position: relative; display: inline-flex; align-items: center; justify-content: center; }
606
+ #claude-state-bar, #codex-state-bar { order: 10; }
607
+ #claude-picker-panel, #claude-resume-panel, #claude-fork-panel,
608
+ #codex-model-panel, #codex-resume-panel, #codex-fork-panel, #codex-prompt-panel, #codex-skill-panel { order: 20; }
609
+ #shortcut-rail { width: min(100%, var(--control-content-max)); min-height: 40px; gap: 7px; margin: 0 auto; padding: 0 66px 10px 14px; scroll-snap-type: none; scroll-padding: 0; }
610
+ .shortcut-group { display: contents; }
611
+ .shortcut-group .key-btn, .shortcut-group .command-key { width: auto; min-width: 48px; height: 40px; padding: 0 10px; flex: 0 0 auto; }
612
+ #attachment-strip { border-color: var(--line); background: var(--surface-raised); }
613
+ #timed-send-panel { margin-bottom: 8px; border-color: var(--line); background: var(--surface-raised); }
614
+ #timed-tag-rail { top: auto; bottom: calc(100% + 6px); }
615
+ #scroll-controls { position: absolute; }
616
+
617
+ .theme-choice { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
618
+ .theme-choice button { min-height: 44px; display: flex; align-items: center; justify-content: center; gap: 8px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface-soft); color: var(--text); font-size: 13px; font-weight: 700; cursor: pointer; }
619
+ .theme-choice button[aria-pressed="true"] { border-color: var(--primary); box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 18%, transparent); }
620
+ .theme-swatch { width: 18px; height: 18px; border: 1px solid rgba(127,127,127,.35); border-radius: 50%; }
621
+ .theme-swatch.light { background: #fff; }
622
+ .theme-swatch.dark { background: #1c1c1e; }
623
+ .settings-theme-help { margin: 8px 0 0; color: var(--text-dim); font-size: 11px; }
624
+
625
+ @media (min-width: 920px) {
626
+ #lobby-view { display: flex !important; width: min(var(--sidebar-w), calc(100vw - 484px)); flex: 0 0 min(var(--sidebar-w), calc(100vw - 484px)); border-right: 1px solid var(--line); background: var(--bg); }
627
+ #lobby { width: 100%; padding: 18px 14px; }
628
+ #lobby-view .header { margin-bottom: 18px; }
629
+ #lobby-view .header h1 { font-size: 23px; }
630
+ #lobby-view .header-action-btn:not(.icon-only) { width: 36px; padding: 0; font-size: 0; }
631
+ #lobby-view .header-action-btn:not(.icon-only) span:first-child { font-size: 20px; }
632
+ #sidebar-resizer { display: block; width: 4px; flex: 0 0 4px; cursor: col-resize; background: var(--line); touch-action: none; transition: background .15s; }
633
+ #sidebar-resizer:hover, #sidebar-resizer.dragging { background: var(--primary); }
634
+ #detail-pane:not(:has(.view.active)) #detail-empty { display: flex; }
635
+ #back-btn, .usage-nav-button { visibility: hidden; pointer-events: none; }
636
+ .session-card.selected { border: 1px solid color-mix(in srgb, var(--primary) 48%, transparent); background: color-mix(in srgb, var(--primary) 10%, var(--card-bg)); }
637
+ }
638
+
639
+ @media (max-width: 919px) {
640
+ #lobby-view { flex: 1; }
641
+ #detail-pane { display: contents; }
642
+ #detail-empty { display: none !important; }
643
+ }
644
+
645
+ /* Light palette overrides for legacy selectors that still carry dark literals. */
646
+ html[data-theme="light"] body,
647
+ html[data-theme="light"] #terminal-view,
648
+ html[data-theme="light"] #terminal-container,
649
+ html[data-theme="light"] #git-content,
650
+ html[data-theme="light"] #usage-view { background: var(--bg) !important; color: var(--text); }
651
+ html[data-theme="light"] #top-ui,
652
+ html[data-theme="light"] #nav-bar,
653
+ html[data-theme="light"] .usage-nav,
654
+ html[data-theme="light"] #codex-control-panel { background: var(--surface) !important; border-color: var(--line) !important; }
655
+ html[data-theme="light"] .session-card,
656
+ html[data-theme="light"] .schedule-card,
657
+ html[data-theme="light"] .usage-panel,
658
+ html[data-theme="light"] .usage-summary-card,
659
+ html[data-theme="light"] #tool-modal,
660
+ html[data-theme="light"] #settings-modal,
661
+ html[data-theme="light"] #usage-source-modal,
662
+ html[data-theme="light"] #schedule-modal,
663
+ html[data-theme="light"] #claude-picker-panel,
664
+ html[data-theme="light"] #claude-resume-panel,
665
+ html[data-theme="light"] #claude-fork-panel,
666
+ html[data-theme="light"] #codex-model-panel,
667
+ html[data-theme="light"] #codex-resume-panel,
668
+ html[data-theme="light"] #codex-fork-panel,
669
+ html[data-theme="light"] #codex-prompt-panel,
670
+ html[data-theme="light"] #codex-skill-panel { background: var(--card-bg) !important; border-color: var(--line) !important; color: var(--text) !important; }
671
+ html[data-theme="light"] #modal-overlay,
672
+ html[data-theme="light"] #settings-modal-overlay,
673
+ html[data-theme="light"] #usage-source-overlay { background: var(--overlay-bg); }
674
+ html[data-theme="light"] #claude-chat-container,
675
+ html[data-theme="light"] #codex-chat-container,
676
+ html[data-theme="light"] #history-content { background: var(--chat-bg); color: var(--text); }
677
+ html[data-theme="light"] .history-meta { background: #f0f2f5; border-color: var(--line); }
678
+ html[data-theme="light"] .claude-message.assistant,
679
+ html[data-theme="light"] .codex-message,
680
+ html[data-theme="light"] .claude-tool strong,
681
+ html[data-theme="light"] .claude-tool-header,
682
+ html[data-theme="light"] .claude-picker-option,
683
+ html[data-theme="light"] .codex-picker-option,
684
+ html[data-theme="light"] .claude-resume-item,
685
+ html[data-theme="light"] .codex-prompt-text,
686
+ html[data-theme="light"] .usage-nav-title strong { color: var(--text); }
687
+ html[data-theme="light"] .claude-tool,
688
+ html[data-theme="light"] .claude-work-group,
689
+ html[data-theme="light"] .codex-work-group { border-color: var(--line); background: rgba(15,23,42,.035); color: #344054; }
690
+ html[data-theme="light"] .claude-tool-code,
691
+ html[data-theme="light"] .claude-md pre { background: #f4f6f8; color: #27364a; border-color: var(--line); }
692
+ html[data-theme="light"] .claude-select,
693
+ html[data-theme="light"] .claude-ctrl-btn,
694
+ html[data-theme="light"] .codex-select-label,
695
+ html[data-theme="light"] .composer-action-btn,
696
+ html[data-theme="light"] .key-btn,
697
+ html[data-theme="light"] .command-key,
698
+ html[data-theme="light"] .small-btn { border-color: var(--line); background: var(--surface-soft); color: var(--text); }
699
+ html[data-theme="light"] .claude-ctrl-btn.primary { color: #065fbd; background: rgba(0,122,255,.11); }
700
+ html[data-theme="light"] .claude-ctrl-btn.danger,
701
+ html[data-theme="light"] .small-btn.danger { color: #d92d20; background: rgba(217,45,32,.09); }
702
+ html[data-theme="light"] #settings-modal input,
703
+ html[data-theme="light"] #settings-modal select,
704
+ html[data-theme="light"] .form-field input,
705
+ html[data-theme="light"] .form-field select,
706
+ html[data-theme="light"] .form-field textarea,
707
+ html[data-theme="light"] #cwd-field { background: #f2f4f7 !important; border-color: var(--line) !important; color: var(--text) !important; }
708
+ html[data-theme="light"] .tab-bar,
709
+ html[data-theme="light"] .list-item { border-color: var(--line); }
710
+ html[data-theme="light"] .copy-dir-btn,
711
+ html[data-theme="light"] .serverchan-toggle { border-color: var(--line); background: var(--surface-soft); }
712
+
713
+ /* Light theme refinement: stronger hierarchy and readable provider surfaces. */
714
+ html[data-theme="light"] #lobby-view { background: #f0f2f5; }
715
+ html[data-theme="light"] .lobby-tabs { border: 1px solid #e0e5ec; box-shadow: 0 1px 2px rgba(15,23,42,.03); }
716
+ html[data-theme="light"] .session-card,
717
+ html[data-theme="light"] .schedule-card { border: 1px solid #e0e5ec; box-shadow: 0 1px 2px rgba(15,23,42,.04); }
718
+ html[data-theme="light"] .session-card.selected { border-color: #8fc5ff; background: #eaf4ff; box-shadow: 0 0 0 1px rgba(0,122,255,.08); }
719
+ html[data-theme="light"] .btn-join { background: #e8f2ff; color: #006acb; }
720
+ html[data-theme="light"] .weekday-btn { border-color: #d7dde7; background: #f7f8fa; color: #475467; }
721
+ html[data-theme="light"] .weekday-btn.active { border-color: #007aff; background: #007aff; color: #fff; }
722
+ html[data-theme="light"] .step-card { border-color: #dde2ea; background: #f7f8fa; }
723
+ html[data-theme="light"] .small-btn.primary { border-color: #007aff; background: #007aff; color: #fff; }
724
+
725
+ html[data-theme="light"] .claude-message.user,
726
+ html[data-theme="light"] .codex-message.user { border-color: #b8d9fb; background: #e8f3ff; color: #173b61; }
727
+ html[data-theme="light"] .claude-message-attachment { border-color: #c7d9ec; background: rgba(255,255,255,.68); color: #344054; }
728
+ html[data-theme="light"] .claude-md code { border-color: #d9dee7; background: #eef1f5; color: #24364b; }
729
+ html[data-theme="light"] .claude-md blockquote { border-left-color: #aeb8c6; color: #475467; }
730
+ html[data-theme="light"] .claude-md th,
731
+ html[data-theme="light"] .claude-md td { border-color: #d9dee7; }
732
+ html[data-theme="light"] .claude-md th { background: #eef1f5; color: #172033; }
733
+ html[data-theme="light"] .claude-command-chip { border-color: #d9dee7; background: #f2f4f7; color: #27364a; }
734
+ html[data-theme="light"] .claude-picker-option-value { color: #667085; }
735
+ html[data-theme="light"] .claude-picker-column + .claude-picker-column,
736
+ html[data-theme="light"] .codex-picker-column + .codex-picker-column { border-color: #dfe4eb; }
737
+ html[data-theme="light"] .claude-picker-option.selected,
738
+ html[data-theme="light"] .codex-picker-option.selected { background: #dcecff; color: #075fae; }
739
+
740
+ html[data-theme="light"] .codex-status-card { border-color: #cfe0ef; background: #f5f9fc; color: #172033; box-shadow: 0 1px 2px rgba(15,23,42,.03); }
741
+ html[data-theme="light"] .codex-status-title { color: #0070c9; }
742
+ html[data-theme="light"] .codex-status-item { border-color: #dbe3eb; background: #fff; }
743
+ html[data-theme="light"] .codex-status-label { color: #667085; }
744
+ html[data-theme="light"] .codex-status-value { color: #24364b; }
745
+ html[data-theme="light"] .codex-warning-card { border-color: #f2cc60; background: #fff8df; color: #5e4300; }
746
+ html[data-theme="light"] .codex-warning-icon { background: #ffefb3; color: #9a6700; }
747
+ html[data-theme="light"] .codex-warning-title { color: #7a5200; }
748
+ html[data-theme="light"] .codex-warning-text { color: #725d28; }
749
+ html[data-theme="light"] .codex-compaction-card { border-color: #d9c4ef; background: #f7f0ff; color: #3f2a56; }
750
+ html[data-theme="light"] .codex-compaction-meta { color: #725d86; }
751
+ html[data-theme="light"] .codex-context-meter { --context-color: #168447; border-color: #69c98a; background: #effaf3; color: #125f34; }
752
+ html[data-theme="light"] .codex-context-meter::before { background: #bcebc9; }
753
+ html[data-theme="light"] .codex-context-meter.warn { --context-color: #a66f00; border-color: #e0b53e; background: #fff9df; color: #715000; }
754
+ html[data-theme="light"] .codex-context-meter.warn::before { background: #ffe59a; }
755
+ html[data-theme="light"] .codex-context-meter.danger { --context-color: #b53b33; border-color: #e58d86; background: #fff1f0; color: #8f2d27; }
756
+ html[data-theme="light"] .codex-context-meter.danger::before { background: #fac5c1; }
757
+
758
+ html[data-theme="light"] .codex-tool { border-color: #dbe1e8; background: #f7f9fb; color: #344054; }
759
+ html[data-theme="light"] .codex-tool-title,
760
+ html[data-theme="light"] .codex-inline-permission-title,
761
+ html[data-theme="light"] .claude-edit-file > summary,
762
+ html[data-theme="light"] .codex-patch-file > summary { color: #24364b; }
763
+ html[data-theme="light"] .codex-tool-icon,
764
+ html[data-theme="light"] .codex-tool-command,
765
+ html[data-theme="light"] .codex-tool-code,
766
+ html[data-theme="light"] .codex-work-group > summary,
767
+ html[data-theme="light"] .codex-subagent-message { color: #526174; }
768
+ html[data-theme="light"] .codex-tool-body,
769
+ html[data-theme="light"] .codex-patch-file,
770
+ html[data-theme="light"] .claude-edit-body { border-color: #dbe1e8; }
771
+ html[data-theme="light"] .codex-work-group { border-left-color: #cbd3dd; background: transparent; }
772
+ html[data-theme="light"] .codex-subagent-message { border-left-color: #cbd3dd; }
773
+ html[data-theme="light"] .codex-diff { border-color: #d9dee7; background: #f7f8fa; }
774
+ html[data-theme="light"] .codex-diff-line { color: #344054; }
775
+ html[data-theme="light"] .codex-diff-line.add { color: #176b3a; background: #dff4e7; }
776
+ html[data-theme="light"] .codex-diff-line.del { color: #a12b24; background: #fde5e3; }
777
+ html[data-theme="light"] .codex-diff-line.hunk { color: #075fae; background: #e6f2ff; }
778
+ html[data-theme="light"] .claude-permission,
779
+ html[data-theme="light"] .codex-inline-permission { border-color: #edc84f; background: #fff6cf; }
780
+ html[data-theme="light"] .claude-inline-permission-title { color: #795500; }
781
+ html[data-theme="light"] .codex-permission-result { color: #725d28; }
782
+
783
+ html[data-theme="light"] .codex-skill-header,
784
+ html[data-theme="light"] .codex-prompt-header { border-color: #dce2ea; background: #f3f6f9; color: #344054; }
785
+ html[data-theme="light"] .codex-skill-search { border-color: #cbd3dd; background: #fff; color: #172033; }
786
+ html[data-theme="light"] .codex-skill-search::placeholder { color: #8a94a6; }
787
+ html[data-theme="light"] .codex-skill-search:focus { border-color: #007aff; background: #fff; }
788
+ html[data-theme="light"] .codex-skill-section + .codex-skill-section,
789
+ html[data-theme="light"] .codex-prompt-item { border-color: #e1e5eb; }
790
+ html[data-theme="light"] .codex-skill-section-title { background: #e9edf2; color: #475467; }
791
+ html[data-theme="light"] .codex-skill-item { border-color: #e5e9ef; background: #fff; color: #172033; }
792
+ html[data-theme="light"] .codex-skill-item:hover { background: #f7f9fb; }
793
+ html[data-theme="light"] .codex-skill-item.selected { background: #e6f2ff; }
794
+ html[data-theme="light"] .codex-skill-source { border-color: #99c8f7; background: #e8f3ff; color: #006acb; }
795
+ html[data-theme="light"] .codex-skill-description { color: #667085; }
796
+ html[data-theme="light"] .codex-skill-directory { border-color: #d9dee7; background: #f2f4f7; color: #667085; }
797
+ html[data-theme="light"] .codex-prompt-time { color: #667085; }
798
+
799
+ html[data-theme="light"] .usage-period-picker select { border-color: #d9dee7; color: #172033; }
800
+ html[data-theme="light"] .usage-summary-card,
801
+ html[data-theme="light"] .usage-panel { border-color: #dfe4eb; box-shadow: 0 1px 2px rgba(15,23,42,.025); }
802
+ html[data-theme="light"] .usage-summary-card .value,
803
+ html[data-theme="light"] .usage-table tfoot td { color: #172033; }
804
+ html[data-theme="light"] .usage-summary-card .exact,
805
+ html[data-theme="light"] .usage-chart-label,
806
+ html[data-theme="light"] .usage-chart-total,
807
+ html[data-theme="light"] .usage-pricing-note { color: #667085; }
808
+ html[data-theme="light"] .usage-chart-track { background: #e9edf2; }
809
+ html[data-theme="light"] .usage-table th { border-color: #dfe4eb; color: #667085; }
810
+ html[data-theme="light"] .usage-table td { border-color: #edf0f3; color: #344054; }
@@ -0,0 +1,60 @@
1
+ const GLAD_THEME_KEY = 'glad-theme';
2
+
3
+ function getGladTheme() {
4
+ const stored = localStorage.getItem(GLAD_THEME_KEY);
5
+ if (stored === 'light' || stored === 'dark') return stored;
6
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
7
+ }
8
+
9
+ function getGladTerminalTheme(theme = getGladTheme()) {
10
+ return theme === 'light'
11
+ ? {
12
+ background: '#ffffff', foreground: '#172033', cursor: '#007aff',
13
+ selectionBackground: 'rgba(0, 122, 255, 0.22)', black: '#172033', white: '#f5f6f8',
14
+ brightBlack: '#667085', brightWhite: '#ffffff'
15
+ }
16
+ : {
17
+ background: '#000000', foreground: '#ffffff', cursor: '#34c759',
18
+ selectionBackground: 'rgba(0, 122, 255, 0.32)'
19
+ };
20
+ }
21
+
22
+ function syncGladThemeControls(theme = getGladTheme()) {
23
+ ['light', 'dark'].forEach(value => {
24
+ const button = document.getElementById(`theme-${value}-btn`);
25
+ if (button) button.setAttribute('aria-pressed', String(value === theme));
26
+ });
27
+ }
28
+
29
+ function applyGladTheme(theme, options = {}) {
30
+ const resolved = theme === 'light' ? 'light' : 'dark';
31
+ document.documentElement.dataset.theme = resolved;
32
+ document.documentElement.style.removeProperty('background-color');
33
+ const meta = document.querySelector('meta[name="theme-color"]');
34
+ if (meta) meta.content = resolved === 'light' ? '#f5f6f8' : '#000000';
35
+ syncGladThemeControls(resolved);
36
+ try {
37
+ if (typeof term !== 'undefined' && term) {
38
+ term.options.theme = getGladTerminalTheme(resolved);
39
+ term.refresh(0, Math.max(0, term.rows - 1));
40
+ }
41
+ } catch (_) {}
42
+ if (!options.silent) window.dispatchEvent(new CustomEvent('glad-theme-change', { detail: resolved }));
43
+ }
44
+
45
+ function setGladTheme(theme) {
46
+ if (theme !== 'light' && theme !== 'dark') return;
47
+ localStorage.setItem(GLAD_THEME_KEY, theme);
48
+ applyGladTheme(theme);
49
+ }
50
+
51
+ applyGladTheme(getGladTheme(), { silent: true });
52
+
53
+ const gladSystemTheme = window.matchMedia('(prefers-color-scheme: dark)');
54
+ gladSystemTheme.addEventListener('change', () => {
55
+ if (!localStorage.getItem(GLAD_THEME_KEY)) applyGladTheme(getGladTheme());
56
+ });
57
+ window.addEventListener('storage', event => {
58
+ if (event.key === GLAD_THEME_KEY) applyGladTheme(getGladTheme());
59
+ });
60
+ document.addEventListener('DOMContentLoaded', () => syncGladThemeControls());
@@ -115,7 +115,6 @@
115
115
  function editTimedInput(id) {
116
116
  const item = timedInputs.find(value => value.id === id);
117
117
  if (!item) return;
118
- closeComposerMenu();
119
118
  initTimedDelaySelectors();
120
119
  editingTimedInputId = id;
121
120
  inputEl.value = item.text || '';
@@ -173,19 +172,13 @@
173
172
  }
174
173
 
175
174
  document.getElementById('send-btn').addEventListener('click', performSend);
176
- document.getElementById('timer-btn').addEventListener('click', () => {
177
- const menu = document.getElementById('composer-menu');
178
- const willOpen = !menu.classList.contains('active');
179
- document.getElementById('timed-send-panel').classList.remove('active');
180
- menu.classList.toggle('active', willOpen);
181
- syncComposerButtonState();
182
- updateTerminalControlsHeight();
183
- });
184
175
  document.getElementById('attach-image-btn').addEventListener('click', () => {
185
- closeComposerMenu();
186
176
  imageFileInput.click();
187
177
  });
188
- document.getElementById('schedule-send-btn').addEventListener('click', openTimedSendPanel);
178
+ document.getElementById('schedule-send-btn').addEventListener('click', () => {
179
+ if (document.getElementById('timed-send-panel').classList.contains('active')) closeTimedSendPanel();
180
+ else openTimedSendPanel();
181
+ });
189
182
  imageFileInput.addEventListener('change', () => {
190
183
  const files = imageFileInput.files;
191
184
  if (files?.length) void uploadImageFiles(files);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glad-web",
3
- "version": "1.0.43",
3
+ "version": "1.0.44",
4
4
  "description": "Glad transforms terminal-based AI coding tools into a polished, mobile-friendly local Web interface.",
5
5
  "bin": {
6
6
  "glad": "bin/cli.js"