@ra3orblade/swarm 0.12.1 → 0.13.1

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.
@@ -196,22 +196,26 @@ function otherLiveInSameTree(current, sessions, now, withinMs = LIVE_WINDOW_MS)
196
196
  }
197
197
  return null;
198
198
  }
199
+ var GIT_GLOBAL_OPTS = "(?:\\s+(?:-C\\s+\\S+|-c\\s+\\S+|--git-dir(?:=|\\s+)\\S+|--work-tree(?:=|\\s+)\\S+|--namespace(?:=|\\s+)\\S+|--no-pager|--paginate|--bare|--literal-pathspecs|--exec-path(?:=\\S+)?))*";
200
+ function gitVerb(verb, tail) {
201
+ return new RegExp(`\\bgit${GIT_GLOBAL_OPTS}\\s+${verb}${tail}`);
202
+ }
199
203
  function isBroadStage(cmd) {
200
204
  const c = cmd.trim();
201
- if (/\bgit\s+add\s+(-A\b|--all\b|\.(\s|$))/.test(c))
205
+ if (gitVerb("add", "\\s+(-A\\b|--all\\b|\\.(\\s|$))").test(c))
202
206
  return true;
203
- if (/\bgit\s+commit\b[^|&;]*\s-[a-zA-Z]*a/.test(c))
207
+ if (gitVerb("commit", "\\b[^|&;]*\\s-[a-zA-Z]*a").test(c))
204
208
  return true;
205
- if (/\bgit\s+add\s*$/.test(c))
209
+ if (gitVerb("add", "\\s*$").test(c))
206
210
  return true;
207
211
  return false;
208
212
  }
209
213
  function isDestructiveGit(cmd) {
210
214
  const c = cmd.trim();
211
- return /\bgit\s+reset\s+[^|&;]*--hard\b/.test(c) || /\bgit\s+checkout\s+(--\s+)?\.(\s|$)/.test(c) || /\bgit\s+checkout\s+-f\b/.test(c) || /\bgit\s+restore\s+(--\s+)?\.(\s|$)/.test(c) || /\bgit\s+clean\s+[^|&;]*-[a-zA-Z]*f/.test(c) || /\bgit\s+stash\s+(drop|clear)\b/.test(c) || /\bgit\s+branch\s+[^|&;]*-[a-zA-Z]*D/.test(c);
215
+ return gitVerb("reset", "\\s+[^|&;]*--hard\\b").test(c) || gitVerb("checkout", "\\s+(--\\s+)?\\.(\\s|$)").test(c) || gitVerb("checkout", "\\s+-f\\b").test(c) || gitVerb("restore", "\\s+(--\\s+)?\\.(\\s|$)").test(c) || gitVerb("clean", "\\s+[^|&;]*-[a-zA-Z]*f").test(c) || gitVerb("stash", "\\s+(drop|clear)\\b").test(c) || gitVerb("branch", "\\s+[^|&;]*-[a-zA-Z]*D").test(c);
212
216
  }
213
217
  function isPatternKill(cmd) {
214
- return /\bpkill\s+-f\b/.test(cmd) || /\bpgrep\s+-f\b[^|]*\|\s*[^|]*\bkill\b/.test(cmd);
218
+ return /\bpkill\s+(-[a-zA-Z]*f\b|--full\b)/.test(cmd) || /\bkillall\b/.test(cmd) || /\bpgrep\s+(-[a-zA-Z]*f\b|--full\b)[^|]*\|\s*[^|]*\bkill\b/.test(cmd);
215
219
  }
216
220
  function killedPorts(cmd) {
217
221
  const ports = new Set;
package/dist/swarmd.js CHANGED
@@ -1678,22 +1678,26 @@ function otherLiveInSameTree(current, sessions, now, withinMs = LIVE_WINDOW_MS)
1678
1678
  }
1679
1679
  return null;
1680
1680
  }
1681
+ var GIT_GLOBAL_OPTS = "(?:\\s+(?:-C\\s+\\S+|-c\\s+\\S+|--git-dir(?:=|\\s+)\\S+|--work-tree(?:=|\\s+)\\S+|--namespace(?:=|\\s+)\\S+|--no-pager|--paginate|--bare|--literal-pathspecs|--exec-path(?:=\\S+)?))*";
1682
+ function gitVerb(verb, tail) {
1683
+ return new RegExp(`\\bgit${GIT_GLOBAL_OPTS}\\s+${verb}${tail}`);
1684
+ }
1681
1685
  function isBroadStage(cmd) {
1682
1686
  const c = cmd.trim();
1683
- if (/\bgit\s+add\s+(-A\b|--all\b|\.(\s|$))/.test(c))
1687
+ if (gitVerb("add", "\\s+(-A\\b|--all\\b|\\.(\\s|$))").test(c))
1684
1688
  return true;
1685
- if (/\bgit\s+commit\b[^|&;]*\s-[a-zA-Z]*a/.test(c))
1689
+ if (gitVerb("commit", "\\b[^|&;]*\\s-[a-zA-Z]*a").test(c))
1686
1690
  return true;
1687
- if (/\bgit\s+add\s*$/.test(c))
1691
+ if (gitVerb("add", "\\s*$").test(c))
1688
1692
  return true;
1689
1693
  return false;
1690
1694
  }
1691
1695
  function isDestructiveGit(cmd) {
1692
1696
  const c = cmd.trim();
1693
- return /\bgit\s+reset\s+[^|&;]*--hard\b/.test(c) || /\bgit\s+checkout\s+(--\s+)?\.(\s|$)/.test(c) || /\bgit\s+checkout\s+-f\b/.test(c) || /\bgit\s+restore\s+(--\s+)?\.(\s|$)/.test(c) || /\bgit\s+clean\s+[^|&;]*-[a-zA-Z]*f/.test(c) || /\bgit\s+stash\s+(drop|clear)\b/.test(c) || /\bgit\s+branch\s+[^|&;]*-[a-zA-Z]*D/.test(c);
1697
+ return gitVerb("reset", "\\s+[^|&;]*--hard\\b").test(c) || gitVerb("checkout", "\\s+(--\\s+)?\\.(\\s|$)").test(c) || gitVerb("checkout", "\\s+-f\\b").test(c) || gitVerb("restore", "\\s+(--\\s+)?\\.(\\s|$)").test(c) || gitVerb("clean", "\\s+[^|&;]*-[a-zA-Z]*f").test(c) || gitVerb("stash", "\\s+(drop|clear)\\b").test(c) || gitVerb("branch", "\\s+[^|&;]*-[a-zA-Z]*D").test(c);
1694
1698
  }
1695
1699
  function isPatternKill(cmd) {
1696
- return /\bpkill\s+-f\b/.test(cmd) || /\bpgrep\s+-f\b[^|]*\|\s*[^|]*\bkill\b/.test(cmd);
1700
+ return /\bpkill\s+(-[a-zA-Z]*f\b|--full\b)/.test(cmd) || /\bkillall\b/.test(cmd) || /\bpgrep\s+(-[a-zA-Z]*f\b|--full\b)[^|]*\|\s*[^|]*\bkill\b/.test(cmd);
1697
1701
  }
