@way_marks/server 2.0.2 → 3.0.0

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.
Files changed (64) hide show
  1. package/dist/api/events.js +45 -0
  2. package/dist/api/server.js +49 -6
  3. package/package.json +2 -2
  4. package/src/ui-dist/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  5. package/src/ui-dist/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  6. package/src/ui-dist/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  7. package/src/ui-dist/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  8. package/src/ui-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  9. package/src/ui-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  10. package/src/ui-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  11. package/src/ui-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  12. package/src/ui-dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  13. package/src/ui-dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  14. package/src/ui-dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  15. package/src/ui-dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  16. package/src/ui-dist/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  17. package/src/ui-dist/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  18. package/src/ui-dist/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  19. package/src/ui-dist/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  20. package/src/ui-dist/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  21. package/src/ui-dist/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  22. package/src/ui-dist/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  23. package/src/ui-dist/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  24. package/src/ui-dist/assets/ibm-plex-sans-cyrillic-400-normal-BTotfTJu.woff +0 -0
  25. package/src/ui-dist/assets/ibm-plex-sans-cyrillic-400-normal-DZqxrq2p.woff2 +0 -0
  26. package/src/ui-dist/assets/ibm-plex-sans-cyrillic-500-normal-ByOcLdNv.woff +0 -0
  27. package/src/ui-dist/assets/ibm-plex-sans-cyrillic-500-normal-CocWQlwt.woff2 +0 -0
  28. package/src/ui-dist/assets/ibm-plex-sans-cyrillic-600-normal-71GNu3SW.woff2 +0 -0
  29. package/src/ui-dist/assets/ibm-plex-sans-cyrillic-600-normal-BGq0mW3O.woff +0 -0
  30. package/src/ui-dist/assets/ibm-plex-sans-cyrillic-ext-400-normal-Dsrv2Tcn.woff +0 -0
  31. package/src/ui-dist/assets/ibm-plex-sans-cyrillic-ext-400-normal-g30qAdWV.woff2 +0 -0
  32. package/src/ui-dist/assets/ibm-plex-sans-cyrillic-ext-500-normal-Cs5J6C77.woff2 +0 -0
  33. package/src/ui-dist/assets/ibm-plex-sans-cyrillic-ext-500-normal-DB5PtV2g.woff +0 -0
  34. package/src/ui-dist/assets/ibm-plex-sans-cyrillic-ext-600-normal-Bz0x94Yp.woff +0 -0
  35. package/src/ui-dist/assets/ibm-plex-sans-cyrillic-ext-600-normal-DUMzJB7m.woff2 +0 -0
  36. package/src/ui-dist/assets/ibm-plex-sans-greek-400-normal-D9ESIMu3.woff +0 -0
  37. package/src/ui-dist/assets/ibm-plex-sans-greek-400-normal-_efipK4i.woff2 +0 -0
  38. package/src/ui-dist/assets/ibm-plex-sans-greek-500-normal-CuWXN6rf.woff +0 -0
  39. package/src/ui-dist/assets/ibm-plex-sans-greek-500-normal-JMMifIXV.woff2 +0 -0
  40. package/src/ui-dist/assets/ibm-plex-sans-greek-600-normal-D-CqTdkO.woff +0 -0
  41. package/src/ui-dist/assets/ibm-plex-sans-greek-600-normal-DzTrcv_p.woff2 +0 -0
  42. package/src/ui-dist/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  43. package/src/ui-dist/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  44. package/src/ui-dist/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  45. package/src/ui-dist/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  46. package/src/ui-dist/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  47. package/src/ui-dist/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  48. package/src/ui-dist/assets/ibm-plex-sans-latin-ext-400-normal-C5H60-Va.woff2 +0 -0
  49. package/src/ui-dist/assets/ibm-plex-sans-latin-ext-400-normal-RBey6euL.woff +0 -0
  50. package/src/ui-dist/assets/ibm-plex-sans-latin-ext-500-normal-D0aIdm-b.woff +0 -0
  51. package/src/ui-dist/assets/ibm-plex-sans-latin-ext-500-normal-DakdToA3.woff2 +0 -0
  52. package/src/ui-dist/assets/ibm-plex-sans-latin-ext-600-normal-DIrixKbi.woff +0 -0
  53. package/src/ui-dist/assets/ibm-plex-sans-latin-ext-600-normal-DOrvGEcy.woff2 +0 -0
  54. package/src/ui-dist/assets/ibm-plex-sans-vietnamese-400-normal-DG4YqDda.woff2 +0 -0
  55. package/src/ui-dist/assets/ibm-plex-sans-vietnamese-400-normal-fK1oJ5dG.woff +0 -0
  56. package/src/ui-dist/assets/ibm-plex-sans-vietnamese-500-normal-BEb3_waV.woff +0 -0
  57. package/src/ui-dist/assets/ibm-plex-sans-vietnamese-500-normal-e4dixQRQ.woff2 +0 -0
  58. package/src/ui-dist/assets/ibm-plex-sans-vietnamese-600-normal-DgdngZtN.woff +0 -0
  59. package/src/ui-dist/assets/ibm-plex-sans-vietnamese-600-normal-DpPYBSTl.woff2 +0 -0
  60. package/src/ui-dist/assets/index-DNdosrlQ.css +1 -0
  61. package/src/ui-dist/assets/index-EkwgRogY.js +87 -0
  62. package/src/ui-dist/index.html +14 -0
  63. package/src/ui/index.html +0 -1452
  64. package/src/ui/index.html.bak +0 -429
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.attachSubscriber = attachSubscriber;
4
+ exports.emit = emit;
5
+ const subscribers = new Set();
6
+ function attachSubscriber(res) {
7
+ // SSE headers
8
+ res.setHeader('Content-Type', 'text/event-stream');
9
+ res.setHeader('Cache-Control', 'no-cache, no-transform');
10
+ res.setHeader('Connection', 'keep-alive');
11
+ res.setHeader('X-Accel-Buffering', 'no');
12
+ res.flushHeaders?.();
13
+ const sub = { res, closed: false };
14
+ subscribers.add(sub);
15
+ // Initial hello so the client opens onmessage cleanly
16
+ res.write(`event: hello\ndata: {"ok":true}\n\n`);
17
+ return () => {
18
+ sub.closed = true;
19
+ subscribers.delete(sub);
20
+ };
21
+ }
22
+ function emit(topic, payload = {}) {
23
+ const data = JSON.stringify({ topic, ...payload });
24
+ const frame = `event: ${topic}\ndata: ${data}\n\n`;
25
+ for (const sub of subscribers) {
26
+ if (sub.closed)
27
+ continue;
28
+ try {
29
+ sub.res.write(frame);
30
+ }
31
+ catch { /* client disconnected; cleanup happens via close handler */ }
32
+ }
33
+ }
34
+ // Heartbeat keeps proxies / browsers from closing the stream.
35
+ const HEARTBEAT_MS = 25000;
36
+ setInterval(() => {
37
+ for (const sub of subscribers) {
38
+ if (sub.closed)
39
+ continue;
40
+ try {
41
+ sub.res.write(`: heartbeat\n\n`);
42
+ }
43
+ catch { /* noop */ }
44
+ }
45
+ }, HEARTBEAT_MS).unref?.();
@@ -46,6 +46,7 @@ const engine_1 = require("../policies/engine");
46
46
  const handler_1 = require("../approvals/handler");
