ai-or-die 0.1.16 → 0.1.18

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.
@@ -77,6 +77,97 @@ jobs:
77
77
  playwright-report/
78
78
  retention-days: 14
79
79
 
80
+ test-browser-mobile:
81
+ runs-on: ${{ matrix.os }}
82
+ strategy:
83
+ matrix:
84
+ os: [ubuntu-latest, windows-latest]
85
+ steps:
86
+ - uses: actions/checkout@v4
87
+ - uses: actions/setup-node@v4
88
+ with:
89
+ node-version: '22'
90
+ - run: npm ci
91
+ - name: Install Playwright browsers
92
+ run: npx playwright install chromium --with-deps
93
+ - name: Run mobile portrait tests (iPhone 14)
94
+ run: npx playwright test --config e2e/playwright.config.js --project mobile-iphone
95
+ - name: Run mobile portrait tests (Pixel 7)
96
+ run: npx playwright test --config e2e/playwright.config.js --project mobile-pixel
97
+ - name: Upload Playwright report
98
+ uses: actions/upload-artifact@v4
99
+ if: ${{ !cancelled() }}
100
+ with:
101
+ name: playwright-mobile-${{ matrix.os }}
102
+ path: |
103
+ e2e/test-results/
104
+ playwright-report/
105
+ retention-days: 14
106
+
107
+ test-browser-visual:
108
+ runs-on: ${{ matrix.os }}
109
+ strategy:
110
+ matrix:
111
+ os: [ubuntu-latest, windows-latest]
112
+ steps:
113
+ - uses: actions/checkout@v4
114
+ - uses: actions/setup-node@v4
115
+ with:
116
+ node-version: '22'
117
+ - run: npm ci
118
+ - name: Install Playwright browsers
119
+ run: npx playwright install chromium --with-deps
120
+ - name: Run visual regression tests
121
+ run: npx playwright test --config e2e/playwright.config.js --project visual-regression
122
+ - name: Upload Playwright report
123
+ uses: actions/upload-artifact@v4
124
+ if: ${{ !cancelled() }}
125
+ with:
126
+ name: playwright-visual-${{ matrix.os }}
127
+ path: |
128
+ e2e/test-results/
129
+ playwright-report/
130
+ retention-days: 14
131
+ - name: Upload generated baselines
132
+ uses: actions/upload-artifact@v4
133
+ if: ${{ !cancelled() }}
134
+ with:
135
+ name: screenshot-baselines-${{ matrix.os }}
136
+ path: e2e/tests/**/*-snapshots/
137
+ retention-days: 30
138
+ - name: Upload screenshot diffs
139
+ uses: actions/upload-artifact@v4
140
+ if: failure()
141
+ with:
142
+ name: screenshot-diffs-${{ matrix.os }}
143
+ path: e2e/tests/**/*-diff.png
144
+ retention-days: 14
145
+
146
+ test-browser-new-features:
147
+ runs-on: ${{ matrix.os }}
148
+ strategy:
149
+ matrix:
150
+ os: [ubuntu-latest, windows-latest]
151
+ steps:
152
+ - uses: actions/checkout@v4
153
+ - uses: actions/setup-node@v4
154
+ with:
155
+ node-version: '22'
156
+ - run: npm ci
157
+ - name: Install Playwright browsers
158
+ run: npx playwright install chromium --with-deps
159
+ - name: Run new feature tests
160
+ run: npx playwright test --config e2e/playwright.config.js --project new-features
161
+ - name: Upload Playwright report
162
+ uses: actions/upload-artifact@v4
163
+ if: ${{ !cancelled() }}
164
+ with:
165
+ name: playwright-new-features-${{ matrix.os }}
166
+ path: |
167
+ e2e/test-results/
168
+ playwright-report/
169
+ retention-days: 14
170
+
80
171
  build-binary:
81
172
  runs-on: ${{ matrix.os }}
82
173
  strategy:
