@sleep2agi/agent-network-dashboard 0.4.3-preview.1 → 0.4.5-preview.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 (272) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +6 -6
  3. package/.next/diagnostics/route-bundle-stats.json +106 -92
  4. package/.next/fallback-build-manifest.json +3 -3
  5. package/.next/prerender-manifest.json +3 -3
  6. package/.next/server/app/_global-error/page/build-manifest.json +3 -3
  7. package/.next/server/app/_global-error.html +1 -1
  8. package/.next/server/app/_global-error.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/server/app/_not-found/page/build-manifest.json +3 -3
  15. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  16. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  17. package/.next/server/app/_not-found.html +2 -2
  18. package/.next/server/app/_not-found.rsc +12 -12
  19. package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  20. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  21. package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  22. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  23. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  24. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  25. package/.next/server/app/admin/page/build-manifest.json +3 -3
  26. package/.next/server/app/admin/page.js.nft.json +1 -1
  27. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  28. package/.next/server/app/admin.html +2 -2
  29. package/.next/server/app/admin.rsc +14 -14
  30. package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
  31. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  32. package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
  33. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  34. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  35. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  36. package/.next/server/app/index.html +2 -2
  37. package/.next/server/app/index.rsc +14 -14
  38. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  39. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  40. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  41. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  42. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  43. package/.next/server/app/login/page/build-manifest.json +3 -3
  44. package/.next/server/app/login/page.js.nft.json +1 -1
  45. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  46. package/.next/server/app/login.html +2 -2
  47. package/.next/server/app/login.rsc +14 -14
  48. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  49. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  50. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  51. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  52. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  53. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  54. package/.next/server/app/logs/page/build-manifest.json +3 -3
  55. package/.next/server/app/logs/page.js.nft.json +1 -1
  56. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/logs.html +2 -2
  58. package/.next/server/app/logs.rsc +14 -14
  59. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  60. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  61. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  62. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  64. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  65. package/.next/server/app/messages/page/build-manifest.json +3 -3
  66. package/.next/server/app/messages/page.js.nft.json +1 -1
  67. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  68. package/.next/server/app/messages.html +2 -2
  69. package/.next/server/app/messages.rsc +14 -14
  70. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  71. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  72. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  73. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  74. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  75. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  76. package/.next/server/app/node/page/build-manifest.json +3 -3
  77. package/.next/server/app/node/page.js.nft.json +1 -1
  78. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  79. package/.next/server/app/node.html +2 -2
  80. package/.next/server/app/node.rsc +14 -14
  81. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  82. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  83. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  84. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  85. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  86. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  87. package/.next/server/app/nodes/page/build-manifest.json +3 -3
  88. package/.next/server/app/nodes/page.js.nft.json +1 -1
  89. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  90. package/.next/server/app/nodes.html +2 -2
  91. package/.next/server/app/nodes.rsc +14 -14
  92. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  93. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  94. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  95. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  96. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  97. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  98. package/.next/server/app/page/build-manifest.json +3 -3
  99. package/.next/server/app/page.js.nft.json +1 -1
  100. package/.next/server/app/page_client-reference-manifest.js +1 -1
  101. package/.next/server/app/server-logs/page/build-manifest.json +3 -3
  102. package/.next/server/app/server-logs/page.js.nft.json +1 -1
  103. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  104. package/.next/server/app/server-logs.html +2 -2
  105. package/.next/server/app/server-logs.rsc +14 -14
  106. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  107. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  108. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  109. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  110. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  111. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  112. package/.next/server/app/settings/networks/page/build-manifest.json +3 -3
  113. package/.next/server/app/settings/networks/page.js.nft.json +1 -1
  114. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  115. package/.next/server/app/settings/networks.html +2 -2
  116. package/.next/server/app/settings/networks.rsc +14 -14
  117. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  118. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  119. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  120. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  121. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  122. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  123. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  124. package/.next/server/app/settings/page/build-manifest.json +3 -3
  125. package/.next/server/app/settings/page.js.nft.json +1 -1
  126. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  127. package/.next/server/app/settings/tokens/page/build-manifest.json +3 -3
  128. package/.next/server/app/settings/tokens/page.js.nft.json +1 -1
  129. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  130. package/.next/server/app/settings/tokens.html +2 -2
  131. package/.next/server/app/settings/tokens.rsc +14 -14
  132. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  133. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  134. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  135. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  136. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  137. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  138. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  139. package/.next/server/app/settings.html +2 -2
  140. package/.next/server/app/settings.rsc +14 -14
  141. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  142. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  143. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  144. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  145. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  146. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  147. package/.next/server/app/tasks/[id]/page/build-manifest.json +3 -3
  148. package/.next/server/app/tasks/[id]/page.js.nft.json +1 -1
  149. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  150. package/.next/server/app/tasks/page/build-manifest.json +3 -3
  151. package/.next/server/app/tasks/page.js.nft.json +1 -1
  152. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  153. package/.next/server/app/tasks.html +2 -2
  154. package/.next/server/app/tasks.rsc +14 -14
  155. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  156. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  157. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  158. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  159. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  160. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  161. package/.next/server/chunks/ssr/[root-of-the-server]__013i9e9._.js +9 -0
  162. package/.next/server/chunks/ssr/[root-of-the-server]__013i9e9._.js.map +1 -0
  163. package/.next/server/chunks/ssr/[root-of-the-server]__0_jnflc._.js +3 -0
  164. package/.next/server/chunks/ssr/[root-of-the-server]__0_jnflc._.js.map +1 -0
  165. package/.next/server/chunks/ssr/[root-of-the-server]__12mpoig._.js +3 -0
  166. package/.next/server/chunks/ssr/[root-of-the-server]__12mpoig._.js.map +1 -0
  167. package/.next/server/chunks/ssr/_0d-fuox._.js +3 -0
  168. package/.next/server/chunks/ssr/_0d-fuox._.js.map +1 -0
  169. package/.next/server/chunks/ssr/app_0.0wpa~._.js +3 -0
  170. package/.next/server/chunks/ssr/app_0.0wpa~._.js.map +1 -0
  171. package/.next/server/chunks/ssr/app_0dk5wao._.js +1 -1
  172. package/.next/server/chunks/ssr/app_0dk5wao._.js.map +1 -1
  173. package/.next/server/chunks/ssr/app_0et5yua._.js +3 -0
  174. package/.next/server/chunks/ssr/app_0et5yua._.js.map +1 -0
  175. package/.next/server/chunks/ssr/app_0ff~kjj._.js +9 -0
  176. package/.next/server/chunks/ssr/app_0ff~kjj._.js.map +1 -0
  177. package/.next/server/chunks/ssr/app_0izi28j._.js +3 -0
  178. package/.next/server/chunks/ssr/app_0izi28j._.js.map +1 -0
  179. package/.next/server/chunks/ssr/app_login_page_tsx_0z71ad3._.js +1 -1
  180. package/.next/server/chunks/ssr/app_login_page_tsx_0z71ad3._.js.map +1 -1
  181. package/.next/server/chunks/ssr/app_page_tsx_0es_sk2._.js +1 -1
  182. package/.next/server/chunks/ssr/app_page_tsx_0es_sk2._.js.map +1 -1
  183. package/.next/server/chunks/ssr/app_server-logs_page_tsx_0_kpp1p._.js +1 -1
  184. package/.next/server/chunks/ssr/app_server-logs_page_tsx_0_kpp1p._.js.map +1 -1
  185. package/.next/server/chunks/ssr/app_tasks_page_tsx_0z-d6mp._.js +1 -1
  186. package/.next/server/chunks/ssr/app_tasks_page_tsx_0z-d6mp._.js.map +1 -1
  187. package/.next/server/chunks/ssr/node_modules_0ck2~9g._.js +3 -0
  188. package/.next/server/chunks/ssr/node_modules_0ck2~9g._.js.map +1 -0
  189. package/.next/server/middleware-build-manifest.js +6 -6
  190. package/.next/server/pages/404.html +2 -2
  191. package/.next/server/pages/500.html +1 -1
  192. package/.next/server/server-reference-manifest.js +1 -1
  193. package/.next/server/server-reference-manifest.json +1 -1
  194. package/.next/static/chunks/0257pdz1-imal.js +2 -0
  195. package/.next/static/chunks/02flrewxlf_m_.js +1 -0
  196. package/.next/static/chunks/051gslfjka1kj.js +1 -0
  197. package/.next/static/chunks/06t_vzei8s9_h.js +1 -0
  198. package/.next/static/chunks/0e-.zgw81gela.js +31 -0
  199. package/.next/static/chunks/0e7c40p~wq665.js +1 -0
  200. package/.next/static/chunks/0g7m7zk.v7g6h.js +1 -0
  201. package/.next/static/chunks/0hyhubmh-__wg.js +1 -0
  202. package/.next/static/chunks/0khpyxpdrp~ty.js +1 -0
  203. package/.next/static/chunks/0lo_g.q1fm-hr.js +7 -0
  204. package/.next/static/chunks/0snd~q2ucn.h1.css +2 -0
  205. package/.next/static/chunks/0w1zwt8kq_7nb.js +1 -0
  206. package/.next/static/chunks/0xn9.0ejo1s_a.js +7 -0
  207. package/.next/static/chunks/0y0lo-~k2c~00.js +1 -0
  208. package/.next/static/chunks/10njj7kn9ylkf.js +1 -0
  209. package/.next/static/chunks/13.dst7liziwo.js +1 -0
  210. package/.next/static/chunks/{turbopack-0vsvjs02l449s.js → turbopack-01f1xs~cq_~si.js} +1 -1
  211. package/.next/trace +2 -2
  212. package/.next/trace-build +1 -1
  213. package/app/components/EmptyState.tsx +189 -0
  214. package/app/components/LoadingSkeleton.tsx +3 -18
  215. package/app/components/Sidebar.tsx +72 -11
  216. package/app/components/StatsBar.tsx +7 -1
  217. package/app/components/TopoGraph.tsx +151 -62
  218. package/app/globals.css +108 -8
  219. package/app/login/page.tsx +26 -13
  220. package/app/logs/page.tsx +24 -11
  221. package/app/messages/page.tsx +2 -5
  222. package/app/nodes/page.tsx +6 -1
  223. package/app/page.tsx +36 -12
  224. package/app/server-logs/page.tsx +22 -11
  225. package/app/settings/networks/page.tsx +6 -1
  226. package/app/settings/page.tsx +56 -29
  227. package/app/settings/tokens/page.tsx +6 -1
  228. package/app/tasks/page.tsx +7 -9
  229. package/docs/loop-design-rounds.md +502 -0
  230. package/docs/oss-readiness-report.md +189 -0
  231. package/package.json +1 -1
  232. package/.mcp.json +0 -11
  233. package/.next/server/chunks/ssr/[root-of-the-server]__093ymui._.js +0 -3
  234. package/.next/server/chunks/ssr/[root-of-the-server]__093ymui._.js.map +0 -1
  235. package/.next/server/chunks/ssr/[root-of-the-server]__0i1gizm._.js +0 -9
  236. package/.next/server/chunks/ssr/[root-of-the-server]__0i1gizm._.js.map +0 -1
  237. package/.next/server/chunks/ssr/[root-of-the-server]__0~yad5w._.js +0 -3
  238. package/.next/server/chunks/ssr/[root-of-the-server]__0~yad5w._.js.map +0 -1
  239. package/.next/server/chunks/ssr/_06_xh1~._.js +0 -3
  240. package/.next/server/chunks/ssr/_06_xh1~._.js.map +0 -1
  241. package/.next/server/chunks/ssr/app_01vhyfx._.js +0 -9
  242. package/.next/server/chunks/ssr/app_01vhyfx._.js.map +0 -1
  243. package/.next/server/chunks/ssr/app_023a94i._.js +0 -3
  244. package/.next/server/chunks/ssr/app_023a94i._.js.map +0 -1
  245. package/.next/server/chunks/ssr/app_0basiqb._.js +0 -3
  246. package/.next/server/chunks/ssr/app_0basiqb._.js.map +0 -1
  247. package/.next/server/chunks/ssr/app_0pljk5o._.js +0 -3
  248. package/.next/server/chunks/ssr/app_0pljk5o._.js.map +0 -1
  249. package/.next/server/chunks/ssr/node_modules_next_dist_0t-hj0x._.js +0 -3
  250. package/.next/server/chunks/ssr/node_modules_next_dist_0t-hj0x._.js.map +0 -1
  251. package/.next/static/chunks/0.z~9klrgd.t-.js +0 -1
  252. package/.next/static/chunks/08aj9~75raaah.js +0 -7
  253. package/.next/static/chunks/08iom_z8w-wmz.js +0 -7
  254. package/.next/static/chunks/08v0-ki6vwpxf.js +0 -1
  255. package/.next/static/chunks/0agna31q2fjq1.css +0 -2
  256. package/.next/static/chunks/0dkkv90do9a4r.js +0 -1
  257. package/.next/static/chunks/0e-wrhqh4h3r5.js +0 -2
  258. package/.next/static/chunks/0ejz4zwhu3a3y.js +0 -1
  259. package/.next/static/chunks/0o5zj~5vjeks3.js +0 -1
  260. package/.next/static/chunks/0ttvcv6~g0f6i.js +0 -1
  261. package/.next/static/chunks/0uqfv6rc-xdru.js +0 -1
  262. package/.next/static/chunks/0xrnwfefpke-g.js +0 -31
  263. package/.next/static/chunks/0y_po~f~fk~ap.js +0 -1
  264. package/.next/static/chunks/0~93h~zb55yh9.js +0 -1
  265. package/.next/static/chunks/140l7~i5q9wrv.js +0 -1
  266. package/AGENTS.md +0 -5
  267. package/DASHBOARD_OPTIMIZATION_PLAN.md +0 -414
  268. package/docs/iteration-plan.md +0 -62
  269. package/docs/v4-plan.md +0 -38
  270. /package/.next/static/{7dwKlhAm5uyghPmkAubQK → 2m1yIfZCIYDWMYrrJEKSC}/_buildManifest.js +0 -0
  271. /package/.next/static/{7dwKlhAm5uyghPmkAubQK → 2m1yIfZCIYDWMYrrJEKSC}/_clientMiddlewareManifest.js +0 -0
  272. /package/.next/static/{7dwKlhAm5uyghPmkAubQK → 2m1yIfZCIYDWMYrrJEKSC}/_ssgManifest.js +0 -0