47
47
  const manager_2 = require("../approval/manager");
48
48
  const manager_3 = require("../escalation/manager");
49
+ const events_1 = require("./events");
49
50
  // Import registry for Phase 2 hub navigation
50
51
  const registryPath = path.join(process.env.HOME || process.env.USERPROFILE || '', '.waymark', 'registry.json');
51
52
  function getRegistryProjects() {
@@ -96,9 +97,22 @@ const app = (0, express_1.default)();
96
97
  const PORT = parseInt(process.env.WAYMARK_PORT || '3001', 10);
97
98
  app.use(express_1.default.json());
98
99
  app.use(express_1.default.urlencoded({ extended: true }));
99
- // Serve UI — path works for both ts-node (src/api/) and compiled (dist/api/)
100
- const UI_DIR = path.resolve(__dirname, '../../src/ui');
101
- app.use(express_1.default.static(UI_DIR));
100
+ // Serve UI — path works for both ts-node (src/api/) and compiled (dist/api/).
101
+ const UI_DIR = path.resolve(__dirname, '../../src/ui-dist');
102
+ const UI_INDEX = path.join(UI_DIR, 'index.html');
103
+ const UI_BUILT = fs.existsSync(UI_INDEX);
104
+ if (UI_BUILT) {
105
+ app.use(express_1.default.static(UI_DIR));
106
+ }
107
+ else {
108
+ console.warn('[waymark] ui-dist/ not found — dashboard will return a setup banner. ' +
109
+ 'Run `npm run build -w @way_marks/web` to build the dashboard.');
110
+ }
111
+ // GET /api/events — Server-Sent Events stream for live UI updates
112
+ app.get('/api/events', (req, res) => {
113
+ const detach = (0, events_1.attachSubscriber)(res);
114
+ req.on('close', detach);
115
+ });
102
116
  // GET /api/actions — list all actions (or ?count=true for pending count)
103
117
  app.get('/api/actions', (req, res) => {
104
118
  try {
@@ -192,6 +206,7 @@ app.post('/api/actions/:action_id/approve', async (req, res) => {
192
206
  const status = result.error === 'Action not found' ? 404 : 400;
193
207
  return res.status(status).json({ error: result.error });
194
208
  }
209
+ (0, events_1.emit)('actions', { action_id: req.params.action_id, kind: 'approved' });
195
210
  res.json(result);
196
211
  }
197
212
  catch (err) {
@@ -207,6 +222,7 @@ app.post('/api/actions/:action_id/reject', async (req, res) => {
207
222
  const status = result.error === 'Action not found' ? 404 : 400;
208
223
  return res.status(status).json({ error: result.error });
209
224
  }
225
+ (0, events_1.emit)('actions', { action_id: req.params.action_id, kind: 'rejected' });
210
226
  res.json(result);
211
227
  }
212
228
  catch (err) {
@@ -266,12 +282,14 @@ app.post('/api/actions/:action_id/rollback', (req, res) => {
266
282
  if (!action.before_snapshot) {
267
283
  fs.unlinkSync(action.target_path);
268
284
  (0, database_1.markRolledBack)(action.action_id);
285
+ (0, events_1.emit)('actions', { action_id: action.action_id, kind: 'rolled_back' });
269
286
  return res.json({ success: true, action: 'deleted', message: `Deleted new file: ${action.target_path}` });
270
287
  }
271
288
  // Restore file to before_snapshot
272
289
  fs.mkdirSync(path.dirname(action.target_path), { recursive: true });
273
290
  fs.writeFileSync(action.target_path, action.before_snapshot, 'utf8');
274
291
  (0, database_1.markRolledBack)(action.action_id);
292
+ (0, events_1.emit)('actions', { action_id: action.action_id, kind: 'rolled_back' });
275
293
  res.json({ success: true, action: 'restored', message: `Restored ${action.target_path} to previous state` });
276
294
  }
277
295
  catch (err) {
@@ -384,6 +402,8 @@ app.post('/api/sessions/:session_id/rollback', (req, res) => {
384
402
  message: 'Rollback failed',
385
403
  });
386
404
  }
405
+ (0, events_1.emit)('sessions', { session_id, kind: 'rolled_back', count: actions.length });
406
+ (0, events_1.emit)('actions', { session_id, kind: 'session_rolled_back' });
387
407
  res.json({
388
408
  success: true,
389
409
  message: `Rolled back session ${session_id}`,
@@ -488,6 +508,7 @@ app.post('/api/team/members', (req, res) => {
488
508
  return res.status(400).json({ error: `Email ${email} already in use` });
489
509
  }
490
510
  (0, database_1.addTeamMember)(member_id, name, email, added_by, slack_id);
511
+ (0, events_1.emit)('team', { member_id, kind: 'added' });
491
512
  res.json({ success: true, member_id, message: `Added team member: ${name}` });
492
513
  }
493
514
  catch (err) {
@@ -503,6 +524,7 @@ app.delete('/api/team/members/:member_id', (req, res) => {
503
524
  return res.status(404).json({ error: `Team member ${member_id} not found` });
504
525
  }
505
526
  (0, database_1.removeTeamMember)(member_id);
527
+ (0, events_1.emit)('team', { member_id, kind: 'removed' });
506
528
  res.json({ success: true, message: `Removed team member: ${member.name}` });
507
529
  }
508
530
  catch (err) {
@@ -528,6 +550,7 @@ app.post('/api/approval-routes', (req, res) => {
528
550
  return res.status(400).json({ error: 'Missing required fields: route_id, name, approver_ids (array)' });
529
551
  }
530
552
  (0, database_1.addApprovalRoute)(route_id, name, approver_ids, created_by, description, condition_type, condition_json);
553
+ (0, events_1.emit)('approval-routes', { route_id, kind: 'added' });
531
554
  res.json({ success: true, route_id, message: `Created approval route: ${name}` });
532
555
  }
533
556
  catch (err) {
@@ -544,6 +567,7 @@ app.put('/api/approval-routes/:route_id', (req, res) => {
544
567
  return res.status(404).json({ error: `Approval route ${route_id} not found` });
545
568
  }
546
569
  (0, database_1.updateApprovalRoute)(route_id, { name, description, approver_ids });
570
+ (0, events_1.emit)('approval-routes', { route_id, kind: 'updated' });
547
571
  res.json({ success: true, message: `Updated approval route: ${route_id}` });
548
572
  }
549
573
  catch (err) {
@@ -559,6 +583,7 @@ app.delete('/api/approval-routes/:route_id', (req, res) => {
559
583
  return res.status(404).json({ error: `Approval route ${route_id} not found` });
560
584
  }
561
585
  (0, database_1.deleteApprovalRoute)(route_id);
586
+ (0, events_1.emit)('approval-routes', { route_id, kind: 'deleted' });
562
587
  res.json({ success: true, message: `Deleted approval route: ${route_id}` });
563
588
  }
564
589
  catch (err) {
@@ -608,6 +633,7 @@ app.post('/api/approvals/:request_id/approve', (req, res) => {
608
633
  return res.status(400).json({ error: 'Missing required field: approver_id' });
609
634
  }
610
635
  const status = (0, manager_2.submitApprovalDecision)(request_id, approver_id, 'approve', reason);
636
+ (0, events_1.emit)('approvals', { request_id, kind: 'approve', approver_id });
611
637
  res.json({
612
638
  success: true,
613
639
  message: `Approved by ${approver_id}`,
@@ -627,6 +653,7 @@ app.post('/api/approvals/:request_id/reject', (req, res) => {
627
653
  return res.status(400).json({ error: 'Missing required field: approver_id' });
628
654
  }
629
655
  const status = (0, manager_2.submitApprovalDecision)(request_id, approver_id, 'reject', reason);
656
+ (0, events_1.emit)('approvals', { request_id, kind: 'reject', approver_id });
630
657
  res.json({
631
658
  success: true,
632
659
  message: `Rejected by ${approver_id}`,
@@ -674,6 +701,7 @@ app.post('/api/escalations/rules', (req, res) => {
674
701
  return res.status(400).json({ error: 'Missing required fields: rule_id, name, escalation_targets (array)' });
675
702
  }
676
703
  (0, database_1.addEscalationRule)(rule_id, name, escalation_targets, created_by, description, timeout_hours);
704
+ (0, events_1.emit)('escalation-rules', { rule_id, kind: 'added' });
677
705
  res.json({ success: true, rule_id, message: `Created escalation rule: ${name}` });
678
706
  }
679
707
  catch (err) {
@@ -690,6 +718,7 @@ app.put('/api/escalations/rules/:rule_id', (req, res) => {
690
718
  return res.status(404).json({ error: `Escalation rule ${rule_id} not found` });
691
719
  }
692
720
  (0, database_1.updateEscalationRule)(rule_id, { name, description, escalation_targets, timeout_hours });
721
+ (0, events_1.emit)('escalation-rules', { rule_id, kind: 'updated' });
693
722
  res.json({ success: true, message: `Updated escalation rule: ${rule_id}` });
694
723
  }
695
724
  catch (err) {
@@ -705,6 +734,7 @@ app.delete('/api/escalations/rules/:rule_id', (req, res) => {
705
734
  return res.status(404).json({ error: `Escalation rule ${rule_id} not found` });
706
735
  }
707
736
  (0, database_1.deleteEscalationRule)(rule_id);
737
+ (0, events_1.emit)('escalation-rules', { rule_id, kind: 'deleted' });
708
738
  res.json({ success: true, message: `Deleted escalation rule: ${rule_id}` });
709
739
  }
710
740
  catch (err) {
@@ -756,6 +786,8 @@ app.post('/api/escalations/:request_id/decide', (req, res) => {
756
786
  return res.status(400).json({ error: 'Decision must be "proceed" or "block"' });
757
787
  }
758
788
  const status = (0, manager_3.submitEscalationDecision)(request_id, target_id, decision, reason);
789
+ (0, events_1.emit)('escalations', { request_id, kind: 'decided', decision, target_id });
790
+ (0, events_1.emit)('approvals', { request_id, kind: 'escalation_decided' });
759
791
  res.json({
760
792
  success: true,
761
793
  message: `${target_id} decided to ${decision}`,
@@ -958,9 +990,20 @@ app.delete('/api/remediation/policies/:policy_id', (req, res) => {
958
990
  res.status(500).json({ error: err.message });
959
991
  }
960
992
  });
961
- // Fallback: serve UI for any unmatched route
962
- app.get('*', (req, res) => {
963
- res.sendFile(path.join(UI_DIR, 'index.html'));
993
+ // Fallback: serve UI for any unmatched route. If the dashboard hasn't been
994
+ // built yet, emit a friendly setup banner instead of a 404.
995
+ app.get('*', (_req, res) => {
996
+ if (UI_BUILT) {
997
+ return res.sendFile(UI_INDEX);
998
+ }
999
+ res.status(503).type('html').send(`<!doctype html>
1000
+ <html><head><meta charset="utf-8"/><title>Waymark — dashboard not built</title>
1001
+ <style>body{font:14px/1.5 -apple-system,system-ui,sans-serif;color:#1d2126;background:#fafaf8;display:grid;place-items:center;min-height:100vh;margin:0}main{max-width:520px;padding:32px}h1{margin:0 0 8px;font-size:18px}code{background:#ebebe8;padding:2px 6px;border-radius:4px;font-family:ui-monospace,Menlo,monospace}</style>
1002
+ </head><body><main>
1003
+ <h1>Dashboard not built</h1>
1004
+ <p>The Waymark API is running, but the React dashboard hasn't been built yet.</p>
1005
+ <p>Run <code>npm run build -w @way_marks/web</code> from the project root, then refresh.</p>
1006
+ </main></body></html>`);
964
1007
  });
965
1008
  app.listen(PORT, () => {
966
1009
  console.log(`Waymark UI + API running at http://localhost:${PORT}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@way_marks/server",
3
- "version": "2.0.2",
3
+ "version": "3.0.0",
4
4
  "description": "Waymark MCP server and dashboard",
5
5
  "author": "Waymark <hello@waymarks.dev>",
6
6
  "license": "MIT",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "files": [
29
29
  "dist/**",
30
- "src/ui/**"
30
+ "src/ui-dist/**"
31
31
  ],
32
32
  "scripts": {
33
33
  "build": "tsc",
@@ -0,0 +1 @@
1
+ @font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/ibm-plex-sans-cyrillic-ext-400-normal-g30qAdWV.woff2) format("woff2"),url(/assets/ibm-plex-sans-cyrillic-ext-400-normal-Dsrv2Tcn.woff) format("woff");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/ibm-plex-sans-cyrillic-400-normal-DZqxrq2p.woff2) format("woff2"),url(/assets/ibm-plex-sans-cyrillic-400-normal-BTotfTJu.woff) format("woff");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/ibm-plex-sans-greek-400-normal-_efipK4i.woff2) format("woff2"),url(/assets/ibm-plex-sans-greek-400-normal-D9ESIMu3.woff) format("woff");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/ibm-plex-sans-vietnamese-400-normal-DG4YqDda.woff2) format("woff2"),url(/assets/ibm-plex-sans-vietnamese-400-normal-fK1oJ5dG.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/ibm-plex-sans-latin-ext-400-normal-C5H60-Va.woff2) format("woff2"),url(/assets/ibm-plex-sans-latin-ext-400-normal-RBey6euL.woff) format("woff");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2) format("woff2"),url(/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/ibm-plex-sans-cyrillic-ext-500-normal-Cs5J6C77.woff2) format("woff2"),url(/assets/ibm-plex-sans-cyrillic-ext-500-normal-DB5PtV2g.woff) format("woff");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/ibm-plex-sans-cyrillic-500-normal-CocWQlwt.woff2) format("woff2"),url(/assets/ibm-plex-sans-cyrillic-500-normal-ByOcLdNv.woff) format("woff");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/ibm-plex-sans-greek-500-normal-JMMifIXV.woff2) format("woff2"),url(/assets/ibm-plex-sans-greek-500-normal-CuWXN6rf.woff) format("woff");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/ibm-plex-sans-vietnamese-500-normal-e4dixQRQ.woff2) format("woff2"),url(/assets/ibm-plex-sans-vietnamese-500-normal-BEb3_waV.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/ibm-plex-sans-latin-ext-500-normal-DakdToA3.woff2) format("woff2"),url(/assets/ibm-plex-sans-latin-ext-500-normal-D0aIdm-b.woff) format("woff");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2) format("woff2"),url(/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:600;src:url(/assets/ibm-plex-sans-cyrillic-ext-600-normal-DUMzJB7m.woff2) format("woff2"),url(/assets/ibm-plex-sans-cyrillic-ext-600-normal-Bz0x94Yp.woff) format("woff");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:600;src:url(/assets/ibm-plex-sans-cyrillic-600-normal-71GNu3SW.woff2) format("woff2"),url(/assets/ibm-plex-sans-cyrillic-600-normal-BGq0mW3O.woff) format("woff");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:600;src:url(/assets/ibm-plex-sans-greek-600-normal-DzTrcv_p.woff2) format("woff2"),url(/assets/ibm-plex-sans-greek-600-normal-D-CqTdkO.woff) format("woff");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:600;src:url(/assets/ibm-plex-sans-vietnamese-600-normal-DpPYBSTl.woff2) format("woff2"),url(/assets/ibm-plex-sans-vietnamese-600-normal-DgdngZtN.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:600;src:url(/assets/ibm-plex-sans-latin-ext-600-normal-DOrvGEcy.woff2) format("woff2"),url(/assets/ibm-plex-sans-latin-ext-600-normal-DIrixKbi.woff) format("woff");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:600;src:url(/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2) format("woff2"),url(/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2) format("woff2"),url(/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff) format("woff");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2) format("woff2"),url(/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff) format("woff");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2) format("woff2"),url(/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2) format("woff2"),url(/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff) format("woff");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2) format("woff2"),url(/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2) format("woff2"),url(/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff) format("woff");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2) format("woff2"),url(/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff) format("woff");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2) format("woff2"),url(/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2) format("woff2"),url(/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff) format("woff");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2) format("woff2"),url(/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--font-sans: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;--bg-0: #0a0b0d;--bg-1: #101215;--bg-2: #16191d;--bg-3: #1d2126;--bg-4: #262b31;--line: #22262c;--line-strong: #2d3238;--ink-0: #f5f6f7;--ink-1: #d6d9dd;--ink-2: #a8aeb6;--ink-3: #8c929b;--ink-4: #6a7079;--acc: oklch(.72 .09 195);--acc-dim: oklch(.72 .09 195 / .15);--acc-ink: oklch(.95 .03 195);--ok: oklch(.74 .13 155);--ok-dim: oklch(.74 .13 155 / .14);--warn: oklch(.78 .13 75);--warn-dim: oklch(.78 .13 75 / .14);--err: oklch(.68 .19 25);--err-dim: oklch(.68 .19 25 / .14);--info: oklch(.72 .12 240);--info-dim: oklch(.72 .12 240 / .14);--pending: oklch(.8 .14 85);--pending-dim: oklch(.8 .14 85 / .14);--r-1: 4px;--r-2: 6px;--r-3: 10px;--r-4: 14px;--nav-w: 232px;--drawer-w: 560px;--shadow-drawer: 0 24px 48px -12px rgba(0, 0, 0, .6), 0 8px 16px -4px rgba(0, 0, 0, .4);--shadow-pop: 0 8px 24px -8px rgba(0, 0, 0, .5);--row-py: 10px;--gap: 14px;--focus-ring: 0 0 0 2px var(--bg-0), 0 0 0 4px var(--acc)}[data-theme=light]{--bg-0: #fafaf8;--bg-1: #ffffff;--bg-2: #f4f4f2;--bg-3: #ebebe8;--bg-4: #dcdcd8;--line: #dedcd6;--line-strong: #c4c2bc;--ink-0: #0a0b0d;--ink-1: #1d2126;--ink-2: #3a3e44;--ink-3: #5a5e65;--ink-4: #8c9099;--acc: oklch(.5 .1 195);--acc-dim: oklch(.5 .1 195 / .1);--acc-ink: oklch(.32 .09 195);--ok: oklch(.48 .14 155);--ok-dim: oklch(.48 .14 155 / .12);--warn: oklch(.56 .14 75);--warn-dim: oklch(.56 .14 75 / .12);--err: oklch(.5 .2 25);--err-dim: oklch(.5 .2 25 / .12);--info: oklch(.5 .14 240);--info-dim: oklch(.5 .14 240 / .12);--pending: oklch(.58 .15 85);--pending-dim: oklch(.58 .15 85 / .12)}[data-density=compact]{--row-py: 8px;--gap: 10px}[data-density=comfy]{--row-py: 14px;--gap: 16px}[data-density=spacious]{--row-py: 20px;--gap: 22px}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}html,body{height:100%}body{font-family:var(--font-sans);font-feature-settings:"ss01","cv11";font-size:13px;line-height:1.5;color:var(--ink-1);background:var(--bg-0);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}button{font:inherit;color:inherit;background:none;border:none;cursor:pointer}button:focus-visible{outline:none;box-shadow:var(--focus-ring);border-radius:var(--r-2)}input,select,textarea{font:inherit;color:inherit}input:focus-visible{outline:none}a{color:inherit;text-decoration:none}code,kbd,.mono{font-family:var(--font-mono)}@media (prefers-reduced-motion: reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}#root{min-height:100vh}.app{display:grid;grid-template-columns:var(--nav-w) 1fr;min-height:100vh}.nav{background:var(--bg-0);border-right:1px solid var(--line);padding:18px 14px;display:flex;flex-direction:column;gap:22px;position:sticky;top:0;height:100vh;overflow-y:auto}.brand{display:flex;align-items:center;gap:10px;padding:2px 4px}.brand-mark{width:26px;height:26px;border-radius:var(--r-2);background:linear-gradient(135deg,var(--acc),oklch(.66 .1 230));display:grid;place-items:center;color:var(--bg-0);font-family:var(--font-mono);font-weight:600;font-size:13px;letter-spacing:-.04em}.brand-name{font-weight:600;font-size:14px;color:var(--ink-0);letter-spacing:-.01em}.brand-sub{font-size:10.5px;color:var(--ink-3);font-family:var(--font-mono)}.nav-section-label{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--ink-3);padding:0 8px;margin-bottom:4px;font-weight:600}.nav-list{list-style:none;display:flex;flex-direction:column;gap:1px}.nav-item{display:flex;align-items:center;gap:10px;padding:6px 8px;border-radius:var(--r-2);color:var(--ink-2);cursor:pointer;font-size:13px;transition:background .1s,color .1s;position:relative;width:100%;text-align:left}.nav-item:hover{background:var(--bg-2);color:var(--ink-1)}.nav-item.active{background:var(--bg-2);color:var(--ink-0)}.nav-item.active:before{content:"";position:absolute;left:-14px;top:6px;bottom:6px;width:2px;background:var(--acc);border-radius:0 2px 2px 0}.nav-icon{width:16px;height:16px;opacity:.85;flex-shrink:0}.nav-label{flex:1}.nav-count{font-family:var(--font-mono);font-size:11px;color:var(--ink-3);background:var(--bg-2);padding:1px 6px;border-radius:10px;min-width:18px;text-align:center}.nav-item.active .nav-count{background:var(--bg-3);color:var(--ink-1)}.nav-count.attn{background:var(--pending-dim);color:var(--pending)}.nav-footer{margin-top:auto;padding-top:16px;border-top:1px solid var(--line);display:flex;flex-direction:column;gap:10px;font-size:11.5px}.status-dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--ok);box-shadow:0 0 0 3px #5ec3862e;animation:pulse 2s ease-in-out infinite;flex-shrink:0}.status-dot.off{background:var(--ink-4);box-shadow:none;animation:none}@keyframes pulse{0%,to{box-shadow:0 0 0 2px #5ec38647}50%{box-shadow:0 0 0 5px #5ec38600}}.server-row{display:flex;align-items:center;gap:8px;color:var(--ink-2)}.server-port{margin-left:auto;color:var(--ink-3);font-family:var(--font-mono)}.main{display:flex;flex-direction:column;min-width:0;background:var(--bg-0)}.topbar{display:flex;align-items:center;gap:14px;padding:12px 28px;border-bottom:1px solid var(--line);background:color-mix(in srgb,var(--bg-0) 85%,transparent);position:sticky;top:0;z-index:40;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.crumbs{display:flex;align-items:center;gap:8px;min-width:0}.crumb{color:var(--ink-2);font-size:13px;white-space:nowrap}.crumb.current{color:var(--ink-0);font-weight:500}.crumb-sep{color:var(--ink-4)}.crumb-project{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-2);color:var(--ink-1);font-size:12.5px;cursor:pointer;transition:background .1s}.crumb-project:hover{background:var(--bg-3)}.crumb-project .mono{color:var(--ink-3);font-size:11px}.search{flex:1;max-width:420px;display:flex;align-items:center;gap:8px;padding:6px 10px;background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2);color:var(--ink-2);transition:border-color .1s}.search:focus-within{border-color:var(--ink-4)}.search input{flex:1;background:none;border:none;outline:none;font-size:13px;color:var(--ink-0);min-width:0}.search input::placeholder{color:var(--ink-3)}.kbd{font-family:var(--font-mono);font-size:10.5px;color:var(--ink-3);padding:1px 5px;border:1px solid var(--line);border-radius:3px;background:var(--bg-2)}.topbar-right{display:flex;align-items:center;gap:8px;margin-left:auto}.icon-btn{width:30px;height:30px;display:grid;place-items:center;border-radius:var(--r-2);color:var(--ink-2);transition:background .1s,color .1s;position:relative}.icon-btn:hover{background:var(--bg-2);color:var(--ink-0)}.icon-btn svg{width:16px;height:16px}.icon-btn .dot-badge{position:absolute;top:6px;right:6px;width:6px;height:6px;border-radius:50%;background:var(--pending)}.page{padding:28px 28px 40px;max-width:1280px;width:100%;margin:0 auto}.page-header{display:flex;align-items:flex-end;justify-content:space-between;gap:20px;margin-bottom:24px;flex-wrap:wrap}.page-title{font-size:22px;font-weight:600;color:var(--ink-0);letter-spacing:-.015em;line-height:1.1}.page-sub{margin-top:6px;color:var(--ink-2);font-size:13px;max-width:640px}.page-meta{display:flex;gap:16px;align-items:center;font-size:12px;color:var(--ink-3);font-family:var(--font-mono)}.page-meta .live{display:inline-flex;align-items:center;gap:6px;color:var(--ok)}.pill-row{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:18px}.pill{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;background:var(--bg-1);border:1px solid var(--line);border-radius:999px;color:var(--ink-2);font-size:12px;cursor:pointer;transition:background .1s,border-color .1s,color .1s;white-space:nowrap}.pill:hover{background:var(--bg-2);color:var(--ink-1)}.pill.active{background:var(--bg-3);border-color:var(--line-strong);color:var(--ink-0)}.pill-count{font-family:var(--font-mono);font-size:10.5px;color:var(--ink-3);padding:0 5px;background:var(--bg-2);border-radius:10px;min-width:18px;text-align:center}.pill.active .pill-count{background:var(--bg-1);color:var(--ink-1)}.pill.attn{color:var(--pending)}.pill.attn .pill-count{background:var(--pending-dim);color:var(--pending)}.toolbar{display:flex;align-items:center;gap:8px;margin-bottom:14px;flex-wrap:wrap}.toolbar-spacer{flex:1}.select{display:inline-flex;align-items:center;gap:6px;padding:5px 10px;background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2);font-size:12px;color:var(--ink-1);cursor:pointer}.select:hover{background:var(--bg-2)}.select svg{width:12px;height:12px;opacity:.7}.approval-banner{margin-bottom:18px;padding:14px 18px;background:linear-gradient(180deg,var(--pending-dim),transparent);border:1px solid oklch(.8 .14 85 / .35);border-radius:var(--r-3);display:flex;align-items:center;gap:16px}.approval-banner-dot{width:8px;height:8px;border-radius:50%;background:var(--pending);box-shadow:0 0 0 4px var(--pending-dim);animation:pulse-p 1.6s ease-in-out infinite;flex-shrink:0}@keyframes pulse-p{0%,to{box-shadow:0 0 0 4px #e7b64347}50%{box-shadow:0 0 0 9px #e7b64300}}.approval-banner-text{flex:1}.approval-banner-title{font-weight:600;color:var(--ink-0);font-size:13.5px}.approval-banner-sub{color:var(--ink-2);font-size:12.5px;margin-top:2px}.session-group{margin-bottom:18px;border:1px solid var(--line);border-radius:var(--r-3);background:var(--bg-1);overflow:hidden}.session-group.live{border-color:#5ec38652}.session-header{display:flex;align-items:center;gap:14px;padding:12px 16px;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .1s}.session-header:hover{background:var(--bg-2)}.session-chevron{width:14px;height:14px;color:var(--ink-3);transition:transform .15s;flex-shrink:0}.session-group.collapsed .session-chevron{transform:rotate(-90deg)}.session-avatar{width:26px;height:26px;border-radius:50%;background:var(--bg-3);display:grid;place-items:center;font-family:var(--font-mono);font-size:10.5px;color:var(--ink-1);flex-shrink:0}.session-summary{flex:1;min-width:0}.session-summary-top{display:flex;align-items:baseline;gap:10px;color:var(--ink-0);font-size:13.5px;font-weight:500}.session-summary-bottom{font-size:11.5px;color:var(--ink-3);font-family:var(--font-mono);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.session-live-tag{display:inline-flex;align-items:center;gap:5px;font-size:11px;color:var(--ok);font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.06em}.session-live-tag:before{content:"";width:6px;height:6px;border-radius:50%;background:var(--ok);animation:pulse-s 1.4s ease-in-out infinite}@keyframes pulse-s{0%,to{opacity:1}50%{opacity:.4}}.session-stats{display:flex;gap:14px;font-family:var(--font-mono);font-size:11px;color:var(--ink-3)}.session-stats strong{color:var(--ink-1);font-weight:500}.session-stats .stat.warn{color:var(--pending)}.session-stats .stat.err{color:var(--err)}.session-actions-menu{display:flex;gap:4px}.session-rollback{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;font-size:11.5px;color:var(--ink-2);border:1px solid var(--line);border-radius:var(--r-2);background:var(--bg-2);opacity:0;transition:opacity .1s,color .1s,background .1s}.session-header:hover .session-rollback,.session-header:focus-within .session-rollback{opacity:1}.session-rollback:hover{color:var(--ink-0);background:var(--bg-3)}.session-body{border-top:1px solid var(--line);max-height:3000px;overflow:hidden;transition:max-height .2s ease-out}.session-group.collapsed .session-body{max-height:0;border-top-color:transparent}.action-row{display:grid;grid-template-columns:3px 1fr auto;align-items:center;gap:0 14px;border-top:1px solid var(--line);position:relative;transition:background .1s}.action-row:first-child{border-top:none}.action-row:hover,.action-row.focused,.action-row:focus-within{background:var(--bg-2)}.action-row.obs{opacity:.58}.action-row-open{display:grid;grid-template-columns:auto 110px 1fr auto;align-items:center;gap:14px;padding:var(--row-py, 10px) 0;background:transparent;border:none;text-align:left;width:100%;cursor:pointer;color:inherit;font:inherit}.action-row-open:focus-visible{outline:none;box-shadow:inset 3px 0 0 var(--acc);border-radius:0 var(--r-2) var(--r-2) 0}.action-row>.row-rail{padding:2px 0 2px 16px}.action-row>.row-actions{padding:var(--row-py, 10px) 16px var(--row-py, 10px) 0}.row-rail{align-self:stretch;background:transparent;border-radius:2px;margin:2px 0}.action-row[data-state=pending] .row-rail{background:var(--pending)}.action-row[data-state=blocked] .row-rail{background:var(--err)}.action-row[data-state=rejected] .row-rail{background:var(--err);opacity:.7}.action-row[data-state=error] .row-rail{background:var(--err)}.action-row[data-state=rolledback] .row-rail{background:var(--ink-4)}.action-row[data-state=approved] .row-rail{background:var(--ok)}.action-row[data-state=ok] .row-rail{background:transparent}.row-time{font-family:var(--font-mono);font-size:11px;color:var(--ink-3);white-space:nowrap}.tool-tag{display:inline-flex;align-items:center;gap:6px;padding:2px 8px;border-radius:var(--r-2);font-family:var(--font-mono);font-size:11px;background:var(--bg-2);border:1px solid var(--line);color:var(--ink-1);white-space:nowrap;line-height:1.5}.tool-tag .tdot{width:6px;height:6px;border-radius:50%;background:var(--ink-3)}.tool-tag[data-tool=write_file] .tdot{background:var(--warn)}.tool-tag[data-tool=read_file] .tdot{background:var(--info)}.tool-tag[data-tool=bash] .tdot{background:#a299b4}.tool-tag[data-tool=delete_file] .tdot{background:var(--err)}.row-intent{min-width:0;display:flex;flex-direction:column;gap:2px}.intent-line{display:flex;align-items:baseline;gap:8px;color:var(--ink-0);font-size:13.5px;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.intent-path{font-family:var(--font-mono);font-size:12.5px;color:var(--ink-1);overflow:hidden;text-overflow:ellipsis}.intent-path .dim{color:var(--ink-3)}.intent-sub{font-size:11.5px;color:var(--ink-3);font-family:var(--font-mono);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;gap:8px}.policy-chip{display:inline-flex;align-items:center;gap:4px;padding:1px 7px;border-radius:10px;font-size:10.5px;font-family:var(--font-mono);border:1px solid var(--line);background:var(--bg-2);color:var(--ink-2);white-space:nowrap;flex-shrink:0}.policy-chip.allow{color:var(--ok);border-color:#5ec3864d;background:var(--ok-dim)}.policy-chip.block{color:var(--err);border-color:#f75d594d;background:var(--err-dim)}.policy-chip.pending{color:var(--pending);border-color:#e7b64359;background:var(--pending-dim)}.policy-chip.obs{color:var(--info);border-color:#55aee84d;background:var(--info-dim)}.status-tag{font-family:var(--font-mono);font-size:11px;color:var(--ink-2);white-space:nowrap;display:flex;align-items:center;gap:6px}.status-tag .sdot{width:6px;height:6px;border-radius:50%}.status-tag[data-s=success] .sdot{background:var(--ok)}.status-tag[data-s=pending] .sdot{background:var(--pending);animation:pulse-s 1.4s ease-in-out infinite}.status-tag[data-s=error] .sdot,.status-tag[data-s=blocked] .sdot{background:var(--err)}.status-tag[data-s=rejected] .sdot{background:var(--err);opacity:.7}.status-tag[data-s=rolledback] .sdot{background:var(--ink-4)}.status-tag[data-s=approved] .sdot{background:var(--ok)}.row-actions{display:flex;gap:6px;align-items:center}.btn{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:var(--r-2);font-size:11.5px;border:1px solid var(--line);background:var(--bg-2);color:var(--ink-1);transition:background .1s,border-color .1s,color .1s;white-space:nowrap}.btn:hover{background:var(--bg-3)}.btn.primary{background:var(--acc);border-color:var(--acc);color:var(--bg-0);font-weight:500}[data-theme=light] .btn.primary{color:#fff}.btn.primary:hover{filter:brightness(1.08)}.btn.ok{background:var(--ok-dim);border-color:#5ec38666;color:var(--ok)}.btn.ok:hover{background:#5ec38638}.btn.danger{background:var(--err-dim);border-color:#f75d5959;color:var(--err)}.btn.danger:hover{background:#f75d5938}.btn.ghost{background:transparent}.btn:disabled{opacity:.5;cursor:default}.btn-lg{padding:7px 14px;font-size:13px}.muted{color:var(--ink-3)}.error-text{color:var(--err);font-family:var(--font-mono);font-size:11px}.empty{padding:56px 24px;text-align:center;color:var(--ink-3);border:1px dashed var(--line);border-radius:var(--r-3);background:var(--bg-1)}.empty-title{color:var(--ink-1);font-size:14px;margin-bottom:4px;font-weight:500}.empty-sub{font-size:12.5px;max-width:420px;margin:0 auto;line-height:1.5}.empty-hint{margin-top:14px;padding:10px 14px;background:var(--bg-0);border:1px solid var(--line);border-radius:var(--r-2);font-family:var(--font-mono);font-size:11.5px;color:var(--ink-2);display:inline-block;text-align:left}.banner{padding:12px 16px;border-radius:var(--r-3);border:1px solid var(--line);background:var(--bg-1);font-size:12.5px;color:var(--ink-2);margin-bottom:18px;display:flex;align-items:center;gap:10px}.banner.err{border-color:#f75d5959;background:var(--err-dim);color:var(--err)}.banner.warn{border-color:#e7b64359;background:var(--pending-dim);color:var(--pending)}.skeleton{background:linear-gradient(90deg,var(--bg-1) 0%,var(--bg-2) 50%,var(--bg-1) 100%);background-size:200% 100%;animation:shimmer 1.2s linear infinite;border-radius:var(--r-2)}@keyframes shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}.scrim{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000073;backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);z-index:80;animation:fadein .15s}.drawer{position:fixed;top:0;right:0;width:var(--drawer-w);max-width:100vw;height:100vh;background:var(--bg-1);border-left:1px solid var(--line);box-shadow:var(--shadow-drawer);z-index:90;display:flex;flex-direction:column;animation:slidein .18s ease-out}@keyframes fadein{0%{opacity:0}to{opacity:1}}@keyframes slidein{0%{transform:translate(24px);opacity:0}to{transform:translate(0);opacity:1}}.drawer-head{padding:16px 20px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:12px}.drawer-head-title{font-size:14px;font-weight:600;color:var(--ink-0)}.drawer-head-sub{font-family:var(--font-mono);font-size:11px;color:var(--ink-3);margin-top:2px}.drawer-close{width:28px;height:28px;display:grid;place-items:center;border-radius:var(--r-2);color:var(--ink-2);margin-left:auto}.drawer-close:hover{background:var(--bg-2);color:var(--ink-0)}.drawer-body{flex:1;overflow-y:auto;padding:20px}.drawer-section{margin-bottom:24px}.drawer-section-title{font-size:10.5px;text-transform:uppercase;letter-spacing:.09em;color:var(--ink-3);margin-bottom:10px;font-weight:500}.kv-grid{display:grid;grid-template-columns:120px 1fr;gap:6px 16px;font-size:12.5px}.kv-grid dt{color:var(--ink-3);font-family:var(--font-mono);font-size:11.5px}.kv-grid dd{color:var(--ink-1);font-family:var(--font-mono);font-size:12px;word-break:break-all}.code-block{background:var(--bg-0);border:1px solid var(--line);border-radius:var(--r-2);padding:10px 12px;font-family:var(--font-mono);font-size:11.5px;line-height:1.55;color:var(--ink-1);white-space:pre-wrap;word-break:break-all;max-height:320px;overflow-y:auto}.code-block .comment{color:var(--ink-3)}.diff{border:1px solid var(--line);border-radius:var(--r-2);background:var(--bg-0);overflow:hidden}.diff-hdr{display:flex;font-size:10.5px;text-transform:uppercase;letter-spacing:.08em;color:var(--ink-3);background:var(--bg-2);border-bottom:1px solid var(--line)}.diff-hdr>div{flex:1;padding:6px 12px}.diff-hdr>div+div{border-left:1px solid var(--line)}.diff-body{display:flex;font-family:var(--font-mono);font-size:11px;line-height:1.55;max-height:260px;overflow:auto}.diff-body>pre{flex:1;padding:10px 12px;margin:0;white-space:pre-wrap;word-break:break-all;color:var(--ink-2)}.diff-body>pre+pre{border-left:1px solid var(--line)}.diff-body .add{background:#5ec38621;color:var(--ok);display:block;margin:0 -12px;padding:0 12px}.diff-body .del{background:#f75d5921;color:var(--err);display:block;margin:0 -12px;padding:0 12px}.drawer-foot{border-top:1px solid var(--line);padding:14px 20px;display:flex;gap:10px;align-items:center;background:var(--bg-1)}.drawer-foot .spacer{flex:1}.card{background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-3);overflow:hidden}.card-header{padding:14px 18px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:12px}.card-title{font-weight:600;color:var(--ink-0);font-size:13.5px}.card-sub{color:var(--ink-3);font-size:12px}.card-body{padding:18px}.card-body.flush{padding:0}.list{list-style:none}.list>li{padding:12px 18px;border-top:1px solid var(--line);display:flex;align-items:center;gap:14px}.list>li:first-child{border-top:none}.list-title{color:var(--ink-0);font-size:13px;font-weight:500}.list-sub{color:var(--ink-3);font-size:11.5px;font-family:var(--font-mono);margin-top:2px}.toast-stack{position:fixed;bottom:20px;left:50%;transform:translate(-50%);z-index:120;display:flex;flex-direction:column;gap:8px;pointer-events:none}.toast{pointer-events:auto;background:var(--bg-1);border:1px solid var(--line-strong);border-radius:var(--r-3);padding:10px 14px;font-size:12.5px;color:var(--ink-1);box-shadow:var(--shadow-pop);min-width:220px;max-width:480px;display:flex;gap:10px;align-items:center;animation:slideup .2s ease-out}@keyframes slideup{0%{transform:translateY(8px);opacity:0}to{transform:translateY(0);opacity:1}}.toast.ok{border-color:#5ec38666}.toast.err{border-color:#f75d5966;color:var(--err)}.modal-scrim{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000080;backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);z-index:110;display:grid;place-items:center;padding:16px;animation:fadein .12s}.modal{width:min(480px,100%);background:var(--bg-1);border:1px solid var(--line-strong);border-radius:var(--r-3);box-shadow:var(--shadow-pop);overflow:hidden}.modal-head{padding:16px 20px;border-bottom:1px solid var(--line)}.modal-title{font-size:14px;font-weight:600;color:var(--ink-0)}.modal-body{padding:16px 20px;color:var(--ink-1);font-size:13px}.modal-body textarea,.modal-body input{margin-top:10px;width:100%;padding:8px 10px;background:var(--bg-0);border:1px solid var(--line);border-radius:var(--r-2);color:var(--ink-0);font-size:12.5px;font-family:var(--font-mono);outline:none;resize:vertical}.modal-body textarea:focus,.modal-body input:focus{border-color:var(--ink-4)}.modal-foot{padding:12px 20px;border-top:1px solid var(--line);display:flex;justify-content:flex-end;gap:8px}.tweaks{position:fixed;bottom:20px;right:20px;z-index:70;background:var(--bg-1);border:1px solid var(--line-strong);border-radius:var(--r-3);box-shadow:var(--shadow-pop);padding:14px 16px;width:260px}.tweaks h4{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--ink-3);margin-bottom:10px;font-weight:500}.tweak-row{display:flex;align-items:center;justify-content:space-between;padding:5px 0;font-size:12px}.tweak-row label{color:var(--ink-1)}.tweak-group{display:inline-flex;background:var(--bg-2);padding:2px;border-radius:var(--r-2);gap:2px}.tweak-group button{padding:3px 8px;border-radius:4px;font-size:11px;color:var(--ink-2)}.tweak-group button.on{background:var(--bg-4);color:var(--ink-0)}.accent-swatches{display:flex;gap:6px}.accent-swatch{width:20px;height:20px;border-radius:50%;border:2px solid transparent;transition:border-color .1s,transform .1s;cursor:pointer}.accent-swatch.on{border-color:var(--ink-0);transform:scale(1.08)}.settings{display:grid;grid-template-columns:200px 1fr;gap:28px}.settings-nav{display:flex;flex-direction:column;gap:2px;position:sticky;top:84px;align-self:start}.settings-nav a{display:flex;align-items:center;gap:10px;padding:6px 10px;border-radius:var(--r-2);color:var(--ink-2);font-size:13px;transition:background .1s,color .1s;cursor:pointer;text-decoration:none}.settings-nav a:hover{background:var(--bg-2);color:var(--ink-1)}.settings-nav a.active{background:var(--bg-2);color:var(--ink-0)}.settings-nav a svg{opacity:.8;flex-shrink:0}.settings-section{display:flex;flex-direction:column;gap:18px;min-width:0}.settings-section h2{font-size:15px;font-weight:600;color:var(--ink-0);letter-spacing:-.01em}.form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px 14px}.field{display:flex;flex-direction:column;gap:6px}.field label{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--ink-3);font-weight:500}.field input,.field textarea,.field select{padding:7px 10px;background:var(--bg-0);border:1px solid var(--line);border-radius:var(--r-2);color:var(--ink-0);font-size:12.5px;font-family:var(--font-mono);outline:none;transition:border-color .1s}.field input:focus,.field textarea:focus,.field select:focus{border-color:var(--ink-4)}.field .hint{font-size:11px;color:var(--ink-3)}@media (max-width: 900px){.settings{grid-template-columns:1fr;gap:18px}.settings-nav{position:static;flex-direction:row;overflow-x:auto;border-bottom:1px solid var(--line);padding-bottom:6px}}.queue{display:grid;grid-template-columns:repeat(auto-fit,minmax(420px,1fr));gap:14px}.queue-card{display:flex;flex-direction:column}.queue-card .card-body{flex:1}.queue-card .drawer-foot{padding:12px 18px;border-top:1px solid var(--line)}.policy-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:14px}.policy-card{background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-3);padding:14px 16px}.policy-card-title{display:flex;align-items:center;gap:8px;font-size:12.5px;font-weight:600;color:var(--ink-0);margin-bottom:10px}.policy-rule{display:flex;align-items:center;gap:8px;font-family:var(--font-mono);font-size:12px;padding:5px 0;border-top:1px dashed var(--line);color:var(--ink-1)}.policy-rule:first-of-type{border-top:none}.policy-rule .tag{margin-left:auto;font-size:9.5px;color:var(--ink-3);text-transform:uppercase;letter-spacing:.08em;font-weight:600}.stat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;margin-bottom:18px}.stat-card{background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-3);padding:14px 16px}.stat-label{font-size:10.5px;text-transform:uppercase;letter-spacing:.08em;color:var(--ink-3);margin-bottom:8px}.stat-value{font-size:26px;font-weight:500;color:var(--ink-0);font-family:var(--font-sans);letter-spacing:-.02em;line-height:1}.stat-delta{font-family:var(--font-mono);font-size:11px;margin-top:6px;color:var(--ink-3)}.stat-delta.up{color:var(--ok)}.stat-delta.down{color:var(--err)}.spark{width:100%;height:50px;margin-top:10px}.drawer-body::-webkit-scrollbar,.code-block::-webkit-scrollbar{width:8px}.drawer-body::-webkit-scrollbar-thumb,.code-block::-webkit-scrollbar-thumb{background:var(--bg-3);border-radius:4px}@media (max-width: 900px){.app{grid-template-columns:1fr}.nav{position:static;height:auto;border-right:none;border-bottom:1px solid var(--line)}.page{padding:20px 16px 32px}.topbar{padding:10px 16px;gap:10px}.search{max-width:none}.action-row{grid-template-columns:3px 1fr}.action-row>.row-actions{grid-column:2;padding:0 14px 12px;justify-self:end}.action-row-open{grid-template-columns:auto 1fr;grid-template-areas:"time intent" "tool tool" "status status";gap:8px 12px;padding:12px 14px}.action-row-open>.row-time{grid-area:time}.action-row-open>.tool-tag{grid-area:tool;justify-self:start}.action-row-open>.row-intent{grid-area:intent}.action-row-open>.status-tag{grid-area:status;justify-self:start}.drawer{width:100vw}.kv-grid{grid-template-columns:100px 1fr}}