deckide 3.5.42 → 3.5.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/bin/deckide.js +50 -1
- package/dist/routes/codeserver.js +8 -0
- package/dist/utils/agent-browser.js +22 -0
- package/dist/utils/browser-webrtc.js +28 -6
- package/dist/utils/code-server.js +73 -4
- package/package.json +1 -1
- package/web/dist/assets/index-CoU58o2I.css +32 -0
- package/web/dist/assets/index-Dk3QbSwK.js +178 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-C1OzsVds.css +0 -32
- package/web/dist/assets/index-DFNjEZYB.js +0 -178
package/README.md
CHANGED
|
@@ -64,6 +64,11 @@ deckide config 全設定表示
|
|
|
64
64
|
deckide config set <key> <val> 設定値変更
|
|
65
65
|
deckide config get <key> 設定値取得
|
|
66
66
|
deckide config reset 設定リセット
|
|
67
|
+
|
|
68
|
+
deckide codeserver install code-server(VS Code エディタ)を取得/インストール
|
|
69
|
+
deckide codeserver status code-server のインストール状態
|
|
70
|
+
deckide codeserver path code-server バイナリのパスを表示
|
|
71
|
+
deckide codeserver uninstall 取得済み code-server を削除
|
|
67
72
|
```
|
|
68
73
|
|
|
69
74
|
### 起動オプション
|
|
@@ -167,6 +172,29 @@ ide/
|
|
|
167
172
|
| `AGENT_BROWSER_FFMPEG` | ffmpeg 実行ファイル | PATH から自動検出 |
|
|
168
173
|
| `AGENT_BROWSER_PACTL` | pactl 実行ファイル | PATH から自動検出 |
|
|
169
174
|
| `AGENT_BROWSER_AUDIO_SOURCE` | 音声キャプチャ元 | 既定 sink の monitor source |
|
|
175
|
+
| `AGENT_BROWSER_WEBRTC` | エージェントブラウザ映像を WebRTC 配信(`true`/`auto`/`false`、現状はオプトイン) | false |
|
|
176
|
+
| `AGENT_BROWSER_WEBRTC_BITRATE` | WebRTC ビットレート(ffmpeg `-b:v` 形式) | 3M |
|
|
177
|
+
| `AGENT_BROWSER_WEBRTC_FPS` | WebRTC フレームレート | 30 |
|
|
178
|
+
| `AGENT_BROWSER_WEBRTC_ICE_SERVERS` | ICE サーバ(JSON配列)。空ならホスト候補のみ(同一ホスト/LAN) | — |
|
|
179
|
+
| `AGENT_BROWSER_DISPLAY` | キャプチャする X ディスプレイ(未設定なら Chrome から自動検出) | — |
|
|
180
|
+
| `AGENT_BROWSER_CDP_PORT` | 共有ブラウザの CDP ポート | 9222 |
|
|
181
|
+
| `AGENT_BROWSER_REAP_MCP` | リークした chrome-devtools-mcp スタックを自動回収 | true |
|
|
182
|
+
| `AGENT_BROWSER_MAX_MCP_PER_AGENT` | 1エージェントが保持できる MCP スタック上限(超過分の古いものを回収) | 3 |
|
|
183
|
+
| `AGENT_BROWSER_MCP_IDLE_MS` | 回収対象とみなすアイドル閾値 (ms) | 600000 |
|
|
184
|
+
| `DECKIDE_CODE_SERVER_BIN` | code-server バイナリ(未設定なら ~/.deckide → PATH → 初回自動DL) | — |
|
|
185
|
+
| `DECKIDE_CODE_SERVER_VERSION` | 取得する code-server バージョン | 4.125.0 |
|
|
186
|
+
|
|
187
|
+
## Deck Drive シェルと code-server
|
|
188
|
+
|
|
189
|
+
既定の UI は **Deck Drive**(Google Drive 風のファイル/ワークスペース・ブラウザ)です。ファイルを開くと **code-server(ブラウザ版 VS Code)** が同一オリジンのリバースプロキシ(`/codeserver/:wsId/*`)越しに起動します。ターミナルデッキ・エージェントブラウザもタブとして同じシェルに統合されています。
|
|
190
|
+
|
|
191
|
+
- **code-server の取得**: 初回利用時に固定版(`DECKIDE_CODE_SERVER_VERSION`、既定 4.125.0)を `~/.deckide/code-server` へ自動ダウンロードします。手動なら `deckide codeserver install`。
|
|
192
|
+
- **拡張機能**: Microsoft マーケットプレイスは利用不可(Open VSX のみ)。Pylance/Copilot/Remote-* は使えません(Pyright 等で代替)。
|
|
193
|
+
- **リソース**: code-server はワークスペース毎に1プロセス(目安 +1GB RAM / +2 コア)。アイドル20分で自動停止します。
|
|
194
|
+
- **認証**: code-server は `--auth none` で起動し、Deck IDE の Basic 認証配下の同一オリジンプロキシで保護します。
|
|
195
|
+
- **旧 UI**: 従来の Monaco/分割ターミナル・シェルは `?shell=legacy` で引き続き利用できます。
|
|
196
|
+
|
|
197
|
+
> エージェントブラウザの WebRTC 配信(`AGENT_BROWSER_WEBRTC` / `?webrtc=1`)は実験的なオプトインです。既定は従来の JPEG ストリーミングです。音声トラックは PulseAudio がある環境でのみ動作します。
|
|
170
198
|
|
|
171
199
|
## MCP / OAuth
|
|
172
200
|
|
package/bin/deckide.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import path from 'node:path';
|
|
@@ -172,6 +172,11 @@ Usage:
|
|
|
172
172
|
deckide config get <key> Get a config value
|
|
173
173
|
deckide config reset Reset all settings
|
|
174
174
|
|
|
175
|
+
deckide codeserver install Download/install code-server (the VS Code editor)
|
|
176
|
+
deckide codeserver status Show code-server install status
|
|
177
|
+
deckide codeserver path Print the code-server binary path
|
|
178
|
+
deckide codeserver uninstall Remove the downloaded code-server
|
|
179
|
+
|
|
175
180
|
Options (for start):
|
|
176
181
|
-p, --port <port> Port (default: 8787)
|
|
177
182
|
--host <host> Host (default: 0.0.0.0)
|
|
@@ -338,6 +343,50 @@ if (command === 'auth') {
|
|
|
338
343
|
}
|
|
339
344
|
|
|
340
345
|
// ── deckide status ──
|
|
346
|
+
// ── deckide codeserver ──
|
|
347
|
+
if (command === 'codeserver') {
|
|
348
|
+
const sub = args[1] || 'status';
|
|
349
|
+
process.env.DECKIDE_DATA_DIR = dataDir;
|
|
350
|
+
const modPath = path.join(__dirname, '..', 'dist', 'utils', 'code-server.js');
|
|
351
|
+
let CodeServerService;
|
|
352
|
+
try {
|
|
353
|
+
({ CodeServerService } = await import(modPath));
|
|
354
|
+
} catch (e) {
|
|
355
|
+
console.error(`code-server module not found — is Deck IDE built? (${e.message})`);
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
const cs = new CodeServerService(new Map());
|
|
359
|
+
if (sub === 'install') {
|
|
360
|
+
try {
|
|
361
|
+
const bin = await cs.download((m) => console.log(` ${m}`));
|
|
362
|
+
console.log(`\x1b[32mInstalled\x1b[0m → ${bin}`);
|
|
363
|
+
process.exit(0);
|
|
364
|
+
} catch (e) {
|
|
365
|
+
console.error(`\x1b[31mInstall failed:\x1b[0m ${e.message}`);
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
} else if (sub === 'status') {
|
|
369
|
+
const s = await cs.getStatus();
|
|
370
|
+
console.log('code-server');
|
|
371
|
+
console.log(` acquired: ${s.acquired}`);
|
|
372
|
+
console.log(` binPath: ${s.binPath || '(none)'}`);
|
|
373
|
+
if (s.reason) console.log(` note: ${s.reason}`);
|
|
374
|
+
process.exit(0);
|
|
375
|
+
} else if (sub === 'path') {
|
|
376
|
+
const bin = await cs.resolveBin();
|
|
377
|
+
if (bin) { console.log(bin); process.exit(0); }
|
|
378
|
+
console.error('code-server is not installed (run: deckide codeserver install)');
|
|
379
|
+
process.exit(1);
|
|
380
|
+
} else if (sub === 'uninstall') {
|
|
381
|
+
await cs.uninstall();
|
|
382
|
+
console.log('Uninstalled code-server.');
|
|
383
|
+
process.exit(0);
|
|
384
|
+
} else {
|
|
385
|
+
console.error('usage: deckide codeserver install|status|path|uninstall');
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
341
390
|
if (command === 'status') {
|
|
342
391
|
const settings = loadSettings();
|
|
343
392
|
const port = settings.port || 8787;
|
|
@@ -71,6 +71,14 @@ export function createCodeServerRouter(codeServer) {
|
|
|
71
71
|
}
|
|
72
72
|
return c.json(await codeServer.getStatus(wsId || undefined));
|
|
73
73
|
});
|
|
74
|
+
// Kick off the (single-flight) code-server download without blocking — the
|
|
75
|
+
// client polls /status until acquired. No-op if already installed.
|
|
76
|
+
router.post('/install', async (c) => {
|
|
77
|
+
if (!(await codeServer.isAcquired())) {
|
|
78
|
+
void codeServer.download().catch(() => undefined);
|
|
79
|
+
}
|
|
80
|
+
return c.json(await codeServer.getStatus());
|
|
81
|
+
});
|
|
74
82
|
// Reverse-proxy everything else under /codeserver/:wsId/* to that workspace's
|
|
75
83
|
// code-server instance (prefix stripped).
|
|
76
84
|
router.all('/:wsId/*', (c) => proxy(c, codeServer));
|
|
@@ -480,6 +480,25 @@ export class AgentBrowserService {
|
|
|
480
480
|
})
|
|
481
481
|
.catch(() => undefined);
|
|
482
482
|
}
|
|
483
|
+
// Pin the active tab's render viewport to a fixed size (the WebRTC framebuffer)
|
|
484
|
+
// so captured pixels equal page coordinates. Does not mutate this.viewport, so
|
|
485
|
+
// the JPEG path's client-driven resize is unaffected when WebRTC isn't active.
|
|
486
|
+
async pinViewport(width, height) {
|
|
487
|
+
await this.ensureActiveCdp();
|
|
488
|
+
if (!this.activeCdp) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
await this.activeCdp
|
|
492
|
+
.send('Emulation.setDeviceMetricsOverride', {
|
|
493
|
+
width,
|
|
494
|
+
height,
|
|
495
|
+
deviceScaleFactor: 1,
|
|
496
|
+
mobile: false,
|
|
497
|
+
screenWidth: width,
|
|
498
|
+
screenHeight: height,
|
|
499
|
+
})
|
|
500
|
+
.catch(() => undefined);
|
|
501
|
+
}
|
|
483
502
|
async navigate(input) {
|
|
484
503
|
const url = normalizeBrowserUrl(input);
|
|
485
504
|
await this.start();
|
|
@@ -623,6 +642,9 @@ export class AgentBrowserService {
|
|
|
623
642
|
this.port = null;
|
|
624
643
|
this.disconnectActiveCdp();
|
|
625
644
|
this.closeBrowserCdp();
|
|
645
|
+
// Tear down the WebRTC relay too, so a Chrome crash never leaves an
|
|
646
|
+
// orphaned x11grab/ffmpeg encoder pointed at a dead display.
|
|
647
|
+
void this.webrtcRelay.stop();
|
|
626
648
|
this.tabs.clear();
|
|
627
649
|
this.activeTargetId = null;
|
|
628
650
|
this.ready = false;
|
|
@@ -4,7 +4,7 @@ import fs from 'node:fs/promises';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import { WebSocket } from 'ws';
|
|
7
|
-
import { AGENT_BROWSER_WEBRTC, AGENT_BROWSER_WEBRTC_CODEC, AGENT_BROWSER_WEBRTC_BITRATE, AGENT_BROWSER_WEBRTC_FPS, AGENT_BROWSER_DISPLAY, AGENT_BROWSER_WEBRTC_ICE_SERVERS, } from '../config.js';
|
|
7
|
+
import { AGENT_BROWSER_WEBRTC, AGENT_BROWSER_WEBRTC_CODEC, AGENT_BROWSER_WEBRTC_BITRATE, AGENT_BROWSER_WEBRTC_FPS, AGENT_BROWSER_DISPLAY, AGENT_BROWSER_WEBRTC_ICE_SERVERS, AGENT_BROWSER_CDP_PORT, } from '../config.js';
|
|
8
8
|
const execFileAsync = promisify(execFile);
|
|
9
9
|
let weriftPromise = null;
|
|
10
10
|
function loadWerift() {
|
|
@@ -51,7 +51,7 @@ async function resolveExecutable(envName, names) {
|
|
|
51
51
|
// picks the display number for the Chrome child and exports DISPLAY/XAUTHORITY
|
|
52
52
|
// only into that child's environment, so the server process can't read them from
|
|
53
53
|
// its own env — we recover them from /proc/<pid>/environ instead.
|
|
54
|
-
async function resolveXEnv() {
|
|
54
|
+
async function resolveXEnv(cdpPort) {
|
|
55
55
|
if (AGENT_BROWSER_DISPLAY) {
|
|
56
56
|
return { display: AGENT_BROWSER_DISPLAY, xauthority: process.env.XAUTHORITY ?? null };
|
|
57
57
|
}
|
|
@@ -63,6 +63,8 @@ async function resolveXEnv() {
|
|
|
63
63
|
// Not Linux / no procfs.
|
|
64
64
|
return process.env.DISPLAY ? { display: process.env.DISPLAY, xauthority: process.env.XAUTHORITY ?? null } : null;
|
|
65
65
|
}
|
|
66
|
+
const portToken = cdpPort ? `--remote-debugging-port=${cdpPort}` : null;
|
|
67
|
+
let fallback = null;
|
|
66
68
|
for (const pid of pids) {
|
|
67
69
|
let comm = '';
|
|
68
70
|
try {
|
|
@@ -87,9 +89,27 @@ async function resolveXEnv() {
|
|
|
87
89
|
continue;
|
|
88
90
|
}
|
|
89
91
|
const xauthority = vars.find((v) => v.startsWith('XAUTHORITY='))?.slice('XAUTHORITY='.length) ?? null;
|
|
90
|
-
|
|
92
|
+
const candidate = { display, xauthority };
|
|
93
|
+
if (!portToken) {
|
|
94
|
+
return candidate;
|
|
95
|
+
}
|
|
96
|
+
// With multiple Chrome instances, prefer the one bound to OUR CDP port so we
|
|
97
|
+
// capture (and control) the same browser. Fall back to the first found.
|
|
98
|
+
let cmdline = '';
|
|
99
|
+
try {
|
|
100
|
+
cmdline = (await fs.readFile(`/proc/${pid}/cmdline`, 'utf8')).replace(/\0/g, ' ');
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// ignore
|
|
104
|
+
}
|
|
105
|
+
if (cmdline.includes(portToken)) {
|
|
106
|
+
return candidate;
|
|
107
|
+
}
|
|
108
|
+
if (!fallback) {
|
|
109
|
+
fallback = candidate;
|
|
110
|
+
}
|
|
91
111
|
}
|
|
92
|
-
return process.env.DISPLAY ? { display: process.env.DISPLAY, xauthority: process.env.XAUTHORITY ?? null } : null;
|
|
112
|
+
return fallback ?? (process.env.DISPLAY ? { display: process.env.DISPLAY, xauthority: process.env.XAUTHORITY ?? null } : null);
|
|
93
113
|
}
|
|
94
114
|
// Query the real screen geometry of the display so x11grab captures the exact
|
|
95
115
|
// region (a mismatch makes ffmpeg error or grab garbage).
|
|
@@ -145,7 +165,7 @@ export class BrowserWebRtcRelay {
|
|
|
145
165
|
else {
|
|
146
166
|
const werift = await loadWerift();
|
|
147
167
|
const ffmpeg = await resolveExecutable('AGENT_BROWSER_FFMPEG', ['ffmpeg']);
|
|
148
|
-
const xenv = await resolveXEnv();
|
|
168
|
+
const xenv = await resolveXEnv(AGENT_BROWSER_CDP_PORT);
|
|
149
169
|
if (!werift) {
|
|
150
170
|
available = false;
|
|
151
171
|
reason = 'werift is not installed (npm install werift)';
|
|
@@ -298,7 +318,7 @@ export class BrowserWebRtcRelay {
|
|
|
298
318
|
if (!ffmpeg) {
|
|
299
319
|
throw new Error('ffmpeg is required for WebRTC streaming');
|
|
300
320
|
}
|
|
301
|
-
const xenv = await resolveXEnv();
|
|
321
|
+
const xenv = await resolveXEnv(AGENT_BROWSER_CDP_PORT);
|
|
302
322
|
if (!xenv) {
|
|
303
323
|
throw new Error('No X display found to capture');
|
|
304
324
|
}
|
|
@@ -311,6 +331,8 @@ export class BrowserWebRtcRelay {
|
|
|
311
331
|
// Size the page's OS window to fill the captured screen so the framebuffer
|
|
312
332
|
// region maps 1:1 to page coordinates. Best-effort; ignore failures.
|
|
313
333
|
await this.host.setActiveWindowBounds(0, 0, this.size.width, this.size.height).catch(() => undefined);
|
|
334
|
+
// Pin the page render size to the framebuffer so input coords map 1:1.
|
|
335
|
+
await this.host.pinViewport(this.size.width, this.size.height).catch(() => undefined);
|
|
314
336
|
// Bind an ephemeral UDP port; ffmpeg sends RTP here and we fan each packet
|
|
315
337
|
// out to every viewer's track (no re-encode in werift).
|
|
316
338
|
const udp = dgram.createSocket('udp4');
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
1
|
+
import { spawn, execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
2
3
|
import net from 'node:net';
|
|
3
4
|
import fs from 'node:fs/promises';
|
|
4
5
|
import path from 'node:path';
|
|
@@ -6,6 +7,9 @@ import { dataDir } from '../config.js';
|
|
|
6
7
|
const IDLE_TIMEOUT_MS = 20 * 60 * 1000;
|
|
7
8
|
const START_TIMEOUT_MS = 30_000;
|
|
8
9
|
const REAP_INTERVAL_MS = 60_000;
|
|
10
|
+
// Pinned code-server version for reproducible auto-download.
|
|
11
|
+
const CODE_SERVER_VERSION = process.env.DECKIDE_CODE_SERVER_VERSION || '4.125.0';
|
|
12
|
+
const execFileAsync = promisify(execFile);
|
|
9
13
|
function sleep(ms) {
|
|
10
14
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
15
|
}
|
|
@@ -49,6 +53,9 @@ export class CodeServerService {
|
|
|
49
53
|
workspaces;
|
|
50
54
|
instances = new Map();
|
|
51
55
|
reaper = null;
|
|
56
|
+
// Single-flight auto-download of the code-server binary.
|
|
57
|
+
downloadPromise = null;
|
|
58
|
+
downloading = false;
|
|
52
59
|
// Directory layout under ~/.deckide/code-server (set by config.dataDir).
|
|
53
60
|
root = path.join(dataDir, 'code-server');
|
|
54
61
|
constructor(workspaces) {
|
|
@@ -70,6 +77,62 @@ export class CodeServerService {
|
|
|
70
77
|
async isAcquired() {
|
|
71
78
|
return (await this.resolveBin()) !== null;
|
|
72
79
|
}
|
|
80
|
+
// Download + install the pinned code-server build into dataDir (single-flight).
|
|
81
|
+
// Returns the resolved binary path. Auto-invoked by ensure() when not present.
|
|
82
|
+
download(onLog) {
|
|
83
|
+
if (this.downloadPromise) {
|
|
84
|
+
return this.downloadPromise;
|
|
85
|
+
}
|
|
86
|
+
this.downloading = true;
|
|
87
|
+
this.downloadPromise = this.doDownload(onLog).finally(() => {
|
|
88
|
+
this.downloading = false;
|
|
89
|
+
this.downloadPromise = null;
|
|
90
|
+
});
|
|
91
|
+
return this.downloadPromise;
|
|
92
|
+
}
|
|
93
|
+
async doDownload(onLog) {
|
|
94
|
+
const version = CODE_SERVER_VERSION;
|
|
95
|
+
const os = process.platform === 'darwin' ? 'macos' : process.platform === 'linux' ? 'linux' : null;
|
|
96
|
+
if (!os) {
|
|
97
|
+
throw new Error(`Automatic code-server download is unsupported on ${process.platform}. Install code-server manually and set DECKIDE_CODE_SERVER_BIN.`);
|
|
98
|
+
}
|
|
99
|
+
const arch = process.arch === 'arm64' ? 'arm64' : process.arch === 'x64' ? 'amd64' : null;
|
|
100
|
+
if (!arch) {
|
|
101
|
+
throw new Error(`Automatic code-server download is unsupported on architecture ${process.arch}.`);
|
|
102
|
+
}
|
|
103
|
+
const name = `code-server-${version}-${os}-${arch}`;
|
|
104
|
+
const url = `https://github.com/coder/code-server/releases/download/v${version}/${name}.tar.gz`;
|
|
105
|
+
const libDir = path.join(this.root, 'lib');
|
|
106
|
+
const tarPath = path.join(this.root, `${name}.tar.gz`);
|
|
107
|
+
await fs.mkdir(libDir, { recursive: true });
|
|
108
|
+
const log = (m) => { onLog?.(m); console.log(`[code-server] ${m}`); };
|
|
109
|
+
log(`downloading ${url}`);
|
|
110
|
+
const res = await fetch(url, { redirect: 'follow' });
|
|
111
|
+
if (!res.ok) {
|
|
112
|
+
throw new Error(`code-server download failed: HTTP ${res.status}`);
|
|
113
|
+
}
|
|
114
|
+
const bytes = Buffer.from(await res.arrayBuffer());
|
|
115
|
+
await fs.writeFile(tarPath, bytes);
|
|
116
|
+
log(`extracting ${(bytes.length / 1024 / 1024).toFixed(0)}MB`);
|
|
117
|
+
await execFileAsync('tar', ['-xzf', tarPath, '-C', libDir]);
|
|
118
|
+
await fs.rm(tarPath, { force: true });
|
|
119
|
+
const innerBin = path.join(libDir, name, 'bin', 'code-server');
|
|
120
|
+
if (!(await pathExists(innerBin))) {
|
|
121
|
+
throw new Error('code-server binary not found after extraction');
|
|
122
|
+
}
|
|
123
|
+
const binDir = path.join(this.root, 'bin');
|
|
124
|
+
await fs.mkdir(binDir, { recursive: true });
|
|
125
|
+
const link = path.join(binDir, 'code-server');
|
|
126
|
+
await fs.rm(link, { force: true });
|
|
127
|
+
await fs.symlink(innerBin, link);
|
|
128
|
+
log('installed');
|
|
129
|
+
return link;
|
|
130
|
+
}
|
|
131
|
+
// Remove the downloaded code-server install (and its per-workspace data).
|
|
132
|
+
async uninstall() {
|
|
133
|
+
await this.stopAll();
|
|
134
|
+
await fs.rm(this.root, { recursive: true, force: true });
|
|
135
|
+
}
|
|
73
136
|
async getStatus(wsId) {
|
|
74
137
|
const bin = await this.resolveBin();
|
|
75
138
|
const instance = wsId ? this.instances.get(wsId) : undefined;
|
|
@@ -79,8 +142,13 @@ export class CodeServerService {
|
|
|
79
142
|
binPath: bin,
|
|
80
143
|
running: instance ? instance.proc.exitCode == null : false,
|
|
81
144
|
instances: this.instances.size,
|
|
145
|
+
downloading: this.downloading,
|
|
82
146
|
wsId: wsId ?? null,
|
|
83
|
-
reason: bin
|
|
147
|
+
reason: bin
|
|
148
|
+
? undefined
|
|
149
|
+
: this.downloading
|
|
150
|
+
? 'code-server is downloading…'
|
|
151
|
+
: 'code-server will be downloaded automatically on first use',
|
|
84
152
|
error: instance?.error,
|
|
85
153
|
};
|
|
86
154
|
}
|
|
@@ -99,9 +167,10 @@ export class CodeServerService {
|
|
|
99
167
|
if (!workspace) {
|
|
100
168
|
throw new Error(`Unknown workspace: ${wsId}`);
|
|
101
169
|
}
|
|
102
|
-
|
|
170
|
+
let bin = await this.resolveBin();
|
|
103
171
|
if (!bin) {
|
|
104
|
-
|
|
172
|
+
// Auto-acquire on first use (single-flight ~100MB download).
|
|
173
|
+
bin = await this.download();
|
|
105
174
|
}
|
|
106
175
|
const port = await getFreePort();
|
|
107
176
|
const userDataDir = path.join(this.root, 'user', wsId);
|
package/package.json
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
@import"https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Noto+Sans+JP:wght@400;500;600&display=swap";/**
|
|
2
|
+
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
|
3
|
+
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
|
4
|
+
* https://github.com/chjj/term.js
|
|
5
|
+
* @license MIT
|
|
6
|
+
*
|
|
7
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
*
|
|
14
|
+
* The above copyright notice and this permission notice shall be included in
|
|
15
|
+
* all copies or substantial portions of the Software.
|
|
16
|
+
*
|
|
17
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
23
|
+
* THE SOFTWARE.
|
|
24
|
+
*
|
|
25
|
+
* Originally forked from (with the author's permission):
|
|
26
|
+
* Fabrice Bellard's javascript vt100 for jslinux:
|
|
27
|
+
* http://bellard.org/jslinux/
|
|
28
|
+
* Copyright (c) 2011 Fabrice Bellard
|
|
29
|
+
* The original design remains. The terminal itself
|
|
30
|
+
* has been extended to include xterm CSI codes, among
|
|
31
|
+
* other features.
|
|
32
|
+
*/.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}/*! tailwindcss v4.2.1 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:"JetBrains Mono", monospace;--color-red-400:oklch(70.4% .191 22.216);--color-black:#000;--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--font-weight-semibold:600;--tracking-wide:.025em;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-bg:#f3f3f3;--color-bg-soft:#e8e8e8;--color-panel:#fff;--color-sidebar:#f3f3f3;--color-activitybar:#2c2c2c;--color-activitybar-fg:#fff;--color-activitybar-inactive:#fff9;--color-border:#e5e5e5;--color-ink:#333;--color-ink-muted:#6e6e6e;--color-muted:#717171;--color-accent:#007acc;--color-statusbar:#007acc;--color-statusbar-fg:#fff;--color-list-hover:#0000000a;--color-list-active:#0078d41a;--color-focus:#0090f1;--color-title-bar:#ddd;--color-git-modified:#cca700;--color-git-untracked:#4ec9b0;--color-git-deleted:#f14c4c;--color-git-staged:#73c991;--color-git-renamed:#4fc1ff;--color-git-conflicted:#ff6b6b;--font-ui:"Noto Sans JP", sans-serif}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*,:before,:after{box-sizing:border-box}html,body,#root{height:100%;min-height:100%;margin:0}body{font-family:var(--font-ui);background:var(--color-bg);color:var(--color-ink);font-size:13px;line-height:1.4;overflow:hidden}button,input,select,textarea{color:inherit;font-family:inherit}button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid var(--color-focus);outline-offset:2px}@supports (height:100dvh){html,body,#root{height:100dvh;min-height:100dvh}}html,body,#root{height:var(--viewport-height,100dvh);min-height:var(--viewport-height,100dvh)}*{scrollbar-width:thin;scrollbar-color:#8080804d transparent}:root[data-theme=dark] *{scrollbar-color:#ffffff26 transparent}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:#8080804d;border-radius:3px}::-webkit-scrollbar-thumb:hover{background:#80808080}:root[data-theme=dark] ::-webkit-scrollbar-thumb{background:#ffffff26}:root[data-theme=dark] ::-webkit-scrollbar-thumb:hover{background:#ffffff4d}::-webkit-scrollbar-corner{background:0 0}}@layer components{.activity-bar-item.active:before{content:"";background:var(--color-activitybar-fg);width:2px;position:absolute;top:0;bottom:0;left:0}.workspace-editor-grid .activity-bar-item.active:before{background:var(--color-accent)}.app>nav.activity-bar .activity-bar-item.active:before{width:auto;height:2px;inset:0 0 auto}.git-tab.active:after{content:"";background:var(--color-accent);height:2px;position:absolute;bottom:0;left:0;right:0}.editor-tab:hover .editor-tab-close,.editor-tab.active .editor-tab-close{opacity:1}.editor-tab.dirty .editor-tab-close{opacity:0}.editor-tab.dirty:hover .editor-tab-close{opacity:1}.editor-tab.dirty:hover .editor-tab-dirty{display:none}.deck-tab:hover .deck-tab-close,.deck-tab.active .deck-tab-close{opacity:.5}.deck-tab-close:hover{color:var(--color-ink);background:#ffffff1f;opacity:1!important}.git-file-row:hover .git-file-actions{opacity:1}.tree-row.is-open .tree-chevron-icon{transform:rotate(90deg)}.terminal-split-container.is-drop-target:after{content:"デッキをここにドロップして分割";border:2px dashed var(--color-accent);color:var(--color-ink-muted);pointer-events:none;z-index:2;background:#0078d414;border-radius:6px;justify-content:center;align-items:center;font-size:12px;font-weight:600;display:flex;position:absolute;top:10px;right:10px;bottom:10px;left:10px}.git-tree-modified .tree-label{color:var(--color-git-modified)}.git-tree-untracked .tree-label{color:var(--color-git-untracked)}.git-tree-deleted .tree-label{color:var(--color-git-deleted)}.git-tree-staged .tree-label{color:var(--color-git-staged)}.git-tree-renamed .tree-label{color:var(--color-git-renamed)}.git-tree-conflicted .tree-label{color:var(--color-git-conflicted)}.sidebar-content .panel{box-shadow:none;background:0 0;border:none;border-radius:0}.sidebar-content .panel-header{background:var(--color-sidebar);z-index:1;margin-bottom:0;padding:8px 12px;position:sticky;top:0}.sidebar-content .panel-body{padding:0}}@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.top-1{top:calc(var(--spacing) * 1)}.right-1{right:calc(var(--spacing) * 1)}.z-50{z-index:50}.z-\[500\]{z-index:500}.z-\[999\]{z-index:999}.z-\[1000\]{z-index:1000}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing) * 0)}.my-1{margin-block:calc(var(--spacing) * 1)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-7{height:calc(var(--spacing) * 7)}.h-\[22px\]{height:22px}.h-full{height:100%}.h-px{height:1px}.min-h-0{min-height:calc(var(--spacing) * 0)}.min-h-\[28px\]{min-height:28px}.min-h-\[36px\]{min-height:36px}.min-h-\[40px\]{min-height:40px}.min-h-\[44px\]{min-height:44px}.min-h-\[50px\]{min-height:50px}.min-h-screen{min-height:100vh}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-7{width:calc(var(--spacing) * 7)}.w-\[22px\]{width:22px}.w-\[220px\]{width:220px}.w-\[500px\]{width:500px}.w-full{width:100%}.max-w-\[160px\]{max-width:160px}.max-w-\[180px\]{max-width:180px}.max-w-\[500px\]{max-width:500px}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[44px\]{min-width:44px}.min-w-\[140px\]{min-width:140px}.min-w-\[160px\]{min-width:160px}.flex-1{flex:1}.flex-shrink-0,.shrink-0{flex-shrink:0}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.resize{resize:both}.resize-y{resize:vertical}.grid-rows-\[35px_minmax\(0\,1fr\)\]{grid-template-rows:35px minmax(0,1fr)}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.\!overflow-hidden{overflow:hidden!important}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-\[2px\]{border-radius:2px}.rounded-\[3px\]{border-radius:3px}.rounded-\[6px\]{border-radius:6px}.rounded-full{border-radius:3.40282e38px}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-accent{border-color:var(--color-accent)}.border-border{border-color:var(--color-border)}.bg-\[\#f14c4c\]{background-color:#f14c4c}.bg-accent{background-color:var(--color-accent)}.bg-accent\/20{background-color:#007acc33}@supports (color:color-mix(in lab,red,red)){.bg-accent\/20{background-color:color-mix(in oklab,var(--color-accent) 20%,transparent)}}.bg-bg{background-color:var(--color-bg)}.bg-bg-soft{background-color:var(--color-bg-soft)}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab,red,red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black) 50%,transparent)}}.bg-border{background-color:var(--color-border)}.bg-list-active{background-color:var(--color-list-active)}.bg-list-hover{background-color:var(--color-list-hover)}.bg-panel{background-color:var(--color-panel)}.bg-sidebar{background-color:var(--color-sidebar)}.bg-title-bar{background-color:var(--color-title-bar)}.bg-transparent{background-color:#0000}.fill-current{fill:currentColor}.p-0{padding:calc(var(--spacing) * 0)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pl-3{padding-left:calc(var(--spacing) * 3)}.text-center{text-align:center}.text-left{text-align:left}.font-\[inherit\]{font-family:inherit}.font-mono{font-family:var(--font-mono)}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.75rem\]{font-size:.75rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[14px\]{font-size:14px}.text-\[15px\]{font-size:15px}.text-\[16px\]{font-size:16px}.text-\[32px\]{font-size:32px}.leading-none{--tw-leading:1;line-height:1}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-\[0\.5px\]{--tw-tracking:.5px;letter-spacing:.5px}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.break-words{overflow-wrap:break-word}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[\#f14c4c\]{color:#f14c4c}.text-accent{color:var(--color-accent)}.text-ink{color:var(--color-ink)}.text-ink-muted{color:var(--color-ink-muted)}.text-muted{color:var(--color-muted)}.text-red-400{color:var(--color-red-400)}.text-white{color:var(--color-white)}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.italic{font-style:italic}.shadow-\[0_4px_12px_rgba\(0\,0\,0\,0\.15\)\]{--tw-shadow:0 4px 12px var(--tw-shadow-color,#00000026);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.group-hover\:flex:is(:where(.group):hover *){display:flex}.hover\:bg-\[rgba\(0\,0\,0\,0\.08\)\]:hover{background-color:#00000014}.hover\:bg-\[rgba\(241\,76\,76\,0\.1\)\]:hover{background-color:#f14c4c1a}.hover\:bg-list-hover:hover{background-color:var(--color-list-hover)}.hover\:bg-red-400\/10:hover{background-color:#ff65681a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-red-400\/10:hover{background-color:color-mix(in oklab,var(--color-red-400) 10%,transparent)}}.hover\:text-ink:hover{color:var(--color-ink)}.hover\:text-red-400:hover{color:var(--color-red-400)}.hover\:opacity-90:hover{opacity:.9}}.focus\:border-focus:focus{border-color:var(--color-focus)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:cursor-default:disabled{cursor:default}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media(hover:hover){.dark\:hover\:bg-\[rgba\(255\,255\,255\,0\.08\)\]:where([data-theme=dark],[data-theme=dark] *):hover{background-color:#ffffff14}}.scrollbar-none{scrollbar-width:none}.scrollbar-none::-webkit-scrollbar{display:none}.spin{animation:1s linear infinite spin}.slide-in{animation:.2s slideIn}}:root[data-theme=dark]{--color-bg:#1e1e1e;--color-bg-soft:#252526;--color-panel:#1e1e1e;--color-sidebar:#252526;--color-activitybar:#333;--color-activitybar-inactive:#fff6;--color-border:#3c3c3c;--color-ink:#ccc;--color-ink-muted:#9e9e9e;--color-muted:#8b8b8b;--color-accent:#0078d4;--color-list-hover:#ffffff0a;--color-list-active:#0078d433;--color-focus:#007fd4;--color-title-bar:#3c3c3c}@keyframes spin{to{transform:rotate(360deg)}}@keyframes slideIn{0%{transform:translate(-100%)}to{transform:translate(0)}}.app{grid-template-rows:minmax(0,1fr) 22px;grid-template-columns:48px minmax(0,1fr);height:100%;display:grid;overflow:hidden}.activity-bar{background:var(--color-activitybar);flex-direction:column;grid-row:1/3;width:48px;display:flex}.activity-bar-top{flex-direction:column;display:flex}.activity-bar-bottom{flex-direction:column;margin-top:auto;display:flex}.activity-bar-item{width:48px;height:48px;color:var(--color-activitybar-inactive);cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;transition:color .15s;display:flex;position:relative}.activity-bar-item:hover,.activity-bar-item.active{color:var(--color-activitybar-fg)}.activity-bar-item svg{width:24px;height:24px}.activity-bar-badge{text-align:center;background:var(--color-accent);color:#fff;border-radius:8px;min-width:16px;height:16px;padding:0 4px;font-size:10px;font-weight:600;line-height:16px;position:absolute;top:8px;right:6px}.main{background:var(--color-bg);flex-direction:column;min-height:0;display:flex}.workspace-editor-overlay{background:var(--color-bg);z-index:30;grid-template-rows:35px minmax(0,1fr);display:grid;position:fixed;top:0;right:0;bottom:0;left:0}.workspace-editor-header{background:var(--color-title-bar);border-bottom:1px solid var(--color-border);align-items:center;gap:12px;padding:0 12px;display:flex}.workspace-meta{color:var(--color-muted);font-size:12px;font-family:var(--font-mono);text-overflow:ellipsis;white-space:nowrap;text-align:center;flex:1;overflow:hidden}.workspace-editor-grid{grid-template-rows:minmax(0,1fr);grid-template-columns:48px 220px minmax(0,1fr);height:100%;min-height:0;display:grid}.sidebar-panel{background:var(--color-sidebar);border-right:1px solid var(--color-border);flex-direction:column;height:100%;min-height:0;display:flex}.sidebar-content{flex:1;min-height:0;overflow:auto}.workspace-editor-grid>.activity-bar{background:var(--color-activitybar);border-right:1px solid var(--color-border);grid-row:unset;flex-direction:column;width:48px;display:flex}.workspace-editor-grid .activity-bar-item{color:var(--color-activitybar-fg);opacity:.6;transition:opacity .15s}.workspace-editor-grid .activity-bar-item:hover,.workspace-editor-grid .activity-bar-item.active{opacity:1}.panel{background:var(--color-panel);border:1px solid var(--color-border);border-radius:0;flex-direction:column;min-height:0;padding:0;display:flex}.panel-header{border-bottom:1px solid var(--color-border);background:var(--color-sidebar);justify-content:space-between;align-items:center;gap:8px;padding:8px 12px;display:flex}.panel-title{text-transform:uppercase;letter-spacing:.5px;color:var(--color-ink-muted);font-size:11px;font-weight:600}.panel-subtitle{color:var(--color-muted);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.panel-body{flex:1;min-height:0;overflow:auto}.modal{background:var(--color-panel);border:1px solid var(--color-border);border-radius:4px;width:480px;max-width:96vw;max-height:90dvh;padding:16px;overflow-y:auto;box-shadow:0 8px 32px #0000004d}.modal-close-btn{color:var(--color-muted);cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;width:32px;height:32px;padding:0;font-size:24px;line-height:1;display:flex}.modal-close-btn:hover{color:var(--color-ink);background:var(--color-list-hover)}.tree-body{padding:4px 0}.tree-row{color:var(--color-ink);text-align:left;cursor:pointer;background:0 0;border:none;align-items:center;gap:4px;width:100%;min-height:22px;padding:2px 8px 2px 4px;font-size:13px;transition:background .1s;display:flex}.tree-row:hover{background:var(--color-list-hover)}.tree-row:focus-visible{outline:2px solid var(--color-focus);outline-offset:-2px}.tree-chevron{width:16px;height:16px;color:var(--color-ink-muted);flex-shrink:0;justify-content:center;align-items:center;display:flex}.tree-chevron-icon{width:12px;height:12px;transition:transform .1s}.tree-icon{flex-shrink:0;justify-content:center;align-items:center;width:16px;height:16px;display:flex}.tree-svg{width:16px;height:16px}.tree-label{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.tree-meta{color:var(--color-muted);font-size:11px;font-family:var(--font-mono);margin-left:auto}.tree-state{color:var(--color-muted);padding:8px 12px;font-size:12px}.tree-actions{align-items:center;gap:4px;display:flex}.tree-input-row{align-items:center;gap:4px;min-height:22px;padding:2px 8px 2px 4px;display:flex}.tree-input{border:1px solid var(--color-accent);background:var(--color-panel);min-width:0;color:var(--color-ink);border-radius:2px;outline:none;flex:1;padding:2px 6px;font-size:13px}.tree-input::placeholder{color:var(--color-muted)}.editor-container{background:var(--color-panel);flex-direction:column;height:100%;display:flex;overflow:hidden}.editor-container.editor-empty{justify-content:center;align-items:center;display:flex}.editor-welcome{color:var(--color-ink-muted);text-align:center;flex-direction:column;align-items:center;gap:16px;padding:60px 40px;display:flex}.editor-welcome-icon{opacity:.2;width:80px;height:80px}.editor-welcome-icon svg{width:100%;height:100%;stroke:var(--color-ink)}.editor-welcome-text{color:var(--color-ink-muted);font-size:18px;font-weight:400}.editor-welcome-hint{opacity:.6;font-size:13px}.editor-tabs{background:var(--color-bg-soft);border-bottom:1px solid var(--color-border);flex-shrink:0;align-items:stretch;min-height:35px;display:flex}.editor-tabs-list{scrollbar-width:none;display:flex;overflow-x:auto}.editor-tabs-list::-webkit-scrollbar{display:none}.editor-tab{background:var(--color-bg-soft);border:none;border-right:1px solid var(--color-border);min-width:100px;max-width:180px;height:35px;color:var(--color-ink-muted);cursor:pointer;align-items:center;gap:6px;padding:0 10px;font-size:13px;transition:background .1s,color .1s;display:flex;position:relative}.editor-tab:hover{background:var(--color-panel);color:var(--color-ink)}.editor-tab.active{background:var(--color-panel);color:var(--color-ink);border-bottom:1px solid var(--color-panel);margin-bottom:-1px}.editor-tab-icon{font-size:10px;font-weight:600;font-family:var(--font-mono);flex-shrink:0}.editor-tab-name{text-overflow:ellipsis;white-space:nowrap;text-align:left;flex:1;overflow:hidden}.editor-tab-dirty{color:var(--color-ink);flex-shrink:0;font-size:18px;line-height:1}.editor-tab-saving{flex-shrink:0;width:14px;height:14px}.editor-tab-saving svg{width:100%;height:100%;stroke:var(--color-ink-muted)}.editor-tab-close{width:20px;height:20px;color:var(--color-ink-muted);cursor:pointer;opacity:0;background:0 0;border:none;border-radius:3px;flex-shrink:0;justify-content:center;align-items:center;padding:0;transition:opacity .1s,background .1s;display:flex}.editor-tab-close svg{width:16px;height:16px}.editor-tab-close:hover{background:var(--color-list-hover);color:var(--color-ink)}.editor-breadcrumb{background:var(--color-panel);border-bottom:1px solid var(--color-border);color:var(--color-ink-muted);flex-shrink:0;align-items:center;height:22px;padding:0 12px;font-size:12px;display:flex}.editor-breadcrumb-path{text-overflow:ellipsis;white-space:nowrap;font-family:var(--font-mono);overflow:hidden}.editor-content{flex:1;min-height:0;position:relative}.editor-no-file{height:100%;color:var(--color-ink-muted);justify-content:center;align-items:center;display:flex}.editor-statusbar{background:var(--color-statusbar);height:22px;color:var(--color-statusbar-fg);flex-shrink:0;justify-content:space-between;align-items:center;padding:0 12px;font-size:12px;display:flex}.editor-statusbar-left,.editor-statusbar-right{align-items:center;gap:14px;display:flex}.editor-status-item{cursor:default;align-items:center;gap:4px;display:flex}.editor-status-item svg{width:14px;height:14px}.terminal-layout{overflow-anchor:none;flex-direction:column;flex:1;min-width:0;min-height:0;display:flex;overflow:hidden}.terminal-topbar{background:var(--color-sidebar);border-bottom:1px solid var(--color-border);flex-shrink:0;align-items:stretch;min-width:0;display:flex;overflow:hidden}.topbar-left{flex:1;align-items:stretch;min-width:0;display:flex;overflow:hidden}.deck-tabs{scrollbar-width:none;flex:1;align-items:stretch;min-width:0;display:flex;overflow-x:auto}.deck-tabs::-webkit-scrollbar{display:none}.deck-tab{border:none;border-top:2px solid #0000;border-right:1px solid var(--color-border);min-width:80px;max-width:200px;color:var(--color-muted);cursor:pointer;white-space:nowrap;background:0 0;flex-shrink:0;align-items:center;gap:4px;padding:0 8px 0 12px;font-size:12px;font-weight:500;transition:background .12s,color .12s;display:flex}.deck-tab[draggable=true]{cursor:grab}.deck-tab.is-dragging{opacity:.5}.deck-tab.is-drag-over{background:var(--color-list-active);color:var(--color-ink)}.deck-tab:hover{background:var(--color-list-hover);color:var(--color-ink)}.deck-tab.active{background:var(--color-panel);color:var(--color-ink);border-top-color:var(--color-accent)}.deck-tab-name{text-overflow:ellipsis;flex:1;min-width:0;overflow:hidden}.deck-tab-close{opacity:0;width:16px;height:16px;color:var(--color-muted);border-radius:3px;flex-shrink:0;justify-content:center;align-items:center;font-size:13px;line-height:1;transition:opacity .12s,background .12s;display:flex}.deck-tab-add{border-top:none;justify-content:center;min-width:35px;max-width:35px;padding:0;font-size:16px;font-weight:300}.deck-tab-add:hover{background:var(--color-list-hover);color:var(--color-ink)}.terminal-split-container{flex:1;grid-auto-rows:minmax(0,1fr);gap:4px;width:100%;min-width:0;max-width:100%;min-height:0;padding:4px;display:grid;position:relative}.deck-split-pane{border:1px solid var(--color-border);border-radius:4px;flex-direction:column;min-width:0;min-height:0;display:flex;overflow:hidden}.topbar-btn-sm{border:1px solid var(--color-border);color:var(--color-muted);cursor:pointer;background:0 0;border-radius:3px;padding:2px 6px;font-size:11px;font-weight:600;transition:all .15s}.topbar-btn-sm:hover{background:var(--color-list-hover);color:var(--color-ink);border-color:var(--color-accent)}.topbar-btn-sm.topbar-btn-claude{color:#d97757}.topbar-btn-sm.topbar-btn-claude:hover{background:#d9775726;border-color:#d97757}.topbar-btn-sm.topbar-btn-codex{color:#00a86b}.topbar-btn-sm.topbar-btn-codex:hover{background:#00a86b26;border-color:#00a86b}.drive-mini-btn{color:var(--color-muted);cursor:pointer;background:0 0;border:0;border-radius:3px;padding:1px 5px;font-size:11px;line-height:1.4}.drive-mini-btn:hover{background:var(--color-list-hover);color:var(--color-ink)}.drive-body.drive-drag-active{outline:2px dashed var(--color-accent);outline-offset:-6px;background:var(--color-list-hover)}.drive-tab{cursor:pointer;background:0 0;border:0;border-bottom:2px solid #0000}.code-server-frame{background:#1e1e1e;border:0;width:100%;height:100%;display:block}.drive-mobilebar{display:none}@media(max-width:720px){.drive-mobilebar{border-bottom:1px solid var(--color-border);background:var(--color-sidebar);flex-shrink:0;align-items:center;gap:8px;min-height:40px;padding:6px 10px;display:flex}.drive-rail{z-index:50;transition:transform .2s;position:fixed;top:0;bottom:0;left:0;transform:translate(-100%);box-shadow:2px 0 10px #00000059}.drive-rail.is-open{transform:translate(0)}.drive-rail-backdrop{z-index:49;background:#00000073;position:fixed;top:0;right:0;bottom:0;left:0}}.terminal-pane{overflow-anchor:none;flex-direction:column;flex:1;min-width:0;min-height:0;padding:4px;display:flex}.terminal-grid{flex:1;gap:4px;width:100%;min-width:0;min-height:0;display:grid;overflow:hidden}.terminal-tile-slot{display:contents}.terminal-switcher{display:none}.terminal-grid-single{flex-direction:column!important;flex:1!important;gap:0!important;min-height:0!important;padding:0!important;display:flex!important;overflow:hidden!important}.terminal-tile-slot-single-active{flex-direction:column;flex:1;min-height:0;overflow:hidden;display:flex!important}.terminal-tile-slot-single-active .terminal-tile{border:none;border-radius:0;flex:1;height:auto!important;min-height:0!important}.terminal-tile-slot-single-hidden{display:none!important}.deck-terminals-single .terminal-switcher{background:var(--color-bg-soft);min-width:120px;max-width:50%;color:var(--color-ink);border:1px solid var(--color-border);border-radius:3px;flex:0 auto;padding:2px 6px;font-size:11px;display:block}.terminal-empty{background:var(--color-bg-soft);border:1px dashed var(--color-border);color:var(--color-muted);border-radius:4px;flex:1;justify-content:center;align-items:center;font-size:12px;display:flex}.terminal-tile{border:1px solid var(--color-border);color:#d4d4d4;background:#1e1e1e;border-radius:4px;flex-direction:column;min-width:0;min-height:0;transition:background .15s ease-out,box-shadow .15s ease-out;display:flex;overflow:hidden}.terminal-tile.bell-flash{background:#2a2a3a;transition:none;box-shadow:inset 0 0 0 2px #78aaffb3}.terminal-tile-header{background:#2d2d2d;border-bottom:1px solid #404040;justify-content:space-between;align-items:center;gap:8px;padding:6px 10px;font-size:12px;display:flex}.terminal-tile-header span{text-overflow:ellipsis;white-space:nowrap;font-family:var(--font-mono);flex:1;overflow:hidden}.terminal-close-btn{color:#858585;cursor:pointer;background:0 0;border:none;border-radius:3px;flex-shrink:0;justify-content:center;align-items:center;width:20px;height:20px;transition:background .1s,color .1s;display:flex}.terminal-close-btn:hover{color:#fff;background:#404040}.terminal-tile-body{overflow-anchor:none;overscroll-behavior:contain;touch-action:pan-y;flex:1;min-height:0;overflow:hidden}.terminal-tile-body .xterm,.terminal-tile-body .xterm-viewport,.terminal-tile-body .xterm-screen{overflow-anchor:none}.terminal-tile-body .xterm-helper-textarea{z-index:0!important}.terminal-tile-body .xterm-viewport{overscroll-behavior-y:contain;-webkit-overflow-scrolling:touch;touch-action:pan-y}.browser-layout{background:var(--color-panel);flex-direction:column;flex:1;min-width:0;min-height:0;display:flex;overflow:hidden}.browser-toolbar{background:var(--color-sidebar);border-bottom:1px solid var(--color-border);justify-content:space-between;align-items:center;gap:10px;min-height:38px;padding:4px 8px;display:flex}.browser-address-group{flex:1;align-items:center;gap:6px;min-width:0;display:flex}.browser-toolbar-button,.browser-go-button{border:1px solid var(--color-border);background:var(--color-bg-soft);width:28px;height:28px;color:var(--color-ink);cursor:pointer;border-radius:4px;justify-content:center;align-items:center;font-size:12px;line-height:1;display:flex}.browser-toolbar-button:hover,.browser-go-button:hover{background:var(--color-list-hover)}.browser-toolbar-button-active{border-color:var(--color-accent);color:var(--color-accent)}.browser-toolbar-button svg{width:16px;height:16px}.browser-toolbar-button:disabled,.browser-go-button:disabled{opacity:.45;cursor:not-allowed}.browser-address-form{flex:1;align-items:center;gap:6px;min-width:0;display:flex}.browser-address-input{border:1px solid var(--color-border);background:var(--color-panel);min-width:0;height:28px;color:var(--color-ink);font-family:var(--font-mono);border-radius:4px;flex:1;padding:0 9px;font-size:12px}.browser-status-line{color:var(--color-ink-muted);font-size:11px;font-family:var(--font-mono);text-overflow:ellipsis;white-space:nowrap;flex-shrink:0;align-items:center;gap:6px;max-width:180px;display:flex;overflow:hidden}.browser-dot{background:var(--color-muted);border-radius:50%;width:8px;height:8px}.browser-dot-running{background:var(--color-git-staged)}.browser-error{border-bottom:1px solid var(--color-border);color:var(--color-git-deleted);text-overflow:ellipsis;white-space:nowrap;background:#f14c4c1f;flex-shrink:0;padding:6px 10px;font-size:12px;overflow:hidden}.browser-tabs{background:var(--color-sidebar);border-bottom:1px solid var(--color-border);scrollbar-width:thin;flex-shrink:0;align-items:stretch;gap:4px;padding:4px 6px;display:flex;overflow-x:auto}.browser-tab{border:1px solid var(--color-border);background:var(--color-bg-soft);min-width:92px;max-width:180px;color:var(--color-ink-muted);cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:6px;align-items:center;gap:6px;padding:4px 6px 4px 10px;font-size:12px;display:flex}.browser-tab:hover{background:var(--color-list-hover)}.browser-tab-active{background:var(--color-panel);color:var(--color-ink);border-color:var(--color-accent)}.browser-tab-title{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.browser-tab-close{width:16px;height:16px;color:inherit;cursor:pointer;opacity:.6;background:0 0;border:none;border-radius:4px;flex-shrink:0;justify-content:center;align-items:center;font-size:13px;line-height:1;display:flex}.browser-tab-close:hover{background:var(--color-list-hover);opacity:1}.browser-tab-new{border:1px solid var(--color-border);background:var(--color-bg-soft);width:28px;color:var(--color-ink);cursor:pointer;border-radius:6px;flex-shrink:0;font-size:16px;line-height:1}.browser-tab-new:hover{background:var(--color-list-hover)}.browser-audio{display:none}.browser-viewport{background:#111;outline:none;flex:1;justify-content:center;align-items:center;min-width:0;min-height:0;display:flex;overflow:hidden}.browser-viewport:focus-visible{box-shadow:inset 0 0 0 2px var(--color-focus)}.browser-frame{object-fit:contain;-webkit-user-select:none;user-select:none;cursor:default;width:100%;height:100%;display:block}.browser-state{text-align:center;color:#c8c8c8;font-family:var(--font-mono);flex-direction:column;align-items:center;gap:12px;padding:24px;display:flex}.browser-state-title{color:#e6e6e6;font-size:13px}.browser-state-hint{color:#9a9a9a;max-width:280px;font-size:12px;line-height:1.5}.browser-state-action{border:1px solid var(--color-border);background:var(--color-bg-soft);color:var(--color-ink);cursor:pointer;font-size:12px;font-family:var(--font-mono);border-radius:4px;padding:6px 16px}.browser-state-action:hover:not(:disabled){background:var(--color-list-hover)}.browser-state-action:disabled{opacity:.45;cursor:not-allowed}.browser-spinner{border:2px solid #ffffff2e;border-top-color:var(--color-accent);border-radius:50%;width:22px;height:22px;animation:.8s linear infinite browser-spin}@keyframes browser-spin{to{transform:rotate(360deg)}}.git-modified{color:var(--color-git-modified)}.git-untracked{color:var(--color-git-untracked)}.git-deleted{color:var(--color-git-deleted)}.git-staged{color:var(--color-git-staged)}.git-renamed{color:var(--color-git-renamed)}.git-conflicted{color:var(--color-git-conflicted)}.git-tabs{border-bottom:1px solid var(--color-border);background:var(--color-sidebar);flex-shrink:0;display:flex}.git-tab{color:var(--color-ink-muted);cursor:pointer;background:0 0;border:none;align-items:center;gap:6px;padding:6px 12px;font-size:12px;transition:color .1s,background .1s;display:flex;position:relative}.git-tab:hover{color:var(--color-ink);background:var(--color-list-hover)}.git-tab.active{color:var(--color-ink)}.git-file-row{align-items:center;gap:4px;transition:background .1s;display:flex}.git-file-row:hover{background:var(--color-list-hover)}.git-file-main{color:var(--color-ink);cursor:pointer;text-align:left;background:0 0;border:none;flex:1;align-items:center;gap:6px;min-width:0;min-height:22px;padding:4px 12px;display:flex}.git-status-badge{width:16px;height:16px;font-size:11px;font-weight:600;font-family:var(--font-mono);flex-shrink:0;justify-content:center;align-items:center;display:flex}.git-file-path{text-overflow:ellipsis;white-space:nowrap;font-size:13px;overflow:hidden}.git-file-actions{opacity:0;gap:2px;padding-right:8px;transition:opacity .1s;display:flex}.git-action-btn{width:22px;height:22px;color:var(--color-ink-muted);cursor:pointer;background:0 0;border:none;border-radius:3px;justify-content:center;align-items:center;padding:0;transition:background .1s,color .1s;display:flex}.git-action-btn:hover{background:var(--color-list-hover);color:var(--color-ink)}.git-action-btn svg{fill:currentColor;width:14px;height:14px}.status-float{right:20px;bottom:calc(40px + env(safe-area-inset-bottom,0px));background:var(--color-panel);border:1px solid var(--color-border);color:var(--color-ink);border-radius:4px;max-width:min(400px,100vw - 40px);padding:8px 14px;font-size:12px;position:fixed;box-shadow:0 4px 16px #0003}.sidebar-toggle-btn,.sidebar-overlay{display:none}@media(max-width:1024px){.workspace-editor-grid{grid-template-rows:200px minmax(0,1fr);grid-template-columns:48px 1fr}.workspace-editor-grid>.activity-bar{grid-row:1/-1}.sidebar-panel{border-right:none;border-bottom:1px solid var(--color-border)}}@media(max-width:720px){.app{padding-bottom:env(safe-area-inset-bottom);grid-template-rows:minmax(0,1fr) 48px 22px;grid-template-columns:1fr}.app>nav.activity-bar{width:auto;height:48px;padding:0 max(8px,env(safe-area-inset-right,0px)) 0 max(8px,env(safe-area-inset-left,0px));border-top:1px solid var(--color-border);flex-direction:row;grid-row:2;justify-content:space-between;gap:8px}.activity-bar-top,.activity-bar-bottom{flex-direction:row;align-items:center;gap:4px}.activity-bar-bottom{margin-top:0;margin-left:0}.main{grid-row:1;min-width:0;overflow:hidden}.sidebar-toggle-btn{justify-content:center;align-items:center;display:flex}.activity-bar-item{min-width:44px;min-height:44px}.deck-tab{min-height:40px;padding:0 8px 0 14px;font-size:13px}.topbar-btn-sm{min-width:44px;min-height:40px;padding:0 10px}.tree-row,.editor-tab{min-height:36px}.modal-close-btn{min-width:44px;min-height:44px}.modal{border-radius:8px;max-width:calc(100vw - 16px);max-height:90dvh;margin:8px}.deck-tabs{-webkit-overflow-scrolling:touch;scrollbar-width:none;flex-wrap:nowrap;overflow-x:auto}.deck-tabs::-webkit-scrollbar{display:none}.terminal-split-container{scroll-snap-type:none;-webkit-overflow-scrolling:touch;overscroll-behavior-x:contain;touch-action:auto;scrollbar-width:none;width:100%;max-width:100%;overflow:scroll hidden;flex-direction:row!important;gap:0!important;padding:0!important;display:flex!important}.terminal-split-container::-webkit-scrollbar{display:none}.deck-split-pane{scroll-snap-align:start;border-left:none;border-right:none;border-radius:0;flex:0 0 100%;width:100%;max-width:100%;overflow:hidden}.terminal-pane{flex:auto;width:100%;max-width:100%;min-height:0;overflow:hidden;padding:0!important}.terminal-grid-mobile{flex-direction:column!important;flex:1!important;gap:0!important;min-height:0!important;padding:0!important;display:flex!important;overflow:hidden!important}.terminal-tile-slot-mobile-active{flex-direction:column;flex:1;min-height:0;overflow:hidden;display:flex!important}.terminal-tile-slot-mobile-active .terminal-tile{border:none;border-radius:0;flex:1;height:auto!important;min-height:0!important}.terminal-tile-slot-mobile-hidden{display:none!important}.terminal-switcher{background:var(--color-bg-soft);min-width:0;max-width:60%;height:28px;color:var(--color-ink);border:1px solid var(--color-border);font-size:12px;font-family:var(--font-mono);border-radius:4px;flex:auto;padding:0 8px;display:block}.status-float{right:12px;bottom:calc(78px + env(safe-area-inset-bottom,0px));max-width:calc(100vw - 24px)}html.keyboard-open .app{grid-template-rows:minmax(0,1fr);padding-bottom:0}html.keyboard-open .app>nav.activity-bar{display:none}html.keyboard-open .status-float{bottom:calc(env(safe-area-inset-bottom,0px) + 8px)}.workspace-editor-grid{grid-template-rows:minmax(0,1fr);grid-template-columns:1fr;position:relative}.workspace-editor-grid>.activity-bar{z-index:101;border-right:1px solid var(--color-border);border-top:none;flex-direction:column;width:48px;height:auto;transition:transform .2s;position:absolute;top:0;bottom:0;left:0;transform:translate(-100%)}.sidebar-panel{z-index:100;border-bottom:none;border-right:1px solid var(--color-border);width:260px;transition:transform .2s;position:absolute;top:0;bottom:0;left:48px;overflow-y:auto;transform:translate(calc(-100% - 48px))}.drawer-open .workspace-editor-grid>.activity-bar,.drawer-open .sidebar-panel{transform:translate(0)}.sidebar-overlay{z-index:99;background:#0006;display:none;position:absolute;top:0;right:0;bottom:0;left:0}.drawer-open .sidebar-overlay{display:block}.workspace-editor-header{gap:8px;padding:0 8px}.workspace-editor-header .sidebar-toggle-btn{order:3;margin-left:auto}.workspace-editor-header .workspace-meta{text-align:left;min-width:0}}@media(pointer:coarse){.modal-close-btn:hover,.tree-row:hover,.editor-tab:hover,.deck-tab:hover,.deck-tab-add:hover,.topbar-btn-sm:hover,.terminal-close-btn:hover,.git-file-row:hover,.git-action-btn:hover,.git-tab:hover{border-color:inherit;color:inherit;opacity:1;background:0 0}.editor-tab:hover{color:var(--color-ink-muted);background:0 0}.git-file-actions{opacity:1}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}
|