deckide 3.5.41 → 3.5.42

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.
@@ -0,0 +1,291 @@
1
+ import { spawn } from 'node:child_process';
2
+ import net from 'node:net';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { dataDir } from '../config.js';
6
+ const IDLE_TIMEOUT_MS = 20 * 60 * 1000;
7
+ const START_TIMEOUT_MS = 30_000;
8
+ const REAP_INTERVAL_MS = 60_000;
9
+ function sleep(ms) {
10
+ return new Promise((resolve) => setTimeout(resolve, ms));
11
+ }
12
+ async function pathExists(filePath) {
13
+ try {
14
+ await fs.access(filePath);
15
+ return true;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
21
+ async function findInPath(name) {
22
+ for (const dir of (process.env.PATH || '').split(path.delimiter)) {
23
+ if (!dir)
24
+ continue;
25
+ const candidate = path.join(dir, name);
26
+ if (await pathExists(candidate)) {
27
+ return candidate;
28
+ }
29
+ }
30
+ return null;
31
+ }
32
+ function getFreePort() {
33
+ return new Promise((resolve, reject) => {
34
+ const srv = net.createServer();
35
+ srv.once('error', reject);
36
+ srv.listen(0, '127.0.0.1', () => {
37
+ const addr = srv.address();
38
+ const port = typeof addr === 'object' && addr ? addr.port : 0;
39
+ srv.close(() => (port ? resolve(port) : reject(new Error('Could not get a free port'))));
40
+ });
41
+ });
42
+ }
43
+ /**
44
+ * Manages per-workspace code-server child processes — the "power editor" surface.
45
+ * Mirrors AgentBrowserService: spawn on demand, supervise, poll readiness, and
46
+ * idle-reap (auto-stop, not auto-restart). One instance per workspace id.
47
+ */
48
+ export class CodeServerService {
49
+ workspaces;
50
+ instances = new Map();
51
+ reaper = null;
52
+ // Directory layout under ~/.deckide/code-server (set by config.dataDir).
53
+ root = path.join(dataDir, 'code-server');
54
+ constructor(workspaces) {
55
+ this.workspaces = workspaces;
56
+ }
57
+ // Resolve the code-server executable: explicit env → standalone install under
58
+ // dataDir → PATH. Returns null when not acquired.
59
+ async resolveBin() {
60
+ const configured = process.env.DECKIDE_CODE_SERVER_BIN;
61
+ if (configured) {
62
+ return (await pathExists(configured)) ? configured : null;
63
+ }
64
+ const standalone = path.join(this.root, 'bin', 'code-server');
65
+ if (await pathExists(standalone)) {
66
+ return standalone;
67
+ }
68
+ return findInPath('code-server');
69
+ }
70
+ async isAcquired() {
71
+ return (await this.resolveBin()) !== null;
72
+ }
73
+ async getStatus(wsId) {
74
+ const bin = await this.resolveBin();
75
+ const instance = wsId ? this.instances.get(wsId) : undefined;
76
+ return {
77
+ available: bin !== null,
78
+ acquired: bin !== null,
79
+ binPath: bin,
80
+ running: instance ? instance.proc.exitCode == null : false,
81
+ instances: this.instances.size,
82
+ wsId: wsId ?? null,
83
+ reason: bin ? undefined : 'code-server is not installed (run: deckide codeserver install)',
84
+ error: instance?.error,
85
+ };
86
+ }
87
+ // Ensure an instance for this workspace is running and ready; return its port.
88
+ async ensure(wsId) {
89
+ const existing = this.instances.get(wsId);
90
+ if (existing && existing.proc.exitCode == null) {
91
+ existing.lastActive = Date.now();
92
+ await existing.ready;
93
+ return existing.port;
94
+ }
95
+ if (existing) {
96
+ this.instances.delete(wsId);
97
+ }
98
+ const workspace = this.workspaces.get(wsId);
99
+ if (!workspace) {
100
+ throw new Error(`Unknown workspace: ${wsId}`);
101
+ }
102
+ const bin = await this.resolveBin();
103
+ if (!bin) {
104
+ throw new Error('code-server is not installed (run: deckide codeserver install)');
105
+ }
106
+ const port = await getFreePort();
107
+ const userDataDir = path.join(this.root, 'user', wsId);
108
+ const extensionsDir = path.join(this.root, 'ext');
109
+ await fs.mkdir(userDataDir, { recursive: true });
110
+ await fs.mkdir(extensionsDir, { recursive: true });
111
+ // Per-instance config file so the user's global ~/.config/code-server/config.yaml
112
+ // (which may set auth/bind-addr) can never override our flags.
113
+ const configPath = path.join(userDataDir, 'config.yaml');
114
+ await fs.writeFile(configPath, `bind-addr: 127.0.0.1:${port}\nauth: none\ncert: false\n`, 'utf8');
115
+ // code-server honors the PORT env var ahead of --bind-addr, so strip it
116
+ // (Deck IDE's own PORT would otherwise collide).
117
+ const childEnv = { ...process.env };
118
+ delete childEnv.PORT;
119
+ const proc = spawn(bin, [
120
+ '--config', configPath,
121
+ '--auth', 'none', // Deck IDE owns auth at the same-origin proxy.
122
+ '--disable-telemetry',
123
+ '--disable-update-check',
124
+ '--bind-addr', `127.0.0.1:${port}`,
125
+ '--user-data-dir', userDataDir,
126
+ '--extensions-dir', extensionsDir,
127
+ workspace.path,
128
+ ], { stdio: ['ignore', 'pipe', 'pipe'], env: childEnv });
129
+ let stderrTail = '';
130
+ proc.stderr.setEncoding('utf8');
131
+ proc.stderr.on('data', (chunk) => {
132
+ stderrTail = (stderrTail + chunk).slice(-2000);
133
+ });
134
+ proc.stdout.resume();
135
+ const instance = {
136
+ wsId,
137
+ proc,
138
+ port,
139
+ lastActive: Date.now(),
140
+ startedAt: Date.now(),
141
+ ready: this.waitForReady(port, proc, () => stderrTail),
142
+ };
143
+ this.instances.set(wsId, instance);
144
+ proc.once('exit', (code, signal) => {
145
+ const current = this.instances.get(wsId);
146
+ if (current === instance) {
147
+ this.instances.delete(wsId);
148
+ }
149
+ if (code !== 0 && code != null) {
150
+ console.error(`[code-server] ${wsId} exited (${signal ?? code}): ${stderrTail.trim()}`);
151
+ }
152
+ });
153
+ proc.once('error', (error) => {
154
+ instance.error = error.message;
155
+ });
156
+ this.ensureReaper();
157
+ try {
158
+ await instance.ready;
159
+ }
160
+ catch (error) {
161
+ await this.stop(wsId);
162
+ throw error;
163
+ }
164
+ return port;
165
+ }
166
+ async waitForReady(port, proc, tail) {
167
+ const deadline = Date.now() + START_TIMEOUT_MS;
168
+ while (Date.now() < deadline) {
169
+ if (proc.exitCode != null) {
170
+ throw new Error(`code-server exited before becoming ready: ${tail().trim()}`);
171
+ }
172
+ try {
173
+ const res = await fetch(`http://127.0.0.1:${port}/healthz`, {
174
+ signal: AbortSignal.timeout(2000),
175
+ });
176
+ if (res.ok) {
177
+ return;
178
+ }
179
+ }
180
+ catch {
181
+ // not up yet
182
+ }
183
+ await sleep(250);
184
+ }
185
+ throw new Error('Timed out waiting for code-server to become ready');
186
+ }
187
+ // Returns the running port for a ready instance (for the HTTP proxy), bumping
188
+ // its idle timer. Starts the instance if needed.
189
+ async portFor(wsId) {
190
+ return this.ensure(wsId);
191
+ }
192
+ touch(wsId) {
193
+ const instance = this.instances.get(wsId);
194
+ if (instance) {
195
+ instance.lastActive = Date.now();
196
+ }
197
+ }
198
+ // Proxy a WebSocket upgrade for a ready instance to its loopback port. The
199
+ // prefix /codeserver/:wsId has already been stripped from req.url by the caller.
200
+ proxyUpgrade(wsId, req, clientSocket, head) {
201
+ const instance = this.instances.get(wsId);
202
+ if (!instance || instance.proc.exitCode != null) {
203
+ clientSocket.destroy();
204
+ return;
205
+ }
206
+ instance.lastActive = Date.now();
207
+ const upstream = net.connect(instance.port, '127.0.0.1', () => {
208
+ const headerLines = [`${req.method} ${req.url} HTTP/1.1`];
209
+ const headers = req.headers;
210
+ for (const [key, value] of Object.entries(headers)) {
211
+ if (value === undefined)
212
+ continue;
213
+ if (Array.isArray(value)) {
214
+ for (const v of value)
215
+ headerLines.push(`${key}: ${v}`);
216
+ }
217
+ else {
218
+ headerLines.push(`${key}: ${value}`);
219
+ }
220
+ }
221
+ upstream.write(headerLines.join('\r\n') + '\r\n\r\n');
222
+ if (head && head.length) {
223
+ upstream.write(head);
224
+ }
225
+ upstream.pipe(clientSocket);
226
+ clientSocket.pipe(upstream);
227
+ });
228
+ const teardown = () => {
229
+ try {
230
+ upstream.destroy();
231
+ }
232
+ catch { /* ignore */ }
233
+ try {
234
+ clientSocket.destroy();
235
+ }
236
+ catch { /* ignore */ }
237
+ };
238
+ upstream.on('error', teardown);
239
+ clientSocket.on('error', teardown);
240
+ }
241
+ async stop(wsId) {
242
+ const instance = this.instances.get(wsId);
243
+ if (!instance)
244
+ return;
245
+ this.instances.delete(wsId);
246
+ const proc = instance.proc;
247
+ if (proc.exitCode == null && proc.signalCode == null) {
248
+ await new Promise((resolve) => {
249
+ const killTimer = setTimeout(() => {
250
+ try {
251
+ proc.kill('SIGKILL');
252
+ }
253
+ catch { /* ignore */ }
254
+ }, 3000);
255
+ killTimer.unref?.();
256
+ proc.once('exit', () => { clearTimeout(killTimer); resolve(); });
257
+ try {
258
+ proc.kill('SIGTERM');
259
+ }
260
+ catch {
261
+ clearTimeout(killTimer);
262
+ resolve();
263
+ }
264
+ });
265
+ }
266
+ }
267
+ async stopAll() {
268
+ if (this.reaper) {
269
+ clearInterval(this.reaper);
270
+ this.reaper = null;
271
+ }
272
+ await Promise.all([...this.instances.keys()].map((wsId) => this.stop(wsId)));
273
+ }
274
+ ensureReaper() {
275
+ if (this.reaper)
276
+ return;
277
+ this.reaper = setInterval(() => {
278
+ const now = Date.now();
279
+ for (const [wsId, instance] of this.instances) {
280
+ if (now - instance.lastActive > IDLE_TIMEOUT_MS) {
281
+ void this.stop(wsId);
282
+ }
283
+ }
284
+ if (this.instances.size === 0 && this.reaper) {
285
+ clearInterval(this.reaper);
286
+ this.reaper = null;
287
+ }
288
+ }, REAP_INTERVAL_MS);
289
+ this.reaper.unref?.();
290
+ }
291
+ }
package/dist/websocket.js CHANGED
@@ -196,8 +196,47 @@ function readBufferedRange(session, startOffset, endOffset) {
196
196
  }
197
197
  return Buffer.from(raw.subarray(alignedStart, alignedEnd));
198
198
  }
199
- export function setupWebSocketServer(server, terminals, browserService) {
200
- const wss = new WebSocketServer({ server, maxPayload: MAX_MESSAGE_SIZE });
199
+ export function setupWebSocketServer(server, terminals, browserService, codeServerService) {
200
+ // noServer mode: we own the HTTP 'upgrade' event so code-server's WebSocket can
201
+ // be reverse-proxied alongside the app's own sockets. Everything that isn't a
202
+ // /codeserver/ upgrade is handed to wss exactly as before.
203
+ const wss = new WebSocketServer({ noServer: true, maxPayload: MAX_MESSAGE_SIZE });
204
+ server.on('upgrade', (req, socket, head) => {
205
+ let pathname;
206
+ try {
207
+ pathname = new URL(req.url || '', `http://localhost:${PORT}`).pathname;
208
+ }
209
+ catch {
210
+ socket.destroy();
211
+ return;
212
+ }
213
+ if (codeServerService && pathname.startsWith('/codeserver/')) {
214
+ if (!verifyWebSocketAuth(req)) {
215
+ socket.destroy();
216
+ return;
217
+ }
218
+ const rest = pathname.slice('/codeserver/'.length);
219
+ const slash = rest.indexOf('/');
220
+ const wsId = slash === -1 ? rest : rest.slice(0, slash);
221
+ const prefix = `/codeserver/${wsId}`;
222
+ const original = req.url || '';
223
+ req.url = original.slice(prefix.length) || '/';
224
+ if (!req.url.startsWith('/')) {
225
+ req.url = `/${req.url}`;
226
+ }
227
+ codeServerService
228
+ .ensure(wsId)
229
+ .then(() => codeServerService.proxyUpgrade(wsId, req, socket, head))
230
+ .catch(() => {
231
+ try {
232
+ socket.destroy();
233
+ }
234
+ catch { /* ignore */ }
235
+ });
236
+ return;
237
+ }
238
+ wss.handleUpgrade(req, socket, head, (ws) => wss.emit('connection', ws, req));
239
+ });
201
240
  const heartbeatState = new WeakMap();
202
241
  const heartbeatInterval = setInterval(() => {
203
242
  for (const socket of wss.clients) {
@@ -298,6 +337,19 @@ export function setupWebSocketServer(server, terminals, browserService) {
298
337
  browserService.attachAudioWebSocket(socket);
299
338
  return;
300
339
  }
340
+ if (url.pathname === '/api/browser/webrtc/ws') {
341
+ if (!browserService) {
342
+ untrackConnection(clientIP, socket);
343
+ socket.close(1011, 'Browser service unavailable');
344
+ return;
345
+ }
346
+ socket.on('close', () => {
347
+ heartbeatState.delete(socket);
348
+ untrackConnection(clientIP, socket);
349
+ });
350
+ browserService.attachWebRtcWebSocket(socket);
351
+ return;
352
+ }
301
353
  const match = url.pathname.match(/\/api\/terminals\/(.+)/);
302
354
  if (!match) {
303
355
  untrackConnection(clientIP, socket);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deckide",
3
- "version": "3.5.41",
3
+ "version": "3.5.42",
4
4
  "description": "Deck IDE - Browser-based IDE with terminal, file explorer, and git integration",
5
5
  "type": "module",
6
6
  "bin": {
@@ -66,5 +66,8 @@
66
66
  "developer-tools"
67
67
  ],
68
68
  "author": "tako0614",
69
- "license": "MIT"
69
+ "license": "MIT",
70
+ "optionalDependencies": {
71
+ "werift": "^0.23.0"
72
+ }
70
73
  }
@@ -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)}.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}.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-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}