@sleep2agi/agent-network-dashboard 0.4.4-preview.0 → 0.4.5-preview.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) 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]__0_jnflc._.js +3 -0
  162. package/.next/server/chunks/ssr/[root-of-the-server]__0_jnflc._.js.map +1 -0
  163. package/.next/server/chunks/ssr/[root-of-the-server]__0a0ffmv._.js +3 -0
  164. package/.next/server/chunks/ssr/[root-of-the-server]__0a0ffmv._.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_components_08ys5y3._.js +9 -0
  180. package/.next/server/chunks/ssr/app_components_08ys5y3._.js.map +1 -0
  181. package/.next/server/chunks/ssr/app_login_page_tsx_0z71ad3._.js +1 -1
  182. package/.next/server/chunks/ssr/app_login_page_tsx_0z71ad3._.js.map +1 -1
  183. package/.next/server/chunks/ssr/app_page_tsx_0es_sk2._.js +1 -1
  184. package/.next/server/chunks/ssr/app_page_tsx_0es_sk2._.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/0a1wbl-spby3_.css +2 -0
  199. package/.next/static/chunks/0e-.zgw81gela.js +31 -0
  200. package/.next/static/chunks/0e7c40p~wq665.js +1 -0
  201. package/.next/static/chunks/0g7m7zk.v7g6h.js +1 -0
  202. package/.next/static/chunks/0gs9-~h7-pkuu.js +7 -0
  203. package/.next/static/chunks/0j3s_1ee66awv.js +1 -0
  204. package/.next/static/chunks/0khpyxpdrp~ty.js +1 -0
  205. package/.next/static/chunks/0lo_g.q1fm-hr.js +7 -0
  206. package/.next/static/chunks/0w1zwt8kq_7nb.js +1 -0
  207. package/.next/static/chunks/1031v6nw33ud3.js +1 -0
  208. package/.next/static/chunks/13.dst7liziwo.js +1 -0
  209. package/.next/static/chunks/{turbopack-0vsvjs02l449s.js → turbopack-01f1xs~cq_~si.js} +1 -1
  210. package/.next/trace +2 -2
  211. package/.next/trace-build +1 -1
  212. package/app/components/AppShell.tsx +9 -1
  213. package/app/components/CommandPalette.tsx +316 -0
  214. package/app/components/EmptyState.tsx +189 -0
  215. package/app/components/HealthBanner.tsx +132 -0
  216. package/app/components/HelpOverlay.tsx +121 -0
  217. package/app/components/LoadingSkeleton.tsx +81 -40
  218. package/app/components/Sidebar.tsx +90 -11
  219. package/app/components/TopoGraph.tsx +151 -62
  220. package/app/components/UserBar.tsx +16 -2
  221. package/app/globals.css +175 -0
  222. package/app/login/page.tsx +26 -13
  223. package/app/logs/page.tsx +2 -5
  224. package/app/messages/page.tsx +2 -5
  225. package/app/nodes/page.tsx +6 -1
  226. package/app/page.tsx +109 -12
  227. package/app/settings/networks/page.tsx +6 -1
  228. package/app/settings/page.tsx +56 -29
  229. package/app/settings/tokens/page.tsx +6 -1
  230. package/app/tasks/page.tsx +39 -14
  231. package/docs/loop-design-rounds.md +797 -0
  232. package/docs/oss-readiness-report.md +189 -0
  233. package/docs/screenshots/0.4.5-preview.1/cyber-admin.png +0 -0
  234. package/docs/screenshots/0.4.5-preview.1/cyber-cmdk-search.png +0 -0
  235. package/docs/screenshots/0.4.5-preview.1/cyber-cmdk.png +0 -0
  236. package/docs/screenshots/0.4.5-preview.1/cyber-login.png +0 -0
  237. package/docs/screenshots/0.4.5-preview.1/cyber-overview.png +0 -0
  238. package/docs/screenshots/0.4.5-preview.1/cyber-settings.png +0 -0
  239. package/docs/screenshots/0.4.5-preview.1/cyber-tasks.png +0 -0
  240. package/docs/screenshots/0.4.5-preview.1/light-admin.png +0 -0
  241. package/docs/screenshots/0.4.5-preview.1/light-cmdk-search.png +0 -0
  242. package/docs/screenshots/0.4.5-preview.1/light-cmdk.png +0 -0
  243. package/docs/screenshots/0.4.5-preview.1/light-login.png +0 -0
  244. package/docs/screenshots/0.4.5-preview.1/light-overview.png +0 -0
  245. package/docs/screenshots/0.4.5-preview.1/light-settings.png +0 -0
  246. package/docs/screenshots/0.4.5-preview.1/light-tasks.png +0 -0
  247. package/package.json +1 -1
  248. package/.next/server/chunks/ssr/[root-of-the-server]__093ymui._.js +0 -3
  249. package/.next/server/chunks/ssr/[root-of-the-server]__093ymui._.js.map +0 -1
  250. package/.next/server/chunks/ssr/[root-of-the-server]__0i1gizm._.js +0 -9
  251. package/.next/server/chunks/ssr/[root-of-the-server]__0i1gizm._.js.map +0 -1
  252. package/.next/server/chunks/ssr/[root-of-the-server]__0~yad5w._.js +0 -3
  253. package/.next/server/chunks/ssr/[root-of-the-server]__0~yad5w._.js.map +0 -1
  254. package/.next/server/chunks/ssr/_06_xh1~._.js +0 -3
  255. package/.next/server/chunks/ssr/_06_xh1~._.js.map +0 -1
  256. package/.next/server/chunks/ssr/app_01vhyfx._.js +0 -9
  257. package/.next/server/chunks/ssr/app_01vhyfx._.js.map +0 -1
  258. package/.next/server/chunks/ssr/app_023a94i._.js +0 -3
  259. package/.next/server/chunks/ssr/app_023a94i._.js.map +0 -1
  260. package/.next/server/chunks/ssr/app_0basiqb._.js +0 -3
  261. package/.next/server/chunks/ssr/app_0basiqb._.js.map +0 -1
  262. package/.next/server/chunks/ssr/app_0pljk5o._.js +0 -3
  263. package/.next/server/chunks/ssr/app_0pljk5o._.js.map +0 -1
  264. package/.next/server/chunks/ssr/node_modules_next_dist_0t-hj0x._.js +0 -3
  265. package/.next/server/chunks/ssr/node_modules_next_dist_0t-hj0x._.js.map +0 -1
  266. package/.next/static/chunks/0.z~9klrgd.t-.js +0 -1
  267. package/.next/static/chunks/08aj9~75raaah.js +0 -7
  268. package/.next/static/chunks/08iom_z8w-wmz.js +0 -7
  269. package/.next/static/chunks/08v0-ki6vwpxf.js +0 -1
  270. package/.next/static/chunks/0dkkv90do9a4r.js +0 -1
  271. package/.next/static/chunks/0e-wrhqh4h3r5.js +0 -2
  272. package/.next/static/chunks/0ejz4zwhu3a3y.js +0 -1
  273. package/.next/static/chunks/0o5zj~5vjeks3.js +0 -1
  274. package/.next/static/chunks/0uqfv6rc-xdru.js +0 -1
  275. package/.next/static/chunks/0u~_.32f.midj.css +0 -2
  276. package/.next/static/chunks/0xrnwfefpke-g.js +0 -31
  277. package/.next/static/chunks/0y_po~f~fk~ap.js +0 -1
  278. package/.next/static/chunks/14sm.ta0zylsj.js +0 -1
  279. package/.next/static/chunks/18ac93ii3mjlj.js +0 -1
  280. /package/.next/static/{jTh5m77FIfQccc0gftvik → wcTnZXyx_R19_Ur1_cGMP}/_buildManifest.js +0 -0
  281. /package/.next/static/{jTh5m77FIfQccc0gftvik → wcTnZXyx_R19_Ur1_cGMP}/_clientMiddlewareManifest.js +0 -0
  282. /package/.next/static/{jTh5m77FIfQccc0gftvik → wcTnZXyx_R19_Ur1_cGMP}/_ssgManifest.js +0 -0
