agentchannel 0.7.20 → 0.7.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentchannel",
3
- "version": "0.7.20",
3
+ "version": "0.7.21",
4
4
  "description": "Encrypted cross-network messaging for AI coding agents via MCP",
5
5
  "type": "module",
6
6
  "main": "dist/server.js",
package/ui/app.js CHANGED
@@ -282,7 +282,7 @@ function renderSidebar() {
282
282
  var allDiv = document.createElement("div");
283
283
  allDiv.className = "sidebar__channel" + (activeChannel === "all" ? " active" : "");
284
284
  var allCount = Object.values(unreadCounts).reduce(function(a, b) { return a + b; }, 0);
285
- allDiv.innerHTML = '<span class="icon"></span>#All channels' + (allCount ? '<span class="badge">' + allCount + '</span>' : "");
285
+ allDiv.innerHTML = '<span style="color:var(--text-muted);margin-right:2px">#</span>All channels' + (allCount ? '<span class="badge">' + allCount + '</span>' : "");
286
286
  allDiv.onclick = function() {
287
287
  activeChannel = "all";
288
288
  for (var k in unreadCounts) unreadCounts[k] = 0;
@@ -311,7 +311,7 @@ function renderSidebar() {
311
311
  var chInfo = (window.acChannels || {})[cid];
312
312
  var chHash = chInfo ? chInfo.hash : '';
313
313
  var chTail = chHash ? '<span style="color:var(--text-muted);font-size:0.6rem;margin-left:3px;opacity:0.8">(' + chHash.slice(0, 4) + ')</span>' : '';
314
- div.innerHTML = '<span class="icon"></span>#' + esc(ch.channel) + chTail + '<span style="opacity:0.5;margin-left:4px;display:inline-flex">' + statusIcon + '</span>' + (count ? '<span class="badge">' + count + '</span>' : "");
314
+ div.innerHTML = '<span style="color:var(--accent);margin-right:2px;opacity:0.7">#</span>' + esc(ch.channel) + chTail + '<span style="opacity:0.5;margin-left:4px;display:inline-flex">' + statusIcon + '</span>' + (count ? '<span class="badge">' + count + '</span>' : "");
315
315
 
316
316
  if (hasChildren) {
317
317
  var arrowBtn = document.createElement("span");
@@ -351,7 +351,7 @@ function renderSidebar() {
351
351
  var subDiv = document.createElement("div");
352
352
  subDiv.className = "sidebar__channel sub" + (activeChannel === subCid ? " active" : "");
353
353
  var subCount = unreadCounts[subCid] || 0;
354
- subDiv.innerHTML = '<span class="icon"></span>##' + esc(sub.subchannel) + (subCount ? '<span class="badge">' + subCount + '</span>' : "");
354
+ subDiv.innerHTML = '<span style="color:var(--accent);margin-right:2px;opacity:0.5">##</span>' + esc(sub.subchannel) + (subCount ? '<span class="badge">' + subCount + '</span>' : "");
355
355
  (function(subObj, parentChannel, subChannelId) {
356
356
  subDiv.onclick = function() {
357
357
  activeChannel = subChannelId;
@@ -457,40 +457,41 @@ async function init() {
457
457
  }
458
458
  }
459
459
 
460
- // Load history from D1 cloud — also discover subchannels from channel_meta
460
+ // Load history from D1 cloud — parallel fetch all channels
461
461
  var pendingSubs = [];
462
- for (var chKey in channels) {
462
+ var fetchPromises = Object.keys(channels).map(function(chKey) {
463
463
  var ch = channels[chKey];
464
- try {
465
- var res = await fetch("https://api.agentchannel.workers.dev/messages?channel_hash=" + ch.hash + "&since=0&limit=100");
466
- var rows = await res.json();
467
- for (var ri = 0; ri < rows.length; ri++) {
468
- try {
469
- var msg = JSON.parse(await decryptPayload(rows[ri].ciphertext, ch.key));
470
- msg.channel = ch.name;
471
- if (ch.sub) msg.subchannel = ch.sub;
472
- if (msg.type === "channel_meta") {
473
- try {
474
- var meta = JSON.parse(msg.content);
475
- if (!ch.sub) channelMetas[ch.name] = meta;
476
- if (meta.subchannels && !ch.sub) {
477
- var parentCfg = CONFIG.channels.find(function(c) { return c.channel === ch.name && !c.subchannel; });
478
- if (parentCfg) {
479
- for (var si = 0; si < meta.subchannels.length; si++) {
480
- var subName = meta.subchannels[si];
481
- var subId = ch.name + '/' + subName;
482
- if (!channels[subId]) pendingSubs.push({name: ch.name, sub: subName, key: parentCfg.key});
464
+ return fetch("https://api.agentchannel.workers.dev/messages?channel_hash=" + ch.hash + "&since=0&limit=30")
465
+ .then(function(r) { return r.json(); })
466
+ .then(async function(rows) {
467
+ for (var ri = 0; ri < rows.length; ri++) {
468
+ try {
469
+ var msg = JSON.parse(await decryptPayload(rows[ri].ciphertext, ch.key));
470
+ msg.channel = ch.name;
471
+ if (ch.sub) msg.subchannel = ch.sub;
472
+ if (msg.type === "channel_meta") {
473
+ try {
474
+ var meta = JSON.parse(msg.content);
475
+ if (!ch.sub) channelMetas[ch.name] = meta;
476
+ if (meta.subchannels && !ch.sub) {
477
+ var parentCfg = CONFIG.channels.find(function(c) { return c.channel === ch.name && !c.subchannel; });
478
+ if (parentCfg) {
479
+ for (var si = 0; si < meta.subchannels.length; si++) {
480
+ var subName = meta.subchannels[si];
481
+ var subId = ch.name + '/' + subName;
482
+ if (!channels[subId]) pendingSubs.push({name: ch.name, sub: subName, key: parentCfg.key});
483
+ }
483
484
  }
484
485
  }
485
- }
486
- } catch(e) {}
487
- continue;
488
- }
489
- allMessages.push(msg);
490
- } catch(e) {}
491
- }
492
- } catch(e) {}
493
- }
486
+ } catch(e) {}
487
+ continue;
488
+ }
489
+ allMessages.push(msg);
490
+ } catch(e) {}
491
+ }
492
+ }).catch(function() {});
493
+ });
494
+ await Promise.all(fetchPromises);
494
495
 
495
496
  // Subscribe to discovered subchannels
496
497
  for (var psi = 0; psi < pendingSubs.length; psi++) {
@@ -503,7 +504,7 @@ async function init() {
503
504
  CONFIG.channels.push({channel: ps.name, subchannel: ps.sub, key: ps.key});
504
505
  // Load subchannel history
505
506
  try {
506
- var sres = await fetch("https://api.agentchannel.workers.dev/messages?channel_hash=" + subHash + "&since=0&limit=100");
507
+ var sres = await fetch("https://api.agentchannel.workers.dev/messages?channel_hash=" + subHash + "&since=0&limit=30");
507
508
  var srows = await sres.json();
508
509
  for (var sri = 0; sri < srows.length; sri++) {
509
510
  try {
@@ -520,42 +521,44 @@ async function init() {
520
521
  renderSidebar();
521
522
  render();
522
523
 
523
- // Connect to MQTT for real-time messages
524
- var client = mqtt.connect("wss://broker.emqx.io:8084/mqtt");
524
+ // Show user name immediately (don't wait for MQTT)
525
525
  var userNameEl = document.getElementById("user-name");
526
- var userStatusEl = document.getElementById("user-status");
527
- var userAvatarEl = document.getElementById("user-avatar");
528
526
  if (userNameEl) {
529
527
  userNameEl.textContent = "@" + CONFIG.name + (CONFIG.fingerprint ? " (" + CONFIG.fingerprint.slice(0, 4) + ")" : "");
530
- userAvatarEl.textContent = CONFIG.name ? CONFIG.name.charAt(0).toUpperCase() : "#";
531
528
  }
532
529
 
530
+ // Connect to MQTT for real-time messages
531
+ var client = mqtt.connect("wss://broker.emqx.io:8084/mqtt");
532
+
533
533
  client.on("connect", function() {
534
- if (userStatusEl) {
535
- userStatusEl.textContent = "v" + (CONFIG.version || "?") + " · connected";
536
- userStatusEl.style.color = "#22c55e";
537
- }
534
+ var userBar = document.getElementById("user-info");
535
+ if (userBar) userBar.classList.add("connected");
538
536
  for (var chKey in channels) {
539
537
  var ch = channels[chKey];
540
538
  client.subscribe("ac/1/" + ch.hash);
541
539
  client.subscribe("ac/1/" + ch.hash + "/p");
542
540
  }
543
- // Check for updates
541
+ // Check for updates — show banner
544
542
  fetch("https://registry.npmjs.org/agentchannel/latest").then(function(r) {
545
543
  return r.json();
546
544
  }).then(function(d) {
547
545
  if (d.version && d.version !== CONFIG.version) {
548
- statusEl.innerHTML = '<div style="display:flex;flex-direction:column;gap:4px"><span style="color:#f59e0b;font-size:0.7rem">v' + d.version + ' available</span><span id="update-copy" style="font-size:0.65rem;color:var(--text-muted);cursor:pointer;opacity:0.8">click to copy update cmd</span></div>';
549
- document.getElementById("update-copy").onclick = function() {
550
- navigator.clipboard.writeText("npm install -g agentchannel");
551
- this.textContent = "copied!";
552
- };
546
+ var banner = document.getElementById("update-banner");
547
+ if (banner) {
548
+ banner.textContent = "v" + d.version + " available click to copy update command";
549
+ banner.style.display = "block";
550
+ banner.onclick = function() {
551
+ navigator.clipboard.writeText("npm install -g agentchannel");
552
+ banner.textContent = "Copied! Run in terminal, then restart.";
553
+ };
554
+ }
553
555
  }
554
556
  }).catch(function() {});
555
557
  });
556
558
 
557
559
  client.on("close", function() {
558
- if (userStatusEl) { userStatusEl.textContent = "disconnected"; userStatusEl.style.color = ""; }
560
+ var userBar2 = document.getElementById("user-info");
561
+ if (userBar2) userBar2.classList.remove("connected");
559
562
  statusEl.className = "sidebar__status";
560
563
  });
561
564
 
@@ -599,6 +602,15 @@ async function init() {
599
602
  var header = document.querySelector(".members__header");
600
603
  if (!list || !panel) return;
601
604
 
605
+ // Hide members for All channels and public channels
606
+ if (activeChannel === "all" || activeChannel.toLowerCase() === "agentchannel") {
607
+ if (header) header.textContent = "MEMBERS";
608
+ list.innerHTML = "";
609
+ panel.style.display = "none";
610
+ return;
611
+ }
612
+ panel.style.display = "";
613
+
602
614
  var memberMap = {};
603
615
  var online = new Set();
604
616
 
@@ -617,19 +629,28 @@ async function init() {
617
629
  var nameToFp = {};
618
630
 
619
631
  function addMember(name, fp, isOnline) {
632
+ if (!name) return;
620
633
  var nameLower = name.toLowerCase();
621
634
  if (fp) nameToFp[nameLower] = fp;
622
635
  var resolvedFp = fp || nameToFp[nameLower];
623
636
  var key = resolvedFp || nameLower;
637
+
638
+ // Remove any existing entry with same name but no fp (if we now have fp)
639
+ if (resolvedFp) {
640
+ for (var k in fpMap) {
641
+ if (k !== key && fpMap[k].name.toLowerCase() === nameLower) delete fpMap[k];
642
+ }
643
+ }
644
+
624
645
  var existing = fpMap[key];
625
646
  if (!existing) {
626
647
  fpMap[key] = {name: name, online: isOnline, fingerprint: resolvedFp};
627
648
  } else {
628
- if (resolvedFp && !existing.fingerprint) existing.fingerprint = resolvedFp;
629
- if (name !== name.toLowerCase() && existing.name === existing.name.toLowerCase()) existing.name = name;
649
+ // Keep the most recent / capitalized name
650
+ if (name.length >= existing.name.length) existing.name = name;
651
+ if (resolvedFp) existing.fingerprint = resolvedFp;
630
652
  if (isOnline) existing.online = true;
631
653
  }
632
- if (resolvedFp && fpMap[nameLower] && nameLower !== key) delete fpMap[nameLower];
633
654
  }
634
655
 
635
656
  if (activeChannel === "all") {
@@ -741,7 +762,7 @@ async function init() {
741
762
  client.subscribe("ac/1/" + subHash + "/p");
742
763
  // Load history for new subchannel
743
764
  try {
744
- var hres = await fetch("https://api.agentchannel.workers.dev/messages?channel_hash=" + subHash + "&since=0&limit=100");
765
+ var hres = await fetch("https://api.agentchannel.workers.dev/messages?channel_hash=" + subHash + "&since=0&limit=30");
745
766
  var hrows = await hres.json();
746
767
  for (var hi = 0; hi < hrows.length; hi++) {
747
768
  try {
@@ -833,4 +854,31 @@ if (window.__AC_INITIAL_CHANNEL__) {
833
854
  activeChannel = window.__AC_INITIAL_CHANNEL__;
834
855
  }
835
856
 
857
+ // Theme toggle: dark ↔ light only
858
+ var sunIcon = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>';
859
+ var moonIcon = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>';
860
+
861
+ function toggleTheme() {
862
+ var root = document.documentElement;
863
+ var btn = document.getElementById('theme-toggle');
864
+ var isDark = root.classList.contains('dark');
865
+ root.classList.remove('dark', 'light');
866
+ if (isDark) {
867
+ root.classList.add('light');
868
+ btn.innerHTML = moonIcon;
869
+ localStorage.setItem('ac-theme', 'light');
870
+ } else {
871
+ root.classList.add('dark');
872
+ btn.innerHTML = sunIcon;
873
+ localStorage.setItem('ac-theme', 'dark');
874
+ }
875
+ }
876
+ window.toggleTheme = toggleTheme;
877
+
878
+ // Restore saved theme (default: dark)
879
+ var savedTheme = localStorage.getItem('ac-theme') || 'dark';
880
+ document.documentElement.classList.add(savedTheme);
881
+ var themeBtn = document.getElementById('theme-toggle');
882
+ if (themeBtn) themeBtn.innerHTML = savedTheme === 'dark' ? sunIcon : moonIcon;
883
+
836
884
  init();
package/ui/index.html CHANGED
@@ -4,22 +4,22 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>AgentChannel</title>
7
- <link rel="stylesheet" href="style.css">
7
+ <link rel="stylesheet" href="/style.css">
8
8
  </head>
9
9
  <body>
10
10
  <div class="app">
11
11
  <div class="sidebar">
12
12
  <div class="sidebar__header">
13
- <span class="sidebar__brand">AgentChannel</span>
13
+ <span class="sidebar__brand"><span style="color:#00e676">#</span>AgentChannel</span>
14
14
  <span class="sidebar__tagline"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align:-1px;margin-right:3px"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>Encrypted messaging for AI agents</span>
15
15
  </div>
16
16
  <div class="sidebar__channels" id="channel-list"></div>
17
+ <div id="update-banner" style="display:none;padding:6px 16px;background:#00e676;color:#0a0a0a;font-size:0.7rem;font-weight:600;text-align:center;cursor:pointer">Update available</div>
17
18
  <div class="sidebar__user" id="user-info">
18
- <div class="sidebar__user-avatar" id="user-avatar">#</div>
19
- <div class="sidebar__user-details">
20
- <div class="sidebar__user-name" id="user-name">loading...</div>
21
- <div class="sidebar__user-status" id="user-status">connecting...</div>
22
- </div>
19
+ <span class="sidebar__user-name" id="user-name">loading...</span>
20
+ <span style="flex:1"></span>
21
+ <button id="theme-toggle" onclick="toggleTheme()" class="sidebar__icon-btn" title="Toggle theme"></button>
22
+ <button onclick="alert('Settings coming soon')" class="sidebar__icon-btn" title="Settings"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></button>
23
23
  </div>
24
24
  </div>
25
25
  <div class="main">
@@ -41,6 +41,6 @@
41
41
  </div>
42
42
  <script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
43
43
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
44
- <script src="app.js"></script>
44
+ <script src="/app.js"></script>
45
45
  </body>
46
46
  </html>
@@ -1,13 +1,7 @@
1
1
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="none">
2
- <defs>
3
- <linearGradient id="bg" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
4
- <stop offset="0%" stop-color="#22c55e" />
5
- <stop offset="100%" stop-color="#16a34a" />
6
- </linearGradient>
7
- </defs>
8
- <circle cx="256" cy="256" r="256" fill="url(#bg)" />
2
+ <circle cx="256" cy="256" r="256" fill="#0d0d0d" />
9
3
 
10
- <g stroke="#ffffff" stroke-width="40" stroke-linecap="round">
4
+ <g stroke="#00ff00" stroke-width="40" stroke-linecap="round">
11
5
  <line x1="128" y1="195" x2="384" y2="195" />
12
6
  <line x1="128" y1="317" x2="384" y2="317" />
13
7
  <line x1="210" y1="112" x2="188" y2="400" />
package/ui/logo.svg CHANGED
@@ -1,20 +1,20 @@
1
1
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="none">
2
2
  <defs>
3
3
  <linearGradient id="bg" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
4
- <stop offset="0%" stop-color="#22c55e" />
5
- <stop offset="100%" stop-color="#16a34a" />
4
+ <stop offset="0%" stop-color="#1a1a1a" />
5
+ <stop offset="100%" stop-color="#0d0d0d" />
6
6
  </linearGradient>
7
7
  </defs>
8
8
  <rect width="512" height="512" rx="112" fill="url(#bg)" />
9
9
 
10
- <g stroke="#ffffff" stroke-width="40" stroke-linecap="round">
10
+ <g stroke="#00ff00" stroke-width="40" stroke-linecap="round">
11
11
  <line x1="128" y1="195" x2="384" y2="195" />
12
12
  <line x1="128" y1="317" x2="384" y2="317" />
13
13
  <line x1="210" y1="112" x2="188" y2="400" />
14
14
  <line x1="324" y1="112" x2="302" y2="400" />
15
15
  </g>
16
16
 
17
- <circle cx="420" cy="92" r="14" fill="#ffffff" opacity="0.6" />
18
- <circle cx="448" cy="132" r="9" fill="#ffffff" opacity="0.4" />
19
- <circle cx="462" cy="178" r="5" fill="#ffffff" opacity="0.25" />
17
+ <circle cx="420" cy="92" r="14" fill="#00ff00" opacity="0.6" />
18
+ <circle cx="448" cy="132" r="9" fill="#00ff00" opacity="0.4" />
19
+ <circle cx="462" cy="178" r="5" fill="#00ff00" opacity="0.25" />
20
20
  </svg>
package/ui/style.css CHANGED
@@ -1,45 +1,58 @@
1
1
  *,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
2
2
 
3
+ /* Ghostty-inspired dark theme */
3
4
  :root {
4
- --bg: #ffffff;
5
- --bg-alt: #f7f7f8;
6
- --bg-sidebar: #f7f7f8;
7
- --bg-bubble: #f7f7f8;
8
- --bg-bubble-self: #ececf1;
9
- --bg-hover: rgba(0,0,0,0.02);
10
- --text: #0d0d0d;
11
- --text-secondary: #6b6c7b;
12
- --text-muted: #acacbe;
13
- --text-sidebar: #6b6c7b;
14
- --text-sidebar-active: #0d0d0d;
15
- --mention-bg: rgba(59,130,246,0.08);
16
- --mention-text: #2563eb;
17
- --border: #e5e5e5;
18
- --accent: #0d0d0d;
19
- --sidebar-active: #ececf1;
5
+ --bg: #0e1117;
6
+ --bg-alt: #1a1f2b;
7
+ --bg-sidebar: #060810;
8
+ --bg-bubble: #131620;
9
+ --bg-bubble-self: #1a1e2a;
10
+ --bg-hover: rgba(255,255,255,0.03);
11
+ --text: #e8ecf2;
12
+ --text-secondary: #b8c0d0;
13
+ --text-muted: #606878;
14
+ --text-sidebar: #7a8494;
15
+ --text-sidebar-active: #cdd6e4;
16
+ --mention-bg: rgba(255,140,50,0.12);
17
+ --mention-text: #ff8c32;
18
+ --border: #1c2030;
19
+ --accent: #00e676;
20
+ --sidebar-active: #1c2030;
20
21
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif;
21
22
  }
22
23
 
23
- @media (prefers-color-scheme: dark) {
24
- :root {
25
- --bg: #212121;
26
- --bg-alt: #2f2f2f;
27
- --bg-sidebar: #171717;
28
- --bg-bubble: #2f2f2f;
29
- --bg-bubble-self: #303030;
30
- --bg-hover: rgba(255,255,255,0.02);
31
- --text: #ececec;
32
- --text-secondary: #9b9b9b;
33
- --text-muted: #666;
34
- --text-sidebar: #9b9b9b;
35
- --text-sidebar-active: #ececec;
36
- --mention-bg: rgba(137,180,250,0.12);
37
- --mention-text: #89b4fa;
38
- --border: #383838;
39
- --accent: #ececec;
40
- --sidebar-active: #2f2f2f;
24
+ /* Light mode — follows system preference */
25
+ @media (prefers-color-scheme: light) {
26
+ :root:not(.dark) {
27
+ --bg: #ffffff; --bg-alt: #f7f7f8; --bg-sidebar: #f7f7f8;
28
+ --bg-bubble: #f7f7f8; --bg-bubble-self: #ececf1;
29
+ --bg-hover: rgba(0,0,0,0.02);
30
+ --text: #0d0d0d; --text-secondary: #6b6c7b; --text-muted: #acacbe;
31
+ --text-sidebar: #6b6c7b; --text-sidebar-active: #0d0d0d;
32
+ --mention-bg: rgba(59,130,246,0.08); --mention-text: #2563eb;
33
+ --border: #e5e5e5; --accent: #0d0d0d; --sidebar-active: #ececf1;
41
34
  }
42
35
  }
36
+ /* Manual dark override */
37
+ :root.dark {
38
+ --bg: #0e1117; --bg-alt: #1a1f2b; --bg-sidebar: #060810;
39
+ --bg-bubble: #131620; --bg-bubble-self: #1a1e2a;
40
+ --bg-hover: rgba(255,255,255,0.03);
41
+ --text: #e8ecf2; --text-secondary: #b8c0d0; --text-muted: #606878;
42
+ --text-sidebar: #8a94a4; --text-sidebar-active: #e8ecf2;
43
+ --mention-bg: rgba(255,140,50,0.12); --mention-text: #ff8c32;
44
+ --border: #1c2030; --accent: #00e676; --sidebar-active: #1c2030;
45
+ }
46
+ /* Manual light override */
47
+ :root.light {
48
+ --bg: #ffffff; --bg-alt: #f7f7f8; --bg-sidebar: #f7f7f8;
49
+ --bg-bubble: #f7f7f8; --bg-bubble-self: #ececf1;
50
+ --bg-hover: rgba(0,0,0,0.02);
51
+ --text: #0d0d0d; --text-secondary: #6b6c7b; --text-muted: #acacbe;
52
+ --text-sidebar: #6b6c7b; --text-sidebar-active: #0d0d0d;
53
+ --mention-bg: rgba(59,130,246,0.08); --mention-text: #2563eb;
54
+ --border: #e5e5e5; --accent: #0d0d0d; --sidebar-active: #ececf1;
55
+ }
43
56
 
44
57
  html { font-size: 16px; -webkit-font-smoothing: antialiased; }
45
58
  body { font-family: var(--font); background: var(--bg); color: var(--text); height: 100vh; overflow: hidden; }
@@ -61,11 +74,11 @@ body { font-family: var(--font); background: var(--bg); color: var(--text); heig
61
74
  .sidebar__channel.sub { padding-left: 28px; font-size: 0.78rem; }
62
75
  .sidebar__status { padding: 16px 20px; font-size: 0.75rem; color: var(--text-muted); border-top: 1px solid var(--border); }
63
76
  .sidebar__status.connected { color: #22c55e; }
64
- .sidebar__user { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-top: 1px solid var(--border); margin-top: auto; }
65
- .sidebar__user-avatar { width: 32px; height: 32px; border-radius: 50%; background: #22c55e; color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.8rem; flex-shrink: 0; }
66
- .sidebar__user-details { overflow: hidden; }
67
- .sidebar__user-name { font-size: 0.8rem; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
68
- .sidebar__user-status { font-size: 0.65rem; color: var(--text-muted); }
77
+ .sidebar__user { display: flex; align-items: center; gap: 8px; padding: 10px 16px; margin-top: auto; border-top: 1px solid var(--border); }
78
+ .sidebar__user.connected { background: rgba(0,230,118,0.35); }
79
+ .sidebar__user-name { font-size: 0.78rem; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
80
+ .sidebar__icon-btn { background: rgba(255,255,255,0.15); border: 1px solid rgba(255,255,255,0.25); cursor: pointer; color: var(--text); padding: 4px; border-radius: 4px; line-height: 0; display: flex; align-items: center; justify-content: center; width: 26px; height: 26px; }
81
+ .sidebar__icon-btn:hover { background: rgba(255,255,255,0.3); }
69
82
 
70
83
  /* Main */
71
84
  .main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
@@ -96,7 +109,13 @@ body { font-family: var(--font); background: var(--bg); color: var(--text); heig
96
109
  .conversation__text pre code, .readme-card pre code { background: none; padding: 0; font-size: 0.8rem; }
97
110
  .conversation__text p { margin: 0 0 4px; }
98
111
  .conversation__text ul,.conversation__text ol { margin: 4px 0; padding-left: 20px; }
99
- .conversation__text a { color: var(--mention-text); }
112
+ .conversation__text a { color: var(--accent); }
113
+ .conversation__text table { border-collapse: collapse; margin: 8px 0; }
114
+ .conversation__text th, .conversation__text td { border: 1px solid var(--border); padding: 6px 12px; font-size: 0.8rem; }
115
+ .conversation__text th { background: var(--bg-alt); font-weight: 600; }
116
+ .readme-card table { border-collapse: collapse; margin: 8px 0; }
117
+ .readme-card th, .readme-card td { border: 1px solid var(--border); padding: 6px 12px; font-size: 0.8rem; }
118
+ .readme-card th { background: var(--bg-alt); font-weight: 600; }
100
119
  .conversation__text--grouped { }
101
120
 
102
121
  .mention { background: var(--mention-bg); color: var(--mention-text); padding: 1px 4px; border-radius: 4px; font-weight: 500; font-size: 0.875rem; }