@@ -63,42 +63,123 @@ function truncate(value: string, max: number) {
63
63
  return value.length > max ? `${value.slice(0, max - 1)}...` : value;
64
64
  }
65
65
 
66
- function nodeStatus(session: Session, isOnline: boolean) {
66
+ function nodeStatus(session: Session, isOnline: boolean, isLight: boolean) {
67
67
  if (!isOnline) {
68
68
  return {
69
69
  label: 'offline',
70
- primary: '#6b7280',
71
- halo: '#111827',
72
- text: '#9ca3af',
70
+ primary: isLight ? '#94a3b8' : '#6b7280',
71
+ halo: isLight ? '#e2e8f0' : '#111827',
72
+ text: isLight ? '#475569' : '#9ca3af',
73
73
  };
74
74
  }
75
-
76
75
  if (session.status === 'working') {
77
76
  return {
78
77
  label: 'working',
79
- primary: '#22c55e',
80
- halo: '#14532d',
81
- text: '#dcfce7',
78
+ primary: isLight ? '#059669' : '#22c55e',
79
+ halo: isLight ? '#d1fae5' : '#14532d',
80
+ text: isLight ? '#065f46' : '#dcfce7',
82
81
  };
83
82
  }
84
-
85
83
  if (session.status === 'idle') {
86
84
  return {
87
85
  label: 'idle',
88
- primary: '#2dd4bf',
89
- halo: '#134e4a',
90
- text: '#ccfbf1',
86
+ primary: isLight ? '#0d9488' : '#2dd4bf',
87
+ halo: isLight ? '#ccfbf1' : '#134e4a',
88
+ text: isLight ? '#115e59' : '#ccfbf1',
91
89
  };
92
90
  }
93
-
94
91
  return {
95
92
  label: session.status || 'online',
96
- primary: '#38bdf8',
97
- halo: '#0c4a6e',
98
- text: '#e0f2fe',
93
+ primary: isLight ? '#0284c7' : '#38bdf8',
94
+ halo: isLight ? '#dbeafe' : '#0c4a6e',
95
+ text: isLight ? '#0c4a6e' : '#e0f2fe',
99
96
  };
100
97
  }
101
98
 
99
+ /** Theme-aware color palette for the topology SVG. */
100
+ interface Palette {
101
+ panelStops: [string, string, string];
102
+ radarStops: { color: string; opacity: number }[];
103
+ arrowFill: string;
104
+ ringStroke: string;
105
+ spokeStroke: { active: string; idle: string };
106
+ flowEdge: string;
107
+ flowPath: string;
108
+ flowParticle: string;
109
+ nodeFill: { online: string; offline: string };
110
+ labelBox: { fill: string; stroke: string };
111
+ legendBox: { fill: string; stroke: string };
112
+ legendText: string;
113
+ legendHeadline: string;
114
+ legendAccent: string;
115
+ containerBg: string;
116
+ containerBorder: string;
117
+ topRailGradient: string;
118
+ }
119
+
120
+ const DARK_PALETTE: Palette = {
121
+ panelStops: ['#0b1220', '#080814', '#101018'],
122
+ radarStops: [
123
+ { color: '#22d3ee', opacity: 0.18 },
124
+ { color: '#22c55e', opacity: 0.045 },
125
+ { color: '#020617', opacity: 0 },
126
+ ],
127
+ arrowFill: '#67e8f9',
128
+ ringStroke: '#164e63',
129
+ spokeStroke: { active: '#22d3ee', idle: '#155e75' },
130
+ flowEdge: '#67e8f9',
131
+ flowPath: '#e0f2fe',
132
+ flowParticle: '#fef08a',
133
+ nodeFill: { online: '#020617', offline: '#080814' },
134
+ labelBox: { fill: '#020617', stroke: '#1f2937' },
135
+ legendBox: { fill: '#020617', stroke: '#1f2937' },
136
+ legendText: '#94a3b8',
137
+ legendHeadline: '#e5e7eb',
138
+ legendAccent: '#67e8f9',
139
+ containerBg: '#080814',
140
+ containerBorder: '#2a2a4a',
141
+ topRailGradient: 'from-transparent via-cyan-400/70 to-transparent',
142
+ };
143
+
144
+ const LIGHT_PALETTE: Palette = {
145
+ panelStops: ['#f8fafc', '#ffffff', '#f1f5f9'],
146
+ radarStops: [
147
+ { color: '#10b981', opacity: 0.06 },
148
+ { color: '#3b82f6', opacity: 0.03 },
149
+ { color: '#ffffff', opacity: 0 },
150
+ ],
151
+ arrowFill: '#10b981',
152
+ ringStroke: '#cbd5e1',
153
+ spokeStroke: { active: '#10b981', idle: '#cbd5e1' },
154
+ flowEdge: '#10b981',
155
+ flowPath: '#475569',
156
+ flowParticle: '#f59e0b',
157
+ nodeFill: { online: '#ffffff', offline: '#f8fafc' },
158
+ labelBox: { fill: '#ffffff', stroke: '#e2e8f0' },
159
+ legendBox: { fill: '#ffffff', stroke: '#e2e8f0' },
160
+ legendText: '#475569',
161
+ legendHeadline: '#0f172a',
162
+ legendAccent: '#0d9488',
163
+ containerBg: '#ffffff',
164
+ containerBorder: '#e3e6eb',
165
+ topRailGradient: 'from-transparent via-emerald-500/40 to-transparent',
166
+ };
167
+
168
+ function useTheme(): 'light' | 'dark' {
169
+ const [theme, setTheme] = useState<'light' | 'dark'>('dark');
170
+ useEffect(() => {
171
+ const read = () => {
172
+ const t = document.documentElement.getAttribute('data-theme') || 'cyber';
173
+ setTheme(t === 'light' || t === 'mint' ? 'light' : 'dark');
174
+ };
175
+ read();
176
+ const obs = new MutationObserver(read);
177
+ obs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
178
+ return () => obs.disconnect();
179
+ }, []);
180
+ return theme;
181
+ }
182
+
102
183
  function buildFlowLinks(messages: MessageFlow[], positions: Record<string, Point>) {
103
184
  const links = new Map<string, FlowLink>();
104
185
 
@@ -127,6 +208,9 @@ function buildFlowLinks(messages: MessageFlow[], positions: Record<string, Point
127
208
  }
128
209
 
129
210
  export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
211
+ const theme = useTheme();
212
+ const isLight = theme === 'light';
213
+ const pal = isLight ? LIGHT_PALETTE : DARK_PALETTE;
130
214
  const [messages, setMessages] = useState<MessageFlow[]>([]);
131
215
 
132
216
  useEffect(() => {
@@ -206,30 +290,35 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
206
290
  </div>
207
291
  </div>
208
292
 
209
- <div className="relative overflow-hidden rounded-lg border border-[#2a2a4a] bg-[#080814] shadow-2xl shadow-cyan-950/30">
210
- <div className="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-cyan-400/70 to-transparent" />
293
+ <div
294
+ className={`relative overflow-hidden rounded-lg border shadow-2xl ${isLight ? 'shadow-zinc-900/5' : 'shadow-cyan-950/30'}`}
295
+ style={{ background: pal.containerBg, borderColor: pal.containerBorder }}
296
+ >
297
+ <div className={`absolute inset-x-0 top-0 h-px bg-gradient-to-r ${pal.topRailGradient}`} />
211
298
 
212
299
  <svg viewBox="0 0 1000 680" className="w-full h-auto block" preserveAspectRatio="xMidYMid meet">
213
300
  <defs>
214
301
  <linearGradient id="topo-panel" x1="0" x2="1" y1="0" y2="1">
215
- <stop offset="0%" stopColor="#0b1220" />
216
- <stop offset="48%" stopColor="#080814" />
217
- <stop offset="100%" stopColor="#101018" />
302
+ <stop offset="0%" stopColor={pal.panelStops[0]} />
303
+ <stop offset="48%" stopColor={pal.panelStops[1]} />
304
+ <stop offset="100%" stopColor={pal.panelStops[2]} />
218
305
  </linearGradient>
219
306
  <radialGradient id="topo-radar" cx="50%" cy="50%" r="55%">
220
- <stop offset="0%" stopColor="#22d3ee" stopOpacity="0.18" />
221
- <stop offset="45%" stopColor="#22c55e" stopOpacity="0.045" />
222
- <stop offset="100%" stopColor="#020617" stopOpacity="0" />
307
+ <stop offset="0%" stopColor={pal.radarStops[0].color} stopOpacity={pal.radarStops[0].opacity} />
308
+ <stop offset="45%" stopColor={pal.radarStops[1].color} stopOpacity={pal.radarStops[1].opacity} />
309
+ <stop offset="100%" stopColor={pal.radarStops[2].color} stopOpacity={pal.radarStops[2].opacity} />
223
310
  </radialGradient>
224
- <filter id="topo-glow">
225
- <feGaussianBlur stdDeviation="4" result="blur" />
226
- <feMerge>
227
- <feMergeNode in="blur" />
228
- <feMergeNode in="SourceGraphic" />
229
- </feMerge>
230
- </filter>
311
+ {!isLight && (
312
+ <filter id="topo-glow">
313
+ <feGaussianBlur stdDeviation="4" result="blur" />
314
+ <feMerge>
315
+ <feMergeNode in="blur" />
316
+ <feMergeNode in="SourceGraphic" />
317
+ </feMerge>
318
+ </filter>
319
+ )}
231
320
  <marker id="topo-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse">
232
- <path d="M 0 0 L 10 5 L 0 10 z" fill="#67e8f9" />
321
+ <path d="M 0 0 L 10 5 L 0 10 z" fill={pal.arrowFill} />
233
322
  </marker>
234
323
  </defs>
235
324
 
@@ -238,7 +327,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
238
327
 
239
328
  {/* radar rings */}
240
329
  {[90, 170, 250, 330].map(radius => (
241
- <circle key={radius} cx={cx} cy={cy} r={radius} fill="none" stroke="#164e63" strokeWidth="1" opacity="0.35" />
330
+ <circle key={radius} cx={cx} cy={cy} r={radius} fill="none" stroke={pal.ringStroke} strokeWidth="1" opacity={isLight ? 0.6 : 0.35} />
242
331
  ))}
243
332
  {[0, 30, 60, 90, 120, 150].map(angle => (
244
333
  <line
@@ -247,9 +336,9 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
247
336
  y1={cy - 360 * Math.sin(angle * Math.PI / 180)}
248
337
  x2={cx + 360 * Math.cos(angle * Math.PI / 180)}
249
338
  y2={cy + 360 * Math.sin(angle * Math.PI / 180)}
250
- stroke="#164e63"
339
+ stroke={pal.ringStroke}
251
340
  strokeWidth="1"
252
- opacity="0.18"
341
+ opacity={isLight ? 0.35 : 0.18}
253
342
  />
254
343
  ))}
255
344
 
@@ -264,7 +353,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
264
353
  key={`hub-${session.alias}`}
265
354
  d={path}
266
355
  fill="none"
267
- stroke={activeAliases.has(session.alias) ? '#22d3ee' : '#155e75'}
356
+ stroke={activeAliases.has(session.alias) ? pal.spokeStroke.active : pal.spokeStroke.idle}
268
357
  strokeWidth={activeAliases.has(session.alias) ? 2 : 1}
269
358
  strokeDasharray={activeAliases.has(session.alias) ? 'none' : '8 10'}
270
359
  opacity={activeAliases.has(session.alias) ? 0.65 : 0.35}
@@ -288,22 +377,22 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
288
377
  <path
289
378
  d={path}
290
379
  fill="none"
291
- stroke="#67e8f9"
380
+ stroke={pal.flowEdge}
292
381
  strokeWidth={width}
293
- opacity="0.28"
294
- filter="url(#topo-glow)"
382
+ opacity={isLight ? 0.22 : 0.28}
383
+ filter={isLight ? undefined : 'url(#topo-glow)'}
295
384
  markerEnd="url(#topo-arrow)"
296
385
  />
297
386
  <path
298
387
  id={`flow-path-${index}`}
299
388
  d={path}
300
389
  fill="none"
301
- stroke="#e0f2fe"
390
+ stroke={pal.flowPath}
302
391
  strokeWidth="1"
303
392
  strokeDasharray="2 12"
304
- opacity="0.75"
393
+ opacity={isLight ? 0.4 : 0.75}
305
394
  />
306
- <circle r="4" fill="#fef08a" filter="url(#topo-glow)">
395
+ <circle r="4" fill={pal.flowParticle} filter={isLight ? undefined : 'url(#topo-glow)'}>
307
396
  <animateMotion dur={`${duration}s`} repeatCount="indefinite" path={path} />
308
397
  </circle>
309
398
  </g>
@@ -340,38 +429,38 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
340
429
 
341
430
  const sseCountFor = (session.network_id ? sseSessions[`${session.network_id}:${session.alias}`] : undefined) ?? sseSessions[session.alias];
342
431
  const isOnline = session.status !== 'offline' || !!sseCountFor;
343
- const status = nodeStatus(session, isOnline);
432
+ const status = nodeStatus(session, isOnline, isLight);
344
433
  const isActive = activeAliases.has(session.alias);
345
434
  const radius = isOnline ? 26 : 18;
346
435
 
347
436
  return (
348
437
  <g key={session.alias} className="transition-opacity">
349
438
  {isActive && (
350
- <circle cx={pos.x} cy={pos.y} r={radius + 14} fill={status.primary} opacity="0.12">
439
+ <circle cx={pos.x} cy={pos.y} r={radius + 14} fill={status.primary} opacity={isLight ? 0.08 : 0.12}>
351
440
  <animate attributeName="r" values={`${radius + 8};${radius + 22};${radius + 8}`} dur="2.4s" repeatCount="indefinite" />
352
- <animate attributeName="opacity" values="0.18;0.04;0.18" dur="2.4s" repeatCount="indefinite" />
441
+ <animate attributeName="opacity" values={isLight ? '0.12;0.02;0.12' : '0.18;0.04;0.18'} dur="2.4s" repeatCount="indefinite" />
353
442
  </circle>
354
443
  )}
355
- <circle cx={pos.x} cy={pos.y} r={radius + 8} fill={status.halo} opacity={isOnline ? 0.55 : 0.25} />
444
+ <circle cx={pos.x} cy={pos.y} r={radius + 8} fill={status.halo} opacity={isOnline ? (isLight ? 0.85 : 0.55) : (isLight ? 0.4 : 0.25)} />
356
445
  <circle
357
446
  cx={pos.x}
358
447
  cy={pos.y}
359
448
  r={radius}
360
- fill={isOnline ? '#020617' : '#080814'}
449
+ fill={isOnline ? pal.nodeFill.online : pal.nodeFill.offline}
361
450
  stroke={status.primary}
362
451
  strokeWidth={isOnline ? 3 : 1.5}
363
452
  strokeDasharray={isOnline ? 'none' : '5 5'}
364
- filter={isOnline ? 'url(#topo-glow)' : undefined}
453
+ filter={isOnline && !isLight ? 'url(#topo-glow)' : undefined}
365
454
  />
366
455
  <circle cx={pos.x} cy={pos.y} r={isOnline ? 8 : 5} fill={status.primary} />
367
456
  {session.status === 'working' && (
368
- <path d={`M${pos.x - 9},${pos.y + 11} h18`} stroke="#fef08a" strokeWidth="3" strokeLinecap="round">
457
+ <path d={`M${pos.x - 9},${pos.y + 11} h18`} stroke={pal.flowParticle} strokeWidth="3" strokeLinecap="round">
369
458
  <animate attributeName="opacity" values="1;0.25;1" dur="1.1s" repeatCount="indefinite" />
370
459
  </path>
371
460
  )}
372
461
 
373
462
  <g transform={`translate(${pos.x}, ${pos.y + radius + 22})`}>
374
- <rect x="-62" y="-14" width="124" height="42" rx="6" fill="#020617" stroke="#1f2937" opacity="0.94" />
463
+ <rect x="-62" y="-14" width="124" height="42" rx="6" fill={pal.labelBox.fill} stroke={pal.labelBox.stroke} opacity={isLight ? 1 : 0.94} />
375
464
  <text x="0" y="1" textAnchor="middle" fill={status.text} fontSize="12" fontFamily="monospace" fontWeight="700">
376
465
  {truncate(session.alias, 14)}
377
466
  </text>
@@ -385,11 +474,11 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
385
474
 
386
475
  {/* latest flow labels */}
387
476
  <g transform="translate(28, 34)">
388
- <rect x="0" y="0" width="285" height={60 + Math.min(flowLinks.length, 4) * 20} rx="8" fill="#020617" stroke="#1f2937" opacity="0.94" />
389
- <text x="16" y="26" fill="#e5e7eb" fontSize="13" fontFamily="monospace" fontWeight="700">recent signal</text>
390
- <text x="178" y="26" fill="#67e8f9" fontSize="11" fontFamily="monospace">{messages.length} messages</text>
477
+ <rect x="0" y="0" width="285" height={60 + Math.min(flowLinks.length, 4) * 20} rx="8" fill={pal.legendBox.fill} stroke={pal.legendBox.stroke} opacity={isLight ? 1 : 0.94} />
478
+ <text x="16" y="26" fill={pal.legendHeadline} fontSize="13" fontFamily="monospace" fontWeight="700">recent signal</text>
479
+ <text x="178" y="26" fill={pal.legendAccent} fontSize="11" fontFamily="monospace">{messages.length} messages</text>
391
480
  {flowLinks.slice(0, 4).map((link, index) => (
392
- <text key={link.key} x="16" y={54 + index * 20} fill="#94a3b8" fontSize="10" fontFamily="monospace">
481
+ <text key={link.key} x="16" y={54 + index * 20} fill={pal.legendText} fontSize="10" fontFamily="monospace">
393
482
  {truncate(link.from, 8)} {'->'} {truncate(link.to, 8)} / {link.count} / {truncate(link.content, 18)}
394
483
  </text>
395
484
  ))}
@@ -397,14 +486,14 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
397
486
 
398
487
  {/* legend */}
399
488
  <g transform="translate(720, 34)">
400
- <rect x="0" y="0" width="250" height="112" rx="8" fill="#020617" stroke="#1f2937" opacity="0.94" />
401
- <circle cx="18" cy="26" r="6" fill="#22c55e" />
402
- <text x="34" y="30" fill="#94a3b8" fontSize="11" fontFamily="monospace">working node</text>
403
- <circle cx="18" cy="52" r="6" fill="#2dd4bf" />
404
- <text x="34" y="56" fill="#94a3b8" fontSize="11" fontFamily="monospace">online idle</text>
405
- <circle cx="18" cy="78" r="6" fill="#6b7280" />
406
- <text x="34" y="82" fill="#94a3b8" fontSize="11" fontFamily="monospace">offline / no SSE</text>
407
- <path d="M150,78 Q176,52 210,78" fill="none" stroke="#67e8f9" strokeWidth="3" markerEnd="url(#topo-arrow)" />
489
+ <rect x="0" y="0" width="250" height="112" rx="8" fill={pal.legendBox.fill} stroke={pal.legendBox.stroke} opacity={isLight ? 1 : 0.94} />
490
+ <circle cx="18" cy="26" r="6" fill={isLight ? '#059669' : '#22c55e'} />
491
+ <text x="34" y="30" fill={pal.legendText} fontSize="11" fontFamily="monospace">working node</text>
492
+ <circle cx="18" cy="52" r="6" fill={isLight ? '#0d9488' : '#2dd4bf'} />
493
+ <text x="34" y="56" fill={pal.legendText} fontSize="11" fontFamily="monospace">online idle</text>
494
+ <circle cx="18" cy="78" r="6" fill={isLight ? '#94a3b8' : '#6b7280'} />
495
+ <text x="34" y="82" fill={pal.legendText} fontSize="11" fontFamily="monospace">offline / no SSE</text>
496
+ <path d="M150,78 Q176,52 210,78" fill="none" stroke={pal.flowEdge} strokeWidth="3" markerEnd="url(#topo-arrow)" />
408
497
  </g>
409
498
  </svg>
410
499
  </div>
package/app/globals.css CHANGED
@@ -429,20 +429,21 @@ body {
429
429
  keep the existing tinted-pill look. */
430
430
  [data-theme="light"] .anet-nav-active,
431
431
  [data-theme="mint"] .anet-nav-active {
432
- /* Active nav: 1 strong accent cue (3px left bar) + faint bg wash + bold text.
433
- Replaces the four-emerald-stack (bg + border + text + ring) with a
434
- calmer Linear/Vercel-style "current page" indicator. */
435
- background-color: rgba(0, 158, 126, 0.06) !important;
432
+ /* 通信龙 final spec (Q1 + 3-piece lockdown): active nav = ONE cue.
433
+ 4px left bar emerald, text in --fg (NOT emerald), NO bg tint.
434
+ Reads as a calm "current page" indicator without competing emerald
435
+ surfaces. (Earlier 0.4.3 had 3px + faint tint + emerald text.) */
436
+ background-color: transparent !important;
436
437
  border-color: transparent !important;
437
- border-left: 3px solid var(--accent) !important;
438
+ border-left: 4px solid var(--accent) !important;
438
439
  border-radius: 0 8px 8px 0 !important;
439
- padding-left: calc(0.75rem - 3px) !important;
440
- color: var(--accent) !important;
440
+ padding-left: calc(0.75rem - 4px) !important;
441
+ color: var(--fg) !important;
441
442
  font-weight: 600;
442
443
  }
443
444
  [data-theme="light"] .anet-nav-active:hover,
444
445
  [data-theme="mint"] .anet-nav-active:hover {
445
- background-color: rgba(0, 158, 126, 0.10) !important;
446
+ background-color: var(--bg-elevated) !important;
446
447
  }
447
448
  /* When sidebar is collapsed (w-16, justify-center) the left bar would look
448
449
  weird against a centered icon — switch to a left-bg accent instead. */
@@ -450,6 +451,105 @@ body {
450
451
  [data-theme="mint"] aside.w-16 .anet-nav-active {
451
452
  border-radius: 8px !important;
452
453
  padding-left: 0 !important;
454
+ border-left: 0 !important;
455
+ background-color: var(--bg-elevated) !important;
456
+ color: var(--accent) !important;
457
+ }
458
+
459
+ /* Q6: native <select> theming for light/mint.
460
+ Without this, dropdowns on Nodes / Messages / Logs / Settings render with
461
+ the OS default chrome (heavy chevron, off-spec font weight, no border
462
+ radius). We strip the native chrome with appearance-none, then paint our
463
+ own chevron via a background-image SVG. ~10 LOC. */
464
+ [data-theme="light"] select,
465
+ [data-theme="mint"] select {
466
+ -webkit-appearance: none !important;
467
+ -moz-appearance: none !important;
468
+ appearance: none !important;
469
+ background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%23525a66' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 8l5 5 5-5'/%3E%3C/svg%3E") !important;
470
+ background-repeat: no-repeat !important;
471
+ background-position: right 0.6rem center !important;
472
+ background-size: 1rem 1rem !important;
473
+ padding-right: 2rem !important;
474
+ background-color: var(--bg-secondary) !important;
475
+ border-radius: 8px;
476
+ }
477
+ [data-theme="light"] select:focus,
478
+ [data-theme="mint"] select:focus {
479
+ outline: 2px solid rgba(0, 158, 126, 0.2);
480
+ outline-offset: -1px;
481
+ border-color: var(--accent) !important;
482
+ }
483
+ /* Mint reuses the same chevron — color picker would need its own gray, but
484
+ accent is similar enough that the default chevron color reads OK. */
485
+
486
+ /* KPI stat cards in light mode: kill the gradient wash (which reads as
487
+ pale-mud on white), replace with a 2px top accent strip per color
488
+ family + real elevation shadow. Dark themes keep the gradient. */
489
+ [data-theme="light"] .anet-stat-card,
490
+ [data-theme="mint"] .anet-stat-card {
491
+ border-top-width: 2px;
492
+ box-shadow: 0 1px 0 rgba(15, 23, 42, 0.04), 0 4px 12px -8px rgba(15, 23, 42, 0.08);
493
+ }
494
+ [data-theme="light"] .anet-stat-card:hover,
495
+ [data-theme="mint"] .anet-stat-card:hover {
496
+ box-shadow: 0 1px 0 rgba(15, 23, 42, 0.06), 0 12px 24px -12px rgba(15, 23, 42, 0.16);
497
+ }
498
+ /* Top-strip colors per stat family */
499
+ [data-theme="light"] .anet-stat-card[data-anet-stat-card="green"] { border-top-color: #10b981; }
500
+ [data-theme="light"] .anet-stat-card[data-anet-stat-card="cyan"] { border-top-color: var(--accent); }
501
+ [data-theme="light"] .anet-stat-card[data-anet-stat-card="gray"] { border-top-color: #94a3b8; }
502
+ [data-theme="light"] .anet-stat-card[data-anet-stat-card="white"] { border-top-color: #3b82f6; }
503
+ [data-theme="mint"] .anet-stat-card[data-anet-stat-card="green"] { border-top-color: #16a34a; }
504
+ [data-theme="mint"] .anet-stat-card[data-anet-stat-card="cyan"] { border-top-color: var(--accent); }
505
+ [data-theme="mint"] .anet-stat-card[data-anet-stat-card="gray"] { border-top-color: #4ade80; }
506
+ [data-theme="mint"] .anet-stat-card[data-anet-stat-card="white"] { border-top-color: #0891b2; }
507
+ /* Drop the gradient wash in light themes — looks like dust on white. */
508
+ [data-theme="light"] .anet-stat-card > .pointer-events-none,
509
+ [data-theme="mint"] .anet-stat-card > .pointer-events-none {
510
+ display: none;
511
+ }
512
+
513
+ /* Generic card hover elevation — every white card on light bg gets a
514
+ subtle lift on hover so clickability is obvious. */
515
+ [data-theme="light"] .bg-\[\#111128\]:hover,
516
+ [data-theme="mint"] .bg-\[\#111128\]:hover {
517
+ box-shadow: 0 6px 18px -10px rgba(15, 23, 42, 0.12);
518
+ }
519
+
520
+ /* ─────────────────────────────────────────────────────────────────────
521
+ Login surface (round 1 polish)
522
+ Restrained, minimal — first impression for new Agent Network users.
523
+ ─────────────────────────────────────────────────────────────────── */
524
+ .anet-login-bg {
525
+ background:
526
+ radial-gradient(circle at 50% 30%, rgba(34, 211, 238, 0.05), transparent 55%),
527
+ radial-gradient(circle at 33% 75%, rgba(59, 130, 246, 0.045), transparent 55%);
528
+ }
529
+ [data-theme="light"] .anet-login-bg,
530
+ [data-theme="mint"] .anet-login-bg {
531
+ background-image:
532
+ radial-gradient(circle at 50% 35%, rgba(0, 158, 126, 0.06), transparent 60%),
533
+ radial-gradient(circle, rgba(15, 23, 42, 0.05) 1px, transparent 1px);
534
+ background-size: 100% 100%, 24px 24px;
535
+ }
536
+ [data-theme="light"] .anet-login-mark,
537
+ [data-theme="mint"] .anet-login-mark {
538
+ background: var(--bg-secondary) !important;
539
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06), 0 0 0 1px var(--border);
540
+ }
541
+
542
+ /* Sidebar brand "online" pulse — slow, restrained, NO glow.
543
+ 1.6s opacity drift between 1.0 and 0.5; nothing scales, nothing blurs. */
544
+ @keyframes anet-brand-pulse-kf {
545
+ 0%, 100% { opacity: 1; }
546
+ 50% { opacity: 0.4; }
547
+ }
548
+ .anet-brand-pulse {
549
+ animation: anet-brand-pulse-kf 1.6s ease-in-out infinite;
550
+ }
551
+ @media (prefers-reduced-motion: reduce) {
552
+ .anet-brand-pulse { animation: none; }
453
553
  }
454
554
 
455
555
  /* P0-3 baseline: error / warning chip colors for light themes.
@@ -44,21 +44,32 @@ export default function LoginPage() {
44
44
  };
45
45
 
46
46
  return (
47
- <main className="min-h-screen bg-[#0a0a1a] text-gray-100 font-mono flex items-center justify-center relative overflow-hidden">
48
- <div className="absolute inset-0 pointer-events-none">
49
- <div className="absolute top-1/4 left-1/2 -translate-x-1/2 w-[600px] h-[600px] rounded-full bg-cyan-500/5 blur-[120px]" />
50
- <div className="absolute bottom-1/4 left-1/3 w-[400px] h-[400px] rounded-full bg-blue-500/5 blur-[100px]" />
51
- </div>
47
+ <main className="min-h-screen bg-[#0a0a1a] text-gray-100 font-mono flex items-center justify-center relative overflow-hidden px-4 py-10 sm:py-16">
48
+ {/* Subtle off-grid background — restraint over AI-glow.
49
+ Dark themes get a faint radial wash; light themes show a low-contrast
50
+ dotted grid for surface texture without noise. */}
51
+ <div className="absolute inset-0 pointer-events-none anet-login-bg" aria-hidden />
52
52
 
53
- <div className="relative w-full max-w-sm mx-4">
54
- <div className="text-center mb-8">
55
- <div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-gradient-to-br from-cyan-500/20 to-blue-500/20 border border-cyan-500/20 mb-4">
56
- <svg className="w-8 h-8 text-cyan-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
57
- <path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z" />
53
+ <div className="relative w-full max-w-sm">
54
+ <div className="text-center mb-7">
55
+ {/* Brand mark uses the favicon 3-node mesh SVG which IS the
56
+ agent-network concept (nodes + edges). Replaces the previous
57
+ flat emerald square placeholder. */}
58
+ <div className="inline-flex items-center justify-center w-16 h-16 mb-4 rounded-2xl bg-[#111128] border border-[#2a2a4a] anet-login-mark">
59
+ <svg className="w-9 h-9" viewBox="0 0 32 32" aria-hidden>
60
+ <circle cx="16" cy="16" r="10" fill="none" stroke="currentColor" strokeWidth="1.5" opacity="0.25" className="text-cyan-400 anet-login-mark-ring" />
61
+ <line x1="16" y1="10" x2="10" y2="20" stroke="currentColor" strokeWidth="1" opacity="0.5" className="text-cyan-400 anet-login-mark-edge" />
62
+ <line x1="16" y1="10" x2="22" y2="20" stroke="currentColor" strokeWidth="1" opacity="0.5" className="text-cyan-400 anet-login-mark-edge" />
63
+ <line x1="10" y1="20" x2="22" y2="20" stroke="currentColor" strokeWidth="1" opacity="0.5" className="text-cyan-400 anet-login-mark-edge" />
64
+ <circle cx="16" cy="10" r="3" fill="currentColor" className="text-cyan-400 anet-login-mark-node-a" />
65
+ <circle cx="10" cy="20" r="3" fill="currentColor" className="text-green-400 anet-login-mark-node-b" />
66
+ <circle cx="22" cy="20" r="3" fill="currentColor" className="text-violet-400 anet-login-mark-node-c" />
58
67
  </svg>
59
68
  </div>
60
- <h1 className="text-2xl font-bold text-white tracking-tight">Agent Network</h1>
61
- <p className="text-sm text-gray-500 mt-1">Dashboard</p>
69
+ <h1 className="text-[22px] sm:text-2xl font-semibold text-white tracking-tight">Agent Network</h1>
70
+ <p className="text-[13px] text-gray-500 mt-1.5 leading-snug">
71
+ One control plane for your AI agents · <span className="text-gray-600">Tasks · Mesh · Messages</span>
72
+ </p>
62
73
  </div>
63
74
 
64
75
  {/* Mode toggle */}
@@ -96,7 +107,9 @@ export default function LoginPage() {
96
107
  </button>
97
108
  </form>
98
109
 
99
- <p className="text-center text-xs text-gray-700 mt-6">Powered by Sleep2AGI</p>
110
+ <p className="text-center text-[11px] text-gray-700 mt-6">
111
+ Sleep2AGI · Apache-2.0 · <a href="https://anet.sh" className="hover:text-gray-500 transition-colors">anet.sh</a>
112
+ </p>
100
113
  </div>
101
114
  </main>
102
115
  );
package/app/logs/page.tsx CHANGED
@@ -1,8 +1,10 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect, useCallback } from 'react';
4
+ import Link from 'next/link';
4
5
  import { timeAgo } from '../components/utils';
5
6
  import { useNetworkId } from '../lib/network-context';
7
+ import { EmptyState } from '../components/EmptyState';
6
8
 
7
9
  interface AuditLog {
8
10
  id: string;
@@ -84,17 +86,32 @@ export default function LogsPage() {
84
86
  </div>
85
87
 
86
88
  {error && (() => {
87
- // Don't dump raw error JSON to users — translate known patterns.
89
+ // Don't dump raw error JSON to users — translate known patterns,
90
+ // and give them a clickable recovery affordance.
88
91
  const isAuth = /unauthor|401|forbidden|403|admin/i.test(error);
89
- const human = isAuth
90
- ? '需要登录才能查看审计日志(admin 角色可看全部)'
91
- : '加载失败 —— 请稍后重试';
92
92
  return (
93
93
  <div className="anet-warn-chip bg-yellow-900/20 border border-yellow-800/40 text-yellow-300 px-4 py-3 rounded-lg mb-6 text-sm flex items-start gap-3" role="alert">
94
94
  <span aria-hidden className="text-base leading-none">⚠</span>
95
95
  <div className="flex-1">
96
- <div className="font-medium">{human}</div>
97
- <div className="text-xs opacity-70 mt-1">如果你已经登录但仍看到此提示,请用 admin 账号重新登录后查看。</div>
96
+ {isAuth ? (
97
+ <>
98
+ <div className="font-medium">需要 admin 权限查看审计日志</div>
99
+ <div className="text-xs opacity-80 mt-1">
100
+ 审计日志记录用户注册 / 登录 / 网络变更等事件,需要 admin 角色。
101
+ </div>
102
+ <Link href="/login" className="inline-block mt-2 text-xs font-medium underline underline-offset-4 hover:opacity-80">
103
+ 切到 admin 账号 →
104
+ </Link>
105
+ </>
106
+ ) : (
107
+ <>
108
+ <div className="font-medium">加载失败</div>
109
+ <div className="text-xs opacity-80 mt-1">请检查 CommHub 是否在线,或稍后重试。</div>
110
+ <button onClick={() => window.location.reload()} className="inline-block mt-2 text-xs font-medium underline underline-offset-4 hover:opacity-80">
111
+ 重新加载 →
112
+ </button>
113
+ </>
114
+ )}
98
115
  </div>
99
116
  </div>
100
117
  );
@@ -105,11 +122,7 @@ export default function LogsPage() {
105
122
  {[1,2,3,4,5].map(i => <div key={i} className="h-14 bg-gray-800/20 rounded-lg" />)}
106
123
  </div>
107
124
  ) : logs.length === 0 ? (
108
- <div className="text-center py-16">
109
- <div className="text-gray-600 text-4xl mb-4">--</div>
110
- <h3 className="text-gray-400 text-lg font-medium mb-2">No audit logs</h3>
111
- <p className="text-gray-600 text-sm">Events will appear here when users register, login, or perform actions.</p>
112
- </div>
125
+ <EmptyState variant="logs" />
113
126
  ) : (
114
127
  <div className="space-y-2">
115
128
  {logs.map(log => (
@@ -3,6 +3,7 @@
3
3
  import { useMemo, useState } from 'react';
4
4
  import { useMessages } from '../lib/hooks';
5
5
  import { timeAgo } from '../components/utils';
6
+ import { EmptyState } from '../components/EmptyState';
6
7
 
7
8
  interface MessageItem {
8
9
  id: string;
@@ -186,11 +187,7 @@ export default function MessagesPage() {
186
187
  {[1, 2, 3, 4, 5].map(i => <div key={i} className="h-20 rounded-2xl bg-gray-800/20" />)}
187
188
  </div>
188
189
  ) : filtered.length === 0 ? (
189
- <div className="text-center py-16">
190
- <div className="text-gray-600 text-4xl mb-4">--</div>
191
- <h3 className="text-gray-400 text-lg font-medium mb-2">No messages</h3>
192
- <p className="text-gray-600 text-sm">Messages between agents will appear here.</p>
193
- </div>
190
+ <EmptyState variant="messages" />
194
191
  ) : viewMode === 'grouped' ? (
195
192
  <div className="space-y-4">
196
193
  {conversations.map((conv, ci) => (