@@ -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) => (
@@ -5,6 +5,7 @@ import Link from 'next/link';
5
5
  import { timeAgo } from '../components/utils';
6
6
  import { useSessions, useHealth } from '../lib/hooks';
7
7
  import { TaskChatPanel } from '../components/TaskChatPanel';
8
+ import { EmptyState } from '../components/EmptyState';
8
9
  import type { Session } from '../components/types';
9
10
 
10
11
  const STATUS_COLORS: Record<string, string> = {
@@ -163,7 +164,11 @@ export default function NodesPage() {
163
164
  {[1,2,3,4].map(i => <div key={i} className="h-16 bg-gray-800/20 rounded-lg" />)}
164
165
  </div>
165
166
  ) : filtered.length === 0 ? (
166
- <div className="text-center py-16 text-gray-600">No nodes match your filters.</div>
167
+ <EmptyState
168
+ variant="nodes"
169
+ title="No nodes match your filters"
170
+ sub="Try clearing search or status filters, or wait for an agent to register."
171
+ />
167
172
  ) : viewMode === 'grid' ? (
168
173
  <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
169
174
  {filtered.map(s => {
package/app/page.tsx CHANGED
@@ -8,7 +8,8 @@ import { BroadcastBar } from './components/BroadcastBar';
8
8
  import { TopoGraph } from './components/TopoGraph';
9
9
  import { AgentCard } from './components/AgentCard';
10
10
  import { InboxPanel } from './components/InboxPanel';
11
- import { LoadingSkeleton, EmptyState } from './components/LoadingSkeleton';
11
+ import { LoadingSkeleton } from './components/LoadingSkeleton';
12
+ import { NodesEmptyState as EmptyState } from './components/EmptyState';
12
13
  import { UserBar } from './components/UserBar';
13
14
  import { CommandCenter, useCommandCenter } from './components/CommandCenter';
14
15
  import { DispatchPanel } from './components/DispatchPanel';
@@ -185,19 +186,115 @@ export default function Dashboard() {
185
186
  </section>
186
187
  )}
187
188
 
188
- {/* Quick Actions */}
189
- <section className="mb-6 grid grid-cols-3 sm:grid-cols-6 gap-2">
189
+ {/* Quick Actions — split into two distinct intents:
190
+ (1) Top: live stat cards (carry data, drill-in on click)
191
+ (2) Bottom: pure nav rail (no number, icon + label)
192
+ Previously these were mixed in one row of 6, mixing data cells
193
+ ("0/0", "--", "0") with placeholder arrows ("→ Messages").
194
+ Users couldn't tell which were stats vs which were navigation. */}
195
+ <section className="mb-3 grid grid-cols-3 gap-2 sm:gap-3">
196
+ {(() => {
197
+ // Build breakdown popover content per card. Pure data — pure CSS
198
+ // popover (no JS state) wires the hover-show transition below.
199
+ const idleCount = sessions.filter(s => isOnline(s) && s.status !== 'working').length;
200
+ const offlineCount = total - online;
201
+ const orderedStatuses = ['running', 'replied', 'failed', 'cancelled', 'expired', 'closed', 'created', 'delivered', 'acked'];
202
+ const failedRecent = tasks.filter((t: { status: string }) => t.status === 'failed').length;
203
+
204
+ const cards = [
205
+ {
206
+ href: '/nodes', label: 'Nodes',
207
+ value: `${online}/${total}`,
208
+ sub: `${total > 0 ? Math.round((online / total) * 100) : 0}% online`,
209
+ color: 'text-green-400 border-green-500/20',
210
+ popover: total === 0 ? null : [
211
+ { k: 'working', v: working, dot: '', color: '#4ade80' },
212
+ { k: 'idle', v: idleCount, dot: '', color: '#22d3ee' },
213
+ { k: 'offline', v: offlineCount, dot: '', color: '#9ca3af' },
214
+ ],
215
+ },
216
+ {
217
+ href: '/tasks', label: 'Tasks',
218
+ value: String(Object.values(taskStats).reduce((a, b) => a + b, 0) || 0),
219
+ sub: 'all-time',
220
+ color: 'text-cyan-400 border-cyan-500/20',
221
+ popover: Object.keys(taskStats).length === 0 ? null
222
+ : orderedStatuses
223
+ .filter(s => taskStats[s])
224
+ .map(s => {
225
+ // Inline hex avoids Tailwind purging dynamic
226
+ // `bg-${color}-400` class names.
227
+ const hex = ({
228
+ running: '#4ade80', replied: '#a78bfa', failed: '#f87171',
229
+ cancelled: '#facc15', expired: '#fb923c', closed: '#9ca3af',
230
+ created: '#9ca3af', delivered: '#60a5fa', acked: '#22d3ee',
231
+ } as Record<string, string>)[s] || '#9ca3af';
232
+ return { k: s, v: taskStats[s], dot: '', color: hex };
233
+ }),
234
+ },
235
+ {
236
+ href: '/tasks?status=failed', label: 'Failed',
237
+ value: String(taskStats['failed'] || 0),
238
+ sub: taskStats['failed'] ? 'needs review' : 'none',
239
+ color: taskStats['failed'] ? 'text-red-400 border-red-500/25' : 'text-gray-500 border-gray-700/30',
240
+ popover: !failedRecent ? [{ k: 'no failures yet', v: '', dot: '', color: '#4b5563' }]
241
+ : [{ k: `${failedRecent} in current view`, v: '', dot: '', color: '#f87171' }],
242
+ },
243
+ ];
244
+
245
+ return cards.map(a => (
246
+ <Link
247
+ key={a.href}
248
+ href={a.href}
249
+ prefetch={false}
250
+ className={`anet-stat-link group relative rounded-xl border ${a.color} bg-[#111128] px-3 py-3 transition-all hover:-translate-y-px`}
251
+ >
252
+ <div className="flex items-baseline justify-between">
253
+ <div className={`text-xl font-semibold tabular-nums ${a.color.split(' ')[0]}`}>{a.value}</div>
254
+ <div className="hidden sm:block text-[10px] text-gray-600 group-hover:text-gray-400 transition-colors">View →</div>
255
+ </div>
256
+ <div className="text-[11px] text-gray-400 mt-0.5">{a.label}</div>
257
+ <div className="text-[10px] text-gray-600 mt-px">{a.sub}</div>
258
+
259
+ {/* Hover popover — CSS-only, restrained. Hidden on touch
260
+ (no :hover) so mobile is unaffected. Positioned just
261
+ below the card so it doesn't fight nav rail. */}
262
+ {a.popover && a.popover.length > 0 && (
263
+ <div className="anet-stat-popover hidden md:block pointer-events-none absolute left-2 right-2 top-full mt-1 z-20 rounded-lg border border-[#2a2a4a] bg-[#0d0d1a] shadow-lg shadow-black/30 px-3 py-2 opacity-0 translate-y-[-2px] group-hover:opacity-100 group-hover:translate-y-0 transition-all duration-150 delay-100">
264
+ <div className="text-[10px] text-gray-600 uppercase tracking-wider mb-1.5">Breakdown</div>
265
+ <ul className="space-y-1">
266
+ {a.popover.map(row => (
267
+ <li key={row.k} className="flex items-center gap-2 text-[11px]">
268
+ <span
269
+ className="inline-block w-1.5 h-1.5 rounded-full shrink-0"
270
+ style={{ backgroundColor: row.color }}
271
+ aria-hidden
272
+ />
273
+ <span className="text-gray-400 capitalize">{row.k}</span>
274
+ {row.v !== '' && <span className="ml-auto text-gray-300 tabular-nums font-medium">{row.v}</span>}
275
+ </li>
276
+ ))}
277
+ </ul>
278
+ </div>
279
+ )}
280
+ </Link>
281
+ ));
282
+ })()}
283
+ </section>
284
+
285
+ {/* Nav rail — pure navigation, icon + label, no data */}
286
+ <section className="mb-6 grid grid-cols-3 gap-2 sm:gap-3">
190
287
  {[
191
- { href: '/nodes', label: 'Nodes', value: `${online}/${total}`, color: 'text-green-400 border-green-500/15' },
192
- { href: '/tasks', label: 'Tasks', value: String(Object.values(taskStats).reduce((a, b) => a + b, 0) || '--'), color: 'text-cyan-400 border-cyan-500/15' },
193
- { href: '/tasks?status=failed', label: 'Failed', value: String(taskStats['failed'] || 0), color: taskStats['failed'] ? 'text-red-400 border-red-500/20' : 'text-gray-600 border-gray-700/30' },
194
- { href: '/messages', label: 'Messages', value: '→', color: 'text-blue-400 border-blue-500/15' },
195
- { href: '/logs', label: 'Logs', value: '→', color: 'text-purple-400 border-purple-500/15' },
196
- { href: '/admin', label: 'Admin', value: '→', color: 'text-gray-400 border-gray-500/15' },
288
+ { href: '/messages', label: 'Messages', icon: 'M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z' },
289
+ { href: '/logs', label: 'Audit log', icon: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z M14 2v6h6 M16 13H8 M16 17H8 M10 9H8' },
290
+ { href: '/admin', label: 'Admin', icon: 'M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4z M6 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2' },
197
291
  ].map(a => (
198
- <Link key={a.href} href={a.href} prefetch={false} className={`text-center rounded-xl border ${a.color} bg-[#111128] px-3 py-2.5 hover:bg-[#161630] transition-colors`}>
199
- <div className={`text-lg font-bold tabular-nums ${a.color.split(' ')[0]}`}>{a.value}</div>
200
- <div className="text-[10px] text-gray-500">{a.label}</div>
292
+ <Link key={a.href} href={a.href} prefetch={false}
293
+ className="anet-nav-tile flex items-center justify-center gap-2 rounded-xl border border-[#2a2a4a] bg-[#111128] px-3 py-2.5 text-[12px] text-gray-400 hover:text-gray-200 transition-colors">
294
+ <svg className="w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
295
+ <path d={a.icon} />
296
+ </svg>
297
+ <span>{a.label}</span>
201
298
  </Link>
202
299
  ))}
203
300
  </section>
@@ -3,6 +3,7 @@
3
3
  import { useEffect, useState, useCallback } from 'react';
4
4
  import Link from 'next/link';
5
5
  import { timeAgo } from '../../components/utils';
6
+ import { EmptyState } from '../../components/EmptyState';
6
7
  import useSWR from 'swr';
7
8
 
8
9
  const fetcher = (url: string) => fetch(url).then(r => r.json());
@@ -126,7 +127,11 @@ export default function NetworksPage() {
126
127
  {loading ? (
127
128
  <div className="animate-pulse space-y-2">{[1,2].map(i => <div key={i} className="h-14 bg-gray-800/20 rounded" />)}</div>
128
129
  ) : networks.length === 0 ? (
129
- <div className="text-xs text-gray-600 text-center py-4">No networks found. Create one or sign in with V3 auth.</div>
130
+ <EmptyState
131
+ variant="networks"
132
+ compact
133
+ sub="Create your first network above, or sign in with V3 auth to see existing ones."
134
+ />
130
135
  ) : (
131
136
  <div className="space-y-3">
132
137
  {networks.map(n => (
@@ -20,7 +20,11 @@ export default function SettingsPage() {
20
20
  <div className="min-h-screen bg-[#0a0a1a] text-gray-100 p-4 sm:p-6 font-mono">
21
21
  <h1 className="text-2xl font-bold text-white mb-6 lg:ml-0 ml-10">Settings</h1>
22
22
 
23
- <div className="max-w-2xl space-y-6">
23
+ <div className="max-w-2xl space-y-8">
24
+ {/* ── Group: Connection ─────────────────────────────────── */}
25
+ <div className="space-y-4">
26
+ <div className="text-[10px] uppercase tracking-[0.12em] text-gray-600 px-1">Connection</div>
27
+
24
28
  {/* CommHub Connection */}
25
29
  <section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
26
30
  <h2 className="text-sm font-semibold text-gray-300 mb-4">CommHub Connection</h2>
@@ -96,25 +100,41 @@ export default function SettingsPage() {
96
100
  </div>
97
101
  </div>
98
102
  </section>
103
+ </div>
104
+
105
+ {/* ── Group: Account ────────────────────────────────────── */}
106
+ <div className="space-y-4">
107
+ <div className="text-[10px] uppercase tracking-[0.12em] text-gray-600 px-1">Account</div>
99
108
 
100
109
  {/* License */}
101
110
  <section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
102
- <h2 className="text-sm font-semibold text-gray-300 mb-4">License</h2>
111
+ <h2 className="text-sm font-semibold text-gray-300 mb-4 flex items-center gap-2">
112
+ License
113
+ {licData?.license && (
114
+ <span
115
+ className={`inline-flex items-center gap-1.5 text-[10px] font-medium px-2 py-0.5 rounded-full border ${
116
+ licData.license.type === 'pro'
117
+ ? 'text-green-300 bg-green-500/10 border-green-500/30'
118
+ : licData.license.days_left <= 7
119
+ ? 'text-red-300 bg-red-500/10 border-red-500/30'
120
+ : 'text-amber-300 bg-amber-500/10 border-amber-500/30'
121
+ }`}
122
+ >
123
+ <span aria-hidden className="w-1.5 h-1.5 rounded-full bg-current" />
124
+ {licData.license.type}{licData.license.days_left ? ` · ${licData.license.days_left}d left` : ''}
125
+ </span>
126
+ )}
127
+ </h2>
103
128
  {licData?.license ? (
104
129
  <div className="space-y-3 text-sm">
105
- <div className={rowClass}>
106
- <span className="text-gray-500">Type</span>
107
- <span className={`font-medium ${licData.license.type === 'pro' ? 'text-green-400' : 'text-yellow-400'}`}>
108
- {licData.license.type}
109
- </span>
110
- </div>
111
- <div className={rowClass}>
112
- <span className="text-gray-500">Days Left</span>
113
- <span className={`${licData.license.days_left <= 7 ? 'text-red-400' : 'text-gray-300'}`}>
114
- {licData.license.days_left} days
115
- {licData.license.days_left <= 7 && ' — expiring soon!'}
116
- </span>
117
- </div>
130
+ {/* Type + Days Left are summarized in the inline chip in the
131
+ header. Surface "expiring soon" only when relevant. */}
132
+ {licData.license.days_left <= 7 && (
133
+ <div className={rowClass}>
134
+ <span className="text-red-400 font-medium">⚠ Expiring soon</span>
135
+ <span className="text-red-400">{licData.license.days_left} days left</span>
136
+ </div>
137
+ )}
118
138
  <div className={rowClass}>
119
139
  <span className="text-gray-500">Expires</span>
120
140
  <span className={`text-gray-300 ${valueClass}`}>{licData.license.expires_at}</span>
@@ -195,6 +215,26 @@ export default function SettingsPage() {
195
215
  {pwdResult && <div className={`mt-2 text-xs ${pwdResult.startsWith('Failed') ? 'text-red-400' : 'text-green-400'}`}>{pwdResult}</div>}
196
216
  </section>
197
217
 
218
+ {/* Session — tone-neutral, no longer "danger zone" red */}
219
+ <section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
220
+ <h2 className="text-sm font-semibold text-gray-300 mb-4">Sign out</h2>
221
+ <p className="text-xs text-gray-500 mb-3">Signing out clears your dashboard session cookie. You'll return to the login page.</p>
222
+ <button
223
+ onClick={async () => {
224
+ await fetch('/api/auth/logout', { method: 'POST' }).catch(() => {});
225
+ window.location.assign('/login');
226
+ }}
227
+ className="px-4 py-2 bg-transparent hover:bg-[#1a1a2a] text-gray-300 text-sm rounded-lg border border-[#2a2a4a] hover:border-[#3a3a5a] transition-colors"
228
+ >
229
+ Sign out
230
+ </button>
231
+ </section>
232
+ </div>
233
+
234
+ {/* ── Group: Resources ──────────────────────────────────── */}
235
+ <div className="space-y-4">
236
+ <div className="text-[10px] uppercase tracking-[0.12em] text-gray-600 px-1">Resources</div>
237
+
198
238
  {/* API Tokens + Networks */}
199
239
  <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
200
240
  <Link href="/settings/tokens" className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5 hover:border-cyan-500/30 transition-colors">
@@ -208,21 +248,8 @@ export default function SettingsPage() {
208
248
  <span className="text-xs text-cyan-400 mt-3 inline-block">Manage &rarr;</span>
209
249
  </Link>
210
250
  </div>
251
+ </div>
211
252
 
212
- {/* Danger Zone */}
213
- <section className="bg-[#111128] border border-red-900/30 rounded-xl p-5">
214
- <h2 className="text-sm font-semibold text-red-400 mb-4">Session</h2>
215
- <p className="text-xs text-gray-500 mb-3">Sign out will clear your dashboard session cookie.</p>
216
- <button
217
- onClick={async () => {
218
- await fetch('/api/auth/logout', { method: 'POST' }).catch(() => {});
219
- window.location.assign('/login');
220
- }}
221
- className="px-4 py-2 bg-red-600/20 hover:bg-red-600/30 text-red-300 text-sm rounded-lg border border-red-800/30 transition-colors"
222
- >
223
- Sign out
224
- </button>
225
- </section>
226
253
  </div>
227
254
  </div>
228
255
  );
@@ -3,6 +3,7 @@
3
3
  import { useEffect, useState, useCallback } from 'react';
4
4
  import Link from 'next/link';
5
5
  import { timeAgo } from '../../components/utils';
6
+ import { EmptyState } from '../../components/EmptyState';
6
7
 
7
8
  interface Token {
8
9
  token_id: string;
@@ -100,7 +101,11 @@ export default function TokensPage() {
100
101
  {loading ? (
101
102
  <div className="animate-pulse space-y-2">{[1,2].map(i => <div key={i} className="h-12 bg-gray-800/20 rounded" />)}</div>
102
103
  ) : tokens.length === 0 ? (
103
- <div className="text-xs text-gray-600 text-center py-4">No tokens. V3 auth required.</div>
104
+ <EmptyState
105
+ variant="tokens"
106
+ compact
107
+ sub="Tokens authenticate CLI tools and external integrations. Sign in with V3 auth, then create one above."
108
+ />
104
109
  ) : (
105
110
  <div className="space-y-2">
106
111
  {tokens.map(t => (
@@ -5,6 +5,7 @@ import { useSearchParams } from 'next/navigation';
5
5
  import Link from 'next/link';
6
6
  import { useNetworkId } from '../lib/network-context';
7
7
  import { TaskDrawer } from '../components/TaskDrawer';
8
+ import { EmptyState } from '../components/EmptyState';
8
9
 
9
10
  interface Task {
10
11
  task_id: string;
@@ -47,6 +48,21 @@ const STATUS_COLORS: Record<string, string> = {
47
48
  expired: 'bg-orange-500/10 text-orange-300 border-orange-500/20',
48
49
  };
49
50
 
51
+ /** Small colored dot per status — visible on every tab (active + inactive)
52
+ * so users can scan the row at a glance. Hex inlined to avoid Tailwind
53
+ * purging dynamic `bg-${family}-400` class names. */
54
+ const STATUS_DOTS: Record<string, string> = {
55
+ created: '#9ca3af',
56
+ delivered: '#60a5fa',
57
+ acked: '#22d3ee',
58
+ running: '#4ade80',
59
+ replied: '#a78bfa',
60
+ closed: '#6b7280',
61
+ failed: '#f87171',
62
+ cancelled: '#facc15',
63
+ expired: '#fb923c',
64
+ };
65
+
50
66
  function statusBadge(status: string) {
51
67
  const color = STATUS_COLORS[status] || 'bg-gray-500/10 text-gray-400 border-gray-500/20';
52
68
  return `text-xs px-2 py-0.5 rounded-md border ${color}`;
@@ -133,16 +149,28 @@ function TasksContent() {
133
149
  </span>
134
150
  </div>
135
151
 
136
- {/* Status Tabs */}
137
- <div className="flex flex-wrap gap-1 mb-4 bg-[#111128] rounded-lg border border-[#2a2a4a] p-1">
152
+ {/* Status Tabs — color-coded dots per status family so users can
153
+ scan the row at a glance. Active tab gets full chip styling
154
+ (bg + border + text color), inactive tabs only carry the dot
155
+ + neutral label so the active state remains the strong cue.
156
+ On narrow viewports (<sm) the row scrolls horizontally with
157
+ subtle fade edges instead of wrapping to 2 lines. */}
158
+ <div className="anet-tabstrip flex sm:flex-wrap gap-1 mb-4 bg-[#111128] rounded-lg border border-[#2a2a4a] p-1 overflow-x-auto sm:overflow-x-visible scrollbar-thin">
138
159
  {['', ...STATUS_OPTIONS.filter(Boolean)].map(s => (
139
160
  <button key={s} onClick={() => setFilterStatus(s)}
140
- className={`px-3 py-1.5 rounded-md text-xs transition-colors ${
161
+ className={`px-3 py-1.5 rounded-md text-xs transition-colors flex items-center gap-1.5 shrink-0 whitespace-nowrap ${
141
162
  filterStatus === s
142
163
  ? `${STATUS_COLORS[s] || 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20'} border`
143
- : 'text-gray-500 hover:text-gray-300'
164
+ : 'text-gray-500 hover:text-gray-300 hover:bg-[#1a1a2a]/40'
144
165
  }`}>
145
- {s || 'All'}
166
+ {s && (
167
+ <span
168
+ aria-hidden
169
+ className="inline-block w-1.5 h-1.5 rounded-full shrink-0"
170
+ style={{ backgroundColor: STATUS_DOTS[s] || '#6b7280' }}
171
+ />
172
+ )}
173
+ <span>{s || 'All'}</span>
146
174
  </button>
147
175
  ))}
148
176
  </div>
@@ -201,15 +229,12 @@ function TasksContent() {
201
229
  ))}
202
230
  </div>
203
231
  ) : tasks.length === 0 ? (
204
- <div className="text-center py-16">
205
- <div className="text-gray-600 text-4xl mb-4">--</div>
206
- <h3 className="text-gray-400 text-lg font-medium mb-2">No tasks found</h3>
207
- <p className="text-gray-600 text-sm">
208
- {filterStatus || filterFrom || filterTo
209
- ? 'Try adjusting your filters.'
210
- : 'Tasks will appear here when agents send them via CommHub.'}
211
- </p>
212
- </div>
232
+ <EmptyState
233
+ variant="tasks"
234
+ sub={(filterStatus || filterFrom || filterTo)
235
+ ? 'No tasks match the current filters. Try clearing them.'
236
+ : 'Tasks will appear here when agents send them via CommHub.'}
237
+ />
213
238
  ) : (
214
239
  <div className="space-y-2">
215
240
  {/* Table header */}