1698
1702
  function killedPorts(cmd) {
1699
1703
  const ports = new Set;
@@ -6223,7 +6227,7 @@ class ForgeService {
6223
6227
  return all.sort((a, b) => a.createdAt < b.createdAt ? 1 : -1);
6224
6228
  }
6225
6229
  async refresh(maxAgeMs = 120000) {
6226
- const projects = this.store.projects();
6230
+ const projects = this.store.liveProjects();
6227
6231
  await Promise.all(projects.map(async (p) => {
6228
6232
  const hit = this.cache.get(p.id);
6229
6233
  if (hit && Date.now() - hit.at < maxAgeMs)
@@ -6246,18 +6250,24 @@ class ForgeService {
6246
6250
  mergedCached(projectId, root) {
6247
6251
  const hit = this.outcomeCache.get(projectId);
6248
6252
  const fresh = !!hit && Date.now() - hit.at < 600000;
6249
- if (!fresh && !this.outcomeInflight.has(projectId)) {
6250
- const run2 = this.merged(projectId, root).finally(() => this.outcomeInflight.delete(projectId));
6251
- this.outcomeInflight.set(projectId, run2);
6252
- }
6253
+ if (!fresh)
6254
+ this.merged(projectId, root).catch(() => {});
6253
6255
  return { merged: hit?.merged ?? [], reverted: hit?.reverted ?? [], fresh };
6254
6256
  }
6255
6257
  async merged(projectId, root) {
6256
6258
  const hit = this.outcomeCache.get(projectId);
6257
6259
  if (hit && Date.now() - hit.at < 600000)
6258
6260
  return hit;
6261
+ const inflight = this.outcomeInflight.get(projectId);
6262
+ if (inflight)
6263
+ return inflight;
6264
+ const run2 = this.fetchMerged(projectId, root).finally(() => this.outcomeInflight.delete(projectId));
6265
+ this.outcomeInflight.set(projectId, run2);
6266
+ return run2;
6267
+ }
6268
+ async fetchMerged(projectId, root) {
6259
6269
  let merged = [];
6260
- const remote = this.remote(root);
6270
+ const remote = await this.remote(root);
6261
6271
  if (remote?.forge === "github") {
6262
6272
  const out = await this.run([
6263
6273
  "gh",
@@ -6293,38 +6303,44 @@ class ForgeService {
6293
6303
  mergeSha: (r.merge_commit_sha ?? null)?.toLowerCase() ?? null
6294
6304
  }));
6295
6305
  }
6296
- const log = Bun.spawnSync([
6297
- "git",
6298
- "-C",
6299
- root,
6300
- "log",
6301
- "--grep",
6302
- "This reverts commit",
6303
- "--format=%B",
6304
- "-n",
6305
- "300"
6306
- ]);
6307
- const reverted = log.exitCode === 0 ? [...parseReverts(new TextDecoder().decode(log.stdout))] : [];
6306
+ const log = await this.run(["git", "log", "--grep", "This reverts commit", "--format=%B", "-n", "300"], root);
6307
+ const reverted = log ? [...parseReverts(log)] : [];
6308
6308
  const entry = { at: Date.now(), merged, reverted };
6309
6309
  this.outcomeCache.set(projectId, entry);
6310
6310
  return entry;
6311
6311
  }
6312
- remote(root) {
6313
- const r = Bun.spawnSync(["git", "-C", root, "remote", "get-url", "origin"]);
6314
- if (r.exitCode !== 0)
6315
- return null;
6316
- return parseRemote(new TextDecoder().decode(r.stdout).trim());
6312
+ remoteCache = new Map;
6313
+ async remote(root) {
6314
+ const hit = this.remoteCache.get(root);
6315
+ if (hit && Date.now() - hit.at < (hit.v ? 600000 : 60000))
6316
+ return hit.v;
6317
+ const out = await this.run(["git", "remote", "get-url", "origin"], root);
6318
+ const v = out ? parseRemote(out.trim()) : null;
6319
+ this.remoteCache.set(root, { at: Date.now(), v });
6320
+ return v;
6317
6321
  }
6318
- async run(cmd, cwd) {
6322
+ async run(cmd, cwd, timeoutMs = 20000) {
6319
6323
  const bin = findBin(cmd[0]);
6320
6324
  if (!bin)
6321
6325
  return null;
6322
- const proc = Bun.spawn([bin, ...cmd.slice(1)], { cwd, stdout: "pipe", stderr: "ignore" });
6323
- const out = await new Response(proc.stdout).text();
6324
- return await proc.exited === 0 ? out : null;
6326
+ let proc;
6327
+ try {
6328
+ proc = Bun.spawn([bin, ...cmd.slice(1)], { cwd, stdout: "pipe", stderr: "ignore" });
6329
+ } catch {
6330
+ return null;
6331
+ }
6332
+ const killer = setTimeout(() => proc.kill(), timeoutMs);
6333
+ try {
6334
+ const out = await new Response(proc.stdout).text();
6335
+ return await proc.exited === 0 ? out : null;
6336
+ } catch {
6337
+ return null;
6338
+ } finally {
6339
+ clearTimeout(killer);
6340
+ }
6325
6341
  }
6326
6342
  async poll(projectId, root) {
6327
- const remote = this.remote(root);
6343
+ const remote = await this.remote(root);
6328
6344
  if (!remote)
6329
6345
  return [];
6330
6346
  let prs = [];
@@ -6352,7 +6368,7 @@ class ForgeService {
6352
6368
  ok: false,
6353
6369
  error: `${worktree2.path} has uncommitted changes \u2014 commit them first (Swarm never commits for you)`
6354
6370
  };
6355
- const remote = this.remote(p.root);
6371
+ const remote = await this.remote(p.root);
6356
6372
  if (!remote)
6357
6373
  return { ok: false, error: "no GitHub/GitLab remote on origin" };
6358
6374
  const cli = remote.forge === "github" ? "gh" : "glab";
@@ -6406,7 +6422,7 @@ class ForgeService {
6406
6422
  const p = this.store.projects().find((x) => x.id === projectId);
6407
6423
  if (!p)
6408
6424
  return { ok: false, output: "unknown project" };
6409
- const remote = this.remote(p.root);
6425
+ const remote = await this.remote(p.root);
6410
6426
  if (!remote)
6411
6427
  return { ok: false, output: "no forge remote" };
6412
6428
  const cmd = remote.forge === "github" ? ["gh", "pr", "merge", String(number), "--squash"] : ["glab", "mr", "merge", String(number), "--squash", "--yes"];
@@ -7062,6 +7078,8 @@ function runBootstrap(plan, opts) {
7062
7078
  }
7063
7079
 
7064
7080
  // packages/daemon/src/task-sources.ts
7081
+ var PENDING = Object.freeze({ at: 0, tasks: [], error: null });
7082
+
7065
7083
  class TaskSources {
7066
7084
  env;
7067
7085
  cache = new Map;
@@ -7073,11 +7091,11 @@ class TaskSources {
7073
7091
  const hit = this.cache.get(projectId);
7074
7092
  if (!hit || Date.now() - hit.at >= ttlMs)
7075
7093
  this.refresh(projectId, kind, root, opts);
7076
- return hit ?? { at: 0, tasks: [], error: null };
7094
+ return hit ?? PENDING;
7077
7095
  }
7078
7096
  async refresh(projectId, kind, root, opts) {
7079
7097
  if (this.inflight.has(projectId))
7080
- return this.cache.get(projectId) ?? { at: 0, tasks: [], error: null };
7098
+ return this.cache.get(projectId) ?? PENDING;
7081
7099
  this.inflight.add(projectId);
7082
7100
  const prev = this.cache.get(projectId);
7083
7101
  let entry;
@@ -8311,6 +8329,7 @@ ${err}
8311
8329
  return null;
8312
8330
  let hit;
8313
8331
  let error = null;
8332
+ let loading = false;
8314
8333
  const kind = taskSourceKind(source);
8315
8334
  if (kind === "github" || kind === "linear") {
8316
8335
  const e = this.taskSources.get(projectId, kind, p.root, {
@@ -8319,10 +8338,17 @@ ${err}
8319
8338
  });
8320
8339
  hit = { tasks: e.tasks };
8321
8340
  error = e.error;
8341
+ loading = e.at === 0 && e.error === null;
8322
8342
  } else {
8323
8343
  const path = join8(p.root, source);
8324
8344
  if (!existsSync6(path))
8325
- return { source, required: this.requiredGates(projectId), tasks: [] };
8345
+ return {
8346
+ source,
8347
+ required: this.requiredGates(projectId),
8348
+ tasks: [],
8349
+ error: `${source} not found in this repository`,
8350
+ loading: false
8351
+ };
8326
8352
  const mtime = statSync(path).mtimeMs;
8327
8353
  let md = this.taskCache.get(projectId);
8328
8354
  if (!md || md.path !== path || md.mtime !== mtime) {
@@ -8351,7 +8377,7 @@ ${err}
8351
8377
  gated: gatesSatisfied(tr, required)
8352
8378
  };
8353
8379
  });
8354
- return { source, required, tasks: board, error };
8380
+ return { source, required, tasks: board, error, loading };
8355
8381
  }
8356
8382
  rulesFor(repoRoot) {
8357
8383
  return this.policyFor(repoRoot).config.rules;
@@ -8672,6 +8698,9 @@ ${err}
8672
8698
  createdAt: r.created_at
8673
8699
  }));
8674
8700
  }
8701
+ liveProjects() {
8702
+ return this.projects().filter((p) => !(p.discovered && isScratchRoot(p.root)));
8703
+ }
8675
8704
  project(id) {
8676
8705
  const r = this.db.query("SELECT * FROM projects WHERE id = ?").get(id);
8677
8706
  if (!r)
@@ -10196,6 +10225,7 @@ ${p2.reason ?? ""}`.trim(),
10196
10225
  checkStalls() {
10197
10226
  const live = this.db.query("SELECT id, project_id FROM sessions WHERE state IN ('active','waiting') AND ended_at IS NULL AND last_seen_at >= ?").all(new Date(Date.now() - IDLE_MS).toISOString());
10198
10227
  const liveIds = new Set(live.map((s) => s.id));
10228
+ const before = JSON.stringify([...this.stalls].map(([id, v]) => [id, v.kind, v.reason]));
10199
10229
  for (const id of [...this.stalls.keys()])
10200
10230
  if (!liveIds.has(id))
10201
10231
  this.stalls.delete(id);
@@ -10237,6 +10267,9 @@ ${p2.reason ?? ""}`.trim(),
10237
10267
  });
10238
10268
  this.touch();
10239
10269
  }
10270
+ const after = JSON.stringify([...this.stalls].map(([id, v]) => [id, v.kind, v.reason]));
10271
+ if (after !== before)
10272
+ this.touch();
10240
10273
  return flagged;
10241
10274
  }
10242
10275
  sweepOrphans() {
@@ -10570,22 +10603,31 @@ ${p2.reason ?? ""}`.trim(),
10570
10603
  this.wtCache.clear();
10571
10604
  }
10572
10605
  async refreshAllWorktrees() {
10573
- await Promise.all(this.projects().map((p) => this.refreshWorktrees(p.id)));
10606
+ await Promise.all(this.liveProjects().map((p) => this.refreshWorktrees(p.id)));
10574
10607
  }
10575
10608
  sessions() {
10609
+ return this.memoised("sessions", 2000, () => this.computeSessions());
10610
+ }
10611
+ computeSessions() {
10612
+ const page = this.db.query("SELECT id FROM sessions ORDER BY last_seen_at DESC LIMIT 200").all();
10613
+ if (!page.length)
10614
+ return [];
10615
+ const ids = page.map((r) => r.id);
10616
+ const holes = ids.map(() => "?").join(",");
10576
10617
  const rows = this.db.query(`SELECT s.*, COUNT(t.id) AS turns, COALESCE(SUM(t.input),0) AS input, COALESCE(SUM(t.output),0) AS output,
10577
10618
  COALESCE(SUM(t.cache_write),0) AS cache_write, COALESCE(SUM(t.cache_read),0) AS cache_read, COALESCE(SUM(t.thinking),0) AS thinking,
10578
10619
  SUM(t.cost_usd) AS cost_usd, MAX(t.cost_usd IS NULL AND t.id IS NOT NULL) AS unpriced,
10579
10620
  (SELECT model FROM turns lt WHERE lt.session_id = s.id AND lt.agent_id IS NULL AND lt.sidechain = 0 ORDER BY lt.ts DESC LIMIT 1) AS live_model,
10580
10621
  (SELECT COUNT(DISTINCT model) FROM turns lm WHERE lm.session_id = s.id AND lm.agent_id IS NULL AND lm.sidechain = 0) AS model_count
10581
10622
  FROM sessions s LEFT JOIN turns t ON t.session_id = s.id
10582
- GROUP BY s.id ORDER BY s.last_seen_at DESC LIMIT 200`).all();
10623
+ WHERE s.id IN (${holes})
10624
+ GROUP BY s.id ORDER BY s.last_seen_at DESC`).all(...ids);
10583
10625
  const idleBefore = Date.now() - IDLE_MS;
10584
10626
  const sparkRows = this.db.query(`SELECT session_id, output, cost_usd FROM (
10585
10627
  SELECT t.session_id, t.output, t.cost_usd, t.ts,
10586
10628
  ROW_NUMBER() OVER (PARTITION BY t.session_id ORDER BY t.ts DESC) AS rn
10587
- FROM turns t WHERE t.agent_id IS NULL AND t.sidechain = 0
10588
- ) WHERE rn <= 24 ORDER BY ts`).all();
10629
+ FROM turns t WHERE t.agent_id IS NULL AND t.sidechain = 0 AND t.session_id IN (${holes})
10630
+ ) WHERE rn <= 24 ORDER BY ts`).all(...ids);
10589
10631
  const sparks = new Map;
10590
10632
  for (const x of sparkRows) {
10591
10633
  const a = sparks.get(x.session_id) ?? [];
@@ -11290,13 +11332,13 @@ ${p2.reason ?? ""}`.trim(),
11290
11332
  }
11291
11333
  snapshot() {
11292
11334
  const worktrees = {};
11293
- const projects = this.projects().filter((p) => !(p.discovered && isScratchRoot(p.root)));
11335
+ const projects = this.liveProjects();
11294
11336
  for (const p of projects)
11295
11337
  worktrees[p.id] = this.worktrees(p.id);
11296
11338
  return {
11297
11339
  projects,
11298
11340
  worktrees,
11299
- sessions: this.memoised("sessions", 2000, () => this.sessions()),
11341
+ sessions: this.sessions(),
11300
11342
  spend: this.memoised("spend", 30000, () => this.spend()),
11301
11343
  spendSparks: this.memoised("spendSparks", 60000, () => this.spendSparks()),
11302
11344
  claims: this.claims(),
@@ -11821,7 +11863,7 @@ class WorkflowEngine {
11821
11863
  }
11822
11864
 
11823
11865
  // packages/daemon/src/app.ts
11824
- var VERSION = "0.12.1";
11866
+ var VERSION = "0.13.1";
11825
11867
  var WEB_DIR = (() => {
11826
11868
  if (process.env.SWARM_WEB_DIR)
11827
11869
  return process.env.SWARM_WEB_DIR;
@@ -11868,6 +11910,13 @@ function diskVersion() {
11868
11910
  return null;
11869
11911
  }
11870
11912
  }
11913
+ function expandHome(p, home = homedir4()) {
11914
+ if (p === "~")
11915
+ return home;
11916
+ if (p.startsWith("~/"))
11917
+ return join10(home, p.slice(2));
11918
+ return p;
11919
+ }
11871
11920
  function createApp(store = new Store, hooks2 = {}) {
11872
11921
  const app = new Hono2;
11873
11922
  const forge2 = new ForgeService(store);
@@ -11903,13 +11952,13 @@ function createApp(store = new Store, hooks2 = {}) {
11903
11952
  schema: store.schemaVersion(),
11904
11953
  auth: store.policyFor(null).config.daemon.auth
11905
11954
  }));
11906
- app.get("/v1/projects", (c) => c.json(store.snapshot().projects));
11955
+ app.get("/v1/projects", (c) => c.json(store.liveProjects()));
11907
11956
  app.post("/v1/projects", async (c) => {
11908
11957
  const { path, name } = await c.req.json();
11909
11958
  if (!path)
11910
11959
  return c.json({ error: "path required" }, 400);
11911
11960
  try {
11912
- return c.json(store.resolveProject(path, true, name), 201);
11961
+ return c.json(store.resolveProject(expandHome(path), true, name), 201);
11913
11962
  } catch (e) {
11914
11963
  return c.json({ error: e.message }, 400);
11915
11964
  }
@@ -11929,10 +11978,12 @@ function createApp(store = new Store, hooks2 = {}) {
11929
11978
  });
11930
11979
  app.delete("/v1/projects/:id", (c) => store.removeProject(c.req.param("id")) ? c.body(null, 204) : c.json({ error: "not found" }, 404));
11931
11980
  app.get("/v1/fs/ls", (c) => {
11932
- const q = c.req.query("path");
11981
+ const q = expandHome(c.req.query("path") ?? "");
11982
+ if (q && !existsSync7(q))
11983
+ return c.json({ error: "no such folder", path: q }, 404);
11933
11984
  let dir;
11934
11985
  try {
11935
- dir = realpathSync3(q && existsSync7(q) ? q : homedir4());
11986
+ dir = realpathSync3(q || homedir4());
11936
11987
  } catch {
11937
11988
  dir = homedir4();
11938
11989
  }
@@ -11944,7 +11995,14 @@ function createApp(store = new Store, hooks2 = {}) {
11944
11995
  return c.json({ error: e.message, path: dir }, 400);
11945
11996
  }
11946
11997
  });
11947
- app.get("/v1/state", (c) => c.json(store.snapshot()));
11998
+ app.get("/v1/state", (c) => {
11999
+ const body = JSON.stringify(store.snapshot());
12000
+ const etag = `W/"${Bun.hash(body).toString(36)}"`;
12001
+ const head = { etag, "cache-control": "no-cache" };
12002
+ if (c.req.header("if-none-match") === etag)
12003
+ return c.body(null, 304, head);
12004
+ return c.body(body, 200, { ...head, "content-type": "application/json" });
12005
+ });
11948
12006
  app.get("/v1/stats", (c) => c.json(store.stats(c.req.query("project") || undefined)));
11949
12007
  app.get("/v1/graphs/collisions", (c) => c.json(store.collisions(c.req.query("project") || undefined)));
11950
12008
  app.get("/v1/graphs/lineage", (c) => c.json(store.lineage(c.req.query("project") || undefined, Math.max(1, Math.min(90, Number(c.req.query("days") ?? 14) || 14)), c.req.queries("expand") ?? [])));
@@ -11971,7 +12029,7 @@ function createApp(store = new Store, hooks2 = {}) {
11971
12029
  })));
11972
12030
  const outcomesFor = async (project, opts = {}) => {
11973
12031
  const blocking = opts.blocking !== false;
11974
- const sessions = store.snapshot().sessions.filter((s) => !project || s.projectId === project).map((s) => ({
12032
+ const sessions = store.sessions().filter((s) => !project || s.projectId === project).map((s) => ({
11975
12033
  id: s.id,
11976
12034
  branch: s.branch,
11977
12035
  model: s.model,
@@ -11982,8 +12040,9 @@ function createApp(store = new Store, hooks2 = {}) {
11982
12040
  const prs = [];
11983
12041
  const reverted = new Set;
11984
12042
  let stale = false;
11985
- for (const p of store.projects().filter((x) => !project || x.id === project)) {
11986
- const o = blocking ? { ...await forge2.merged(p.id, p.root), fresh: true } : forge2.mergedCached(p.id, p.root);
12043
+ const scope = store.liveProjects().filter((x) => !project || x.id === project);
12044
+ const outcomes2 = await Promise.all(scope.map(async (p) => blocking ? { ...await forge2.merged(p.id, p.root), fresh: true } : forge2.mergedCached(p.id, p.root)));
12045
+ for (const o of outcomes2) {
11987
12046
  if (!o.fresh)
11988
12047
  stale = true;
11989
12048
  for (const m of o.merged)
@@ -12079,7 +12138,7 @@ function createApp(store = new Store, hooks2 = {}) {
12079
12138
  acquiredAt: cl.acquiredAt,
12080
12139
  state: cl.state
12081
12140
  }));
12082
- const sessions = store.snapshot().sessions.filter((s) => !project || s.projectId === project).map((s) => ({
12141
+ const sessions = store.sessions().filter((s) => !project || s.projectId === project).map((s) => ({
12083
12142
  id: s.id,
12084
12143
  title: s.title,
12085
12144
  agent: s.agent,
@@ -12679,8 +12738,16 @@ function createApp(store = new Store, hooks2 = {}) {
12679
12738
  });
12680
12739
  });
12681
12740
  app.get("/", (c) => c.html(readFileSync4(join10(WEB_DIR, "index.html"), "utf8")));
12682
- const MIME = { js: "text/javascript", css: "text/css" };
12683
- app.get("/:file{[a-z0-9-]+\\.(js|css)}", (c) => {
12741
+ app.get("/next", (c) => c.redirect("/", 308));
12742
+ const MIME = {
12743
+ js: "text/javascript",
12744
+ css: "text/css",
12745
+ svg: "image/svg+xml",
12746
+ ico: "image/x-icon",
12747
+ png: "image/png"
12748
+ };
12749
+ const BINARY = new Set(["ico", "png"]);
12750
+ app.get("/:file{[a-z0-9-]+\\.(js|css|svg|ico|png)}", (c) => {
12684
12751
  const f = c.req.param("file");
12685
12752
  const p = join10(WEB_DIR, f);
12686
12753
  if (!existsSync7(p))
@@ -12689,8 +12756,9 @@ function createApp(store = new Store, hooks2 = {}) {
12689
12756
  const etag = `W/"${st.size.toString(16)}-${st.mtimeMs.toString(16)}"`;
12690
12757
  if (c.req.header("if-none-match") === etag)
12691
12758
  return c.body(null, 304, { etag });
12692
- return c.body(readFileSync4(p, "utf8"), 200, {
12693
- "content-type": MIME[f.split(".").pop() ?? ""] ?? "text/plain",
12759
+ const ext = f.split(".").pop() ?? "";
12760
+ return c.body(BINARY.has(ext) ? readFileSync4(p) : readFileSync4(p, "utf8"), 200, {
12761
+ "content-type": MIME[ext] ?? "text/plain",
12694
12762
  "cache-control": "no-cache",
12695
12763
  etag
12696
12764
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ra3orblade/swarm",
3
- "version": "0.12.1",
3
+ "version": "0.13.1",
4
4
  "description": "Local-first control plane for AI-agent development: watch every Claude Code / Codex / Grok session on your machine, ledger tasks and worktrees, enforce rules as hook denials.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -0,0 +1 @@
1
+ :root{--bg:#f6f7f9;--panel:#fff;--panel-2:#f0f2f5;--line:#e4e7ec;--line-2:#eef0f3;--fg:#1a1d21;--fg-2:#3d4650;--dim:#8b929c;--faint:#aab1bb;--acc:#388114;--acc-fill:#559b1b;--acc-soft:#559b1b26;--ok:#1f9d55;--ok-soft:#1f9d551f;--warn:#c77800;--warn-soft:#c778001f;--bad:#d1352b;--bad-soft:#d1352b1f;--violet:#7857c9;--c0:#8b929c;--c1:#4d9e2a;--c2:#1b9e77;--c3:#9ab828;--c4:#2f7d4f;--c5:#7fb069;--c6:#0f5c3a;--c7:#c3d84a;--acc-1:#e2f6d8;--acc-2:#b8e4a1;--acc-3:#8bc866;--acc-4:#5aa12e;--acc-5:#35741f;--mono:ui-monospace,"SF Mono",SFMono-Regular,Menlo,monospace;--sans:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;--fs-2xs:11px;--fs-xs:11.5px;--fs-sm:12px;--fs-mono:12.5px;--fs-md:13px;--fs-menu:13.5px;--fs-lg:14px;--fs-xl:15px;--fs-2xl:24px;--pill-h:calc(var(--fs-xs)*1.2 + 8px);--shadow:0 1px 2px #1018280a,0 1px 3px #1018280f;--shadow-pop:0 8px 24px #10182824,0 1px 3px #10182814;--overlay:#00000073;--r:9px;--r-sm:6px;--r-xs:4px;--r-pill:20px;--t-fast:.12s;--t-med:.2s;--t-in:.32s;--t-pulse:2s;--hdr-h:48px;--sb-w:240px;--inset-tl:96px;--acc-contrast:#fff;--pad-cell:9px 12px;--pad-page:20px 22px;--pad-ctl:7px 11px;--pad-row:7px 10px;--gap-sec:18px}@media (prefers-color-scheme:dark){:root:not([data-theme=light]){--bg:#0c0e12;--panel:#14171d;--panel-2:#1a1e26;--line:#242932;--line-2:#1e222a;--fg:#e6e9ee;--fg-2:#c2c8d0;--dim:#7c8592;--faint:#5a626e;--acc:#a3e635;--acc-fill:#a3e635;--acc-soft:#a3e63524;--ok:#3fb571;--ok-soft:#3fb57124;--warn:#d9a441;--warn-soft:#d9a44124;--bad:#f0655a;--bad-soft:#f0655a24;--violet:#a68bec;--c0:#7c8592;--c1:#6bbf3a;--c2:#2bb78f;--c3:#b0cf3a;--c4:#3f9d63;--c5:#93c47d;--c6:#2f8f5a;--c7:#d3e88a;--acc-1:#1c3311;--acc-2:#2f5a18;--acc-3:#4d8a24;--acc-4:#7cc02f;--acc-5:#bfef7a;--shadow:0 1px 2px #0000004d,0 2px 6px #00000040;--shadow-pop:0 8px 24px #00000080,0 1px 3px #0006;--acc-contrast:#0e1013}}:root[data-theme=dark]{--bg:#0c0e12;--panel:#14171d;--panel-2:#1a1e26;--line:#242932;--line-2:#1e222a;--fg:#e6e9ee;--fg-2:#c2c8d0;--dim:#7c8592;--faint:#5a626e;--acc:#a3e635;--acc-fill:#a3e635;--acc-soft:#a3e63524;--ok:#3fb571;--ok-soft:#3fb57124;--warn:#d9a441;--warn-soft:#d9a44124;--bad:#f0655a;--bad-soft:#f0655a24;--violet:#a68bec;--c0:#7c8592;--c1:#6bbf3a;--c2:#2bb78f;--c3:#b0cf3a;--c4:#3f9d63;--c5:#93c47d;--c6:#2f8f5a;--c7:#d3e88a;--acc-1:#1c3311;--acc-2:#2f5a18;--acc-3:#4d8a24;--acc-4:#7cc02f;--acc-5:#bfef7a;--shadow:0 1px 2px #0000004d,0 2px 6px #00000040;--shadow-pop:0 8px 24px #00000080,0 1px 3px #0006;--acc-contrast:#0e1013}*{box-sizing:border-box}html,body{height:100%}html{zoom:var(--ui-zoom,1)}body{background:var(--bg);color:var(--fg);font:var(--fs-lg)/1.5 var(--sans);-webkit-font-smoothing:antialiased;display:grid;grid-template-rows:var(--hdr-h)1fr;grid-template-columns:var(--sb-w)1fr;overflow:hidden;animation:appin var(--t-in)ease both;user-select:none;-webkit-user-select:none;margin:0}@keyframes appin{0%{opacity:0}to{opacity:1}}input,textarea,tbody td,.mono,code,kbd{user-select:text;-webkit-user-select:text}::selection{background:var(--acc-soft)}a{color:var(--acc);text-decoration:none}.dim{color:var(--dim)}.faint{color:var(--faint)}.mono{font-family:var(--mono);font-size:var(--fs-mono)}.num{text-align:right;font-variant-numeric:tabular-nums;font-feature-settings:"tnum"}kbd{font:var(--fs-mono)var(--mono);background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r-xs);color:var(--fg-2);padding:1px 5px}code{font:var(--fs-md)var(--mono);color:var(--fg-2)}body.nosb{grid-template-columns:46px 1fr}body.nosb aside{overflow:hidden;padding:12px 5px}body.nosb #projects{display:none}.sb-grip{position:fixed;top:var(--hdr-h);bottom:0;left:calc(var(--sb-w) - 4px);cursor:col-resize;z-index:40;background:0 0;border:0;border-radius:0;width:9px;padding:0}.sb-grip:hover,.sb-grip:active{outline:none;background:linear-gradient(to right,transparent 3px,var(--acc)3px,var(--acc)5px,transparent 5px)}.sb-grip:focus-visible{outline:none;background:linear-gradient(to right,transparent 3px,var(--acc)3px,var(--acc)5px,transparent 5px)}body.nosb .sb-grip{display:none}#root>aside{grid-column:1;grid-row:2}#root>main{grid-column:2;grid-row:2}#projects h4{display:flex;align-items: center;gap:5px}.h4-act{cursor:pointer;color:var(--faint);display:inline-flex;border-radius:var(--r-xs);margin-left:auto;padding:2px}.h4-act:hover{color:var(--acc)}.pk-h input{background:var(--panel-2);border:1px solid var(--line);color:var(--fg);border-radius:var(--r-sm);font:var(--fs-mono)var(--mono);outline:none;flex:1;min-width:0;padding:5px 9px}.pk-h input:focus{border-color:var(--acc)}header{grid-column:1/3;display:flex;border-bottom:1px solid var(--line);background:var(--panel);z-index:5;align-items: center;gap:6px;padding:0 16px}html.chrome-inset header{padding-left:var(--inset-tl)}html.chrome-inset.fs header{padding-left:16px}html.chrome-inset header{cursor:default}header .logo{letter-spacing:.02em;font-weight:700;font-size:var(--fs-xl);display:flex;align-items: center;gap:8px;margin-right:14px}header .logo svg{image-rendering:pixelated;image-rendering:crisp-edges;width:22px;height:auto;transform:translateY(-2px)}header .nav{color:var(--dim);border-radius:var(--r-sm);transition:color var(--t-fast),background var(--t-fast);padding:6px 11px;font-weight:500}header .nav:hover{color:var(--fg-2)}header .nav.on{color:var(--fg);background:var(--panel-2)}header .sp{flex:1}header #today{color:var(--dim);font-variant-numeric:tabular-nums;margin-right:18px}header #today b{color:var(--fg-2);font-weight:600}#daemon{display:flex;color:var(--dim);font-size:var(--fs-md);align-items: center;gap:7px}#daemon .dot{background:var(--bad);transition:background var(--t-med);border-radius:50%;width:7px;height:7px}#daemon .dot.on{background:var(--ok);box-shadow:0 0 0 3px var(--ok-soft)}#root>aside{border-right:1px solid var(--line);background:var(--panel);overflow:auto;display:flex;flex-direction:column;padding:12px 10px}#viewnav{display:flex;align-items: center;gap:2px}#viewnav .navgrp{display:flex;color:var(--dim);border-radius:var(--r-sm);font:inherit;font-size:var(--fs-md);cursor:pointer;transition:color var(--t-fast),background var(--t-fast);background:0 0;border:0;align-items: center;gap:6px;padding:6px 10px;font-weight:500}#viewnav .navgrp:hover{color:var(--fg-2);background:var(--panel-2)}#viewnav .navgrp.on{color:var(--fg);background:var(--panel-2)}#viewnav .navview{color:var(--acc);font-weight:600}#viewnav .navview:before{content:"";display:inline-block;background:var(--line);width:1px;height:11px;margin:0 7px -1px}#viewnav .navgrp.open{color:var(--fg);background:var(--line)}#viewnav .navgrp.open .chev{opacity:.9;transform:rotate(180deg)}#viewnav .navgrp .chev{opacity:.5;flex:none}#viewnav .navcount{background:var(--bad-soft);color:var(--bad);font-size:var(--fs-2xs);border-radius:99px;padding:0 5px;font-weight:600}#root>aside h4{color:var(--faint);font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.09em;margin:14px 8px 6px;font-weight:600}#root>aside h4:first-child{margin-top:2px}.proj{position:relative;padding:var(--pad-row);border-radius:var(--r-sm);cursor:pointer;display:flex;color:var(--fg-2);transition:background var(--t-fast);user-select:none;align-items: center;gap:9px}.proj:hover{background:var(--panel-2)}.proj.sel{background:var(--acc-soft);color:var(--fg)}.proj.sel .nm{font-weight:600}.proj .st{background:var(--faint);transition:background var(--t-med);border-radius:50%;flex:none;width:7px;height:7px}.proj .st.live{background:var(--ok);box-shadow:0 0 0 3px var(--ok-soft)}.proj .nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.proj small{color:var(--dim);font-variant-numeric:tabular-nums;font-size:var(--fs-sm);text-align:right;flex:none;min-width:15px}.proj .act{position:absolute;right:var(--pad-row-x,7px);display:inline-flex;opacity:0;pointer-events:none;color:var(--faint);font-size:var(--fs-lg);border-radius:var(--r-xs);transition:color var(--t-fast),background var(--t-fast),opacity var(--t-fast);padding:2px;line-height:1;top:50%;transform:translateY(-50%)}.proj:hover .act,.proj.menu-open .act{opacity:1;pointer-events:auto}.proj .act:focus-visible{opacity:1;pointer-events:auto}.proj:hover small,.proj.menu-open small{visibility:hidden}.proj .act:hover{color:var(--fg);background:var(--line)}.proj .act.rm:hover{color:var(--bad)}.srch{display:flex;gap:8px;margin-bottom:14px}.srch input{background:var(--panel);border:1px solid var(--line);color:var(--fg);border-radius:var(--r-sm);font:var(--fs-md)var(--sans);flex:1;max-width:640px;padding:8px 12px}.srch input:focus{border-color:var(--acc);outline:none}.hit{border:1px solid var(--line);border-radius:var(--r-sm);background:var(--panel);margin-bottom:8px;padding:10px 12px}.hit .ht{display:flex;align-items: center;gap:8px;margin-bottom:4px}.hit .hs{font-size:var(--fs-sm);color:var(--fg-2);white-space:pre-wrap}.hit mark{background:var(--warn-soft);color:var(--fg);border-radius:2px;padding:0 1px}.dr-rules{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px 16px;margin:10px 0}.dr-rule{display:flex;font-size:var(--fs-sm);align-items: center;gap:8px}.dr-rule select{background:var(--panel-2);color:var(--fg);border:1px solid var(--line);border-radius:var(--r-sm);padding:2px 4px}.dr-flaky{border:1px solid var(--line);border-radius:var(--r-sm);background:var(--panel-2);margin:6px 0;padding:6px 8px}table.plain{border-collapse:collapse;font-size:var(--fs-sm);width:100%}table.plain td{border-top:1px solid var(--line);vertical-align:top;padding:3px 6px}.pk.pal{align-self: start;margin-top:9vh}.pal .pk-list{min-height:120px;max-height:min(440px,62vh)}.pal .pk-row.on{background:var(--acc-soft);color:var(--fg)}.pal-empty{padding:16px}.pal .pk-row .grp{color:var(--faint);font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.07em;margin-left:auto}#picker:empty{display:none}#picker{position:fixed;background:var(--overlay);display:grid;z-index:50;place-items: center;inset:0}.pk{display:flex;background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow-pop);overflow:hidden;flex-direction:column;width:min(560px,92vw);max-height:78vh}.pk-h{display:flex;border-bottom:1px solid var(--line);align-items: center;gap:8px;padding:12px 14px}.pk-list{overflow:auto;padding:6px}.pk-row{display:flex;padding:var(--pad-row);border-radius:var(--r-sm);cursor:pointer;color:var(--fg-2);align-items: center;gap:9px}button.pk-row{font:inherit;line-height:inherit;text-align:left;background:0 0;border:0;justify-content:flex-start;width:100%}.pk-row:hover{background:var(--panel-2)}.pk-row .nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.pk-row.up{color:var(--dim)}.pk-row .badge{margin:0}.pk-b{display:grid;gap:10px;padding:12px 14px}.pk-b label{display:grid;font:500 var(--fs-sm)var(--sans);color:var(--dim);gap:4px}.pk-b input,.pk-b textarea,.pk-b select{background:var(--panel-2);border:1px solid var(--line);color:var(--fg);border-radius:var(--r-sm);font:var(--fs-md)var(--sans);outline:none;width:100%;padding:7px 9px}.pk-b textarea{resize:vertical;font-family:var(--mono);font-size:var(--fs-sm);min-height:120px;line-height:1.5}.pk-b input:focus,.pk-b textarea:focus,.pk-b select:focus{border-color:var(--acc)}.pk-b .row{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px}.pk.wide{width:min(960px,96vw)}.df-files{display:grid;overflow:auto;border:1px solid var(--line);border-radius:var(--r-sm);gap:1px;max-height:22vh}.df-files a{display:flex;color:var(--fg-2);text-decoration:none;font:var(--fs-sm)var(--mono);gap:10px;padding:4px 8px}.df-files a:hover,.df-files a.on{background:var(--panel-2);color:var(--fg)}.df-files .st{color:var(--dim);width:12px}.df-files .pl{color:var(--ok)}.df-files .mi{color:var(--warn)}.df-files .pa{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.df-patch{overflow:auto;background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r-sm);font:var(--fs-sm)var(--mono);white-space:pre;max-height:44vh;margin:0;padding:8px 10px;line-height:1.45}.df-patch .a{color:var(--ok)}.df-patch .d{color:var(--warn)}.df-patch .h{color:var(--acc)}.df-patch .m{color:var(--dim)}.wn .pk-b{overflow:auto;gap:2px;max-height:64vh}.wn h3{font:600 var(--fs-lg)var(--sans);color:var(--fg);margin:0 0 2px}.wn .date{color:var(--dim);font-size:var(--fs-sm);margin-bottom:12px}.wn h4{font:600 var(--fs-md)var(--sans);color:var(--acc);text-transform:none;letter-spacing:0;margin:14px 0 4px}.wn ul{margin:0 0 6px;padding-left:18px}.wn li{color:var(--fg-2);margin:5px 0;line-height:1.55}.wn p{color:var(--fg-2);margin:0 0 10px;line-height:1.6}.wn code{font-family:var(--mono);font-size:var(--fs-sm);background:var(--panel-2);border-radius:var(--r-xs);padding:1px 5px}.wn a{color:var(--acc)}.wn b{color:var(--fg)}.wn .snip{background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r-sm);font-family:var(--mono);font-size:var(--fs-sm);white-space:pre-wrap;word-break:break-word;color:var(--fg-2);margin:2px 0 12px;padding:9px 11px;line-height:1.5}.wn .cbtn{font-weight:500;font-size:var(--fs-sm);color:var(--acc);margin-left:8px}.rp{height:78vh}.rp .pk-b{overflow:auto;flex:1;gap:2px;min-height:0;max-height:none}.rp .snip{overflow:visible;max-height:none}.rp .pk-f input[type=range]{accent-color:var(--acc)}.perm{background:var(--warn-soft);border:1px solid var(--warn);border-radius:var(--r-sm);margin-top:10px;padding:10px 12px}.perm-t{font:500 var(--fs-md)var(--sans);color:var(--fg)}.perm-c{font-family:var(--mono);font-size:var(--fs-sm);color:var(--fg-2);word-break:break-all;background:var(--panel-2);border-radius:var(--r-xs);margin:6px 0 8px;padding:6px 8px}.perm-b{display:flex;gap:8px}.perm-b .ok{color:var(--acc);border-color:var(--acc-soft)}.perm-b .danger{color:var(--warn);border-color:var(--warn-soft)}.stdin{display:flex;align-items: center;gap:8px;margin-top:10px}.stdin input{background:var(--panel-2);border:1px solid var(--line);color:var(--fg);border-radius:var(--r-sm);font:var(--fs-md)var(--sans);outline:none;flex:1;padding:8px 10px}.stdin input:focus{border-color:var(--acc)}.stdin button.danger{color:var(--warn);border-color:var(--warn-soft)}.stdin .hint{color:var(--dim);font-size:var(--fs-sm)}.pk-f{display:flex;border-top:1px solid var(--line);gap:8px;padding:12px 14px}.pk-f button{flex:none}.pk-err{color:var(--bad);font-size:var(--fs-sm)}.pk-f .pk-err{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;align-self: center}.grow{flex:1}.pk-f .primary{background:var(--acc);border-color:var(--acc);color:var(--acc-contrast)}.pk-f .primary:hover{filter:brightness(1.06);color:var(--acc-contrast)}.proj .nm .pdir{opacity:.5;font-weight:400}.proj[draggable]{cursor:grab}.proj.dragging{opacity:.35;background:var(--panel-2);outline:1px dashed var(--acc)}button{background:var(--panel);border:1px solid var(--line);color:var(--fg-2);padding:var(--pad-ctl);border-radius:var(--r-sm);font:600 var(--fs-md)var(--sans);cursor:pointer;transition:border-color var(--t-fast),color var(--t-fast);display:inline-flex;justify-content:center;align-items: center;gap:6px;line-height:1}.pk-h button{padding:6px;line-height:0}button:hover{border-color:var(--acc);color:var(--fg)}main{overflow:auto;padding:var(--pad-page)}h2.hrow{display:flex;flex-wrap:wrap;align-items: center;gap:7px}h2.hrow .s{margin:0}h2{font-size:var(--fs-md);color:var(--dim);text-transform:uppercase;letter-spacing:.07em;display:flex;align-items:baseline;gap:10px;margin:0 0 12px;font-weight:600}h2 span{text-transform:none;letter-spacing:0;color:var(--faint);font-weight:500;font-size:var(--fs-md)}h2 .nav{display:inline-flex;font:500 var(--fs-md)var(--sans);text-transform:none;letter-spacing:0;color:var(--acc);text-decoration:none;border-radius:var(--r-sm);transition:background var(--t-fast);align-items: center;gap:5px;padding:2px 8px}h2 .nav:hover,h2 .nav.on{background:var(--acc-soft)}h2 .nav .ph{display:block}h2 .back{color:var(--fg-2);text-transform:none;letter-spacing:0;font-weight:600;font-size:var(--fs-md);display:inline-flex;border:1px solid var(--line);border-radius:var(--r-pill);background:var(--panel);transition:border-color var(--t-fast),color var(--t-fast);align-items: center;gap:5px;padding:5px 11px 5px 8px}.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);overflow-x:auto}table{border-collapse:collapse;table-layout:fixed;width:100%}.card table{background:var(--panel)}thead th{text-align:left;padding:var(--pad-cell);color:var(--dim);font-weight:500;font-size:var(--fs-sm);border-bottom:1px solid var(--line);background:var(--panel-2);white-space:nowrap}tbody td{padding:var(--pad-cell);border-bottom:1px solid var(--line-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;vertical-align:middle}tbody tr:last-child td{border-bottom:0}tbody td.td-tools{text-overflow:clip;text-align:center;padding-left:0;padding-right:0;line-height:0}tbody td.td-badge{text-overflow:clip}tbody td.td-badge .badge{margin-right:0}tbody tr{transition:background var(--t-fast)}tbody tr[data-s]{cursor:pointer}tbody tr:hover{background:var(--panel-2)}tbody td.td-lead{overflow:visible}tbody td{--cap:calc(var(--fs-lg)*.352)}tbody td .pg{vertical-align:calc(var(--cap) - 8px)}tbody td .pg:before{content:"";display:block;width:0;height:16px}tbody td>*+.badge{overflow:hidden;vertical-align:calc(var(--cap) - var(--pill-h)/2)}tbody td:not(.td-tools)>svg.ph[width="12"]{vertical-align:calc(var(--cap) - 6px)}tbody td:not(.td-tools)>svg.ph[width="13"]{vertical-align:calc(var(--cap) - 6.5px)}tbody td:not(.td-tools)>svg.ph[width="14"]{vertical-align:calc(var(--cap) - 7px)}tbody td:not(.td-tools)>svg.ph[width="16"]{vertical-align:calc(var(--cap) - 8px)}.dt thead th{position:relative;user-select:none;-webkit-user-select:none}.dt thead tr:first-child th[data-col]:not(:last-child):after{content:"";position:absolute;border-right:1px solid var(--line);height:50%;top:25%;right:0}.dt thead th[data-sort]{cursor:pointer}.dt thead th[data-sort]:hover{color:var(--fg-2)}.dt thead th.dragging{opacity:.4}.dt .th-l{display:inline-flex;align-items: center;gap:5px}.dt th.num .th-l{justify-content:flex-end;width:100%}.dt .sort{display:inline-block;border-left:4px solid #0000;border-right:4px solid #0000;width:0;height:0}.dt .sort.asc{border-bottom:5px solid var(--acc)}.dt .sort.desc{border-top:5px solid var(--acc)}.dt .th-grip{position:absolute;cursor:col-resize;z-index:2;width:9px;height:100%;top:0;right:-4px}.dt .th-grip:hover{background:linear-gradient(to right,transparent 3px,var(--acc)3px,var(--acc)5px,transparent 5px)}.dt thead th:last-child .th-grip{right:0}.dt thead th:last-child .th-grip:hover{background:linear-gradient(to left,var(--acc)0,var(--acc)2px,transparent 2px)}body.col-resizing,body.col-resizing .dt thead th{cursor:col-resize}.dt .th-tools{text-align:right}.dt .th-tools,.dt .td-tools{position:sticky;z-index:1;right:0}.dt .th-tools{background:var(--panel-2)}.dt .td-tools{background:var(--panel)}tbody tr:hover .td-tools{background:var(--panel-2)}.dt .th-cols{cursor:pointer;color:var(--faint);display:inline-flex}.dt .th-cols:hover{color:var(--fg-2)}.dt tr.filters th{background:var(--panel);padding:4px 8px}.dt tr.filters input{background:var(--panel-2);border:1px solid var(--line);color:var(--fg);border-radius:var(--r-sm);font:var(--fs-sm)var(--mono);outline:none;width:100%;padding:3px 7px}.dt tr.filters input:focus{border-color:var(--acc)}.dt .tbl-empty td{color:var(--dim);text-align:center;cursor:default;padding:18px}td b{color:var(--fg);font-weight:600}td.now,.br{font-family:var(--mono);font-size:var(--fs-mono);color:var(--fg-2)}.br+.badge{margin-left:6px}td.now{color:var(--dim)}td a{color:var(--acc)}.num,th.num{text-align:right}.s{display:inline-block;background:var(--faint);border-radius:50%;flex:none;width:8px;height:8px}.s.active{background:var(--ok);box-shadow:0 0 0 3px var(--ok-soft);animation:pulse var(--t-pulse)ease-in-out infinite}.s.waiting{background:var(--warn);box-shadow:0 0 0 3px var(--warn-soft)}.s.idle{background:var(--faint)}.s.ended{background:var(--line);border:1px solid var(--faint);box-shadow:none}@keyframes pulse{0%,to{box-shadow:0 0 0 3px var(--ok-soft)}50%{box-shadow:0 0 0 5px #0000}}.badge{display:inline-block;font:500 var(--fs-xs)/1.2 var(--sans);border-radius:var(--r-pill);background:var(--panel-2);color:var(--dim);vertical-align:middle;margin-right:4px;padding:4px 8px}.badge.warn{color:var(--warn);background:var(--warn-soft)}.badge.acc{color:var(--acc);background:var(--acc-soft)}.badge.ok{color:var(--ok);background:var(--ok-soft)}.badge.bad{color:var(--bad);background:var(--bad-soft)}.badge.agent{color:var(--violet);background:color-mix(in srgb,var(--violet)14%,transparent);font-weight:600}.err-h{color:var(--bad);align-items: center}.err-h .err-ic{color:var(--bad);flex:none}.err-card{border:1px solid var(--bad);max-width:760px;padding:18px}.err-detail{background:var(--panel-2);border-radius:var(--r-sm);font:var(--fs-mono)/1.5 var(--mono);color:var(--fg-2);white-space:pre-wrap;overflow-x:auto;border:0;max-height:220px;margin:0;padding:10px 12px}.clusters{list-style:none;margin:0;padding:0}.clusters li{display:grid;grid-template-columns:minmax(0,13em)3.2em minmax(0,1fr);border-bottom:1px solid var(--line-2);font-size:var(--fs-md);align-items: center;gap:10px;padding:6px 0}.clusters li:last-child{border-bottom:0}.clusters li>*{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}.clusters li b{font-family:var(--mono);font-size:var(--fs-mono);color:var(--fg)}.clusters li .n{color:var(--fg-2);text-align:right;font-variant-numeric:tabular-nums}.clusters li>span:last-child{color:var(--dim);font-family:var(--mono);font-size:var(--fs-mono)}.plainlist{list-style:none;display:flex;flex-direction:column;gap:6px;margin:0;padding:0}.plainlist li{display:flex;font-size:var(--fs-md);color:var(--fg-2);border-bottom:1px solid var(--line-2);align-items: center;gap:8px;padding:5px 0}.plainlist li:last-child{border-bottom:0}.plainlist li b{font-family:var(--mono);font-size:var(--fs-mono);color:var(--fg);font-weight:600}td.path,span.path{font-family:var(--mono);font-size:var(--fs-mono)}td.path b,span.path b{color:var(--fg);font-weight:600}td.path .dim,span.path .dim{font-size:var(--fs-xs)}.heatlist{list-style:none;display:flex;flex-direction:column;gap:2px;margin:0;padding:0}.heatlist li{position:relative;display:flex;font-size:var(--fs-md);color:var(--fg-2);border-radius:var(--r-sm);overflow:hidden;align-items: center;gap:10px;padding:6px 10px}.heatlist li .bar{position:absolute;width:var(--w);background:var(--acc-soft);border-radius:var(--r-sm);inset:0 auto 0 0}.heatlist li>span.clip{position:relative;font-family:var(--mono);font-size:var(--fs-mono);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}.heatlist li b{position:relative;font-variant-numeric:tabular-nums;color:var(--fg)}.heatlist li .dim{position:relative;font-size:var(--fs-sm);white-space:nowrap}.empty{color:var(--dim);text-align:center;border:1px dashed var(--line);border-radius:var(--r);background:var(--panel);padding:44px 24px;line-height:1.7}.sess{display:grid;grid-template-columns:1fr 1fr;align-items:stretch;gap:16px}.sess>*{height:calc(100vh - 140px);min-height:320px}.sess.has-stdin>*{height:calc(100vh - 196px)}@media (max-width:980px){.sess{grid-template-columns:1fr}.sess>*{height:auto;max-height:calc(100vh - 140px)}}#log{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);overflow:auto;padding:6px 0}.ev{display:grid;grid-template-columns:56px 54px 1fr;align-items:baseline;gap:10px;padding:5px 14px}.ev:hover{background:var(--panel-2)}.ev .t{color:var(--faint);font:var(--fs-sm)var(--mono)}.ev .k{color:var(--dim);font:500 var(--fs-sm)var(--mono);text-transform:lowercase;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ev .m{white-space:pre-wrap;word-break:break-word;color:var(--fg-2);font-size:var(--fs-menu)}.ev.assistant .k{color:var(--violet)}.ev.assistant .m{color:var(--fg)}.ev.tool\.requested .k{color:var(--acc)}.ev.tool\.requested .m{color:var(--dim);font-family:var(--mono);font-size:var(--fs-mono)}.ev.incident\.opened .k,.ev.incident\.opened .m{color:var(--warn)}.ev.session\.ended .k{color:var(--bad)}.ev.prompt\.submitted .k{color:var(--ok)}.ev.prompt\.submitted .m{color:var(--fg);font-weight:500}.side{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);overflow:auto;padding:14px 18px}.side .stats{display:grid;grid-template-columns:1fr 1fr;gap:0 28px}.stat{display:flex;border-bottom:1px solid var(--line-2);justify-content:space-between;align-items:baseline;padding:7px 0}.stat:last-of-type{border-bottom:0}.stat span{color:var(--dim)}.stat b{font-variant-numeric:tabular-nums;font-weight:600}.stat small{color:var(--faint);font-weight:400}.side h4{color:var(--faint);font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.08em;margin:16px 0 6px;font-weight:600}table.mini{table-layout:fixed;border:0;width:100%}table.mini td{font-size:var(--fs-md);vertical-align:middle;border:0;padding:6px 0}table.mini th{vertical-align:middle;padding-top:6px;padding-bottom:6px}table.mini th+th,table.mini td+td{padding-left:14px}table.mini td:first-child{color:var(--fg-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}table.mini td.num{color:var(--dim);width:34px}table.mini td.clip{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bars{display:flex;background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);margin-bottom:var(--gap-sec);align-items: flex-end;gap:5px;height:110px;padding:14px 16px 26px}.bar{position:relative;display:flex;flex:1;align-items: flex-end;min-width:0;height:100%}.bar>div{background:linear-gradient(180deg,var(--acc),color-mix(in srgb,var(--acc)70%,transparent));border-radius:var(--r-xs)var(--r-xs)0 0;transition:opacity var(--t-fast);width:100%;min-height:2px}.bar:hover>div{opacity:.75}.bar span{position:absolute;text-align:center;font:var(--fs-2xs)var(--mono);color:var(--faint);bottom:-20px;left:0;right:0}.cols{display:grid;grid-template-columns:1fr 1fr;gap:var(--gap-sec);align-items: start}.chart{display:block;overflow:visible;width:100%}.chart .grid{stroke:var(--line-2);stroke-width:1;vector-effect:non-scaling-stroke}.chart .base{stroke:var(--line);stroke-width:1;vector-effect:non-scaling-stroke}.chart .ax{fill:var(--faint);font:var(--fs-2xs)var(--mono);text-anchor:start}.chart .ax.mid{text-anchor:middle}.chart .col:hover path{filter:brightness(1.12)}.chart-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);margin-bottom:var(--gap-sec);padding:14px 16px 10px}.chart-card h3{font-size:var(--fs-md);color:var(--fg-2);display:flex;align-items:baseline;gap:10px;margin:0 0 10px;font-weight:600}.chart-card h3 span{color:var(--faint);font-weight:400}.legend{display:flex;font-size:var(--fs-mono);color:var(--fg-2);flex-wrap:wrap;gap:4px 14px;margin:8px 0 0}.legend span{display:inline-flex;align-items: center;gap:6px}.legend i{display:inline-block;border-radius:2px;width:9px;height:9px}.legend em{color:var(--dim);font-variant-numeric:tabular-nums;font-style:normal}.legend.small{font-size:var(--fs-sm);margin-top:6px}#tip{position:fixed;display:none;z-index:50;pointer-events:none;background:var(--fg);color:var(--bg);border-radius:var(--r-sm);font:var(--fs-mono)/1.5 var(--sans);box-shadow:var(--shadow-pop);font-variant-numeric:tabular-nums;max-width:280px;padding:7px 10px;top:0;left:0}#tip b{font-weight:600}#tip span{opacity:.7}#tip i{display:inline-block;border-radius:2px;width:8px;height:8px;margin-right:6px}.spark{display:block;overflow:visible;width:88px;height:20px}.spark path{fill:none;stroke-width:1.5;vector-effect:non-scaling-stroke;stroke-linejoin:round}.comp{display:flex;border-radius:var(--r-xs);overflow:hidden;gap:2px;height:12px;margin-top:4px}.comp i{display:block;transition:filter var(--t-fast);min-width:2px}.comp i:hover{filter:brightness(1.15)}.hbars{display:flex;flex-direction:column;gap:4px}.hb{display:grid;grid-template-columns:minmax(0,120px)1fr auto;font-size:var(--fs-md);align-items: center;gap:8px}.hb .k{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--fg-2)}.hb .t{background:var(--panel-2);overflow:hidden;border-radius:3px;height:8px}.hb .t i{display:block;border-radius:3px;min-width:2px;height:100%}.hb .n{text-align:right;white-space:nowrap;color:var(--dim);font-variant-numeric:tabular-nums;font-size:var(--fs-mono)}.cal{position:relative;padding-top:16px}.cal-months{position:absolute;height:14px;top:0;left:0;right:0}.cal-months span{position:absolute;font:var(--fs-2xs)var(--mono);color:var(--faint)}.cal-grid{display:grid;gap:2px}.cal-col{display:grid;grid-template-rows:repeat(7,1fr);aspect-ratio:1/7;gap:2px}.cal-col i{display:block;background:var(--acc);outline:1px solid var(--line-2);outline-offset:-1px;border-radius:2px}.cal-col i:hover{outline-color:var(--fg)}.chart.line .pt circle{opacity:0}.chart.line .pt:hover circle{opacity:1}.records{display:grid;grid-template-columns:1fr 1fr;gap:12px}.rec{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);display:flex;flex-direction:column;gap:2px;padding:10px 14px}.rec .l{font-size:var(--fs-sm);color:var(--dim);text-transform:uppercase;letter-spacing:.06em;font-weight:600}.rec .v{font-size:var(--fs-lg);color:var(--fg);font-variant-numeric:tabular-nums;font-weight:600}.rec .d{font-size:var(--fs-mono);color:var(--faint);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.hm{display:grid;grid-template-columns:34px 1fr;align-items: center;gap:3px 8px}.hm-lbl{font:var(--fs-xs)var(--mono);color:var(--faint)}.hm-row{display:grid;grid-template-columns:repeat(24,1fr);gap:2px;height:16px}.hm-row i{display:block;background:var(--acc);outline:1px solid var(--line-2);outline-offset:-1px;border-radius:2px}.hm-row i:hover{outline-color:var(--fg)}.hm-hours{position:relative;font:var(--fs-2xs)var(--mono);color:var(--faint);height:14px}.hm-hours span{position:absolute;top:0}.tl{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);padding:8px 16px 12px}.tl-row{display:grid;grid-template-columns:200px 1fr;cursor:pointer;border-radius:var(--r-xs);align-items: center;gap:12px;height:24px}.tl-row.claimlane{cursor:default}.tl-row:hover{background:var(--panel-2)}.tl-row.head{cursor:default;position:sticky;background:var(--panel);z-index:2;height:22px;margin-bottom:4px;top:-20px}.tl-row.head:hover{background:var(--panel)}.tl-name{font-size:var(--fs-md);color:var(--fg-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding-left:10px}.tl-track{position:relative;height:100%}.tl-track>i{position:absolute;opacity:.9;border-radius:3px;min-width:3px;height:12px;top:6px}.tl-track>i.live{box-shadow:0 0 0 2px var(--panel),0 0 0 3px currentColor;animation:none}.tl-track>i:hover{opacity:1;filter:brightness(1.1)}.tl-track>i:not([data-tip]){background:var(--line-2);opacity:1;pointer-events:none;border-radius:0;width:1px;min-width:0;height:100%;top:0}.tl-axis{position:absolute;font:var(--fs-2xs)var(--mono);color:var(--faint);inset:0}.tl-axis span{position:absolute;top:3px;transform:translate(-50%)}.tl-now{position:absolute;background:var(--bad);z-index:1;pointer-events:none;width:1px;top:0;bottom:-2000px}.tl-group{position:relative;border-top:1px solid var(--line-2);margin-top:6px;padding-top:4px}.tl-proj{font-size:var(--fs-sm);color:var(--dim);text-transform:uppercase;letter-spacing:.06em;padding:4px 10px 2px;font-weight:600}.tl-proj small{text-transform:none;letter-spacing:0;color:var(--faint);margin-left:8px;font-weight:400}.tl,.tl-group{overflow:hidden}.sw{display:inline-block;vertical-align:baseline;border-radius:2px;width:9px;height:9px;margin-right:7px}.kpis{display:grid;grid-template-columns:repeat(4,1fr);margin-bottom:var(--gap-sec);gap:12px}.kpi{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);padding:12px 14px}.kpi .l{font-size:var(--fs-sm);color:var(--dim);text-transform:uppercase;letter-spacing:.06em;font-weight:600}.kpi .v{font-size:var(--fs-2xl);font-variant-numeric:tabular-nums;color:var(--fg);margin-top:2px;font-weight:600}.kpi .d{font-size:var(--fs-mono);color:var(--faint)}.ph{display:inline-block;vertical-align:-.09em;flex:none}header .nav{display:inline-flex;align-items: center;gap:6px}.navcount{display:inline-block;font:500 var(--fs-xs)var(--sans);text-transform:none;letter-spacing:0;color:var(--warn);background:var(--warn-soft);border-radius:var(--r-pill);padding:4px 8px;line-height:1.2}header .nav .ph{opacity:.75}header .nav.on .ph{opacity:1;color:var(--acc)}.icon-btn{display:inline-flex;border-radius:var(--r-sm);color:var(--dim);background:0 0;border:1px solid #0000;justify-content:center;align-items: center;width:28px;height:28px;padding:0}.icon-btn:hover{background:var(--panel-2);color:var(--fg);border-color:#0000}.icon-btn.on{background:var(--panel-2);color:var(--fg)}header #settings{margin-left:10px}.proj .ph{color:var(--faint)}.proj.sel .ph{color:var(--acc)}.kind{color:var(--faint);margin-right:6px}td .more{opacity:0;color:var(--faint);border-radius:var(--r-xs);transition:opacity var(--t-fast),background var(--t-fast);display:inline-flex;vertical-align:middle;padding:2px;line-height:0}tr:hover td .more,.proj:hover .more,tr.menu-open td .more,.menu-open>.more,.more.menu-open{opacity:1}td .more:hover{background:var(--line);color:var(--fg)}.mini-act{display:inline-flex;color:var(--fg-2);border:1px solid var(--line);border-radius:var(--r-sm);font-size:var(--fs-sm);text-decoration:none;transition:color var(--t-fast),border-color var(--t-fast);align-items: center;padding:2px 9px;font-weight:500}.mini-act:hover{color:var(--acc);border-color:var(--acc)}.mini-act.bad:hover{color:var(--bad);border-color:var(--bad)}.track{display:inline-flex;align-items: center;gap:3px}.track i{background:var(--line);border:1px solid var(--line-2);border-radius:50%;width:7px;height:7px}.track i.on{background:var(--acc);border-color:var(--acc)}.chip.off{opacity:.4;pointer-events:none}.stat .ph{color:var(--faint);margin-right:7px}.empty .ph{display:block;color:var(--faint);margin:0 auto 10px}.empty .px{display:block;image-rendering:pixelated;margin:0 auto 14px}h2 .back:hover{border-color:var(--acc);color:var(--fg)}:root{--fm-surface-bg:var(--panel);--fm-surface-fg:var(--fg);--fm-surface-border:var(--line);--fm-surface-radius:var(--r);--fm-surface-shadow:var(--shadow-pop);--fm-accent:var(--acc);--fm-accent-fg:var(--acc-contrast);--fm-destructive:var(--bad);--fm-muted-fg:var(--dim);--fm-divider-color:var(--line-2);--fm-row-hover-bg:var(--panel-2);--fm-row-active-bg:var(--panel-2);--fm-row-active-fg:var(--fg);--fm-font-size-row:var(--fs-menu);--fm-font-size-caption:var(--fs-sm);--fm-font-size-section:var(--fs-xs);--fm-row-min-h:30px;--fm-row-radius:var(--r-sm)}.fm-row--danger,.fm-row--danger .fm-row__icon{color:var(--bad)}.fm-row--danger:hover{background:var(--bad-soft)}.fm-row[data-pressed=true]{background:var(--acc-soft);color:var(--acc);position:relative;font-weight:600}.fm-row[data-pressed=true]:before{content:"";position:absolute;border-radius:0 var(--r-xs)var(--r-xs)0;background:var(--acc);width:3px;height:58%;top:50%;left:0;transform:translateY(-50%)}.fm-row[data-pressed=true] .fm-ico{color:var(--acc)}.fm-row__icon,.fm-ico{display:inline-flex;justify-content:center;align-items: center}.fm-row__name{flex:auto;min-width:0}.fm-row__caption{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;max-width:45%;margin-left:auto}.fm-ico .ph{vertical-align:0}main>h2:not(:first-child),.mt-sec{margin-top:var(--gap-sec)}.pg{display:inline-flex;font-size:var(--fs-md);color:var(--dim);overflow:visible;flex:none;justify-content:center;align-items: center;width:16px;height:16px;line-height:1}.pg-c1{color:var(--c1)}.pg-c2{color:var(--c2)}.pg-c3{color:var(--c3)}.pg-c4{color:var(--c4)}.pg-c5{color:var(--c5)}.pg-c6{color:var(--c6)}.pg-c7{color:var(--c7)}.pg-img{object-fit:cover;border-radius:var(--r-xs);display:block;width:16px;height:16px}.pg-lg{font-size:var(--fs-2xl);border:1px solid var(--line);border-radius:var(--r-sm);background:var(--panel-2);overflow:hidden;width:34px;height:34px;line-height:1}.pg-lg .pg-img{border-radius:0;width:34px;height:34px}.icon-row{display:flex;align-items: center;gap:8px}.icon-row input{flex:1}.icon-row .btn{display:inline-flex;padding:var(--pad-ctl);border:1px solid var(--line);border-radius:var(--r-sm);background:var(--panel-2);color:var(--fg-2);cursor:pointer;font:500 var(--fs-sm)var(--sans);white-space:nowrap;align-items: center;gap:5px}.icon-row .btn:hover{border-color:var(--acc)}.emoji-row{display:flex;flex-wrap:wrap;gap:4px}.emoji-all{overflow:auto;border:1px solid var(--line);border-radius:var(--r-sm);display:grid;gap:6px;max-height:200px;padding:8px}.emoji-sec{font-size:var(--fs-2xs);color:var(--dim);text-transform:uppercase;letter-spacing:.06em;margin-top:4px;font-weight:600}.more-emoji{color:var(--fg-2);font-weight:700}.emoji{display:inline-flex;border:1px solid var(--line);border-radius:var(--r-sm);cursor:pointer;font-size:var(--fs-xl);background:var(--panel-2);color:var(--dim);overflow:hidden;justify-content:center;align-items: center;width:34px;height:34px;line-height:1}.emoji .ph{width:14px;height:14px}.emoji:hover{border-color:var(--acc)}.emoji.on{border-color:var(--acc);background:var(--acc-soft)}.swatches{display:flex;gap:6px}.swatch{cursor:pointer;box-shadow:inset 0 0 0 2px var(--panel);background:currentColor;border:2px solid #0000;border-radius:50%;width:22px;height:22px}.swatch.none{background:var(--panel-2);color:var(--line);box-shadow:inset 0 0 0 1px var(--line)}.swatch:hover{border-color:var(--fg-2)}.swatch.on{border-color:var(--fg)}.pk-b label.chk{display:flex;flex-direction:row;align-items: center;gap:8px}.pk-b label.chk input{width:auto}.kpis-5{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.kpi.hot .v{color:var(--warn)}.kpi.warm .v{color:var(--acc)}.grow{flex:1}.kanban{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));margin-bottom:var(--gap-sec);align-items: start;gap:12px}.lane{background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r);display:flex;flex-direction:column;gap:8px;min-height:60px;padding:8px}.lane-h{font-size:var(--fs-sm);color:var(--dim);text-transform:uppercase;letter-spacing:.06em;padding:2px 4px;font-weight:600}.lane-h span{color:var(--faint);font-weight:500}.lane-empty{color:var(--faint);text-align:center;padding:10px 0}.lane-more{font-size:var(--fs-xs);padding:2px 4px}.tcard{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-sm);box-shadow:var(--shadow);cursor:pointer;transition:border-color var(--t-fast);padding:8px 10px}.tcard:hover{border-color:var(--acc);outline:none}.tcard:focus-visible{border-color:var(--acc);outline:none}.wt:focus-visible{border-color:var(--acc);outline:none}.more:focus-visible{border-color:var(--acc);outline:none}.more:focus-visible{opacity:1}.tcard.ready{border-color:var(--ok)}.tcard.held{border-color:var(--acc)}.tcard.blocked{border-color:var(--line)}.tcard.done{opacity:.7}.tc-h{display:flex;font-size:var(--fs-sm);align-items: center;gap:8px;margin-bottom:3px}.tc-h .dim{font-family:var(--mono);font-size:var(--fs-2xs);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tc-t{font-size:var(--fs-md);color:var(--fg);display:-webkit-box;-webkit-line-clamp:3;overflow:hidden;-webkit-box-orient:vertical;line-height:1.35}.tc-m{font-size:var(--fs-xs);color:var(--faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:4px}.tc-g{margin-top:6px}.wtmap{display:flex;margin-bottom:var(--gap-sec);flex-direction:column;gap:14px}.wt-proj{font-size:var(--fs-sm);color:var(--dim);text-transform:uppercase;letter-spacing:.06em;margin-bottom:6px;font-weight:600}.wt-proj span{color:var(--faint);font-weight:500}.wt-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:8px}.wt{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-sm);box-shadow:var(--shadow);cursor:pointer;transition:border-color var(--t-fast);min-width:0;padding:8px 10px}.wt:hover{border-color:var(--acc)}.wt.main{background:var(--panel-2)}.wt.live{border-color:var(--ok)}.wt.dirty{border-color:var(--warn)}.wt.merged{opacity:.6}.wt-b{display:flex;align-items: center;gap:6px;min-width:0}.wt-b .s{flex:none;margin:0}.wt-b .br{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wt-m{font-size:var(--fs-xs);color:var(--faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:3px}.wt-m i{font-style:normal}.wt-m i.warn{color:var(--warn)}.wt-m i.acc{color:var(--acc)}.chips{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:14px}.chip{display:inline-flex;font:500 var(--fs-md)var(--sans);border-radius:var(--r-pill);background:var(--panel);border:1px solid var(--line);color:var(--dim);cursor:pointer;user-select:none;align-items: center;gap:5px;padding:4px 11px}.chip:hover{border-color:var(--acc);color:var(--fg-2)}.chip.on{background:var(--acc-soft);color:var(--acc);border-color:#0000}.seg{display:inline-flex;background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r-sm);gap:2px;padding:2px}.seg a{border-radius:var(--r-sm);font:600 var(--fs-md)var(--sans);color:var(--dim);text-decoration:none;transition:color var(--t-fast),background var(--t-fast);letter-spacing:0;text-transform:none;padding:3px 10px}.seg a:hover{color:var(--fg-2)}.seg a.on{background:var(--acc-soft);color:var(--acc)}.chip b{font-weight:700}.wf-steps{display:inline-flex;flex-wrap:wrap;gap:6px}.wfs{font:500 var(--fs-sm)var(--sans);color:var(--dim);background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r-pill);white-space:nowrap;padding:1px 8px}.wfs.ok{color:var(--ok);background:var(--ok-soft);border-color:#0000}.wfs.run{color:var(--acc);background:var(--acc-soft);border-color:#0000}.wfs.bad{color:var(--warn);background:var(--warn-soft);border-color:#0000}.tl-track u{position:absolute;opacity:.95;border-radius:1px;width:2px;top:4px;bottom:4px}.tl-track i.thin{opacity:.25}.tl-row.claimlane .tl-name{font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.06em}.tl-track i.claim{background:var(--violet);opacity:.6;border-radius:2px;height:4px;top:7px}.tl-track i.claim.orphaned{background:var(--warn);opacity:.9}.tl-track i.claim.expired{background:var(--faint)}.proj-spark{display:inline-flex;opacity:.7;flex:none;width:44px}.proj-spark .spark{width:44px;height:14px}.gh-strip{display:inline-flex;gap:12px;font-weight:400}.gh{font-size:var(--fs-sm);color:var(--dim);display:inline-flex;align-items: center;gap:3px}.gh i{display:inline-block;border-radius:2px;width:7px;height:7px}.gh i.ok{background:var(--ok)}.gh i.bad{background:var(--warn)}.msgs{display:flex;overflow:auto;flex-direction:column;gap:6px;max-height:220px;margin-bottom:8px}.msg{background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r-sm);font-size:var(--fs-md);color:var(--fg-2);word-break:break-word;padding:6px 9px}.msg.out{background:var(--acc-soft);border-color:#0000}.msg-f{display:block;font-size:var(--fs-2xs);color:var(--dim);margin-bottom:2px}.msg-compose{display:flex;gap:6px}.msg-compose input{background:var(--panel-2);border:1px solid var(--line);color:var(--fg);border-radius:var(--r-sm);font:400 var(--fs-md)var(--sans);flex:1;min-width:0;padding:7px 10px}.msg-compose input:focus{border-color:var(--acc);outline:none}.msg-compose button{display:inline-flex;background:var(--panel-2);border:1px solid var(--line);color:var(--fg-2);border-radius:var(--r-sm);cursor:pointer;font:500 var(--fs-sm)var(--sans);flex:none;align-items: center;gap:5px;padding:7px 11px}.msg-compose button:hover{color:var(--acc);border-color:var(--acc)}.msg-compose input:disabled,.msg-compose button:disabled{opacity:.45;cursor:not-allowed}.msg-compose button:disabled:hover{color:var(--fg-2);border-color:var(--line)}.msg-hint{display:flex;color:var(--dim);font-size:var(--fs-sm);align-items: center;gap:5px;margin:7px 0 0;line-height:1.4}.msg-hint .ph{color:var(--faint);flex:none}.msg-hint b{color:var(--fg-2);font-weight:600}.pathrow{display:flex;text-align:left;cursor:pointer;background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r-sm);color:var(--fg-2);font:400 var(--fs-sm)var(--mono);align-items: center;gap:6px;width:100%;padding:6px 9px}.pathrow:hover{border-color:var(--acc);color:var(--fg)}.pathrow .dir{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dim);flex:0 9999 auto;min-width:0}.pathrow b{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;font-weight:400}.pathrow .cp{color:var(--faint);flex:none;margin-left:auto}.pathrow:hover .cp{color:var(--acc)}.pathrow.copied{border-color:var(--ok);color:var(--ok)}.pathrow.copied .dir,.pathrow.copied .cp{color:var(--ok)}.pathrow.copy-failed{border-color:var(--bad);color:var(--bad)}.pathrow.copy-failed .dir,.pathrow.copy-failed .cp{color:var(--bad)}.onboard{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);display:flex;flex-direction:column;align-items: flex-start;gap:16px;max-width:640px;margin:40px auto;padding:28px 30px}.onboard h3{font-size:var(--fs-xl);color:var(--fg)}.onboard .px{margin:0 auto}.ob-steps{display:flex;flex-direction:column;gap:12px}.ob-step{display:flex;color:var(--fg-2);align-items: flex-start;gap:12px;line-height:1.5}.ob-step.done{color:var(--dim)}.ob-n{border:1px solid var(--line);display:inline-flex;font:600 var(--fs-sm)var(--sans);color:var(--dim);border-radius:50%;flex:none;justify-content:center;align-items: center;width:22px;height:22px;margin-top:1px}.ob-step.done .ob-n{background:var(--ok-soft);color:var(--ok);border-color:#0000}.nudge{position:fixed;z-index:40;background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow-pop);display:flex;font:var(--fs-md)var(--sans);color:var(--fg-2);align-items: flex-start;gap:12px;max-width:400px;padding:14px 16px;line-height:1.5;bottom:18px;right:18px}.nudge .ic{color:var(--acc);flex:none;margin-top:1px}.nudge b{color:var(--fg);display:block;margin-bottom:2px}.nudge .row{display:flex;white-space:nowrap;align-items: center;gap:8px;margin-top:10px}.nudge button.pri{background:var(--acc-soft);color:var(--acc);border-color:#0000}.nudge a.dim{color:var(--dim);font-size:var(--fs-sm);margin-left:auto}body.nosb .nudge{right:18px}.dt .th-cols{font:inherit;background:0 0;border:0;padding:0}#root{display:contents}.note{font-size:var(--fs-sm);margin-top:10px}.proj,.chip,.h4-act,.nav-group,.mini-act{appearance:none;font:inherit;color:inherit;text-align:left;background:0 0;border:0}.proj{width:100%}.chart-card.flush{margin:0}.stack{display:flex;gap:var(--gap-sec);flex-direction:column;min-width:0}.rule-spark{display:flex;align-items: center;gap:12px;margin-bottom:10px}.rule-spark .dim{font-size:var(--fs-sm)}.rule-shape{font-size:var(--fs-md);margin:0 0 8px}.h4-note{text-transform:none;letter-spacing:0;font-weight:400}.chips.pager{margin-top:10px}.chips.pager .dim{font-size:var(--fs-sm);align-self: center}tr.clickable{cursor:pointer}.link{appearance:none;font:inherit;color:var(--acc);cursor:pointer;text-align:left;background:0 0;border:0;padding:0}.link:hover{text-decoration:underline}.h2-actions{margin-left:auto}.gpager{display:flex;border-top:1px solid var(--line);font-size:var(--fs-sm);align-items: center;gap:10px;padding:7px 12px}.gpager .gp-size{margin-left:auto}.gp-btn{appearance:none;border-radius:var(--r-xs);color:var(--dim);cursor:pointer;display:inline-flex;background:0 0;border:0;padding:2px}.gp-btn:hover:not(:disabled){color:var(--acc);background:var(--panel-2)}.gp-btn:disabled{color:var(--line-2);cursor:default}.mt-panel{margin-top:14px}.bipartite{max-width:1100px}.matrix-scroll{overflow:auto}.card-pad{padding:14px}.card-scroll{overflow:auto;max-height:72vh;padding:14px}.graph-key{display:flex;font-size:var(--fs-sm);flex-wrap:wrap;align-items: center;gap:18px;margin-top:10px}.graph-key svg{vertical-align:middle}.edge-key{display:inline-flex;align-items: center;gap:6px}.dag-node{cursor:pointer}.ring-card{margin-bottom:12px}.ring-list{margin:8px 0 0;padding-left:18px}.more{appearance:none;font:inherit;color:inherit;background:0 0;border:0}.pk-sub{flex:1;margin-left:8px}.rp-summary{font-family:var(--mono);font-size:var(--fs-sm);margin-bottom:8px}.rp-range{flex:1;max-width:280px}.df-commits{font-size:var(--fs-sm)}.df-files button{appearance:none;font:inherit;color:inherit;text-align:left;background:0 0;border:0;width:100%}.wn-full{color:var(--dim);font-size:var(--fs-sm);align-self: center}.linkish{font:inherit;cursor:pointer;text-decoration:underline;background:0 0;border:0;padding:0}.linkish:hover{color:var(--fg-2)}