chapterhouse 0.3.5 → 0.3.7

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.
@@ -39,6 +39,9 @@ const messageRequestSchema = z.object({
39
39
  connectionId: requiredString("Missing or invalid 'connectionId'. Connect to /stream first."),
40
40
  projectPath: z.string().optional(),
41
41
  sessionKey: z.string().optional(),
42
+ /** Optional client-generated correlation ID. Echoed back in the `queued` SSE event
43
+ * so the frontend can match the event to the user message bubble it should update. */
44
+ msgId: z.string().optional(),
42
45
  });
43
46
  const modelRequestSchema = z.object({
44
47
  model: requiredString("Missing 'model' in request body"),
@@ -356,7 +359,7 @@ app.get("/stream", (req, res) => {
356
359
  // Send a message to the orchestrator
357
360
  // ---------------------------------------------------------------------------
358
361
  app.post("/api/message", (req, res) => {
359
- const { prompt, connectionId, projectPath, sessionKey: requestedSessionKey } = parseRequest(messageRequestSchema, req.body);
362
+ const { prompt, connectionId, projectPath, sessionKey: requestedSessionKey, msgId } = parseRequest(messageRequestSchema, req.body);
360
363
  const effectiveSessionKey = requestedSessionKey || "default";
361
364
  if (!sseClients.has(connectionId)) {
362
365
  throw new BadRequestError("Missing or invalid 'connectionId'. Connect to /stream first.");
@@ -367,13 +370,14 @@ app.post("/api/message", (req, res) => {
367
370
  user: req.user,
368
371
  authorizationHeader: typeof req.headers.authorization === "string" ? req.headers.authorization : undefined,
369
372
  projectPath: projectPath || undefined,
370
- }, (text, done) => {
373
+ }, (text, done, turnId) => {
371
374
  const sseRes = sseClients.get(connectionId);
372
375
  if (sseRes) {
373
376
  const event = {
374
377
  type: done ? "message" : "delta",
375
378
  content: text,
376
379
  sessionKey: effectiveSessionKey,
380
+ turnId,
377
381
  };
378
382
  if (done) {
379
383
  const routeResult = getLastRouteResult();
@@ -388,10 +392,21 @@ app.post("/api/message", (req, res) => {
388
392
  }
389
393
  sseRes.write(formatSseData(event));
390
394
  }
391
- }, undefined, (activity) => {
395
+ }, undefined, (activity, turnId) => {
392
396
  const sseRes = sseClients.get(connectionId);
393
397
  if (sseRes) {
394
- sseRes.write(formatSseData({ type: "activity", ...activity, sessionKey: effectiveSessionKey }));
398
+ sseRes.write(formatSseData({ type: "activity", ...activity, sessionKey: effectiveSessionKey, ...(turnId ? { turnId } : {}) }));
399
+ }
400
+ }, (position, turnId) => {
401
+ const sseRes = sseClients.get(connectionId);
402
+ if (sseRes) {
403
+ sseRes.write(formatSseData({
404
+ type: "queued",
405
+ position,
406
+ sessionKey: effectiveSessionKey,
407
+ turnId,
408
+ ...(msgId ? { msgId } : {}),
409
+ }));
395
410
  }
396
411
  });
397
412
  res.json({ status: "queued" });
@@ -1,4 +1,5 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { randomUUID } from "node:crypto";
2
3
  import { approveAll } from "@github/copilot-sdk";
3
4
  import { createTools } from "./tools.js";
4
5
  import { getOrchestratorSystemMessage } from "./system-message.js";
@@ -392,7 +393,7 @@ async function executeOnSession(manager, item) {
392
393
  success: data.success,
393
394
  resultPreview,
394
395
  detailedContent,
395
- });
396
+ }, item.turnId);
396
397
  }
397
398
  });
398
399
  const unsubToolStart = item.onActivity
@@ -404,7 +405,7 @@ async function executeOnSession(manager, item) {
404
405
  toolName: data.toolName,
405
406
  mcpServerName: data.mcpServerName,
406
407
  arguments: data.arguments,
407
- });
408
+ }, item.turnId);
408
409
  })
409
410
  : () => { };
410
411
  const unsubReasoning = item.onActivity
@@ -413,7 +414,7 @@ async function executeOnSession(manager, item) {
413
414
  kind: "thinking_delta",
414
415
  reasoningId: event.data.reasoningId,
415
416
  deltaContent: event.data.deltaContent,
416
- });
417
+ }, item.turnId);
417
418
  })
418
419
  : () => { };
419
420
  const unsubSubStart = item.onActivity
@@ -433,7 +434,7 @@ async function executeOnSession(manager, item) {
433
434
  agentDisplayName: data.agentDisplayName,
434
435
  agentDescription: resolvedDescription,
435
436
  agentSlug,
436
- });
437
+ }, item.turnId);
437
438
  })
438
439
  : () => { };
439
440
  const unsubSubDone = item.onActivity
@@ -445,7 +446,7 @@ async function executeOnSession(manager, item) {
445
446
  agentName: data.agentName,
446
447
  agentDisplayName: data.agentDisplayName,
447
448
  durationMs: data.durationMs,
448
- });
449
+ }, item.turnId);
449
450
  })
450
451
  : () => { };
451
452
  const unsubSubFail = item.onActivity
@@ -457,7 +458,7 @@ async function executeOnSession(manager, item) {
457
458
  agentName: data.agentName,
458
459
  agentDisplayName: data.agentDisplayName,
459
460
  error: data.error,
460
- });
461
+ }, item.turnId);
461
462
  })
462
463
  : () => { };
463
464
  // Always persist SDK subagent dispatches to agent_tasks so Workers tab shows them.
@@ -547,7 +548,7 @@ async function executeOnSession(manager, item) {
547
548
  }
548
549
  toolCallExecuted = false;
549
550
  accumulated += event.data.deltaContent;
550
- item.callback(accumulated, false);
551
+ item.callback(accumulated, false, item.turnId);
551
552
  });
552
553
  try {
553
554
  const result = await session.sendAndWait({ prompt: item.prompt, ...(item.attachments && item.attachments.length > 0 ? { attachments: item.attachments } : {}) }, ORCHESTRATOR_TIMEOUT_MS);
@@ -635,9 +636,13 @@ function isRecoverableError(err) {
635
636
  return false;
636
637
  return /disconnect|connection|EPIPE|ECONNRESET|ECONNREFUSED|socket|closed|ENOENT|spawn|not found|expired|stale/i.test(msg);
637
638
  }
638
- export async function sendToOrchestrator(prompt, source, callback, attachments, onActivity) {
639
+ export async function sendToOrchestrator(prompt, source, callback, attachments, onActivity, onQueued) {
639
640
  updateUserContext(source);
640
641
  updateRequestContext(source);
642
+ // Generate a unique ID for this orchestrator turn. Every SSE event emitted
643
+ // during this turn carries this ID so the frontend can detect turn boundaries
644
+ // and create a new assistant bubble when it changes (fixes #92).
645
+ const turnId = randomUUID();
641
646
  const sourceLabel = source.type === "web" ? "web" : "background";
642
647
  logMessage("in", sourceLabel, prompt);
643
648
  let sessionKey;
@@ -678,6 +683,9 @@ export async function sendToOrchestrator(prompt, source, callback, attachments,
678
683
  // Cast: QueuedMessage.onActivity uses a wide event type to avoid circular
679
684
  // type dependencies. orchestrator.ts always passes valid ActivityEvent objects.
680
685
  onActivity: onActivity,
686
+ turnId,
687
+ // Background messages skip queue visibility — only web/user messages need it.
688
+ onQueued: source.type === "web" ? onQueued : undefined,
681
689
  sourceChannel,
682
690
  targetAgent,
683
691
  channelKey,
@@ -688,7 +696,7 @@ export async function sendToOrchestrator(prompt, source, callback, attachments,
688
696
  reject,
689
697
  });
690
698
  });
691
- callback(finalContent, true);
699
+ callback(finalContent, true, turnId);
692
700
  try {
693
701
  logMessage("out", sourceLabel, finalContent);
694
702
  }
@@ -724,7 +732,7 @@ export async function sendToOrchestrator(prompt, source, callback, attachments,
724
732
  continue;
725
733
  }
726
734
  log.error({ msg }, "Error processing message");
727
- callback(`Error: ${msg}`, true);
735
+ callback(`Error: ${msg}`, true, turnId);
728
736
  return;
729
737
  }
730
738
  }
