glad-web 1.0.44 → 1.0.46

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.
package/lib/web/shell.js CHANGED
@@ -1,6 +1,20 @@
1
1
  async function deleteSession(id, e) {
2
- e.stopPropagation(); if (!confirm('Terminate session?')) return;
3
- try { await fetchWithTimeout('/api/sessions/' + id, { method: 'DELETE' }); refreshSessionsNow(); } catch (e) { alert('Failed to delete'); }
2
+ e?.stopPropagation();
3
+ if (!confirm('Terminate session?')) return;
4
+ const button = e?.currentTarget;
5
+ if (button) button.disabled = true;
6
+ try {
7
+ const response = await fetchWithTimeout('/api/sessions/' + encodeURIComponent(id), { method: 'DELETE' }, 10000);
8
+ if (!response.ok) {
9
+ const data = await response.json().catch(() => ({}));
10
+ throw new Error(data.error || `HTTP ${response.status}`);
11
+ }
12
+ if (id === activeSessionId) showLobby();
13
+ else await refreshSessionsNow();
14
+ } catch (error) {
15
+ if (button?.isConnected) button.disabled = false;
16
+ alert(`Failed to delete session: ${error.message}`);
17
+ }
4
18
  }
5
19
 
6
20
  function showLobby() {
@@ -10,6 +24,7 @@
10
24
  activeSessionId = null;
11
25
  window.activeSessionId = null;
12
26
  activeToolKey = null;
27
+ if (typeof resetComposerSendState === 'function') resetComposerSendState();
13
28
  document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
14
29
  document.getElementById('lobby-view').classList.add('active');
15
30
  refreshSessionsNow();
@@ -0,0 +1,197 @@
1
+ let skillHubSettings = null;
2
+ let skillHallSkills = [];
3
+ let skillHallQuery = '';
4
+
5
+ function applySkillHubAvailability(available) {
6
+ const button = document.getElementById('skill-hall-button');
7
+ if (!button) return;
8
+ button.disabled = !available;
9
+ button.title = available ? 'Skill Hall' : 'Skill暂不可用';
10
+ button.setAttribute('aria-label', button.title);
11
+ }
12
+
13
+ async function refreshSkillHubAvailability() {
14
+ try {
15
+ const response = await fetchWithTimeout('/api/skillhub/status', {}, 5000);
16
+ const data = await response.json();
17
+ applySkillHubAvailability(Boolean(response.ok && data.available));
18
+ } catch (_) {
19
+ applySkillHubAvailability(false);
20
+ }
21
+ }
22
+
23
+ function setSkillHubStatus(message, type = '') {
24
+ const target = document.getElementById('skillhub-settings-status');
25
+ if (!target) return;
26
+ target.textContent = message;
27
+ target.className = `serverchan-settings-status${type ? ` ${type}` : ''}`;
28
+ }
29
+
30
+ function setSkillHubBusy(busy) {
31
+ for (const id of ['skillhub-save-btn', 'skillhub-test-btn', 'skillhub-remove-btn']) {
32
+ const button = document.getElementById(id);
33
+ if (button) button.disabled = busy;
34
+ }
35
+ }
36
+
37
+ function currentSkillHubForm() {
38
+ return {
39
+ baseUrl: document.getElementById('skillhub-base-url').value.trim(),
40
+ token: document.getElementById('skillhub-token').value.trim()
41
+ };
42
+ }
43
+
44
+ async function loadSkillHubSettings() {
45
+ setSkillHubStatus('Loading configuration…');
46
+ try {
47
+ const response = await fetchWithTimeout('/api/skillhub/settings', {}, 10000);
48
+ const data = await response.json();
49
+ if (!response.ok) throw new Error(data.error || 'Could not load SkillHub configuration');
50
+ skillHubSettings = data;
51
+ document.getElementById('skillhub-base-url').value = data.baseUrl || '';
52
+ const token = document.getElementById('skillhub-token');
53
+ token.value = '';
54
+ token.placeholder = data.configured ? data.maskedToken : 'clh_...';
55
+ document.getElementById('skillhub-token-hint').textContent = data.configured
56
+ ? `Saved: ${data.maskedToken}. Leave blank to keep the current Token.`
57
+ : 'The Token is encrypted on this Glad host.';
58
+ document.getElementById('skillhub-remove-btn').style.display = data.configured ? 'inline-flex' : 'none';
59
+ setSkillHubStatus(data.configured ? 'Configuration saved.' : 'SkillHub is not configured.');
60
+ } catch (error) {
61
+ setSkillHubStatus(error.message || 'Could not load SkillHub configuration', 'error');
62
+ }
63
+ }
64
+
65
+ async function saveSkillHubSettings() {
66
+ setSkillHubBusy(true);
67
+ setSkillHubStatus('Testing and saving…');
68
+ try {
69
+ const response = await fetchWithTimeout('/api/skillhub/settings', {
70
+ method: 'PUT',
71
+ headers: { 'Content-Type': 'application/json' },
72
+ body: JSON.stringify(currentSkillHubForm())
73
+ }, 20000);
74
+ const data = await response.json();
75
+ if (!response.ok) throw new Error(data.error || 'Could not save SkillHub configuration');
76
+ skillHubSettings = data.settings;
77
+ const token = document.getElementById('skillhub-token');
78
+ token.value = '';
79
+ token.placeholder = data.settings.maskedToken;
80
+ document.getElementById('skillhub-token-hint').textContent =
81
+ `Saved: ${data.settings.maskedToken}. Leave blank to keep the current Token.`;
82
+ document.getElementById('skillhub-remove-btn').style.display = 'inline-flex';
83
+ const handle = data.user?.handle || data.user?.data?.handle || '';
84
+ setSkillHubStatus(`Connected${handle ? ` as ${handle}` : ''}.`, 'success');
85
+ } catch (error) {
86
+ setSkillHubStatus(error.message || 'Could not save SkillHub configuration', 'error');
87
+ } finally {
88
+ setSkillHubBusy(false);
89
+ }
90
+ }
91
+
92
+ async function testSkillHubSettings() {
93
+ setSkillHubBusy(true);
94
+ setSkillHubStatus('Testing connection…');
95
+ try {
96
+ const response = await fetchWithTimeout('/api/skillhub/settings/test', {
97
+ method: 'POST',
98
+ headers: { 'Content-Type': 'application/json' },
99
+ body: JSON.stringify(currentSkillHubForm())
100
+ }, 20000);
101
+ const data = await response.json();
102
+ if (!response.ok) throw new Error(data.error || 'SkillHub connection failed');
103
+ const handle = data.user?.handle || data.user?.data?.handle || '';
104
+ setSkillHubStatus(`Connection successful${handle ? ` · ${handle}` : ''}.`, 'success');
105
+ } catch (error) {
106
+ setSkillHubStatus(error.message || 'SkillHub connection failed', 'error');
107
+ } finally {
108
+ setSkillHubBusy(false);
109
+ }
110
+ }
111
+
112
+ async function removeSkillHubSettings() {
113
+ if (!confirm('Remove the SkillHub connection?')) return;
114
+ setSkillHubBusy(true);
115
+ try {
116
+ const response = await fetchWithTimeout('/api/skillhub/settings', { method: 'DELETE' }, 10000);
117
+ const data = await response.json();
118
+ if (!response.ok) throw new Error(data.error || 'Could not remove SkillHub configuration');
119
+ skillHubSettings = data.settings;
120
+ document.getElementById('skillhub-base-url').value = '';
121
+ document.getElementById('skillhub-token').value = '';
122
+ document.getElementById('skillhub-token').placeholder = 'clh_...';
123
+ document.getElementById('skillhub-token-hint').textContent = 'The Token is encrypted on this Glad host.';
124
+ document.getElementById('skillhub-remove-btn').style.display = 'none';
125
+ setSkillHubStatus('SkillHub configuration removed.', 'success');
126
+ } catch (error) {
127
+ setSkillHubStatus(error.message || 'Could not remove SkillHub configuration', 'error');
128
+ } finally {
129
+ setSkillHubBusy(false);
130
+ }
131
+ }
132
+
133
+ function skillHallCard(skill, index) {
134
+ const name = String(skill.displayName || skill.name || skill.slug || 'Unnamed Skill');
135
+ const description = String(skill.description || '');
136
+ const meta = [skill.kind, skill.version].filter(Boolean).join(' · ');
137
+ return `<button type="button" class="skill-hall-card" onclick="selectSkillHallSkill(${index})"><span class="skill-hall-card-name">${escapeHtml(name)}</span>${description ? `<span class="skill-hall-card-description">${escapeHtml(description)}</span>` : ''}<span class="skill-hall-card-meta">${escapeHtml(meta)}</span></button>`;
138
+ }
139
+
140
+ function renderSkillHall() {
141
+ const grid = document.getElementById('skill-hall-grid');
142
+ const status = document.getElementById('skill-hall-status');
143
+ if (!grid || !status) return;
144
+ const query = skillHallQuery.trim().toLocaleLowerCase();
145
+ const matches = skillHallSkills.map((skill, index) => ({ skill, index })).filter(({ skill }) => {
146
+ if (!query) return true;
147
+ return [skill.displayName, skill.name, skill.slug, skill.description, skill.kind, skill.version]
148
+ .some(value => String(value || '').toLocaleLowerCase().includes(query));
149
+ });
150
+ status.textContent = matches.length ? `${matches.length} Skill${matches.length === 1 ? '' : 's'}`
151
+ : (query ? 'No matching Skills.' : 'No Skills are available for this account.');
152
+ grid.innerHTML = matches.map(({ skill, index }) => skillHallCard(skill, index)).join('');
153
+ }
154
+
155
+ async function openSkillHall(event = null) {
156
+ event?.stopPropagation();
157
+ if (document.getElementById('skill-hall-button')?.disabled) {
158
+ showAppToast('Skill暂不可用');
159
+ return;
160
+ }
161
+ document.getElementById('skill-hall-overlay').style.display = 'flex';
162
+ document.getElementById('skill-hall-search').value = '';
163
+ document.getElementById('skill-hall-status').textContent = 'Loading Skills…';
164
+ document.getElementById('skill-hall-grid').innerHTML = '';
165
+ skillHallQuery = '';
166
+ try {
167
+ const response = await fetchWithTimeout('/api/skillhub/skills', {}, 30000);
168
+ const data = await response.json();
169
+ if (!response.ok || !data.success) throw new Error(data.error || 'Could not load Skills');
170
+ skillHallSkills = Array.isArray(data.skills) ? data.skills : [];
171
+ renderSkillHall();
172
+ requestAnimationFrame(() => document.getElementById('skill-hall-search')?.focus());
173
+ } catch (error) {
174
+ skillHallSkills = [];
175
+ document.getElementById('skill-hall-status').textContent = error.message || 'Could not load Skills';
176
+ if (error.message === 'Skill暂不可用') applySkillHubAvailability(false);
177
+ }
178
+ }
179
+
180
+ function closeSkillHall(event = null) {
181
+ if (event && event.target.id !== 'skill-hall-overlay') return;
182
+ document.getElementById('skill-hall-overlay').style.display = 'none';
183
+ }
184
+
185
+ function filterSkillHall(value) {
186
+ skillHallQuery = String(value || '');
187
+ renderSkillHall();
188
+ }
189
+
190
+ function selectSkillHallSkill(index) {
191
+ const skill = skillHallSkills[index];
192
+ if (!skill) return;
193
+ closeSkillHall();
194
+ showToolModal(skill);
195
+ }
196
+
197
+ void refreshSkillHubAvailability();
@@ -4,17 +4,20 @@
4
4
  .view.active { display: flex; }
5
5
  #lobby { overflow-y: auto; padding: 20px; padding-top: env(safe-area-inset-top); box-sizing: border-box; }
6
6
  .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; }
