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.
- package/.github/workflows/ci.yml +91 -0
- package/docs/adrs/0007-design-token-system.md +66 -0
- package/docs/specs/client-app.md +35 -1
- package/e2e/playwright.config.js +33 -2
- package/package.json +1 -1
- package/src/public/app.js +145 -63
- package/src/public/base.css +139 -0
- package/src/public/command-palette.js +147 -0
- package/src/public/components/buttons.css +245 -0
- package/src/public/components/cards.css +124 -0
- package/src/public/components/menus.css +68 -0
- package/src/public/components/modals.css +703 -0
- package/src/public/components/notifications.css +22 -0
- package/src/public/components/tabs.css +432 -0
- package/src/public/components/terminal.css +290 -0
- package/src/public/index.html +95 -19
- package/src/public/mobile.css +550 -0
- package/src/public/service-worker.js +12 -1
- package/src/public/session-manager.js +53 -10
- package/src/public/style.css +34 -55
- package/src/public/tokens.css +375 -0
package/.github/workflows/ci.yml
CHANGED
|
@@ -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)
|
package/docs/specs/client-app.md
CHANGED
|
@@ -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)
|
package/e2e/playwright.config.js
CHANGED
|
@@ -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: {
|
|
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
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
|
-
|
|
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: '#
|
|
872
|
-
codex: { icon: 'Cx', color: '#
|
|
873
|
-
copilot: { icon: 'Cp', color: '#
|
|
874
|
-
gemini: { icon: 'G', color: '#
|
|
875
|
-
terminal: { icon: '>_', color: '#
|
|
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
|
-
${
|
|
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
|
|
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: '
|
|
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) || '
|
|
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
|
-
//
|
|
1251
|
-
|
|
1252
|
-
|
|
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
|
|