glad-web 1.0.14 → 1.0.17

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/assets/logo.svg CHANGED
@@ -1,40 +1,43 @@
1
1
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
2
2
  <defs>
3
+ <!-- Normcore / Cold Slate Background Gradient -->
3
4
  <linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
4
- <stop offset="0%" stop-color="#151522" />
5
- <stop offset="100%" stop-color="#2D2B55" />
5
+ <stop offset="0%" stop-color="#1E2024" />
6
+ <stop offset="50%" stop-color="#111215" />
7
+ <stop offset="100%" stop-color="#050505" />
6
8
  </linearGradient>
7
- <linearGradient id="phoneGrad" x1="0%" y1="100%" x2="100%" y2="0%">
8
- <stop offset="0%" stop-color="#FF3366" />
9
- <stop offset="50%" stop-color="#FF9933" />
10
- <stop offset="100%" stop-color="#33CCFF" />
9
+
10
+ <!-- Sunset Coral Gradient (Warm, energetic smile/tongue) -->
11
+ <linearGradient id="faceGrad" gradientUnits="userSpaceOnUse" x1="125" y1="230" x2="387" y2="310">
12
+ <stop offset="0%" stop-color="#FF9E79" />
13
+ <stop offset="60%" stop-color="#FF6F61" />
14
+ <stop offset="100%" stop-color="#D84315" />
11
15
  </linearGradient>
12
16
  </defs>
13
17
 
14
- <!-- Background App Icon Shape -->
15
- <rect x="24" y="24" width="464" height="464" rx="104" fill="url(#bgGrad)" />
18
+ <!-- Background App Icon Shape (Cold basalt gradient, fills the icon canvas) -->
19
+ <rect width="512" height="512" fill="url(#bgGrad)" />
16
20
 
17
- <!-- Mobile Device Outline -->
18
- <rect x="126" y="66" width="260" height="380" rx="36" fill="#1A1A2E" stroke="url(#phoneGrad)" stroke-width="12" />
19
-
20
- <!-- Mobile screen top bar (notch/speaker) -->
21
- <line x1="226" y1="96" x2="286" y2="96" stroke="url(#phoneGrad)" stroke-width="8" stroke-linecap="round" />
21
+ <!-- CLI Face (Expanded to fill the icon area, no phone container) -->
22
+ <!-- Left Eye: Terminal I-Beam Cursor (Bold & Centered) -->
23
+ <path d="M 145 101 H 175 M 160 101 V 151 M 145 151 H 175" fill="none" stroke="#007aff" stroke-width="12" stroke-linecap="round" stroke-linejoin="round" />
22
24
 
23
- <!-- Left Eye: Terminal Prompt ">" -->
24
- <polyline points="180,185 210,205 180,225" fill="none" stroke="#33CCFF" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" />
25
-
26
- <!-- Right Eye: AI Sparkle -->
27
- <path d="M 322 175 Q 322 205 352 205 Q 322 205 322 235 Q 322 205 292 205 Q 322 205 322 175 Z" fill="#FFD700" />
25
+ <!-- Right Eye: Project's Send Button Triangle/Paper Plane (Bold & Centered) -->
26
+ <g transform="translate(352, 126) scale(2.4) translate(-12, -12)">
27
+ <line x1="22" y1="2" x2="11" y2="13" stroke="#007aff" stroke-width="5.0" stroke-linecap="round" stroke-linejoin="round" />
28
+ <polygon points="22 2 15 22 11 13 2 9 22 2" fill="none" stroke="#007aff" stroke-width="5.0" stroke-linecap="round" stroke-linejoin="round" />
29
+ </g>
28
30
 
29
- <!-- Glad Smile -->
30
- <path d="M 175 265 Q 256 315 337 265" fill="none" stroke="#FF3366" stroke-width="16" stroke-linecap="round" />
31
+ <!-- Centered tongue-out mouth -->
32
+ <path d="M 125 230 H 387" fill="none" stroke="url(#faceGrad)" stroke-width="15" stroke-linecap="round" />
33
+ <path d="M 218 230 C 220 250 220 273 226 289 C 232 305 242 313 256 313 C 270 313 280 305 286 289 C 292 273 292 250 294 230" fill="none" stroke="url(#faceGrad)" stroke-width="15" stroke-linecap="round" stroke-linejoin="round" />
34
+ <path d="M 256 290 V 308" fill="none" stroke="url(#faceGrad)" stroke-width="6" stroke-linecap="round" />
31
35
 