7
- .header h1 { font-size: 28px; font-weight: 700; margin: 0; display: flex; align-items: center; gap: 10px; }
8
- .header-logo { width: 32px; height: 32px; flex-shrink: 0; }
9
- .header-actions { display: flex; align-items: center; gap: 5px; }
7
+ .header-actions { display: flex; align-items: center; gap: 5px; margin-left: auto; }
8
+ .skill-hall-entry-btn { height: 36px; padding: 0 11px; border: 1px solid rgba(0,122,255,.38); border-radius: 10px; background: rgba(0,122,255,.12); color: var(--primary); display: inline-flex; align-items: center; justify-content: center; gap: 5px; font-size: 13px; font-weight: 800; cursor: pointer; flex-shrink: 0; }
9
+ .skill-hall-entry-btn:active { background: rgba(0,122,255,.22); transform: scale(.97); }
10
+ .skill-hall-entry-btn:disabled { border-color: rgba(142,142,147,.25); background: rgba(142,142,147,.1); color: var(--text-dim); cursor: not-allowed; transform: none; }
10
11
  .header-action-btn { height: 36px; min-width: 36px; padding: 0 10px; border: 0; border-radius: 10px; background: var(--primary); color: #fff; display: inline-flex; align-items: center; justify-content: center; gap: 3px; font-size: 13px; font-weight: 750; line-height: 1; white-space: nowrap; cursor: pointer; flex-shrink: 0; }
11
12
  .header-action-btn.icon-only { width: 36px; padding: 0; }
12
13
  .header-action-btn:active { background: #0062cc; transform: scale(.97); }
13
14
  .btn-retry { background: #333; color: #fff; border: none; padding: 8px 16px; border-radius: 20px; margin-top: 10px; cursor: pointer; }
14
15
  .session-card { background: var(--card-bg); border-radius: 12px; padding: 12px 16px 8px; margin-bottom: 12px; display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 10px; row-gap: 0; align-items: center; position: relative; }
16
+ .active-session-dot { display: none; }
15
17
  .completion-dot { width: 9px; height: 9px; border-radius: 50%; background: #ff3b30; flex-shrink: 0; }
16
18
  .session-info { flex: 1; min-width: 0; }
17
- .session-info h3 { margin: 0 0 4px 0; font-size: 17px; display: flex; align-items: center; gap: 8px; }
19
+ .session-info h3 { min-width: 0; margin: 0 0 4px 0; font-size: 17px; display: flex; align-items: center; gap: 8px; }
20
+ .session-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
18
21
  .session-info p { margin: 0; font-size: 13px; color: var(--text-dim); }
19
22
  .timer-count-badge { display: inline-flex; align-items: center; gap: 3px; color: #fff; background: rgba(0,122,255,0.22); border: 1px solid rgba(0,122,255,0.34); border-radius: 999px; padding: 2px 7px; font-size: 11px; font-weight: 800; flex-shrink: 0; }
20
23
  .session-dir-row { grid-column: 1 / -1; display: flex; align-items: center; gap: 6px; width: 100%; min-width: 0; }
@@ -37,7 +40,7 @@
37
40
  #modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); z-index: 10000; display: none; align-items: center; justify-content: center; padding: 20px; }
38
41
  #tool-modal { background: var(--card-bg); width: 100%; max-width: 400px; border-radius: 16px; padding: 20px; box-shadow: 0 20px 40px rgba(0,0,0,0.4); }
39
42
  #settings-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.8); z-index: 10020; display: none; align-items: center; justify-content: center; padding: 20px; box-sizing: border-box; }
40
- #settings-modal { background: var(--card-bg); width: 100%; max-width: 450px; border-radius: 16px; padding: 20px; box-shadow: 0 20px 40px rgba(0,0,0,0.4); box-sizing: border-box; }
43
+ #settings-modal { background: var(--card-bg); width: 100%; max-width: 450px; max-height: calc(100vh - 40px); overflow-y: auto; border-radius: 16px; padding: 20px; box-shadow: 0 20px 40px rgba(0,0,0,0.4); box-sizing: border-box; }
41
44
  .settings-modal-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 20px; }
42
45
  .settings-modal-header h2 { margin: 0; font-size: 20px; }
43
46
  .settings-modal-header p { margin: 5px 0 0; color: var(--text-dim); font-size: 12px; line-height: 1.45; }
@@ -58,6 +61,22 @@
58
61
  .serverchan-modal-actions { display: flex; align-items: center; gap: 8px; }
59
62
  .serverchan-action-spacer { flex: 1; }
60
63
  #serverchan-remove-btn { display: none; }
64
+ #skillhub-remove-btn { display: none; }
65
+ .tool-modal-skill { min-height: 16px; margin: 4px 0 0; color: var(--text-dim); font-size: 12px; }
66
+ #skill-hall-overlay { position: fixed; inset: 0; z-index: 10010; display: none; align-items: center; justify-content: center; padding: 18px; box-sizing: border-box; background: rgba(0,0,0,0.8); }
67
+ #skill-hall-modal { display: flex; flex-direction: column; width: min(820px, 100%); max-height: min(760px, calc(100vh - 36px)); padding: 20px; box-sizing: border-box; border-radius: 16px; background: var(--card-bg); box-shadow: 0 20px 50px rgba(0,0,0,0.45); }
68
+ .skill-hall-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
69
+ .skill-hall-header h2 { margin: 0; font-size: 21px; }
70
+ .skill-hall-header p { margin: 5px 0 0; color: var(--text-dim); font-size: 12px; }
71
+ .skill-hall-search { width: 100%; min-height: 42px; margin-top: 16px; padding: 9px 11px; box-sizing: border-box; border: 1px solid rgba(255,255,255,0.12); border-radius: 9px; background: rgba(255,255,255,0.08); color: var(--text); font-size: 14px; outline: none; }
72
+ .skill-hall-search:focus { border-color: rgba(0,122,255,0.65); }
73
+ .skill-hall-status { min-height: 20px; padding: 10px 1px 7px; color: var(--text-dim); font-size: 12px; }
74
+ .skill-hall-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; overflow-y: auto; padding: 1px; }
75
+ .skill-hall-card { display: flex; min-height: 126px; flex-direction: column; align-items: stretch; gap: 8px; padding: 14px; border: 1px solid rgba(255,255,255,0.1); border-radius: 12px; background: rgba(255,255,255,0.045); color: var(--text); text-align: left; cursor: pointer; }
76
+ .skill-hall-card:hover { border-color: rgba(0,122,255,0.55); background: rgba(0,122,255,0.09); }
77
+ .skill-hall-card-name { font-size: 15px; font-weight: 700; overflow-wrap: anywhere; }
78
+ .skill-hall-card-description { display: -webkit-box; overflow: hidden; color: var(--text-dim); font-size: 12px; line-height: 1.45; -webkit-box-orient: vertical; -webkit-line-clamp: 3; }
79
+ .skill-hall-card-meta { margin-top: auto; color: var(--primary); font-size: 11px; }
61
80
  #app-toast { position: fixed; left: 50%; bottom: max(24px, env(safe-area-inset-bottom)); z-index: 10100; max-width: calc(100vw - 32px); transform: translate(-50%, 18px); opacity: 0; pointer-events: none; padding: 9px 14px; border-radius: 999px; background: rgba(44,44,46,0.96); border: 1px solid rgba(255,255,255,0.12); color: #fff; font-size: 13px; box-shadow: 0 10px 28px rgba(0,0,0,0.35); transition: opacity .18s ease, transform .18s ease; }
62
81
  #app-toast.visible { opacity: 1; transform: translate(-50%, 0); }
63
82
  .tool-item { padding: 12px; border-bottom: 1px solid #333; cursor: pointer; display: flex; align-items: center; border-radius: 8px; margin-top: 4px; }
@@ -143,13 +162,16 @@
143
162
  #cmd-input::placeholder { color: rgba(255,255,255,0.45); }
144
163
  #cmd-input:focus { background: rgba(255,255,255,0.1); border-color: rgba(0,122,255,0.42); color: #fff; }
145
164
  #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; }
165
+ #send-btn:disabled { background: color-mix(in srgb, var(--text-dim) 34%, var(--surface)); color: var(--text-dim); opacity: .72; cursor: not-allowed; }
146
166
  #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; }
147
167
  #attachment-strip.active { display: flex; }
148
168
  #attachment-strip::-webkit-scrollbar { display: none; }
149
169
  .attachment-chip { min-width: 0; max-width: 210px; display: flex; align-items: center; gap: 7px; padding: 7px 9px; border: 1px solid rgba(0,122,255,0.45); border-radius: 10px; background: rgba(0,122,255,0.14); color: #e9f2ff; font-size: 12px; }
150
170
  .attachment-chip.uploading { border-color: rgba(255,159,10,0.55); background: rgba(255,159,10,0.12); }
171
+ .attachment-chip.file { border-color: var(--line); background: var(--surface-soft); color: var(--text); }
151
172
  .attachment-chip-content { min-width: 0; flex: 1; }
152
173
  .attachment-chip-name { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
174
+ .attachment-chip-icon { width: 17px; height: 17px; flex: 0 0 17px; }
153
175
  .attachment-progress { display: block; height: 5px; margin-top: 5px; overflow: hidden; border-radius: 999px; background: rgba(255,255,255,0.16); }
154
176
  .attachment-progress > span { display: block; height: 100%; border-radius: inherit; background: #ff9f0a; transition: width .15s ease; }
155
177
  .attachment-progress.estimated > span { background: repeating-linear-gradient(90deg, #ff9f0a 0 12px, #ffd37b 12px 24px); background-size: 48px 100%; animation: attachment-upload-pulse .75s linear infinite; }
@@ -185,7 +207,7 @@
185
207
  .codex-conversation { width: min(100%, var(--chat-content-max)); margin: 0 auto; }
186
208
  .codex-working-indicator, .claude-working-indicator { position: sticky; top: 0; z-index: 4; width: 28px; height: 28px; margin: 0 0 -28px auto; border: 1px solid rgba(255,255,255,.1); border-radius: 50%; background: rgba(28,28,30,.68); box-shadow: 0 5px 16px rgba(0,0,0,.24); backdrop-filter: blur(8px); pointer-events: none; }
187
209
  .codex-working-indicator::after, .claude-working-indicator::after { content: ''; position: absolute; inset: 8px; border: 1.5px solid rgba(255,255,255,.7); border-top-color: transparent; border-radius: 50%; animation: codex-spin .8s linear infinite; }
188
- .codex-skill-bubble { position: sticky; top: 0; z-index: 5; display: flex; width: max-content; max-width: min(280px, 72vw); min-height: 28px; margin: 0 0 -30px auto; padding: 0 4px 0 10px; align-items: center; gap: 7px; border: 1px solid rgba(0,122,255,.42); border-radius: 15px; background: rgba(21,47,78,.92); color: #eaf3ff; box-shadow: 0 5px 18px rgba(0,0,0,.3); backdrop-filter: blur(10px); font-size: 11px; font-weight: 750; }
210
+ .codex-skill-bubble { position: relative; z-index: 5; display: flex; width: max-content; max-width: min(280px, 72vw); min-height: 28px; padding: 0 4px 0 10px; align-items: center; gap: 7px; border: 1px solid rgba(0,122,255,.42); border-bottom: 0; border-radius: 12px 12px 0 0; background: rgba(21,47,78,.96); color: #eaf3ff; box-shadow: 0 -3px 12px rgba(0,0,0,.12); font-size: 11px; font-weight: 750; }
189
211
  .codex-skill-bubble-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
190
212
  .codex-skill-bubble-close { width: 23px; height: 23px; padding: 0; border: 0; border-radius: 50%; background: rgba(255,255,255,.1); color: #fff; cursor: pointer; font-size: 16px; line-height: 1; }
191
213
  .codex-message-block { max-width: 100%; margin: 0 0 12px; }
@@ -323,9 +345,12 @@
323
345
  .claude-message-block.user > .claude-message { max-width: 100%; }
324
346
  .claude-message-time { display: block; width: max-content; margin-top: 4px; padding: 0 2px; color: #8e8e93; font-size: 10px; font-weight: 500; line-height: 1; font-variant-numeric: tabular-nums; }
325
347
  .claude-message-block.user .claude-message-time { margin-left: auto; }
326
- .codex-message-skill { display: inline-flex; max-width: min(220px, 58vw); min-height: 16px; align-items: center; padding: 1px 6px; border: 1px solid rgba(0,122,255,.32); border-radius: 999px; background: rgba(0,122,255,.12); color: #8fc5ff; font-size: 9px; font-weight: 750; line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
348
+ .codex-message-skills { position: relative; z-index: 1; display: flex; justify-content: flex-start; gap: 5px; margin-bottom: -1px; }
349
+ .codex-message-skill { display: inline-flex; max-width: min(220px, 58vw); min-height: 18px; align-items: center; padding: 2px 8px; border: 1px solid rgba(0,122,255,.32); border-bottom: 0; border-radius: 9px 9px 0 0; background: rgba(0,122,255,.16); color: #8fc5ff; font-size: 9px; font-weight: 750; line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
350
+ .codex-message-skills + .codex-message.user { border-top-left-radius: 4px; }
327
351
  .claude-message-attachments { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; }
328
352
  .claude-message-attachment { max-width: 100%; border: 1px solid rgba(255,255,255,.14); border-radius: 999px; padding: 3px 7px; color: #d1d5db; background: rgba(255,255,255,.07); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
353
+ .message-attachment-icon { width: 13px; height: 13px; flex: 0 0 13px; margin-right: 4px; vertical-align: -2px; }
329
354
  .claude-message.user { margin-left: auto; background: rgba(0,122,255,0.24); border: 1px solid rgba(0,122,255,0.32); color: #fff; border-radius: 12px; padding-top: 7px; padding-bottom: 7px; }
330
355
  .claude-message.assistant { max-width: 100%; background: transparent; border: 0; color: #f5f5f7; padding: 0 4px; margin: 0 0 12px 0; }
331
356
  .claude-message.event { max-width: 100%; background: transparent; border: 0; color: var(--text-dim); font-size: 12px; padding: 3px 2px; text-align: center; }
@@ -449,6 +474,9 @@
449
474
  .history-meta { color: var(--text-dim); font-size: 11px; padding: 8px 14px; border-bottom: 1px solid rgba(255,255,255,0.06); background: #0d0d0d; }
450
475
 
451
476
  .tab-bar { display: flex; border-bottom: 1px solid rgba(255,255,255,0.1); background: var(--bg); }
477
+ .subview-nav { flex: 0 0 auto; padding-top: env(safe-area-inset-top); border-bottom: 1px solid var(--line); background: var(--surface); }
478
+ .subview-nav-row { display: flex; align-items: center; padding: 8px 14px; }
479
+ .subview-nav-title { flex: 1; min-width: 0; color: var(--text); text-align: center; font-size: 16px; font-weight: 600; }
452
480
  .tab-btn { flex: 1; text-align: center; padding: 12px 0; font-size: 14px; font-weight: 600; color: var(--text-dim); background: none; border: none; cursor: pointer; position: relative; }
453
481
  .tab-btn.active { color: var(--text); }
454
482
  .tab-btn.active::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); height: 2px; width: 40px; background: var(--primary); border-radius: 2px; }
@@ -520,9 +548,8 @@
520
548
  .usage-summary-card .value { font-size: 21px; }
521
549
  }
522
550
  @media (max-width: 430px) {
523
- #nav-bar > div:last-child .icon-btn:not(#codex-terminal-switch) { width: 34px; font-size: 0 !important; gap: 0 !important; }
524
- #nav-bar > div:last-child .icon-btn:not(#codex-terminal-switch) svg { width: 16px; height: 16px; }
525
- #codex-terminal-switch { padding-left: 8px !important; padding-right: 8px !important; }
551
+ #nav-bar > div:last-child .icon-btn { width: 34px; font-size: 0 !important; gap: 0 !important; }
552
+ #nav-bar > div:last-child .icon-btn svg { width: 16px; height: 16px; }
526
553
  }
527
554
  @media (min-width: 768px) and (max-width: 1199px) {
528
555
  :root { --chat-content-max: 780px; --control-content-max: 920px; }
@@ -546,6 +573,15 @@
546
573
  --input-fg: #ffffff;
547
574
  --input-placeholder: rgba(255,255,255,.45);
548
575
  --overlay-bg: rgba(0,0,0,.80);
576
+ --git-diff-bg: #0d0d0d;
577
+ --git-diff-text: #d1d5db;
578
+ --git-add-text: #4ade80;
579
+ --git-add-bg: rgba(74,222,128,.1);
580
+ --git-del-text: #f87171;
581
+ --git-del-bg: rgba(248,113,113,.1);
582
+ --git-hunk-text: #60a5fa;
583
+ --git-hunk-bg: rgba(96,165,250,.1);
584
+ --git-modified-text: #fbbf24;
549
585
  color-scheme: dark;
550
586
  }
551
587
 
@@ -563,6 +599,15 @@ html[data-theme="light"] {
563
599
  --input-fg: #172033;
564
600
  --input-placeholder: #8a94a6;
565
601
  --overlay-bg: rgba(15,23,42,.45);
602
+ --git-diff-bg: #f7f8fa;
603
+ --git-diff-text: #344054;
604
+ --git-add-text: #176b3a;
605
+ --git-add-bg: #dff4e7;
606
+ --git-del-text: #a12b24;
607
+ --git-del-bg: #fde5e3;
608
+ --git-hunk-text: #075fae;
609
+ --git-hunk-bg: #e6f2ff;
610
+ --git-modified-text: #9a6700;
566
611
  color-scheme: light;
567
612
  }
568
613
 
@@ -581,13 +626,31 @@ html[data-theme="dark"] { color-scheme: dark; }
581
626
  #detail-empty p { max-width: 360px; margin: 8px 0 0; font-size: 13px; line-height: 1.5; }
582
627
  #sidebar-resizer { display: none; }
583
628
 
584
- #terminal-view.active { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; }
629
+ #terminal-view.active { display: grid; grid-template-rows: auto auto minmax(0, 1fr) auto; }
585
630
  #top-ui { display: contents; }
586
631
  #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); }
632
+ #session-status-strip { display: none; grid-row: 2; z-index: 19; padding: 6px 12px; border-bottom: 1px solid var(--line); background: color-mix(in srgb, var(--surface) 97%, transparent); }
633
+ #session-attention-rail { display: flex; width: min(100%, var(--chat-content-max)); min-width: 0; margin: 0 auto; gap: 7px; overflow-x: auto; scrollbar-width: none; white-space: nowrap; }
634
+ #session-attention-rail::-webkit-scrollbar { display: none; }
635
+ .session-attention-pill { display: inline-flex; min-height: 28px; flex: 0 0 auto; align-items: center; gap: 5px; padding: 0 10px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface-soft); color: var(--text-dim); font-size: 11px; font-weight: 750; cursor: pointer; }
636
+ .session-attention-pill.approval { border-color: rgba(255,204,0,.42); background: rgba(255,204,0,.10); color: #ffd60a; }
637
+ .session-attention-pill.subagent { border-color: rgba(100,210,255,.35); background: rgba(100,210,255,.10); color: #64d2ff; }
638
+ .session-attention-pill:active { transform: translateY(1px); }
639
+ html[data-theme="light"] .session-attention-pill.approval { border-color: #e5bd36; background: #fff7d6; color: #795500; }
640
+ html[data-theme="light"] .session-attention-pill.subagent { border-color: #99c8f7; background: #e8f3ff; color: #075fae; }
641
+ #terminal-controls { position: relative; inset: auto; grid-row: 4; 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
642
  #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); }
643
+ #terminal-container, #claude-chat-container, #codex-chat-container { grid-row: 3; min-width: 0; min-height: 0; background: var(--chat-bg); }
644
+ #composer-skill-prefix { display: none; width: min(100%, var(--control-content-max)); margin: 0 auto; padding: 8px 14px 0; box-sizing: border-box; }
645
+ #composer-skill-prefix.active { display: flex; justify-content: flex-start; }
646
+ #composer-skill-prefix.active + #input-row { padding-top: 0; }
590
647
  #input-row { padding-top: 10px; padding-bottom: 7px; }
648
+ @media (max-width: 480px) {
649
+ #composer-skill-prefix { padding-left: 10px; padding-right: 10px; }
650
+ }
651
+ @media (min-width: 768px) and (max-width: 1199px) {
652
+ #composer-skill-prefix { padding-left: 18px; padding-right: 18px; }
653
+ }
591
654
  #cmd-input { width: 100%; min-height: 42px; border-color: var(--line); background: var(--input-bg); color: var(--input-fg); }
592
655
  #cmd-input::placeholder { color: var(--input-placeholder); }
593
656
  #cmd-input:focus { color: var(--input-fg); }
@@ -595,6 +658,8 @@ html[data-theme="dark"] { color-scheme: dark; }
595
658
  .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
659
  .composer-action-btn:hover { color: var(--text); }
597
660
  .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)); }
661
+ #composer-action-tooltip { position: fixed; z-index: 11000; display: none; max-width: min(220px, calc(100vw - 24px)); padding: 6px 9px; border: 1px solid var(--line); border-radius: 7px; background: color-mix(in srgb, var(--surface-raised) 96%, transparent); color: var(--text); box-shadow: 0 6px 18px rgba(0,0,0,.18); font-size: 11px; font-weight: 700; line-height: 1.2; text-align: center; white-space: nowrap; pointer-events: none; }
662
+ #composer-action-tooltip.visible { display: block; }
598
663
  #send-btn { right: max(12px, calc((100% - var(--control-content-max)) / 2 + 14px)); width: 44px; height: 40px; }
599
664
  #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
665
  .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; }
@@ -626,14 +691,16 @@ html[data-theme="dark"] { color-scheme: dark; }
626
691
  #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
692
  #lobby { width: 100%; padding: 18px 14px; }
628
693
  #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; }
694
+ #lobby-view .header-actions { gap: 3px; }
695
+ #lobby-view .header-action-btn.icon-only { width: 34px; min-width: 34px; }
696
+ #lobby-view .header-action-btn:not(.icon-only) { width: auto; min-width: 0; padding: 0 8px; font-size: 13px; }
697
+ #lobby-view .header-action-btn:not(.icon-only) span:first-child { font-size: 18px; }
632
698
  #sidebar-resizer { display: block; width: 4px; flex: 0 0 4px; cursor: col-resize; background: var(--line); touch-action: none; transition: background .15s; }
633
699
  #sidebar-resizer:hover, #sidebar-resizer.dragging { background: var(--primary); }
634
700
  #detail-pane:not(:has(.view.active)) #detail-empty { display: flex; }
635
701
  #back-btn, .usage-nav-button { visibility: hidden; pointer-events: none; }
636
702
  .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)); }
703
+ .session-card.selected .active-session-dot { position: absolute; top: 7px; right: 7px; display: block; width: 9px; height: 9px; border: 2px solid var(--card-bg); border-radius: 50%; background: #34c759; box-shadow: 0 0 0 1px rgba(52,199,89,.25); pointer-events: none; }
637
704
  }
638
705
 
639
706
  @media (max-width: 919px) {
@@ -658,6 +725,7 @@ html[data-theme="light"] .usage-panel,
658
725
  html[data-theme="light"] .usage-summary-card,
659
726
  html[data-theme="light"] #tool-modal,
660
727
  html[data-theme="light"] #settings-modal,
728
+ html[data-theme="light"] #skill-hall-modal,
661
729
  html[data-theme="light"] #usage-source-modal,
662
730
  html[data-theme="light"] #schedule-modal,
663
731
  html[data-theme="light"] #claude-picker-panel,
@@ -670,6 +738,7 @@ html[data-theme="light"] #codex-prompt-panel,
670
738
  html[data-theme="light"] #codex-skill-panel { background: var(--card-bg) !important; border-color: var(--line) !important; color: var(--text) !important; }
671
739
  html[data-theme="light"] #modal-overlay,
672
740
  html[data-theme="light"] #settings-modal-overlay,
741
+ html[data-theme="light"] #skill-hall-overlay,
673
742
  html[data-theme="light"] #usage-source-overlay { background: var(--overlay-bg); }
674
743
  html[data-theme="light"] #claude-chat-container,
675
744
  html[data-theme="light"] #codex-chat-container,
@@ -696,11 +765,14 @@ html[data-theme="light"] .composer-action-btn,
696
765
  html[data-theme="light"] .key-btn,
697
766
  html[data-theme="light"] .command-key,
698
767
  html[data-theme="light"] .small-btn { border-color: var(--line); background: var(--surface-soft); color: var(--text); }
768
+ html[data-theme="light"] .skill-hall-card { border-color: var(--line); background: var(--surface-soft); color: var(--text); }
769
+ html[data-theme="light"] .skill-hall-entry-btn { border-color: rgba(0,122,255,.28); background: rgba(0,122,255,.08); }
699
770
  html[data-theme="light"] .claude-ctrl-btn.primary { color: #065fbd; background: rgba(0,122,255,.11); }
700
771
  html[data-theme="light"] .claude-ctrl-btn.danger,
701
772
  html[data-theme="light"] .small-btn.danger { color: #d92d20; background: rgba(217,45,32,.09); }
702
773
  html[data-theme="light"] #settings-modal input,
703
774
  html[data-theme="light"] #settings-modal select,
775
+ html[data-theme="light"] .skill-hall-search,
704
776
  html[data-theme="light"] .form-field input,
705
777
  html[data-theme="light"] .form-field select,
706
778
  html[data-theme="light"] .form-field textarea,
@@ -748,6 +820,9 @@ html[data-theme="light"] .codex-warning-title { color: #7a5200; }
748
820
  html[data-theme="light"] .codex-warning-text { color: #725d28; }
749
821
  html[data-theme="light"] .codex-compaction-card { border-color: #d9c4ef; background: #f7f0ff; color: #3f2a56; }
750
822
  html[data-theme="light"] .codex-compaction-meta { color: #725d86; }
823
+ html[data-theme="light"] .codex-skill-bubble,
824
+ html[data-theme="light"] .codex-message-skill { border-color: #99c8f7; background: #e8f3ff; color: #075fae; }
825
+ html[data-theme="light"] .codex-skill-bubble-close { background: rgba(7,95,174,.10); color: #075fae; }
751
826
  html[data-theme="light"] .codex-context-meter { --context-color: #168447; border-color: #69c98a; background: #effaf3; color: #125f34; }
752
827
  html[data-theme="light"] .codex-context-meter::before { background: #bcebc9; }
753
828
  html[data-theme="light"] .codex-context-meter.warn { --context-color: #a66f00; border-color: #e0b53e; background: #fff9df; color: #715000; }
@@ -808,3 +883,50 @@ html[data-theme="light"] .usage-pricing-note { color: #667085; }
808
883
  html[data-theme="light"] .usage-chart-track { background: #e9edf2; }
809
884
  html[data-theme="light"] .usage-table th { border-color: #dfe4eb; color: #667085; }
810
885
  html[data-theme="light"] .usage-table td { border-color: #edf0f3; color: #344054; }
886
+ html[data-theme="light"] .tool-modal-header { border-color: #e0e5ec; }
887
+ html[data-theme="light"] .tool-item { border: 1px solid #e0e5ec; background: #f7f9fb; color: #172033; }
888
+ html[data-theme="light"] .tool-item:hover,
889
+ html[data-theme="light"] .tool-item:active { border-color: #99c8f7; background: #eaf4ff; }
890
+ html[data-theme="light"] .tool-icon { background: #dceeff; color: #006acb; }
891
+ html[data-theme="light"] #default-cwd { color: #526174 !important; }
892
+ html[data-theme="light"] .usage-source-item { border: 1px solid #e1e6ed; background: #f7f9fb; color: #172033; }
893
+ html[data-theme="light"] .usage-source-item:hover,
894
+ html[data-theme="light"] .usage-source-item:active { border-color: #99c8f7; background: #eaf4ff; }
895
+ html[data-theme="light"] .usage-source-copy strong { color: #172033; }
896
+ html[data-theme="light"] .usage-source-badge { background: #dceeff; color: #006acb; }
897
+ html[data-theme="light"] .usage-source-arrow { color: #667085; }
898
+ html[data-theme="light"] .attachment-chip { border-color: #99c8f7; background: #e8f3ff; color: #173b61; }
899
+ html[data-theme="light"] .attachment-chip.file { border-color: #c8d0dc; background: #f2f4f7; color: #344054; }
900
+ html[data-theme="light"] .attachment-chip.uploading { border-color: #e6b94c; background: #fff6df; color: #715000; }
901
+ html[data-theme="light"] .attachment-status { color: #725d28; }
902
+ html[data-theme="light"] .attachment-remove { background: rgba(23,59,97,.1); color: #344054; }
903
+ html[data-theme="light"] .attachment-progress { background: rgba(23,59,97,.12); }
904
+ html[data-theme="light"] .claude-message-attachment { border-color: #b9c9dc; background: rgba(255,255,255,.72); color: #344054; }
905
+ html[data-theme="light"] .claude-md a { color: #006acb; }
906
+
907
+ .tool-modal-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 18px; padding-bottom: 12px; border-bottom: 1px solid var(--line); }
908
+ .tool-modal-header h2 { margin: 0; }
909
+ .tool-modal-header .icon-btn { width: 32px; height: 32px; border-radius: 50%; color: var(--text-dim); font-size: 24px; }
910
+ #add-step-type { border-color: var(--line) !important; background: var(--surface-soft) !important; color: var(--text) !important; }
911
+
912
+ .git-change-card { margin-bottom: 8px; overflow: hidden; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-raised); }
913
+ .git-change-header { display: flex; align-items: center; min-height: 42px; padding: 8px 10px; color: var(--text); cursor: pointer; font-size: 13px; font-weight: 600; outline: none; user-select: none; }
914
+ .git-change-header:hover { background: var(--surface-soft); }
915
+ .git-change-path { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
916
+ .git-status-badge { flex: 0 0 auto; margin-left: 8px; padding: 1px 5px; border: 1px solid currentColor; border-radius: 4px; font-size: 10px; font-weight: 800; }
917
+ .git-status-badge.modified { color: var(--git-modified-text); }
918
+ .git-status-badge.added { color: var(--git-staged-color, #34c759); }
919
+ .git-status-badge.deleted { color: var(--git-deleted-color, #ff3b30); }
920
+ .git-inline-diff { border-top: 1px solid var(--line); background: var(--git-diff-bg); }
921
+ .git-inline-diff-body { max-height: 400px; overflow: auto; padding: 4px 0; font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
922
+ .git-inline-diff-footer { padding: 8px; border-top: 1px solid var(--line); background: var(--surface); text-align: center; }
923
+ .git-diff-panel { margin-bottom: 8px; overflow: hidden; border: 1px solid var(--line); border-radius: 6px; }
924
+ .git-diff-summary { padding: 7px 10px; background: var(--surface-raised); color: var(--text); cursor: pointer; font-size: 13px; font-weight: 600; outline: none; user-select: none; }
925
+ .git-diff-body { overflow-x: auto; padding: 4px 0; background: var(--git-diff-bg); font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
926
+ .git-file-code { overflow-x: auto; margin: 0; padding: 12px 0; border-radius: 8px; background: var(--git-diff-bg); color: var(--git-diff-text); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
927
+ .git-file-toolbar-group { display: flex; gap: 2px; margin-right: 8px; padding: 2px; border: 1px solid var(--line); border-radius: 5px; background: var(--surface-soft); }
928
+ .git-file-toolbar-button { padding: 4px 8px; border: 0; border-radius: 3px; background: transparent; color: var(--text-dim); cursor: pointer; font-size: 12px; }
929
+ .git-file-toolbar-button:hover { background: var(--surface-raised); color: var(--text); }
930
+ .git-file-mode-button { padding: 4px 8px; border: 1px solid var(--line); border-radius: 4px; background: var(--surface-soft); color: var(--text); cursor: pointer; font-size: 12px; }
931
+ .git-file-mode-button.active { border-color: var(--primary); background: var(--primary); color: #fff; }
932
+ .git-line-number { width: 36px; flex: 0 0 36px; margin-right: 16px; color: var(--text-dim); text-align: right; user-select: none; }
@@ -172,17 +172,17 @@
172
172
  }
173
173
 
174
174
  document.getElementById('send-btn').addEventListener('click', performSend);
175
- document.getElementById('attach-image-btn').addEventListener('click', () => {
176
- imageFileInput.click();
175
+ document.getElementById('attachment-btn').addEventListener('click', () => {
176
+ attachmentFileInput.click();
177
177
  });
178
178
  document.getElementById('schedule-send-btn').addEventListener('click', () => {
179
179
  if (document.getElementById('timed-send-panel').classList.contains('active')) closeTimedSendPanel();
180
180
  else openTimedSendPanel();
181
181
  });
182
- imageFileInput.addEventListener('change', () => {
183
- const files = imageFileInput.files;
184
- if (files?.length) void uploadImageFiles(files);
185
- imageFileInput.value = '';
182
+ attachmentFileInput.addEventListener('change', () => {
183
+ const files = attachmentFileInput.files;
184
+ if (files?.length) void uploadSelectedAttachments(files);
185
+ attachmentFileInput.value = '';
186
186
  });
187
187
  inputEl.addEventListener('keydown', (e) => {
188
188
  if (e.key === 'Enter' && e.shiftKey) { e.preventDefault(); performSend(); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glad-web",
3
- "version": "1.0.44",
3
+ "version": "1.0.46",
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"
@@ -59,10 +59,13 @@
59
59
  "express": "^5.2.1",
60
60
  "node-pty": "npm:@lydell/node-pty@^1.1.0",
61
61
  "uuid": "^11.1.1",
62
- "ws": "^8.19.0"
62
+ "ws": "^8.19.0",
63
+ "yaml": "^2.8.1",
64
+ "yauzl": "^3.2.0"
63
65
  },
64
66
  "devDependencies": {
65
67
  "@playwright/test": "^1.61.1",
66
- "caxa": "^3.0.1"
68
+ "caxa": "^3.0.1",
69
+ "yazl": "^3.3.1"
67
70
  }
68
71
  }