@@ -126,6 +126,14 @@ export class SessionManager {
126
126
  if (depth > 1) {
127
127
  log.debug({ sessionKey: this.sessionKey, depth }, "session.queue.depth");
128
128
  }
129
+ // Notify the enqueueing caller that its message is waiting behind an active turn.
130
+ // Only fires when _processing is true (another turn is in-flight) and this item
131
+ // is the first or later in the queue (depth >= 1 means "at least one item queued,
132
+ // plus one in-flight"). We check _processing rather than depth > 1 so the first
133
+ // queued message also fires when it arrives while the active turn is still running.
134
+ if (this._processing && depth >= 1) {
135
+ item.onQueued?.(depth, item.turnId);
136
+ }
129
137
  void this.drain();
130
138
  }
131
139
  /**
@@ -32,6 +32,7 @@ function makeDeferred() {
32
32
  prompt: "test prompt",
33
33
  callback: () => { },
34
34
  sessionKey: "default",
35
+ turnId: "test-turn-" + Math.random().toString(36).slice(2),
35
36
  resolve,
36
37
  reject,
37
38
  };
@@ -355,4 +356,74 @@ test("SessionRegistry: concurrent sessions process independently", async () => {
355
356
  assert.equal(resultA, "session-a done");
356
357
  assert.deepEqual(completionOrder, ["session-b", "session-a"]);
357
358
  });
359
+ // ---------------------------------------------------------------------------
360
+ // onQueued tests (#93 — queue visibility)
361
+ // ---------------------------------------------------------------------------
362
+ test("SessionManager: onQueued fires with correct position when message is enqueued behind active turn", async () => {
363
+ const { session } = makeFakeSession();
364
+ const resolvers = [];
365
+ const worker = () => new Promise((r) => { resolvers.push(() => r("done")); });
366
+ const manager = new SessionManager("default", worker, factory(session));
367
+ // Enqueue first item — starts processing immediately, no onQueued expected.
368
+ const first = makeDeferred();
369
+ manager.enqueue(first.item);
370
+ // Wait for the first turn to begin processing.
371
+ await new Promise((r) => setTimeout(r, 0));
372
+ assert.equal(manager.isProcessing, true, "first item should be processing");
373
+ // Enqueue second item while first is active — onQueued should fire with position=1.
374
+ const queuedPositions = [];
375
+ let resolve2;
376
+ let reject2;
377
+ const p2 = new Promise((res, rej) => { resolve2 = res; reject2 = rej; });
378
+ const item2 = {
379
+ prompt: "second",
380
+ callback: () => { },
381
+ sessionKey: "default",
382
+ turnId: "turn-2",
383
+ onQueued: (position, turnId) => { queuedPositions.push({ position, turnId }); },
384
+ resolve: resolve2,
385
+ reject: reject2,
386
+ };
387
+ manager.enqueue(item2);
388
+ assert.equal(queuedPositions.length, 1, "onQueued must fire exactly once");
389
+ assert.equal(queuedPositions[0].position, 1, "position should be 1 (one item queued behind the active turn)");
390
+ assert.equal(queuedPositions[0].turnId, "turn-2", "turnId must match the queued item's turnId");
391
+ // Enqueue a third item — onQueued should fire with position=2.
392
+ let resolve3;
393
+ let reject3;
394
+ const p3 = new Promise((res, rej) => { resolve3 = res; reject3 = rej; });
395
+ const item3 = {
396
+ prompt: "third",
397
+ callback: () => { },
398
+ sessionKey: "default",
399
+ turnId: "turn-3",
400
+ onQueued: (position, turnId) => { queuedPositions.push({ position, turnId }); },
401
+ resolve: resolve3,
402
+ reject: reject3,
403
+ };
404
+ manager.enqueue(item3);
405
+ assert.equal(queuedPositions.length, 2, "onQueued must fire for each queued item");
406
+ assert.equal(queuedPositions[1].position, 2, "third item should see position=2");
407
+ assert.equal(queuedPositions[1].turnId, "turn-3", "turnId must match the third item");
408
+ // Drain the queue: each item requires its own unblock because the worker is re-entered per item.
409
+ resolvers[0]();
410
+ await new Promise((r) => setTimeout(r, 0)); // let item2 start
411
+ resolvers[1]();
412
+ await new Promise((r) => setTimeout(r, 0)); // let item3 start
413
+ resolvers[2]();
414
+ await Promise.all([first.promise, p2, p3]);
415
+ });
416
+ test("SessionManager: onQueued does NOT fire when queue is empty (no active turn)", async () => {
417
+ const { session } = makeFakeSession();
418
+ const worker = () => Promise.resolve("done");
419
+ const manager = new SessionManager("default", worker, factory(session));
420
+ const queuedFired = [];
421
+ const { item, promise } = makeDeferred();
422
+ item.onQueued = () => { queuedFired.push(true); };
423
+ manager.enqueue(item);
424
+ // The first item goes straight to processing — onQueued must NOT fire synchronously.
425
+ assert.equal(queuedFired.length, 0, "onQueued must not fire when there is no active turn ahead");
426
+ // Let the worker complete to avoid unresolved promises.
427
+ await promise.catch(() => { });
428
+ });
358
429
  //# sourceMappingURL=session-manager.test.js.map
@@ -1,5 +1,6 @@
1
- import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
1
+ import { existsSync } from 'fs';
2
2
  import { join } from 'path';
3
+ import { FSStorageProvider, SquadState, NotFoundError } from '@bradygaster/squad-sdk';
3
4
  import { getDb } from '../store/db.js';
4
5
  // ---------------------------------------------------------------------------
5
6
  // DB schema — created lazily on first use
@@ -101,47 +102,38 @@ export async function resolveProjectSquad(projectPath) {
101
102
  catch {
102
103
  // SDK not available — use manual path resolution
103
104
  }
104
- // Enumerate agents from .squad/agents/
105
- const agentsDir = join(resolvedSquadDir, 'agents');
105
+ // Enumerate agents from .squad/agents/ via SDK AgentsCollection.
106
+ // FSStorageProvider wraps the same fs calls with typed returns and ENOENT safety.
106
107
  const agents = [];
107
- if (existsSync(agentsDir)) {
108
- let entries = [];
109
- try {
110
- entries = readdirSync(agentsDir);
111
- }
112
- catch { /* ignore read errors */ }
113
- for (const entry of entries) {
114
- const agentDir = join(agentsDir, entry);
115
- try {
116
- if (!statSync(agentDir).isDirectory())
117
- continue;
118
- }
119
- catch {
120
- continue;
121
- }
122
- const charterPath = join(agentDir, 'charter.md');
123
- if (!existsSync(charterPath))
124
- continue;
125
- let charterContent = '';
108
+ const stateRoot = resolvedSquadDir.endsWith('/.squad')
109
+ ? resolvedSquadDir.slice(0, -'/.squad'.length)
110
+ : projectPath;
111
+ try {
112
+ const storage = new FSStorageProvider();
113
+ const state = SquadState.fromStorage(storage, stateRoot);
114
+ const agentNames = await state.agents.list();
115
+ for (const name of agentNames) {
116
+ const charterPath = join(resolvedSquadDir, 'agents', name, 'charter.md');
117
+ let charterContent;
126
118
  try {
127
- charterContent = readFileSync(charterPath, 'utf-8');
119
+ charterContent = await state.agents.get(name).charter();
128
120
  }
129
- catch {
121
+ catch (err) {
122
+ if (err instanceof NotFoundError)
123
+ continue; // no charter.md — not a valid agent
130
124
  continue;
131
125
  }
132
- const slug = entry;
133
- const role = extractRoleFromCharter(charterContent);
134
- const description = extractDescriptionFromCharter(charterContent);
135
126
  agents.push({
136
- slug,
137
- mention: `@${slug}`,
138
- role,
139
- description,
127
+ slug: name,
128
+ mention: `@${name}`,
129
+ role: extractRoleFromCharter(charterContent),
130
+ description: extractDescriptionFromCharter(charterContent),
140
131
  charterPath,
141
132
  origin: 'project-squad',
142
133
  });
143
134
  }
144
135
  }
136
+ catch { /* SDK unavailable — agents list stays empty */ }
145
137
  // Load squad config via SDK with fallback to empty config
146
138
  let squadConfig = {};
147
139
  try {
@@ -228,25 +220,21 @@ export async function loadProjectSquad(projectPath) {
228
220
  : undefined,
229
221
  origin: 'project-squad',
230
222
  }));
231
- // Repo wins: re-sync any new agents that appeared since last cache
232
- const repoAgentsDir = join(row.squad_dir, 'agents');
233
- if (existsSync(repoAgentsDir)) {
234
- const repoDirs = readdirSync(repoAgentsDir).filter(e => {
235
- try {
236
- return statSync(join(repoAgentsDir, e)).isDirectory();
237
- }
238
- catch {
239
- return false;
240
- }
241
- });
223
+ // Repo wins: re-sync any new agents that appeared since last cache.
224
+ // Use SDK AgentsCollection.list() so we stay consistent with resolveProjectSquad().
225
+ try {
226
+ const cacheStorage = new FSStorageProvider();
227
+ const cacheState = SquadState.fromStorage(cacheStorage, row.project_root);
228
+ const repoAgentNames = await cacheState.agents.list();
242
229
  const cachedSlugs = new Set(agents.map(a => a.slug));
243
- for (const slug of repoDirs) {
230
+ for (const slug of repoAgentNames) {
244
231
  if (!cachedSlugs.has(slug)) {
245
232
  // New agent in repo not in cache — trigger full reload
246
233
  return resolveProjectSquad(projectPath);
247
234
  }
248
235
  }
249
236
  }
237
+ catch { /* SDK unavailable — skip new-agent check */ }
250
238
  return {
251
239
  projectRoot: row.project_root,
252
240
  squadDir: row.squad_dir,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chapterhouse",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Chapterhouse — a team-level AI assistant for engineering teams, built on the GitHub Copilot SDK. Web UI only.",
5
5
  "bin": {
6
6
  "chapterhouse": "dist/cli.js"
@@ -7,4 +7,4 @@ pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5p
7
7
 
8
8
  Outdated base version: https://github.com/primer/github-syntax-dark
9
9
  Current colors taken from GitHub's CSS
10
- */.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id{color:#79c0ff}.hljs-regexp,.hljs-string,.hljs-meta .hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-comment,.hljs-code,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}:root{color-scheme:dark;--bg: #0e1116;--bg-elev: #161b22;--bg-elev-2: #21262d;--fg: #e6edf3;--fg-dim: #8b949e;--border: #30363d;--accent: #3b82f6;--accent-fg: #ffffff;--danger: #f87171;--user-bubble: #1e293b}*{box-sizing:border-box}html,body,#root{height:100%;margin:0}body{background:var(--bg);color:var(--fg);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,sans-serif;font-size:14px;line-height:1.5}a,.link{color:var(--accent);text-decoration:none}a:hover,.link:hover{text-decoration:underline}button,input,textarea,select{font:inherit}button:focus-visible,a:focus-visible,input:focus-visible,textarea:focus-visible,select:focus-visible{outline:2px solid var(--accent);outline-offset:2px}code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.9em;background:var(--bg-elev-2);padding:1px 5px;border-radius:4px}.dim{color:var(--fg-dim)}.small{font-size:12px}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.skip-link{position:absolute;left:16px;top:-48px;z-index:10;padding:10px 14px;border-radius:8px;background:var(--accent);color:var(--accent-fg)}.skip-link:focus{top:16px}.layout{display:grid;grid-template-columns:220px 1fr;height:100%}.sidebar{background:var(--bg-elev);border-right:1px solid var(--border);padding:16px 0;display:flex;flex-direction:column}.sidebar-brand{display:flex;align-items:center;gap:10px;padding:0 18px 18px;font-weight:600;font-size:16px;border-bottom:1px solid var(--border);margin-bottom:12px}.sidebar nav{display:flex;flex-direction:column}.nav-link{padding:9px 18px;color:var(--fg);border-left:2px solid transparent}.nav-link:hover{background:var(--bg-elev-2);text-decoration:none}.nav-link.active{background:var(--bg-elev-2);border-left-color:var(--accent);color:var(--fg)}.nav-group{display:flex;flex-direction:column}.nav-group-row{display:flex;align-items:stretch}.nav-group-label{flex:1}.nav-group-toggle{background:none;border:none;cursor:pointer;padding:0 14px 0 4px;color:var(--fg-muted, var(--fg));display:flex;align-items:center;justify-content:center;border-left:2px solid transparent}.nav-group-toggle:hover{background:var(--bg-elev-2)}.nav-chevron{display:inline-block;font-size:18px;line-height:1;transition:transform .18s ease;transform:rotate(0)}.nav-chevron-open{transform:rotate(90deg)}.nav-recents{list-style:none;margin:0;padding:0}.nav-recent-link{display:flex;align-items:baseline;justify-content:space-between;gap:6px;width:100%;background:none;border:none;border-left:2px solid transparent;padding:6px 18px 6px 28px;cursor:pointer;color:var(--fg);text-align:left;font-size:13px}.nav-recent-link:hover{background:var(--bg-elev-2);text-decoration:none;border-left-color:var(--accent)}.nav-recent-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.nav-recent-hint{font-size:11px;flex-shrink:0;opacity:.6}.nav-recents-empty{padding:4px 28px 6px;font-size:12px;margin:0}.main{overflow:hidden;display:flex;flex-direction:column}.app-header{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:16px 24px;border-bottom:1px solid var(--border);background:var(--bg-elev)}.app-header-title{font-size:16px;font-weight:600;margin:0}.app-header-title-row{display:inline-flex;align-items:center;gap:10px}.app-header-user{color:var(--fg-dim);font-size:13px}.mode-badge{display:inline-flex;align-items:center;border-radius:999px;padding:3px 10px;font-size:12px;font-weight:600;line-height:1;border:1px solid transparent}.mode-standalone{background:var(--bg-elev-2);border-color:var(--border);color:var(--fg-dim)}.mode-team{background:color-mix(in srgb,var(--accent) 14%,transparent);border-color:color-mix(in srgb,var(--accent) 35%,var(--border));color:var(--accent)}.sse-badge{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:3px 10px;font-size:12px;font-weight:600;line-height:1;border:1px solid transparent}.sse-badge__dot{display:inline-block;width:7px;height:7px;border-radius:50%;flex-shrink:0}.sse-badge--reconnecting{background:color-mix(in srgb,#f59e0b 12%,transparent);border-color:color-mix(in srgb,#f59e0b 35%,var(--border));color:#b45309}.sse-badge--reconnecting .sse-badge__dot{background:#f59e0b;animation:sse-pulse 1.2s ease-in-out infinite}.sse-badge--disconnected{background:color-mix(in srgb,#ef4444 12%,transparent);border-color:color-mix(in srgb,#ef4444 35%,var(--border));color:#b91c1c}.sse-badge--disconnected .sse-badge__dot{background:#ef4444}.sse-badge__reconnect-btn{background:none;border:none;padding:0;margin-left:4px;font-size:12px;font-weight:600;color:inherit;cursor:pointer;text-decoration:underline;text-underline-offset:2px}.sse-badge__reconnect-btn:hover{opacity:.8}@keyframes sse-pulse{0%,to{opacity:1}50%{opacity:.35}}max-width: 760px; margin: 0 auto; padding: 32px; } .loading,.empty-state{padding:32px;color:var(--fg-dim)}.empty-state h2{color:var(--fg);margin-top:0;margin-bottom:8px}.empty-state p{margin:0 0 12px}.empty-state-icon{font-size:28px;margin-bottom:8px;line-height:1}.empty-state-action{margin-top:4px}.auth-screen{min-height:100%;display:grid;place-items:center;padding:32px}.auth-card{width:min(420px,100%);background:var(--bg-elev);border:1px solid var(--border);border-radius:12px;padding:24px}.auth-card h1{margin-top:0;margin-bottom:8px}.auth-card p{margin-top:0;margin-bottom:20px;color:var(--fg-dim)}.page{padding:24px 32px;overflow:auto;flex:1;min-width:0}.page-header{margin-bottom:16px}.page-header h1{margin:0 0 4px;font-size:22px}.error-notice{background:#f871711a;border:1px solid var(--danger);color:var(--danger);padding:12px 14px;border-radius:8px;margin-bottom:16px}.error-notice.inline{margin-bottom:12px}.error-notice-title{margin:0 0 4px;font-size:16px}.error-notice-message{margin:0}.error-notice-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}.error-details{background:var(--bg-elev-2);padding:12px;border-radius:8px;overflow:auto}.loading-state{display:flex;align-items:flex-start;gap:12px;padding:16px 0;color:var(--fg-dim)}.loading-state.inline{padding:10px 0}.loading-state.centered{justify-content:center;padding:48px 32px}.loading-spinner{width:18px;height:18px;border:2px solid rgba(59,130,246,.25);border-top-color:var(--accent);border-radius:999px;flex:none;margin-top:2px;animation:spin .9s linear infinite}.loading-state-label{color:var(--fg);font-weight:500}.loading-state-detail{margin-top:2px}.btn{background:var(--bg-elev-2);color:var(--fg);border:1px solid var(--border);border-radius:6px;padding:6px 14px;font-size:13px;cursor:pointer}.btn:hover{background:var(--bg-elev)}.btn:disabled{opacity:.5;cursor:not-allowed}.btn.primary{background:var(--accent);color:var(--accent-fg);border-color:var(--accent)}.btn.primary:hover{filter:brightness(1.1)}.btn.danger{border-color:var(--danger);color:var(--danger)}.btn.cancel{background:var(--danger);border-color:var(--danger);color:var(--accent-fg)}.chat{display:flex;flex-direction:column;height:100%}.chat-scroll{flex:1;overflow:auto;padding:24px 32px 0}.chat-log{display:flex;flex-direction:column}.turn-wrapper{margin-bottom:18px}.turn-wrapper+.turn-wrapper:not(.has-separator) .bubble{border-top:1px solid var(--border);padding-top:14px}.turn-header{display:flex;align-items:center;gap:8px;margin-bottom:6px;font-size:12px}.turn-header--user{justify-content:flex-end}.turn-actor-badge{display:inline-flex;align-items:center;gap:4px;background:var(--bg-elev);border:1px solid var(--border);border-radius:999px;padding:2px 8px;font-size:11px;font-weight:500;-webkit-user-select:none;user-select:none}.turn-actor-icon{font-size:11px;line-height:1}.turn-ts{font-size:11px;color:var(--fg-dim);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;cursor:default;-webkit-user-select:none;user-select:none}.time-separator{display:flex;align-items:center;gap:10px;margin:16px 0 10px;color:var(--fg-dim);font-size:11px;font-style:italic;-webkit-user-select:none;user-select:none}.time-separator-line{flex:1;height:1px;background:var(--border);opacity:.5}.time-separator-label{white-space:nowrap;opacity:.7;letter-spacing:.02em}.bubble{max-width:800px}.bubble.user{margin-left:auto;text-align:right}.bubble.user .user-text{display:inline-block;background:var(--user-bubble);border:1px solid var(--border);padding:8px 14px;border-radius:14px;white-space:pre-wrap;text-align:left;margin:0}.route-tag{font-size:11px;color:var(--fg-dim);margin-top:4px}.copy-btn-wrap{position:relative}.copy-btn{position:absolute;top:6px;right:6px;display:flex;align-items:center;justify-content:center;padding:4px;background:var(--bg-elev);border:1px solid var(--border);border-radius:6px;color:var(--fg-dim);cursor:pointer;z-index:1;line-height:0;transition:color .15s,background .15s}.copy-btn:hover{background:var(--bg-elev-2);color:var(--fg)}.copy-btn--copied{color:#4ade80;border-color:#4ade80}@media (hover: hover){.copy-btn{opacity:0;pointer-events:none;transition:opacity .15s,color .15s,background .15s}.copy-btn-wrap:hover .copy-btn,.copy-btn-wrap:focus-within .copy-btn{opacity:1;pointer-events:auto}}.copy-btn--code{top:8px;right:8px}.activity-strip{margin:0 0 8px;font-size:12px}.activity-summary{display:flex;flex-wrap:wrap;gap:6px}.activity-pill{display:inline-flex;align-items:center;gap:6px;background:var(--bg-elev);border:1px solid var(--border);color:var(--fg-dim);padding:3px 10px;border-radius:999px;cursor:pointer;font-size:12px}.activity-pill:hover{background:var(--bg-elev-2)}.activity-pill.running{color:var(--accent);border-color:#3b82f673}.activity-pill .glyph{font-family:ui-monospace,monospace;font-size:11px}.activity-pill.running .glyph{display:inline-block;animation:spin 1s linear infinite}.activity-pill .caret{color:var(--fg-dim);font-size:10px}.activity-headlines{display:flex;flex-direction:column;gap:2px;margin-top:6px}.activity-headline{display:inline-flex;align-items:center;gap:6px;padding:2px 4px;color:var(--fg-dim)}.activity-headline.status-running{color:var(--accent)}.activity-headline.status-failed{color:var(--danger)}.activity-headline .glyph{font-family:ui-monospace,monospace;font-size:11px;width:12px;text-align:center}.activity-headline.status-running .glyph{animation:spin 1s linear infinite}.agent-tag{font-size:10px;text-transform:lowercase;background:#3b82f629;color:#93c5fd;border:1px solid rgba(59,130,246,.35);padding:1px 6px;border-radius:4px;letter-spacing:.02em}.activity-thinking,.activity-details{margin-top:8px;padding:10px 12px;background:var(--bg-elev);border:1px solid var(--border);border-radius:6px}.activity-details{display:flex;flex-direction:column;gap:6px}.thinking-block{margin:0;padding:8px;background:var(--bg-elev-2);border-radius:4px;white-space:pre-wrap;font-size:12px;line-height:1.5;max-height:280px;overflow:auto}.activity-row{border:1px solid var(--border);border-radius:6px;background:var(--bg-elev-2)}.activity-row.status-running{border-color:#3b82f673}.activity-row.status-failed{border-color:var(--danger)}.activity-row-head{width:100%;display:flex;align-items:center;gap:8px;background:transparent;border:0;color:var(--fg);text-align:left;padding:6px 10px;cursor:pointer;font-size:12px}.activity-row.status-running .activity-row-head .glyph{animation:spin 1s linear infinite;color:var(--accent)}.activity-row.status-failed .activity-row-head .glyph{color:var(--danger)}.activity-row .glyph{font-family:ui-monospace,monospace;width:12px;text-align:center}.activity-row .caret{margin-left:auto;color:var(--fg-dim)}.activity-row-body{padding:0 10px 10px;display:flex;flex-direction:column;gap:6px}.row-label{font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--fg-dim)}.composer{border-top:1px solid var(--border);background:var(--bg-elev);padding:14px 32px;display:flex;flex-direction:column;gap:8px}.composer textarea{width:100%;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--fg);padding:10px;resize:vertical}.composer-help{margin-top:-2px}.dreaming-indicator{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--fg-dim);padding:4px 0;animation:pulse 2s ease-in-out infinite}.dreaming-indicator-glyph{color:#c4b5fd}.composer-actions{display:flex;justify-content:flex-end;gap:6px}.md{line-height:1.55}.md p:first-child{margin-top:0}.md p:last-child{margin-bottom:0}.md pre{background:var(--bg-elev-2);border-radius:6px;padding:12px;overflow:auto}.md pre code{background:transparent;padding:0}.md table{border-collapse:collapse;margin:1em 0}.md th,.md td{border:1px solid var(--border);padding:6px 10px}.workers-layout{display:grid;grid-template-columns:320px 1fr;gap:18px;align-items:start}.workers-list{display:flex;flex-direction:column;gap:6px}.worker-row{text-align:left;background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:10px 12px;cursor:pointer;color:var(--fg)}.worker-row.selected,.worker-row:hover{background:var(--bg-elev-2)}.worker-row-head{display:flex;justify-content:space-between;align-items:center}.worker-status{font-size:11px;font-weight:600;padding:2px 7px;border-radius:10px;text-transform:uppercase;letter-spacing:.04em}.worker-status--running{background:color-mix(in srgb,var(--accent) 15%,transparent);color:var(--accent)}.worker-status--completed{background:color-mix(in srgb,#4caf50 15%,transparent);color:#4caf50}.worker-status--error{background:color-mix(in srgb,#f44336 15%,transparent);color:#f44336}.worker-row-desc{margin-top:4px;font-size:13px;color:var(--fg)}.workers-detail{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:18px}.worker-detail-description{margin:4px 0 8px;font-size:15px}.worker-detail-slug,.worker-detail-taskid{font-size:.75em;font-family:var(--font-mono, monospace)}.worker-detail-meta{display:flex;flex-wrap:wrap;align-items:center;gap:4px;margin-bottom:8px}.worker-events{display:flex;flex-direction:column;gap:4px;max-height:320px;overflow-y:auto;background:var(--bg-elev-2);border-radius:6px;padding:10px 12px;margin-bottom:12px;font-size:12px;font-family:var(--font-mono, monospace)}.worker-event{display:flex;gap:8px;align-items:baseline;line-height:1.5}.worker-event-ts{color:var(--text-dim, #888);flex-shrink:0;font-size:11px}.worker-event-body{display:flex;gap:4px;align-items:baseline;flex-wrap:wrap;overflow:hidden}.worker-event-icon{flex-shrink:0}.worker-event--tool_complete .worker-event-icon{opacity:.7}.msg-queued-indicator{font-size:.8em;opacity:.6;vertical-align:middle;-webkit-user-select:none;user-select:none}.output{background:var(--bg-elev-2);padding:12px;border-radius:6px;overflow:auto;white-space:pre-wrap;font-size:13px}.projects-toolbar{margin-bottom:16px}.projects-register-form{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.projects-path-input{background:var(--bg-elev);border:1px solid var(--border);border-radius:6px;color:var(--fg);font-size:13px;padding:6px 10px;width:380px;max-width:100%}.projects-path-input:focus{outline:none;border-color:var(--accent)}.projects-register-error{font-size:12px}.projects-disabled{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:18px}.projects-empty{padding:24px 0}.projects-list{display:flex;flex-direction:column;gap:8px}.project-row{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:12px 16px;display:flex;align-items:center;justify-content:space-between;gap:12px}.project-row-info{display:flex;flex-direction:column;gap:4px;min-width:0}.project-root{font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.project-meta{display:flex;gap:12px;font-size:12px;flex-wrap:wrap}.project-badge{background:var(--bg-elev-2);border:1px solid var(--border);border-radius:10px;padding:1px 8px;font-size:11px;color:var(--fg)}.project-row-actions{display:flex;gap:6px;flex-shrink:0}.project-context-banner{display:flex;align-items:center;gap:8px;padding:6px 16px;background:color-mix(in srgb,var(--accent) 10%,var(--bg-elev));border-bottom:1px solid color-mix(in srgb,var(--accent) 25%,var(--border));font-size:12px;color:var(--fg-dim);flex-shrink:0}.project-context-icon{font-size:13px;flex-shrink:0}.project-context-name{font-weight:600;color:var(--fg);flex-shrink:0}.project-context-path{color:var(--fg-dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.project-context-clear{background:none;border:none;color:var(--fg-dim);cursor:pointer;font-size:16px;line-height:1;padding:0 2px;flex-shrink:0;border-radius:4px}.project-context-clear:hover{color:var(--fg);background:var(--bg-hover)}.project-chat-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 16px;background:color-mix(in srgb,var(--accent) 8%,var(--bg-elev));border-bottom:1px solid color-mix(in srgb,var(--accent) 20%,var(--border));flex-shrink:0}.project-chat-header-identity{display:flex;align-items:center;gap:8px;min-width:0;overflow:hidden}.project-chat-icon{font-size:16px;flex-shrink:0}.project-chat-title{font-size:14px;white-space:nowrap;flex-shrink:0}.project-chat-path{font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.wiki{display:flex;flex-direction:column;min-height:100%}.wiki-layout{display:grid;grid-template-columns:minmax(320px,360px) minmax(0,1fr);gap:20px;flex:1;min-height:0}.wiki-sidebar,.wiki-main{min-height:0}.wiki-sidebar{display:flex;flex-direction:column;background:var(--bg-elev);border:1px solid var(--border);border-radius:12px;overflow:hidden}.wiki-sidebar-header{position:sticky;top:0;z-index:1;display:flex;flex-direction:column;gap:14px;padding:16px;border-bottom:1px solid var(--border);background:linear-gradient(180deg,var(--bg-elev) 0%,rgba(22,27,34,.98) 100%)}.wiki-sidebar-header-row{display:flex;justify-content:space-between;align-items:flex-start;gap:12px}.wiki-sidebar-header-row h2{margin:0 0 4px;font-size:16px}.wiki-sidebar-header-row p{margin:0}.wiki-search{display:flex;flex-direction:column;gap:12px}.wiki-search-field input,.wiki-filter select{width:100%;background:var(--bg);border:1px solid var(--border);color:var(--fg);padding:9px 10px;border-radius:8px}.wiki-filter{display:flex;flex-direction:column;gap:6px;font-size:12px;color:var(--fg-dim)}.wiki-search-meta,.wiki-shortcuts,.wiki-scope-legend{color:var(--fg-dim)}.wiki-scope-header-row{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.wiki-scope-header-row h1{margin:0}.wiki-shortcuts{border-top:1px solid var(--border);padding-top:12px}.wiki-scope-legend{display:flex;flex-wrap:wrap;gap:8px}.wiki-scope-legend>span{display:inline-flex;align-items:center;gap:4px}.wiki-sidebar-body{flex:1;min-height:0;overflow:auto;padding:12px}.wiki-tree,.wiki-tree-children{list-style:none;margin:0;padding:0}.wiki-tree-children{margin-top:4px}.wiki-node{margin:2px 0}.wiki-node-button{width:100%;display:flex;align-items:center;gap:8px;padding:7px 10px;background:transparent;border:1px solid transparent;border-radius:8px;color:var(--fg);text-align:left;cursor:pointer}.wiki-node-folder-button{color:var(--fg-dim)}.wiki-node-folder-button:hover,.wiki-node-folder-button.expanded,.wiki-node-page-button:hover{background:var(--bg-elev-2);border-color:var(--border);color:var(--fg)}.wiki-node-page-button{align-items:flex-start}.wiki-node-page-button.selected{background:#3b82f61f;border-color:#3b82f659;box-shadow:inset 2px 0 0 var(--accent)}.wiki-node-icon{width:14px;flex:none;text-align:center;color:var(--fg-dim)}.wiki-node-page-button.selected .wiki-node-icon{color:#93c5fd}.wiki-node-page-button.selected .wiki-node-scope-icon-personal{color:#ddd6fe}.wiki-node-page-button.selected .wiki-node-scope-icon-team{color:#a7f3d0}.wiki-node-content{min-width:0;display:flex;flex:1;flex-direction:column;gap:4px}.wiki-node-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wiki-node-meta{display:flex;flex-wrap:wrap;gap:6px;font-size:11px}.wiki-node-count{margin-left:auto;border:1px solid var(--border);border-radius:999px;padding:0 6px;font-size:11px;color:var(--fg-dim)}.wiki-main{min-width:0;display:flex;background:var(--bg-elev);border:1px solid var(--border);border-radius:12px;overflow:hidden}.wiki-main>.wiki-empty-state{width:100%}.wiki-document{width:100%;min-height:0;display:flex;flex-direction:column}.wiki-page-header{position:sticky;top:0;z-index:1;padding:18px 22px 16px;border-bottom:1px solid var(--border);background:linear-gradient(180deg,var(--bg-elev) 0%,rgba(22,27,34,.98) 100%)}.wiki-page-header-main{display:flex;justify-content:space-between;align-items:flex-start;gap:16px}.wiki-page-title-block h2{margin:0;font-size:28px;line-height:1.2}.wiki-page-summary{margin:8px 0 0;max-width:72ch;color:var(--fg-dim)}.wiki-page-actions{display:flex;gap:8px;flex:none}.wiki-breadcrumbs ol{display:flex;flex-wrap:wrap;gap:8px;list-style:none;margin:0 0 12px;padding:0}.wiki-breadcrumbs li{display:flex;align-items:center}.wiki-breadcrumbs li+li:before{content:"/";margin-right:8px;color:var(--fg-dim)}.wiki-breadcrumb-button{padding:0;border:0;background:transparent;color:var(--fg-dim);cursor:pointer}.wiki-breadcrumb-button:hover{color:var(--fg);text-decoration:underline}.wiki-meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:14px;font-size:12px;color:var(--fg-dim)}.wiki-badge,.wiki-tag,.wiki-meta-item{display:inline-flex;align-items:center;border:1px solid var(--border);border-radius:999px;padding:3px 8px;background:var(--bg-elev-2)}.wiki-badge{color:#93c5fd;border-color:#3b82f659}.wiki-scope-badge{display:inline-flex;align-items:center;gap:4px}.wiki-scope-badge-personal{color:#c4b5fd;border-color:#c4b5fd59;background:#c4b5fd14}.wiki-scope-badge-team{color:#6ee7b7;border-color:#6ee7b759;background:#6ee7b714}.wiki-node-scope-icon-personal{color:#c4b5fd}.wiki-node-scope-icon-team{color:#6ee7b7}.wiki-tag{color:var(--fg)}.wiki-meta-path{max-width:100%;overflow:auto;white-space:nowrap}.wiki-document-body{flex:1;min-height:0;overflow:auto}.wiki-article{max-width:76ch;padding:24px 22px 32px}.wiki-empty-state{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;gap:12px;margin:auto;max-width:56ch;padding:32px}.wiki-empty-state.compact{margin:0;max-width:none;padding:20px 12px}.wiki-empty-state h2{margin:0;font-size:20px}.wiki-empty-state p{margin:0;color:var(--fg-dim)}.wiki-empty-state-actions{display:flex;flex-wrap:wrap;gap:8px}@media (max-width: 960px){.wiki-layout{grid-template-columns:1fr}.wiki-sidebar{max-height:50vh}.wiki-page-header-main,.wiki-sidebar-header-row,.wiki-scope-legend{flex-direction:column}.wiki-page-actions{width:100%}.wiki-page-actions .btn{flex:1}}.wiki-edit .row{display:flex;gap:12px;margin-bottom:12px}.wiki-edit input[type=text]{flex:1;background:var(--bg);border:1px solid var(--border);color:var(--fg);padding:8px;border-radius:6px}.wiki-edit label{display:block;width:100%;font-size:12px;color:var(--fg-dim)}.wiki-editor{margin-bottom:16px}.skill-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px}.skill-card{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:14px}.skill-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}.tag{font-size:10px;text-transform:uppercase;padding:2px 6px;border-radius:4px;letter-spacing:.05em;background:var(--bg-elev-2);color:var(--fg-dim)}.tag-bundled{color:#93c5fd}.tag-local{color:#86efac}.tag-global{color:#fcd34d}.history-list{list-style:none;padding:0}.history-list li{padding:6px 0;border-bottom:1px solid var(--border)}.settings section{margin-bottom:28px}.settings-field{display:flex;flex-direction:column;gap:6px}.settings-field-label{font-size:12px;color:var(--fg-dim)}.settings select{background:var(--bg);color:var(--fg);border:1px solid var(--border);border-radius:6px;padding:6px 10px}.row{display:flex;align-items:center;gap:8px}.settings-row{align-items:flex-end}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes pulse{0%,to{opacity:.4}50%{opacity:1}}
10
+ */.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id{color:#79c0ff}.hljs-regexp,.hljs-string,.hljs-meta .hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-comment,.hljs-code,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}:root{color-scheme:dark;--bg: #0e1116;--bg-elev: #161b22;--bg-elev-2: #21262d;--fg: #e6edf3;--fg-dim: #8b949e;--border: #30363d;--accent: #3b82f6;--accent-fg: #ffffff;--danger: #f87171;--user-bubble: #1e293b}*{box-sizing:border-box}html,body,#root{height:100%;margin:0}body{background:var(--bg);color:var(--fg);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,sans-serif;font-size:14px;line-height:1.5}a,.link{color:var(--accent);text-decoration:none}a:hover,.link:hover{text-decoration:underline}button,input,textarea,select{font:inherit}button:focus-visible,a:focus-visible,input:focus-visible,textarea:focus-visible,select:focus-visible{outline:2px solid var(--accent);outline-offset:2px}code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.9em;background:var(--bg-elev-2);padding:1px 5px;border-radius:4px}.dim{color:var(--fg-dim)}.small{font-size:12px}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.skip-link{position:absolute;left:16px;top:-48px;z-index:10;padding:10px 14px;border-radius:8px;background:var(--accent);color:var(--accent-fg)}.skip-link:focus{top:16px}.layout{display:grid;grid-template-columns:220px 1fr;height:100%}.sidebar{background:var(--bg-elev);border-right:1px solid var(--border);padding:16px 0;display:flex;flex-direction:column}.sidebar-brand{display:flex;align-items:center;gap:10px;padding:0 18px 18px;font-weight:600;font-size:16px;border-bottom:1px solid var(--border);margin-bottom:12px}.sidebar nav{display:flex;flex-direction:column}.nav-link{padding:9px 18px;color:var(--fg);border-left:2px solid transparent}.nav-link:hover{background:var(--bg-elev-2);text-decoration:none}.nav-link.active{background:var(--bg-elev-2);border-left-color:var(--accent);color:var(--fg)}.nav-group{display:flex;flex-direction:column}.nav-group-row{display:flex;align-items:stretch}.nav-group-label{flex:1}.nav-group-toggle{background:none;border:none;cursor:pointer;padding:0 14px 0 4px;color:var(--fg-muted, var(--fg));display:flex;align-items:center;justify-content:center;border-left:2px solid transparent}.nav-group-toggle:hover{background:var(--bg-elev-2)}.nav-chevron{display:inline-block;font-size:18px;line-height:1;transition:transform .18s ease;transform:rotate(0)}.nav-chevron-open{transform:rotate(90deg)}.nav-recents{list-style:none;margin:0;padding:0}.nav-recent-link{display:flex;align-items:baseline;justify-content:space-between;gap:6px;width:100%;background:none;border:none;border-left:2px solid transparent;padding:6px 18px 6px 28px;cursor:pointer;color:var(--fg);text-align:left;font-size:13px}.nav-recent-link:hover{background:var(--bg-elev-2);text-decoration:none;border-left-color:var(--accent)}.nav-recent-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.nav-recent-hint{font-size:11px;flex-shrink:0;opacity:.6}.nav-recents-empty{padding:4px 28px 6px;font-size:12px;margin:0}.main{overflow:hidden;display:flex;flex-direction:column}.app-header{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:16px 24px;border-bottom:1px solid var(--border);background:var(--bg-elev)}.app-header-title{font-size:16px;font-weight:600;margin:0}.app-header-title-row{display:inline-flex;align-items:center;gap:10px}.app-header-user{color:var(--fg-dim);font-size:13px}.mode-badge{display:inline-flex;align-items:center;border-radius:999px;padding:3px 10px;font-size:12px;font-weight:600;line-height:1;border:1px solid transparent}.mode-standalone{background:var(--bg-elev-2);border-color:var(--border);color:var(--fg-dim)}.mode-team{background:color-mix(in srgb,var(--accent) 14%,transparent);border-color:color-mix(in srgb,var(--accent) 35%,var(--border));color:var(--accent)}.sse-badge{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:3px 10px;font-size:12px;font-weight:600;line-height:1;border:1px solid transparent}.sse-badge__dot{display:inline-block;width:7px;height:7px;border-radius:50%;flex-shrink:0}.sse-badge--reconnecting{background:color-mix(in srgb,#f59e0b 12%,transparent);border-color:color-mix(in srgb,#f59e0b 35%,var(--border));color:#b45309}.sse-badge--reconnecting .sse-badge__dot{background:#f59e0b;animation:sse-pulse 1.2s ease-in-out infinite}.sse-badge--disconnected{background:color-mix(in srgb,#ef4444 12%,transparent);border-color:color-mix(in srgb,#ef4444 35%,var(--border));color:#b91c1c}.sse-badge--disconnected .sse-badge__dot{background:#ef4444}.sse-badge__reconnect-btn{background:none;border:none;padding:0;margin-left:4px;font-size:12px;font-weight:600;color:inherit;cursor:pointer;text-decoration:underline;text-underline-offset:2px}.sse-badge__reconnect-btn:hover{opacity:.8}@keyframes sse-pulse{0%,to{opacity:1}50%{opacity:.35}}max-width: 760px; margin: 0 auto; padding: 32px; } .loading,.empty-state{padding:32px;color:var(--fg-dim)}.empty-state h2{color:var(--fg);margin-top:0;margin-bottom:8px}.empty-state p{margin:0 0 12px}.empty-state-icon{font-size:28px;margin-bottom:8px;line-height:1}.empty-state-action{margin-top:4px}.auth-screen{min-height:100%;display:grid;place-items:center;padding:32px}.auth-card{width:min(420px,100%);background:var(--bg-elev);border:1px solid var(--border);border-radius:12px;padding:24px}.auth-card h1{margin-top:0;margin-bottom:8px}.auth-card p{margin-top:0;margin-bottom:20px;color:var(--fg-dim)}.page{padding:24px 32px;overflow:auto;flex:1;min-width:0}.page-header{margin-bottom:16px}.page-header h1{margin:0 0 4px;font-size:22px}.error-notice{background:#f871711a;border:1px solid var(--danger);color:var(--danger);padding:12px 14px;border-radius:8px;margin-bottom:16px}.error-notice.inline{margin-bottom:12px}.error-notice-title{margin:0 0 4px;font-size:16px}.error-notice-message{margin:0}.error-notice-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}.error-details{background:var(--bg-elev-2);padding:12px;border-radius:8px;overflow:auto}.loading-state{display:flex;align-items:flex-start;gap:12px;padding:16px 0;color:var(--fg-dim)}.loading-state.inline{padding:10px 0}.loading-state.centered{justify-content:center;padding:48px 32px}.loading-spinner{width:18px;height:18px;border:2px solid rgba(59,130,246,.25);border-top-color:var(--accent);border-radius:999px;flex:none;margin-top:2px;animation:spin .9s linear infinite}.loading-state-label{color:var(--fg);font-weight:500}.loading-state-detail{margin-top:2px}.btn{background:var(--bg-elev-2);color:var(--fg);border:1px solid var(--border);border-radius:6px;padding:6px 14px;font-size:13px;cursor:pointer}.btn:hover{background:var(--bg-elev)}.btn:disabled{opacity:.5;cursor:not-allowed}.btn.primary{background:var(--accent);color:var(--accent-fg);border-color:var(--accent)}.btn.primary:hover{filter:brightness(1.1)}.btn.danger{border-color:var(--danger);color:var(--danger)}.btn.cancel{background:var(--danger);border-color:var(--danger);color:var(--accent-fg)}.chat{display:flex;flex-direction:column;height:100%}.chat-scroll{flex:1;overflow:auto;padding:24px 32px 0}.chat-log{display:flex;flex-direction:column}.turn-wrapper{margin-bottom:18px}.turn-wrapper+.turn-wrapper:not(.has-separator) .bubble{border-top:1px solid var(--border);padding-top:14px}.turn-header{display:flex;align-items:center;gap:8px;margin-bottom:6px;font-size:12px}.turn-header--user{justify-content:flex-end}.turn-actor-badge{display:inline-flex;align-items:center;gap:4px;background:var(--bg-elev);border:1px solid var(--border);border-radius:999px;padding:2px 8px;font-size:11px;font-weight:500;-webkit-user-select:none;user-select:none}.turn-actor-icon{font-size:11px;line-height:1}.turn-ts{font-size:11px;color:var(--fg-dim);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;cursor:default;-webkit-user-select:none;user-select:none}.time-separator{display:flex;align-items:center;gap:10px;margin:16px 0 10px;color:var(--fg-dim);font-size:11px;font-style:italic;-webkit-user-select:none;user-select:none}.time-separator-line{flex:1;height:1px;background:var(--border);opacity:.5}.time-separator-label{white-space:nowrap;opacity:.7;letter-spacing:.02em}.bubble{max-width:800px}.bubble.user{margin-left:auto;text-align:right}.bubble.user .user-text{display:inline-block;background:var(--user-bubble);border:1px solid var(--border);padding:8px 14px;border-radius:14px;white-space:pre-wrap;text-align:left;margin:0}.route-tag{font-size:11px;color:var(--fg-dim);margin-top:4px}.copy-btn-wrap{position:relative}.copy-btn{position:absolute;top:6px;right:6px;display:flex;align-items:center;justify-content:center;padding:4px;background:var(--bg-elev);border:1px solid var(--border);border-radius:6px;color:var(--fg-dim);cursor:pointer;z-index:1;line-height:0;transition:color .15s,background .15s}.copy-btn:hover{background:var(--bg-elev-2);color:var(--fg)}.copy-btn--copied{color:#4ade80;border-color:#4ade80}@media (hover: hover){.copy-btn{opacity:0;pointer-events:none;transition:opacity .15s,color .15s,background .15s}.copy-btn-wrap:hover .copy-btn,.copy-btn-wrap:focus-within .copy-btn{opacity:1;pointer-events:auto}}.copy-btn--code{top:8px;right:8px}.activity-strip{margin:0 0 8px;font-size:12px}.activity-summary{display:flex;flex-wrap:wrap;gap:6px}.activity-pill{display:inline-flex;align-items:center;gap:6px;background:var(--bg-elev);border:1px solid var(--border);color:var(--fg-dim);padding:3px 10px;border-radius:999px;cursor:pointer;font-size:12px}.activity-pill:hover{background:var(--bg-elev-2)}.activity-pill.running{color:var(--accent);border-color:#3b82f673}.activity-pill .glyph{font-family:ui-monospace,monospace;font-size:11px}.activity-pill.running .glyph{display:inline-block;animation:spin 1s linear infinite}.activity-pill .caret{color:var(--fg-dim);font-size:10px}.activity-headlines{display:flex;flex-direction:column;gap:2px;margin-top:6px}.activity-headline{display:inline-flex;align-items:center;gap:6px;padding:2px 4px;color:var(--fg-dim)}.activity-headline.status-running{color:var(--accent)}.activity-headline.status-failed{color:var(--danger)}.activity-headline .glyph{font-family:ui-monospace,monospace;font-size:11px;width:12px;text-align:center}.activity-headline.status-running .glyph{animation:spin 1s linear infinite}.agent-tag{font-size:10px;text-transform:lowercase;background:#3b82f629;color:#93c5fd;border:1px solid rgba(59,130,246,.35);padding:1px 6px;border-radius:4px;letter-spacing:.02em}.activity-thinking,.activity-details{margin-top:8px;padding:10px 12px;background:var(--bg-elev);border:1px solid var(--border);border-radius:6px}.activity-details{display:flex;flex-direction:column;gap:6px}.thinking-block{margin:0;padding:8px;background:var(--bg-elev-2);border-radius:4px;white-space:pre-wrap;font-size:12px;line-height:1.5;max-height:280px;overflow:auto}.activity-row{border:1px solid var(--border);border-radius:6px;background:var(--bg-elev-2)}.activity-row.status-running{border-color:#3b82f673}.activity-row.status-failed{border-color:var(--danger)}.activity-row-head{width:100%;display:flex;align-items:center;gap:8px;background:transparent;border:0;color:var(--fg);text-align:left;padding:6px 10px;cursor:pointer;font-size:12px}.activity-row.status-running .activity-row-head .glyph{animation:spin 1s linear infinite;color:var(--accent)}.activity-row.status-failed .activity-row-head .glyph{color:var(--danger)}.activity-row .glyph{font-family:ui-monospace,monospace;width:12px;text-align:center}.activity-row .caret{margin-left:auto;color:var(--fg-dim)}.activity-row-body{padding:0 10px 10px;display:flex;flex-direction:column;gap:6px}.row-label{font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--fg-dim)}.composer{border-top:1px solid var(--border);background:var(--bg-elev);padding:14px 32px;display:flex;flex-direction:column;gap:8px}.composer textarea{width:100%;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--fg);padding:10px;resize:vertical}.composer-help{margin-top:-2px}.dreaming-indicator{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--fg-dim);padding:4px 0;animation:pulse 2s ease-in-out infinite}.dreaming-indicator-glyph{color:#c4b5fd}.composer-actions{display:flex;justify-content:flex-end;gap:6px}.md{line-height:1.55}.md p:first-child{margin-top:0}.md p:last-child{margin-bottom:0}.md pre{background:var(--bg-elev-2);border-radius:6px;padding:12px;overflow:auto}.md pre code{background:transparent;padding:0}.md table{border-collapse:collapse;margin:1em 0}.md th,.md td{border:1px solid var(--border);padding:6px 10px}.workers-layout{display:grid;grid-template-columns:320px 1fr;gap:18px;align-items:start}.workers-list{display:flex;flex-direction:column;gap:6px}.worker-row{text-align:left;background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:10px 12px;cursor:pointer;color:var(--fg)}.worker-row.selected,.worker-row:hover{background:var(--bg-elev-2)}.worker-row-head{display:flex;justify-content:space-between;align-items:center}.worker-status{font-size:11px;font-weight:600;padding:2px 7px;border-radius:10px;text-transform:uppercase;letter-spacing:.04em}.worker-status--running{background:color-mix(in srgb,var(--accent) 15%,transparent);color:var(--accent)}.worker-status--completed{background:color-mix(in srgb,#4caf50 15%,transparent);color:#4caf50}.worker-status--error{background:color-mix(in srgb,#f44336 15%,transparent);color:#f44336}.worker-row-desc{margin-top:4px;font-size:13px;color:var(--fg)}.workers-detail{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:18px}.worker-detail-description{margin:4px 0 8px;font-size:15px}.worker-detail-slug,.worker-detail-taskid{font-size:.75em;font-family:var(--font-mono, monospace)}.worker-detail-meta{display:flex;flex-wrap:wrap;align-items:center;gap:4px;margin-bottom:8px}.worker-events{display:flex;flex-direction:column;gap:4px;max-height:320px;overflow-y:auto;background:var(--bg-elev-2);border-radius:6px;padding:10px 12px;margin-bottom:12px;font-size:12px;font-family:var(--font-mono, monospace)}.worker-event{display:flex;gap:8px;align-items:baseline;line-height:1.5}.worker-event-ts{color:var(--text-dim, #888);flex-shrink:0;font-size:11px}.worker-event-body{display:flex;gap:4px;align-items:baseline;flex-wrap:wrap;overflow:hidden}.worker-event-icon{flex-shrink:0}.worker-event--tool_complete .worker-event-icon{opacity:.7}.msg-queued-indicator{font-size:.8em;opacity:.6;vertical-align:middle;-webkit-user-select:none;user-select:none}.msg-queued-backend-indicator{display:inline-block;font-size:.78em;opacity:.75;margin-top:4px;color:var(--fg-dim);-webkit-user-select:none;user-select:none}.output{background:var(--bg-elev-2);padding:12px;border-radius:6px;overflow:auto;white-space:pre-wrap;font-size:13px}.projects-toolbar{margin-bottom:16px}.projects-register-form{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.projects-path-input{background:var(--bg-elev);border:1px solid var(--border);border-radius:6px;color:var(--fg);font-size:13px;padding:6px 10px;width:380px;max-width:100%}.projects-path-input:focus{outline:none;border-color:var(--accent)}.projects-register-error{font-size:12px}.projects-disabled{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:18px}.projects-empty{padding:24px 0}.projects-list{display:flex;flex-direction:column;gap:8px}.project-row{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:12px 16px;display:flex;align-items:center;justify-content:space-between;gap:12px}.project-row-info{display:flex;flex-direction:column;gap:4px;min-width:0}.project-root{font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.project-meta{display:flex;gap:12px;font-size:12px;flex-wrap:wrap}.project-badge{background:var(--bg-elev-2);border:1px solid var(--border);border-radius:10px;padding:1px 8px;font-size:11px;color:var(--fg)}.project-row-actions{display:flex;gap:6px;flex-shrink:0}.project-context-banner{display:flex;align-items:center;gap:8px;padding:6px 16px;background:color-mix(in srgb,var(--accent) 10%,var(--bg-elev));border-bottom:1px solid color-mix(in srgb,var(--accent) 25%,var(--border));font-size:12px;color:var(--fg-dim);flex-shrink:0}.project-context-icon{font-size:13px;flex-shrink:0}.project-context-name{font-weight:600;color:var(--fg);flex-shrink:0}.project-context-path{color:var(--fg-dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.project-context-clear{background:none;border:none;color:var(--fg-dim);cursor:pointer;font-size:16px;line-height:1;padding:0 2px;flex-shrink:0;border-radius:4px}.project-context-clear:hover{color:var(--fg);background:var(--bg-hover)}.project-chat-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 16px;background:color-mix(in srgb,var(--accent) 8%,var(--bg-elev));border-bottom:1px solid color-mix(in srgb,var(--accent) 20%,var(--border));flex-shrink:0}.project-chat-header-identity{display:flex;align-items:center;gap:8px;min-width:0;overflow:hidden}.project-chat-icon{font-size:16px;flex-shrink:0}.project-chat-title{font-size:14px;white-space:nowrap;flex-shrink:0}.project-chat-path{font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.wiki{display:flex;flex-direction:column;min-height:100%}.wiki-layout{display:grid;grid-template-columns:minmax(320px,360px) minmax(0,1fr);gap:20px;flex:1;min-height:0}.wiki-sidebar,.wiki-main{min-height:0}.wiki-sidebar{display:flex;flex-direction:column;background:var(--bg-elev);border:1px solid var(--border);border-radius:12px;overflow:hidden}.wiki-sidebar-header{position:sticky;top:0;z-index:1;display:flex;flex-direction:column;gap:14px;padding:16px;border-bottom:1px solid var(--border);background:linear-gradient(180deg,var(--bg-elev) 0%,rgba(22,27,34,.98) 100%)}.wiki-sidebar-header-row{display:flex;justify-content:space-between;align-items:flex-start;gap:12px}.wiki-sidebar-header-row h2{margin:0 0 4px;font-size:16px}.wiki-sidebar-header-row p{margin:0}.wiki-search{display:flex;flex-direction:column;gap:12px}.wiki-search-field input,.wiki-filter select{width:100%;background:var(--bg);border:1px solid var(--border);color:var(--fg);padding:9px 10px;border-radius:8px}.wiki-filter{display:flex;flex-direction:column;gap:6px;font-size:12px;color:var(--fg-dim)}.wiki-search-meta,.wiki-shortcuts,.wiki-scope-legend{color:var(--fg-dim)}.wiki-scope-header-row{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.wiki-scope-header-row h1{margin:0}.wiki-shortcuts{border-top:1px solid var(--border);padding-top:12px}.wiki-scope-legend{display:flex;flex-wrap:wrap;gap:8px}.wiki-scope-legend>span{display:inline-flex;align-items:center;gap:4px}.wiki-sidebar-body{flex:1;min-height:0;overflow:auto;padding:12px}.wiki-tree,.wiki-tree-children{list-style:none;margin:0;padding:0}.wiki-tree-children{margin-top:4px}.wiki-node{margin:2px 0}.wiki-node-button{width:100%;display:flex;align-items:center;gap:8px;padding:7px 10px;background:transparent;border:1px solid transparent;border-radius:8px;color:var(--fg);text-align:left;cursor:pointer}.wiki-node-folder-button{color:var(--fg-dim)}.wiki-node-folder-button:hover,.wiki-node-folder-button.expanded,.wiki-node-page-button:hover{background:var(--bg-elev-2);border-color:var(--border);color:var(--fg)}.wiki-node-page-button{align-items:flex-start}.wiki-node-page-button.selected{background:#3b82f61f;border-color:#3b82f659;box-shadow:inset 2px 0 0 var(--accent)}.wiki-node-icon{width:14px;flex:none;text-align:center;color:var(--fg-dim)}.wiki-node-page-button.selected .wiki-node-icon{color:#93c5fd}.wiki-node-page-button.selected .wiki-node-scope-icon-personal{color:#ddd6fe}.wiki-node-page-button.selected .wiki-node-scope-icon-team{color:#a7f3d0}.wiki-node-content{min-width:0;display:flex;flex:1;flex-direction:column;gap:4px}.wiki-node-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wiki-node-meta{display:flex;flex-wrap:wrap;gap:6px;font-size:11px}.wiki-node-count{margin-left:auto;border:1px solid var(--border);border-radius:999px;padding:0 6px;font-size:11px;color:var(--fg-dim)}.wiki-main{min-width:0;display:flex;background:var(--bg-elev);border:1px solid var(--border);border-radius:12px;overflow:hidden}.wiki-main>.wiki-empty-state{width:100%}.wiki-document{width:100%;min-height:0;display:flex;flex-direction:column}.wiki-page-header{position:sticky;top:0;z-index:1;padding:18px 22px 16px;border-bottom:1px solid var(--border);background:linear-gradient(180deg,var(--bg-elev) 0%,rgba(22,27,34,.98) 100%)}.wiki-page-header-main{display:flex;justify-content:space-between;align-items:flex-start;gap:16px}.wiki-page-title-block h2{margin:0;font-size:28px;line-height:1.2}.wiki-page-summary{margin:8px 0 0;max-width:72ch;color:var(--fg-dim)}.wiki-page-actions{display:flex;gap:8px;flex:none}.wiki-breadcrumbs ol{display:flex;flex-wrap:wrap;gap:8px;list-style:none;margin:0 0 12px;padding:0}.wiki-breadcrumbs li{display:flex;align-items:center}.wiki-breadcrumbs li+li:before{content:"/";margin-right:8px;color:var(--fg-dim)}.wiki-breadcrumb-button{padding:0;border:0;background:transparent;color:var(--fg-dim);cursor:pointer}.wiki-breadcrumb-button:hover{color:var(--fg);text-decoration:underline}.wiki-meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:14px;font-size:12px;color:var(--fg-dim)}.wiki-badge,.wiki-tag,.wiki-meta-item{display:inline-flex;align-items:center;border:1px solid var(--border);border-radius:999px;padding:3px 8px;background:var(--bg-elev-2)}.wiki-badge{color:#93c5fd;border-color:#3b82f659}.wiki-scope-badge{display:inline-flex;align-items:center;gap:4px}.wiki-scope-badge-personal{color:#c4b5fd;border-color:#c4b5fd59;background:#c4b5fd14}.wiki-scope-badge-team{color:#6ee7b7;border-color:#6ee7b759;background:#6ee7b714}.wiki-node-scope-icon-personal{color:#c4b5fd}.wiki-node-scope-icon-team{color:#6ee7b7}.wiki-tag{color:var(--fg)}.wiki-meta-path{max-width:100%;overflow:auto;white-space:nowrap}.wiki-document-body{flex:1;min-height:0;overflow:auto}.wiki-article{max-width:76ch;padding:24px 22px 32px}.wiki-empty-state{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;gap:12px;margin:auto;max-width:56ch;padding:32px}.wiki-empty-state.compact{margin:0;max-width:none;padding:20px 12px}.wiki-empty-state h2{margin:0;font-size:20px}.wiki-empty-state p{margin:0;color:var(--fg-dim)}.wiki-empty-state-actions{display:flex;flex-wrap:wrap;gap:8px}@media (max-width: 960px){.wiki-layout{grid-template-columns:1fr}.wiki-sidebar{max-height:50vh}.wiki-page-header-main,.wiki-sidebar-header-row,.wiki-scope-legend{flex-direction:column}.wiki-page-actions{width:100%}.wiki-page-actions .btn{flex:1}}.wiki-edit .row{display:flex;gap:12px;margin-bottom:12px}.wiki-edit input[type=text]{flex:1;background:var(--bg);border:1px solid var(--border);color:var(--fg);padding:8px;border-radius:6px}.wiki-edit label{display:block;width:100%;font-size:12px;color:var(--fg-dim)}.wiki-editor{margin-bottom:16px}.skill-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px}.skill-card{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:14px}.skill-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}.tag{font-size:10px;text-transform:uppercase;padding:2px 6px;border-radius:4px;letter-spacing:.05em;background:var(--bg-elev-2);color:var(--fg-dim)}.tag-bundled{color:#93c5fd}.tag-local{color:#86efac}.tag-global{color:#fcd34d}.history-list{list-style:none;padding:0}.history-list li{padding:6px 0;border-bottom:1px solid var(--border)}.settings section{margin-bottom:28px}.settings-field{display:flex;flex-direction:column;gap:6px}.settings-field-label{font-size:12px;color:var(--fg-dim)}.settings select{background:var(--bg);color:var(--fg);border:1px solid var(--border);border-radius:6px;padding:6px 10px}.row{display:flex;align-items:center;gap:8px}.settings-row{align-items:flex-end}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes pulse{0%,to{opacity:.4}50%{opacity:1}}