32
- <!-- Vibe Coding Waves (Audio bars) -->
33
- <g stroke-linecap="round" stroke-width="10">
34
- <line x1="196" y1="375" x2="196" y2="400" stroke="#FF9933" />
35
- <line x1="226" y1="355" x2="226" y2="400" stroke="#FF3366" />
36
- <line x1="256" y1="340" x2="256" y2="400" stroke="#33CCFF" />
37
- <line x1="286" y1="360" x2="286" y2="400" stroke="#FFD700" />
38
- <line x1="316" y1="380" x2="316" y2="400" stroke="#FF9933" />
39
- </g>
40
- </svg>
36
+ <!-- Git Diff Lines (Expanded & Centered, no filter, 50% opacity for clear mobile legibility) -->
37
+ <!-- Add (+) Line: Faded Green -->
38
+ <line x1="130" y1="361" x2="382" y2="361" stroke="#2EA44F" stroke-width="14" stroke-linecap="round" opacity="0.5" />
39
+ <!-- Delete (-) Line: Faded Red -->
40
+ <line x1="130" y1="386" x2="300" y2="386" stroke="#CF222E" stroke-width="14" stroke-linecap="round" opacity="0.5" />
41
+ <!-- Add (+) Line: Faded Green -->
42
+ <line x1="130" y1="411" x2="350" y2="411" stroke="#2EA44F" stroke-width="14" stroke-linecap="round" opacity="0.5" />
43
+ </svg>
@@ -344,6 +344,8 @@ async function webCommand(options) {
344
344
 
345
345
  ws.sessionId = sessionId;
346
346
  const session = sessionManager.get(sessionId);
347
+ const isReconnect = session.hasConnectedWebClient;
348
+ session.hasConnectedWebClient = true;
347
349
  if (!session.resizeOwner) {
348
350
  session.resizeOwner = ws;
349
351
  }
@@ -352,7 +354,11 @@ async function webCommand(options) {
352
354
  // Send catchup output. TUI tools may skip the raw circular buffer, so fall
353
355
  // back to the rendered/text history snapshot instead of reconnecting blank.
354
356
  const catchup = sessionManager.getCatchupOutput(sessionId);
355
- if (catchup && catchup.data) {
357
+ ws.needsTuiRedraw = ['antigravity', 'claude-code', 'codex', 'gemini'].includes(session.tool.key)
358
+ && (isReconnect || (catchup && catchup.source === 'rendered-history'));
359
+ if (ws.needsTuiRedraw) {
360
+ ws.send(JSON.stringify({ type: 'reset' }));
361
+ } else if (catchup && catchup.data) {
356
362
  sessionManager.logWsCatchupOutput(sessionId, catchup);
357
363
  ws.send(JSON.stringify({ type: 'output', data: catchup.data }));
358
364
  }
@@ -365,7 +371,12 @@ async function webCommand(options) {
365
371
  }
366
372
  if (payload.type === 'resize' && session.resizeOwner === ws) {
367
373
  sessionManager.logWsResize(sessionId, payload.cols, payload.rows);
368
- sessionManager.resize(sessionId, payload.cols, payload.rows);
374
+ if (ws.needsTuiRedraw) {
375
+ ws.needsTuiRedraw = false;
376
+ sessionManager.redraw(sessionId, payload.cols, payload.rows);
377
+ } else {
378
+ sessionManager.resize(sessionId, payload.cols, payload.rows);
379
+ }
369
380
  }
370
381
  } catch (e) {
371
382
  logger.error('WS Message Error: ' + e.message);
@@ -197,6 +197,20 @@ class PTYManager {
197
197
  }
198
198
  }
199
199
 
200
+ redraw(cols, rows) {
201
+ if (!this.ptyProcess) return false;
202
+
203
+ const targetCols = Math.max(2, Number.parseInt(cols, 10) || 80);
204
+ const targetRows = Math.max(1, Number.parseInt(rows, 10) || 24);
205
+ const pulseCols = targetCols > 2 ? targetCols - 1 : targetCols + 1;
206
+
207
+ this.ptyProcess.resize(pulseCols, targetRows);
208
+ setTimeout(() => {
209
+ if (this.ptyProcess) this.ptyProcess.resize(targetCols, targetRows);
210
+ }, 30);
211
+ return true;
212
+ }
213
+
200
214
  setupLocalResizeListener() {
201
215
  if (!process.stdout.isTTY) return;
202
216
  this.localResizeListener = () => {
@@ -97,6 +97,7 @@ class SessionManager extends EventEmitter {
97
97
  inputSeq: 0,
98
98
  completionReadInputSeq: 0,
99
99
  resizeOwner: null,
100
+ hasConnectedWebClient: false,
100
101
  hasUnreadCompletion: false,
101
102
  write: data => this.write(id, data),
102
103
  isRunning: () => this.has(id) && ptyManager.isRunning(),
@@ -137,6 +138,15 @@ class SessionManager extends EventEmitter {
137
138
  return true;
138
139
  }
139
140
 
141
+ redraw(id, cols, rows) {
142
+ const session = this.get(id);
143
+ if (!session) return false;
144
+ if (session.renderedHistory) {
145
+ session.renderedHistory.resize(cols, rows);
146
+ }
147
+ return session.ptyManager.redraw(cols, rows);
148
+ }
149
+
140
150
  rename(id, name) {
141
151
  const session = this.get(id);
142
152
  if (!session || !name) return null;
@@ -52,7 +52,7 @@
52
52
  #shortcut-rail::-webkit-scrollbar { display: none; }
53
53
  .shortcut-group { flex: 0 0 calc(100vw - 28px); display: grid; gap: 8px; scroll-snap-align: start; }
54
54
  .simple-shortcuts { grid-template-columns: repeat(6, minmax(0, 1fr)); }
55
- .complex-shortcuts { grid-template-columns: repeat(5, minmax(0, 1fr)); }
55
+ .complex-shortcuts { grid-template-columns: repeat(6, minmax(0, 1fr)); }
56
56
  .key-btn, .command-key { flex: 0 0 auto; min-width: 0; height: 40px; border: 1px solid rgba(255,255,255,0.11); border-radius: 12px; background: rgba(255,255,255,0.08); color: #d1d5db; font-size: 12px; font-weight: 700; letter-spacing: 0; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; white-space: nowrap; }
57
57
  .key-btn { font-size: 11px; }
58
58
  .shortcut-group .key-btn, .shortcut-group .command-key { width: 100%; }
@@ -174,9 +174,10 @@
174
174
  <div class="key-btn" data-key="esc">Esc</div>
175
175
  <div class="key-btn" data-key="tab">Tab</div>
176
176
  </div>
177
- <div class="shortcut-group complex-shortcuts">
177
+ <div class="shortcut-group complex-shortcuts" data-role="tool-shortcuts">
178
+ <div class="key-btn special-key" data-key="left">←</div>
179
+ <div class="key-btn special-key" data-key="right">→</div>
178
180
  <div class="key-btn" data-key="ctrl-c">Ctrl+C</div>
179
- <div class="key-btn" data-key="ctrl-y" data-role="tool-action-shortcut" title="Send Ctrl+Y">Ctrl+Y</div>
180
181
  <button class="command-key" data-command="/model" title="Send /model">/model</button>
181
182
  <button class="command-key" data-command="/resume" title="Send /resume">/resume</button>
182
183
  <button class="command-key" data-command="/stat" data-role="usage-shortcut" title="Send /stat">/stat</button>
@@ -831,28 +832,53 @@
831
832
  }
832
833
 
833
834
  function updateToolShortcuts(toolKey) {
834
- const actionBtn = document.querySelector('[data-role="tool-action-shortcut"]');
835
- const usageBtn = document.querySelector('[data-role="usage-shortcut"]');
836
-
837
- if (usageBtn) {
838
- const command = toolKey === 'antigravity' ? '/usage' : '/stat';
839
- usageBtn.dataset.command = command;
840
- usageBtn.textContent = command;
841
- usageBtn.title = `Send ${command}`;
842
- }
843
-
844
- if (!actionBtn) return;
845
-
846
- if (toolKey === 'codex') {
847
- actionBtn.dataset.key = 'codex-perm';
848
- actionBtn.textContent = '/perm';
849
- actionBtn.title = 'Send /perm';
850
- return;
851
- }
852
-
853
- actionBtn.dataset.key = 'ctrl-y';
854
- actionBtn.textContent = 'Ctrl+Y';
855
- actionBtn.title = 'Send Ctrl+Y';
835
+ const usesUsageCommand = ['antigravity', 'claude-code'].includes(toolKey);
836
+ const usageCommand = usesUsageCommand ? '/usage' : '/stat';
837
+ const defaultShortcuts = [
838
+ { key: 'left', label: '←', special: true },
839
+ { key: 'right', label: '', special: true },
840
+ { key: 'ctrl-c', label: 'Ctrl+C' },
841
+ { command: '/model' },
842
+ { command: '/resume' },
843
+ { command: usageCommand }
844
+ ];
845
+ const toolShortcuts = {
846
+ codex: [
847
+ { key: 'ctrl-c', label: 'Ctrl+C' },
848
+ { key: 'codex-perm', label: '/perm', title: 'Send /perm' },
849
+ { command: '/model' },
850
+ { command: '/resume' },
851
+ { command: '/stat' }
852
+ ],
853
+ gemini: [
854
+ { key: 'ctrl-c', label: 'Ctrl+C' },
855
+ { key: 'ctrl-y', label: 'Ctrl+Y', title: 'Send Ctrl+Y' },
856
+ { command: '/model' },
857
+ { command: '/resume' },
858
+ { command: '/stat' }
859
+ ]
860
+ };
861
+ const shortcuts = toolShortcuts[toolKey] || defaultShortcuts;
862
+ const shortcutGroup = document.querySelector('[data-role="tool-shortcuts"]');
863
+ if (!shortcutGroup) return;
864
+
865
+ shortcutGroup.style.gridTemplateColumns = `repeat(${shortcuts.length}, minmax(0, 1fr))`;
866
+ shortcutGroup.replaceChildren(...shortcuts.map(shortcut => {
867
+ const button = document.createElement(shortcut.command ? 'button' : 'div');
868
+ button.className = shortcut.command
869
+ ? 'command-key'
870
+ : `key-btn${shortcut.special ? ' special-key' : ''}`;
871
+ if (shortcut.command) {
872
+ button.dataset.command = shortcut.command;
873
+ button.textContent = shortcut.command;
874
+ button.title = `Send ${shortcut.command}`;
875
+ } else {
876
+ button.dataset.key = shortcut.key;
877
+ button.textContent = shortcut.label;
878
+ button.title = shortcut.title || '';
879
+ }
880
+ return button;
881
+ }));
856
882
  }
857
883
 
858
884
  function joinSession(id, sessionName, toolKey = null) {
@@ -901,6 +927,7 @@
901
927
  currentSocket.onmessage = (e) => {
902
928
  const msg = JSON.parse(e.data);
903
929
  if (msg.type === 'output') term.write(msg.data);
930
+ if (msg.type === 'reset') term.reset();
904
931
  if (msg.type === 'exit') showLobby();
905
932
  };
906
933
  }
@@ -1153,7 +1180,7 @@
1153
1180
  setTimeout(() => { sendWS('\r'); }, 1000);
1154
1181
  return;
1155
1182
  }
1156
- const sequences = { up: '\x1b[A', down: '\x1b[B', enter: '\r', esc: '\x1b', tab: '\t', 'ctrl-c': '\x03', 'ctrl-y': '\x19' };
1183
+ const sequences = { up: '\x1b[A', down: '\x1b[B', left: '\x1b[D', right: '\x1b[C', enter: '\r', esc: '\x1b', tab: '\t', 'ctrl-c': '\x03', 'ctrl-y': '\x19' };
1157
1184
  if (sequences[key]) sendWS(sequences[key]);
1158
1185
  });
1159
1186
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glad-web",
3
- "version": "1.0.14",
3
+ "version": "1.0.17",
4
4
  "description": "Glad transforms terminal-based AI coding tools into a polished, mobile-friendly local Web interface.",
5
5
  "main": "index.js",
6
6
  "bin": {