@@ -0,0 +1,66 @@
1
+ # ADR-0007: Design Token System and Multi-Theme Architecture
2
+
3
+ ## Status
4
+
5
+ **Accepted**
6
+
7
+ ## Date
8
+
9
+ 2026-02-07
10
+
11
+ ## Context
12
+
13
+ The ai-or-die frontend uses a monolithic 2161-line `style.css` with 15 ad-hoc CSS custom properties in `:root`. Colors, spacing, shadows, and z-index values are hardcoded throughout. The app supports only two themes (dark and light) via a `[data-theme="light"]` override block that duplicates every variable.
14
+
15
+ This creates several problems:
16
+ - Adding a new theme requires finding and updating every hardcoded value
17
+ - No formal spacing or typography scale leads to inconsistent sizing
18
+ - No z-index hierarchy makes layering unpredictable
19
+ - Inline styles scattered in HTML and JS (install button, warning text) bypass the theme system entirely
20
+ - The single CSS file is hard to navigate and risky to modify
21
+
22
+ The terminal emulator community has standardized on popular color schemes (Monokai, Nord, Solarized) that users expect to be available. Competitors like Warp and iTerm2 ship with 10+ built-in themes.
23
+
24
+ ## Decision
25
+
26
+ We adopt a three-tier design token architecture in a dedicated `tokens.css` file:
27
+
28
+ 1. **Primitive tokens** define raw values (color hex codes, pixel sizes). These are named by their visual property (e.g., `--color-gray-200: #27272a`). Components never reference primitives directly.
29
+
30
+ 2. **Semantic tokens** define role-based references (e.g., `--surface-primary`, `--text-secondary`, `--accent-default`). All component CSS uses only semantic tokens.
31
+
32
+ 3. **Component tokens** (optional) provide per-component overrides when a semantic token is too generic. Used sparingly.
33
+
34
+ Themes override only semantic tokens via `[data-theme="name"]` CSS selectors. The default theme (Midnight) is defined on `:root`. Adding a new theme requires only one `[data-theme]` block.
35
+
36
+ The monolithic `style.css` will be split into component-specific CSS files (`components/tabs.css`, `components/modals.css`, etc.) loaded via `<link>` tags in `index.html`. This is acceptable because the app is served from localhost, where HTTP overhead is negligible.
37
+
38
+ Seven themes ship at launch: Midnight (default), Classic Dark, Classic Light, Monokai, Nord, Solarized Dark, Solarized Light.
39
+
40
+ ## Consequences
41
+
42
+ ### Positive
43
+
44
+ - Adding a new theme is a single `[data-theme]` block (~20 lines)
45
+ - Components automatically support all themes with no per-theme CSS
46
+ - Consistent spacing, typography, and z-index across all components
47
+ - CSS files are smaller and focused, easier to review and modify
48
+ - Inline styles can be replaced with token-backed CSS classes
49
+
50
+ ### Negative
51
+
52
+ - Multiple `<link>` tags increase the number of HTTP requests (mitigated: localhost only)
53
+ - Renaming semantic tokens requires updating all consuming CSS files
54
+ - Developers must learn the token naming conventions
55
+
56
+ ### Neutral
57
+
58
+ - The existing `[data-theme="light"]` selector continues to work (aliased to `classic-light`)
59
+ - No build step is introduced — all CSS remains plain vanilla
60
+ - Token file serves as living documentation of the design system
61
+
62
+ ## Notes
63
+
64
+ - Theme color values sourced from official specifications: [Nord](https://www.nordtheme.com/docs/colors-and-palettes/), [Monokai](https://monokai.pro/), [Solarized](https://ethanschoonover.com/solarized/)
65
+ - Meslo Nerd Font added as the default terminal font via CDN (nerdfont-webfonts on jsDelivr)
66
+ - xterm.js has a known limitation with double-width Nerd Font glyphs (issue #3342)
@@ -11,12 +11,46 @@ The frontend is a single-page application served from `src/public/`. It runs ent
11
11
  | xterm.js | 5.3.0 | unpkg CDN | Terminal emulator component |
12
12
  | xterm-addon-fit | 0.8.0 | unpkg CDN | Auto-fit terminal to container |
13
13
  | xterm-addon-web-links | 0.9.0 | unpkg CDN | Clickable URLs in terminal output |
14
- | JetBrains Mono | -- | Google Fonts | Monospace font for terminal |
14
+ | JetBrains Mono | -- | Google Fonts | Monospace font for terminal (fallback) |
15
+ | MesloLGS Nerd Font | -- | jsDelivr CDN | Primary terminal font with Nerd Font glyphs |
15
16
  | Inter | -- | Google Fonts | UI font for headers, tabs, controls |
16
17
  | clipboard-handler.js | -- | Local | Keyboard shortcuts (Ctrl+C/V) and clipboard utility functions |
17
18
 
18
19
  ---
19
20
 
21
+ ## Design Token System
22
+
23
+ Source: `src/public/tokens.css`
24
+
25
+ Three-tier architecture loaded before `style.css`:
26
+
27
+ 1. **Primitive tokens** — raw color/size values (e.g., `--color-gray-200`, `--space-4`)
28
+ 2. **Semantic tokens** — role-based references (e.g., `--surface-primary`, `--accent-default`)
29
+ 3. **Theme overrides** — `[data-theme="name"]` blocks override semantic tokens only
30
+
31
+ ### Available Themes
32
+
33
+ | Theme | `data-theme` value | Default? |
34
+ |-------|-------------------|----------|
35
+ | Midnight | (none / omit attribute) | Yes |
36
+ | Classic Dark | `classic-dark` | No |
37
+ | Classic Light | `classic-light` or `light` | No |
38
+ | Monokai | `monokai` | No |
39
+ | Nord | `nord` | No |
40
+ | Solarized Dark | `solarized-dark` | No |
41
+ | Solarized Light | `solarized-light` | No |
42
+
43
+ ### Backward Compatibility
44
+
45
+ `style.css` defines aliases mapping old variable names to new semantic tokens:
46
+ - `--bg-primary` → `var(--surface-primary)`
47
+ - `--accent` → `var(--accent-default)`
48
+ - `--success` → `var(--status-success)`
49
+ - `--error` → `var(--status-error)`
50
+ - `--border` → `var(--border-default)`
51
+
52
+ ---
53
+
20
54
  ## ClaudeCodeWebInterface
21
55
 
22
56
  Source: `src/public/app.js` (~2100 lines)
@@ -1,5 +1,5 @@
1
1
  // @ts-check
2
- const { defineConfig } = require('@playwright/test');
2
+ const { defineConfig, devices } = require('@playwright/test');
3
3
 
4
4
  module.exports = defineConfig({
5
5
  testDir: './tests',
@@ -8,7 +8,14 @@ module.exports = defineConfig({
8
8
  retries: process.env.CI ? 1 : 0,
9
9
  workers: 1,
10
10
  timeout: 60000,
11
- expect: { timeout: 15000 },
11
+ expect: {
12
+ timeout: 15000,
13
+ toHaveScreenshot: {
14
+ maxDiffPixelRatio: 0.01,
15
+ threshold: 0.2,
16
+ animations: 'disabled',
17
+ },
18
+ },
12
19
  reporter: process.env.CI ? [['github'], ['html', { open: 'never' }]] : 'list',
13
20
  use: {
14
21
  browserName: 'chromium',
@@ -28,5 +35,29 @@ module.exports = defineConfig({
28
35
  name: 'functional',
29
36
  testMatch: /0[2-7]-.*\.spec\.js/,
30
37
  },
38
+ {
39
+ name: 'mobile-iphone',
40
+ testMatch: '08-mobile-portrait.spec.js',
41
+ use: { ...devices['iPhone 14'] },
42
+ },
43
+ {
44
+ name: 'mobile-pixel',
45
+ testMatch: '08-mobile-portrait.spec.js',
46
+ use: { ...devices['Pixel 7'] },
47
+ },
48
+ {
49
+ name: 'visual-regression',
50
+ testMatch: '09-visual-regression.spec.js',
51
+ use: {
52
+ viewport: { width: 1280, height: 720 },
53
+ launchOptions: {
54
+ args: ['--font-render-hinting=none', '--disable-font-subpixel-positioning'],
55
+ },
56
+ },
57
+ },
58
+ {
59
+ name: 'new-features',
60
+ testMatch: /1[0-3]-.*\.spec\.js/,
61
+ },
31
62
  ],
32
63
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-or-die",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "Universal AI coding terminal — Claude, Copilot, Gemini & more in your browser",
5
5
  "main": "src/server.js",
6
6
  "bin": {
package/src/public/app.js CHANGED
@@ -337,10 +337,16 @@ class ClaudeCodeWebInterface {
337
337
 
338
338
  this.fitAddon = new FitAddon.FitAddon();
339
339
  this.webLinksAddon = new WebLinksAddon.WebLinksAddon();
340
-
340
+
341
341
  this.terminal.loadAddon(this.fitAddon);
342
342
  this.terminal.loadAddon(this.webLinksAddon);
343
-
343
+
344
+ // Load search addon if available
345
+ if (typeof SearchAddon !== 'undefined') {
346
+ this.searchAddon = new SearchAddon.SearchAddon();
347
+ this.terminal.loadAddon(this.searchAddon);
348
+ }
349
+
344
350
  this.terminal.open(document.getElementById('terminal'));
345
351
  this.fitTerminal();
346
352
 
@@ -351,6 +357,7 @@ class ClaudeCodeWebInterface {
351
357
  }
352
358
  });
353
359
 
360
+ this.setupTerminalSearch();
354
361
  this.setupTerminalContextMenu();
355
362
 
356
363
  this.terminal.onData((data) => {
@@ -709,64 +716,23 @@ class ClaudeCodeWebInterface {
709
716
  break;
710
717
 
711
718
  case 'claude_started':
712
- if (this._startToolTimeout) { clearTimeout(this._startToolTimeout); this._startToolTimeout = null; }
713
- this.hideOverlay();
714
- // Don't auto-focus to avoid focus tracking sequences
715
- // User can click to focus when ready
716
- this.loadSessions(); // Refresh session list
717
- // Request usage stats to start tracking session usage
718
- this.requestUsageStats();
719
-
720
- // Update tab status to active
721
- if (this.sessionTabManager && this.currentClaudeSessionId) {
722
- this.sessionTabManager.updateTabStatus(this.currentClaudeSessionId, 'active');
723
- }
724
- break;
725
719
  case 'codex_started':
726
- if (this._startToolTimeout) { clearTimeout(this._startToolTimeout); this._startToolTimeout = null; }
727
- this.hideOverlay();
728
- this.loadSessions();
729
- this.requestUsageStats();
730
- if (this.sessionTabManager && this.currentClaudeSessionId) {
731
- this.sessionTabManager.updateTabStatus(this.currentClaudeSessionId, 'active');
732
- }
733
- break;
734
720
  case 'agent_started':
735
- if (this._startToolTimeout) { clearTimeout(this._startToolTimeout); this._startToolTimeout = null; }
736
- this.hideOverlay();
737
- this.loadSessions();
738
- this.requestUsageStats();
739
- if (this.sessionTabManager && this.currentClaudeSessionId) {
740
- this.sessionTabManager.updateTabStatus(this.currentClaudeSessionId, 'active');
741
- }
742
- break;
743
721
  case 'copilot_started':
744
- if (this._startToolTimeout) { clearTimeout(this._startToolTimeout); this._startToolTimeout = null; }
745
- this.hideOverlay();
746
- this.loadSessions();
747
- this.requestUsageStats();
748
- if (this.sessionTabManager && this.currentClaudeSessionId) {
749
- this.sessionTabManager.updateTabStatus(this.currentClaudeSessionId, 'active');
750
- }
751
- break;
752
722
  case 'gemini_started':
723
+ case 'terminal_started': {
753
724
  if (this._startToolTimeout) { clearTimeout(this._startToolTimeout); this._startToolTimeout = null; }
754
725
  this.hideOverlay();
755
726
  this.loadSessions();
756
727
  this.requestUsageStats();
757
728
  if (this.sessionTabManager && this.currentClaudeSessionId) {
758
729
  this.sessionTabManager.updateTabStatus(this.currentClaudeSessionId, 'active');
730
+ // Extract tool type from message type (e.g. 'claude_started' → 'claude')
731
+ const toolType = message.type.replace('_started', '');
732
+ this.sessionTabManager.setTabToolType(this.currentClaudeSessionId, toolType === 'agent' ? 'claude' : toolType);
759
733
  }
760
734
  break;
761
- case 'terminal_started':
762
- if (this._startToolTimeout) { clearTimeout(this._startToolTimeout); this._startToolTimeout = null; }
763
- this.hideOverlay();
764
- this.loadSessions();
765
- this.requestUsageStats();
766
- if (this.sessionTabManager && this.currentClaudeSessionId) {
767
- this.sessionTabManager.updateTabStatus(this.currentClaudeSessionId, 'active');
768
- }
769
- break;
735
+ }
770
736
 
771
737
  case 'claude_stopped':
772
738
  this.terminal.writeln(`\r\n\x1b[33m${this.getAlias('claude')} stopped\x1b[0m`);
@@ -868,25 +834,35 @@ class ClaudeCodeWebInterface {
868
834
  container.innerHTML = '';
869
835
 
870
836
  const toolMeta = {
871
- claude: { icon: 'C', color: '#ff6b00', desc: 'Anthropic' },
872
- codex: { icon: 'Cx', color: '#10a37f', desc: 'OpenAI' },
873
- copilot: { icon: 'Cp', color: '#8b5cf6', desc: 'GitHub' },
874
- gemini: { icon: 'G', color: '#4285f4', desc: 'Google' },
875
- terminal: { icon: '>_', color: '#8b949e', desc: 'Shell' }
837
+ claude: { icon: 'C', color: '#d97706', desc: 'Anthropic AI', hint: 'Install: npm i -g @anthropic-ai/claude-code' },
838
+ codex: { icon: 'Cx', color: '#059669', desc: 'OpenAI Codex', hint: 'Install: npm i -g @openai/codex' },
839
+ copilot: { icon: 'Cp', color: '#6366f1', desc: 'GitHub Copilot', hint: 'Install: gh extension install github/gh-copilot' },
840
+ gemini: { icon: 'G', color: '#2563eb', desc: 'Google Gemini', hint: 'Install: npm i -g @anthropic-ai/gemini-cli' },
841
+ terminal: { icon: '>_', color: '#71717a', desc: 'System Shell', hint: '' }
876
842
  };
877
843
 
844
+ let cardIndex = 0;
878
845
  for (const [toolId, tool] of Object.entries(this.tools)) {
879
- const meta = toolMeta[toolId] || { icon: '?', color: '#888', desc: '' };
846
+ const meta = toolMeta[toolId] || { icon: '?', color: '#888', desc: '', hint: '' };
880
847
  const card = document.createElement('div');
881
848
  card.className = 'tool-card' + (tool.available ? '' : ' disabled');
849
+ // Staggered fade-in animation
850
+ card.style.animationDelay = `${cardIndex * 60}ms`;
851
+ card.classList.add('tool-card-enter');
852
+
853
+ const statusText = tool.available ? 'Start' : 'Not installed';
854
+ const hintHtml = !tool.available && meta.hint
855
+ ? `<div class="tool-card-hint">${meta.hint}</div>` : '';
856
+
882
857
  card.innerHTML = `
883
858
  <div class="tool-card-icon" style="background: ${meta.color}">${meta.icon}</div>
884
859
  <div class="tool-card-info">
885
860
  <div class="tool-card-name">${tool.alias}</div>
886
861
  <div class="tool-card-desc">${meta.desc}</div>
862
+ ${hintHtml}
887
863
  </div>
888
864
  <button class="btn btn-primary tool-card-btn" ${tool.available ? '' : 'disabled'}>
889
- ${tool.available ? 'Start' : 'Not installed'}
865
+ ${statusText}
890
866
  </button>
891
867
  `;
892
868
  if (tool.available) {
@@ -895,6 +871,7 @@ class ClaudeCodeWebInterface {
895
871
  });
896
872
  }
897
873
  container.appendChild(card);
874
+ cardIndex++;
898
875
  }
899
876
  }
900
877
 
@@ -983,6 +960,82 @@ class ClaudeCodeWebInterface {
983
960
  }
984
961
  }
985
962
 
963
+ setupTerminalSearch() {
964
+ const bar = document.getElementById('terminalSearchBar');
965
+ const input = document.getElementById('termSearchInput');
966
+ const countEl = document.getElementById('termSearchCount');
967
+ const prevBtn = document.getElementById('termSearchPrev');
968
+ const nextBtn = document.getElementById('termSearchNext');
969
+ const caseBtn = document.getElementById('termSearchCase');
970
+ const regexBtn = document.getElementById('termSearchRegex');
971
+ const closeBtn = document.getElementById('termSearchClose');
972
+ if (!bar || !input || !this.searchAddon) return;
973
+
974
+ let caseSensitive = false;
975
+ let useRegex = false;
976
+
977
+ const doSearch = (direction = 'next') => {
978
+ const query = input.value;
979
+ if (!query) { countEl.textContent = ''; return; }
980
+ const opts = { caseSensitive, regex: useRegex };
981
+ if (direction === 'prev') {
982
+ this.searchAddon.findPrevious(query, opts);
983
+ } else {
984
+ this.searchAddon.findNext(query, opts);
985
+ }
986
+ };
987
+
988
+ const openSearch = () => {
989
+ bar.style.display = 'flex';
990
+ input.focus();
991
+ input.select();
992
+ };
993
+
994
+ const closeSearch = () => {
995
+ bar.style.display = 'none';
996
+ input.value = '';
997
+ countEl.textContent = '';
998
+ this.searchAddon.clearDecorations();
999
+ this.terminal.focus();
1000
+ };
1001
+
1002
+ // Ctrl+F opens search (capture phase to intercept before xterm)
1003
+ document.addEventListener('keydown', (e) => {
1004
+ if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
1005
+ e.preventDefault();
1006
+ e.stopPropagation();
1007
+ openSearch();
1008
+ }
1009
+ }, true);
1010
+
1011
+ input.addEventListener('keydown', (e) => {
1012
+ if (e.key === 'Enter') {
1013
+ e.preventDefault();
1014
+ doSearch(e.shiftKey ? 'prev' : 'next');
1015
+ } else if (e.key === 'Escape') {
1016
+ e.preventDefault();
1017
+ closeSearch();
1018
+ }
1019
+ });
1020
+
1021
+ input.addEventListener('input', () => doSearch('next'));
1022
+ prevBtn.addEventListener('click', () => doSearch('prev'));
1023
+ nextBtn.addEventListener('click', () => doSearch('next'));
1024
+ closeBtn.addEventListener('click', () => closeSearch());
1025
+
1026
+ caseBtn.addEventListener('click', () => {
1027
+ caseSensitive = !caseSensitive;
1028
+ caseBtn.classList.toggle('active', caseSensitive);
1029
+ doSearch('next');
1030
+ });
1031
+
1032
+ regexBtn.addEventListener('click', () => {
1033
+ useRegex = !useRegex;
1034
+ regexBtn.classList.toggle('active', useRegex);
1035
+ doSearch('next');
1036
+ });
1037
+ }
1038
+
986
1039
  setupTerminalContextMenu() {
987
1040
  const menu = document.getElementById('termContextMenu');
988
1041
  if (!menu) return;
@@ -1198,7 +1251,15 @@ class ClaudeCodeWebInterface {
1198
1251
  document.getElementById('fontSize').value = settings.fontSize;
1199
1252
  document.getElementById('fontSizeValue').textContent = settings.fontSize + 'px';
1200
1253
  const themeSelect = document.getElementById('themeSelect');
1201
- if (themeSelect) themeSelect.value = settings.theme === 'light' ? 'light' : 'dark';
1254
+ if (themeSelect) themeSelect.value = settings.theme || 'midnight';
1255
+ const fontFamily = document.getElementById('fontFamily');
1256
+ if (fontFamily) fontFamily.value = settings.fontFamily || "'MesloLGS Nerd Font', 'Meslo Nerd Font', monospace";
1257
+ const cursorStyle = document.getElementById('cursorStyle');
1258
+ if (cursorStyle) cursorStyle.value = settings.cursorStyle || 'block';
1259
+ const cursorBlink = document.getElementById('cursorBlink');
1260
+ if (cursorBlink) cursorBlink.checked = settings.cursorBlink ?? true;
1261
+ const scrollback = document.getElementById('scrollback');
1262
+ if (scrollback) scrollback.value = String(settings.scrollback || 1000);
1202
1263
  document.getElementById('showTokenStats').checked = settings.showTokenStats;
1203
1264
  document.getElementById('dangerousMode').checked = settings.dangerousMode || false;
1204
1265
  }
@@ -1215,8 +1276,12 @@ class ClaudeCodeWebInterface {
1215
1276
  loadSettings() {
1216
1277
  const defaults = {
1217
1278
  fontSize: 14,
1279
+ fontFamily: "'MesloLGS Nerd Font', 'Meslo Nerd Font', monospace",
1280
+ cursorStyle: 'block',
1281
+ cursorBlink: true,
1282
+ scrollback: 1000,
1218
1283
  showTokenStats: true,
1219
- theme: 'dark',
1284
+ theme: 'midnight',
1220
1285
  dangerousMode: false
1221
1286
  };
1222
1287
 
@@ -1232,8 +1297,12 @@ class ClaudeCodeWebInterface {
1232
1297
  saveSettings() {
1233
1298
  const settings = {
1234
1299
  fontSize: parseInt(document.getElementById('fontSize').value),
1300
+ fontFamily: document.getElementById('fontFamily')?.value || "'MesloLGS Nerd Font', 'Meslo Nerd Font', monospace",
1301
+ cursorStyle: document.getElementById('cursorStyle')?.value || 'block',
1302
+ cursorBlink: document.getElementById('cursorBlink')?.checked ?? true,
1303
+ scrollback: parseInt(document.getElementById('scrollback')?.value || '1000'),
1235
1304
  showTokenStats: document.getElementById('showTokenStats').checked,
1236
- theme: (document.getElementById('themeSelect')?.value) || 'dark',
1305
+ theme: (document.getElementById('themeSelect')?.value) || 'midnight',
1237
1306
  dangerousMode: document.getElementById('dangerousMode').checked
1238
1307
  };
1239
1308
 
@@ -1247,16 +1316,29 @@ class ClaudeCodeWebInterface {
1247
1316
  }
1248
1317
 
1249
1318
  applySettings(settings) {
1250
- // Token stats bar removed - no longer needed
1251
- // Apply theme (dark is default; light sets attribute)
1252
- if (settings.theme === 'light') {
1253
- document.documentElement.setAttribute('data-theme', 'light');
1319
+ // Apply theme 'midnight' is default (no attribute), others set data-theme
1320
+ if (settings.theme && settings.theme !== 'midnight') {
1321
+ document.documentElement.setAttribute('data-theme', settings.theme);
1254
1322
  } else {
1255
1323
  document.documentElement.removeAttribute('data-theme');
1256
1324
  }
1257
1325
 
1326
+ // Apply terminal settings
1258
1327
  this.terminal.options.fontSize = settings.fontSize;
1259
-
1328
+ if (settings.fontFamily) this.terminal.options.fontFamily = settings.fontFamily;
1329
+ if (settings.cursorStyle) this.terminal.options.cursorStyle = settings.cursorStyle;
1330
+ this.terminal.options.cursorBlink = settings.cursorBlink ?? true;
1331
+ if (settings.scrollback) this.terminal.options.scrollback = settings.scrollback;
1332
+
1333
+ // Update terminal theme colors to match the current CSS theme
1334
+ const style = getComputedStyle(document.documentElement);
1335
+ this.terminal.options.theme = {
1336
+ background: style.getPropertyValue('--terminal-bg').trim() || style.getPropertyValue('--surface-primary').trim(),
1337
+ foreground: style.getPropertyValue('--terminal-fg').trim() || style.getPropertyValue('--text-primary').trim(),
1338
+ cursor: style.getPropertyValue('--terminal-cursor').trim() || style.getPropertyValue('--accent-default').trim(),
1339
+ selectionBackground: style.getPropertyValue('--terminal-selection').trim() || undefined,
1340
+ };
1341
+
1260
1342
  this.fitTerminal();
1261
1343
  }
1262
1344