@sleep2agi/agent-network-dashboard 0.5.7-preview.7 → 0.5.7-preview.73

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 (376) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +0 -1
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/diagnostics/route-bundle-stats.json +65 -65
  5. package/.next/fallback-build-manifest.json +3 -3
  6. package/.next/prerender-manifest.json +3 -3
  7. package/.next/routes-manifest.json +0 -6
  8. package/.next/server/app/_global-error.html +1 -1
  9. package/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/server/app/_not-found/page/next-font-manifest.json +2 -1
  16. package/.next/server/app/_not-found/page.js +1 -1
  17. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  18. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  19. package/.next/server/app/_not-found.html +4 -4
  20. package/.next/server/app/_not-found.rsc +16 -16
  21. package/.next/server/app/_not-found.segments/_full.segment.rsc +16 -16
  22. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  23. package/.next/server/app/_not-found.segments/_index.segment.rsc +9 -9
  24. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  25. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  26. package/.next/server/app/_not-found.segments/_tree.segment.rsc +3 -3
  27. package/.next/server/app/admin/page/next-font-manifest.json +2 -1
  28. package/.next/server/app/admin/page.js +1 -1
  29. package/.next/server/app/admin/page.js.nft.json +1 -1
  30. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  31. package/.next/server/app/admin.html +4 -4
  32. package/.next/server/app/admin.rsc +20 -19
  33. package/.next/server/app/admin.segments/_full.segment.rsc +20 -19
  34. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  35. package/.next/server/app/admin.segments/_index.segment.rsc +9 -9
  36. package/.next/server/app/admin.segments/_tree.segment.rsc +5 -4
  37. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  38. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  39. package/.next/server/app/index.html +4 -4
  40. package/.next/server/app/index.rsc +20 -19
  41. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  42. package/.next/server/app/index.segments/_full.segment.rsc +20 -19
  43. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  44. package/.next/server/app/index.segments/_index.segment.rsc +9 -9
  45. package/.next/server/app/index.segments/_tree.segment.rsc +5 -4
  46. package/.next/server/app/login/page/next-font-manifest.json +2 -1
  47. package/.next/server/app/login/page.js +1 -1
  48. package/.next/server/app/login/page.js.nft.json +1 -1
  49. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  50. package/.next/server/app/login.html +2 -2
  51. package/.next/server/app/login.rsc +20 -19
  52. package/.next/server/app/login.segments/_full.segment.rsc +20 -19
  53. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  54. package/.next/server/app/login.segments/_index.segment.rsc +9 -9
  55. package/.next/server/app/login.segments/_tree.segment.rsc +5 -4
  56. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  57. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  58. package/.next/server/app/logs/page/next-font-manifest.json +2 -1
  59. package/.next/server/app/logs/page.js +1 -1
  60. package/.next/server/app/logs/page.js.nft.json +1 -1
  61. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  62. package/.next/server/app/logs.html +4 -4
  63. package/.next/server/app/logs.rsc +20 -19
  64. package/.next/server/app/logs.segments/_full.segment.rsc +20 -19
  65. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  66. package/.next/server/app/logs.segments/_index.segment.rsc +9 -9
  67. package/.next/server/app/logs.segments/_tree.segment.rsc +5 -4
  68. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  69. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  70. package/.next/server/app/manifest.webmanifest.body +1 -1
  71. package/.next/server/app/messages/page/next-font-manifest.json +2 -1
  72. package/.next/server/app/messages/page.js +1 -1
  73. package/.next/server/app/messages/page.js.nft.json +1 -1
  74. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/messages.html +4 -4
  76. package/.next/server/app/messages.rsc +20 -19
  77. package/.next/server/app/messages.segments/_full.segment.rsc +20 -19
  78. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  79. package/.next/server/app/messages.segments/_index.segment.rsc +9 -9
  80. package/.next/server/app/messages.segments/_tree.segment.rsc +5 -4
  81. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  82. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  83. package/.next/server/app/node/page/next-font-manifest.json +2 -1
  84. package/.next/server/app/node/page.js +1 -1
  85. package/.next/server/app/node/page.js.nft.json +1 -1
  86. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  87. package/.next/server/app/node.html +4 -4
  88. package/.next/server/app/node.rsc +20 -19
  89. package/.next/server/app/node.segments/_full.segment.rsc +20 -19
  90. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  91. package/.next/server/app/node.segments/_index.segment.rsc +9 -9
  92. package/.next/server/app/node.segments/_tree.segment.rsc +5 -4
  93. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  94. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  95. package/.next/server/app/nodes/page/next-font-manifest.json +2 -1
  96. package/.next/server/app/nodes/page.js +1 -1
  97. package/.next/server/app/nodes/page.js.nft.json +1 -1
  98. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  99. package/.next/server/app/nodes.html +4 -4
  100. package/.next/server/app/nodes.rsc +20 -19
  101. package/.next/server/app/nodes.segments/_full.segment.rsc +20 -19
  102. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  103. package/.next/server/app/nodes.segments/_index.segment.rsc +9 -9
  104. package/.next/server/app/nodes.segments/_tree.segment.rsc +5 -4
  105. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  106. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  107. package/.next/server/app/page/next-font-manifest.json +2 -1
  108. package/.next/server/app/page.js +1 -1
  109. package/.next/server/app/page.js.nft.json +1 -1
  110. package/.next/server/app/page_client-reference-manifest.js +1 -1
  111. package/.next/server/app/server-logs/page/next-font-manifest.json +2 -1
  112. package/.next/server/app/server-logs/page.js +1 -1
  113. package/.next/server/app/server-logs/page.js.nft.json +1 -1
  114. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  115. package/.next/server/app/server-logs.html +4 -4
  116. package/.next/server/app/server-logs.rsc +20 -19
  117. package/.next/server/app/server-logs.segments/_full.segment.rsc +20 -19
  118. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  119. package/.next/server/app/server-logs.segments/_index.segment.rsc +9 -9
  120. package/.next/server/app/server-logs.segments/_tree.segment.rsc +5 -4
  121. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  122. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  123. package/.next/server/app/servers/page/next-font-manifest.json +2 -1
  124. package/.next/server/app/servers/page.js +1 -1
  125. package/.next/server/app/servers/page.js.nft.json +1 -1
  126. package/.next/server/app/servers/page_client-reference-manifest.js +1 -1
  127. package/.next/server/app/servers.html +4 -4
  128. package/.next/server/app/servers.rsc +20 -19
  129. package/.next/server/app/servers.segments/_full.segment.rsc +20 -19
  130. package/.next/server/app/servers.segments/_head.segment.rsc +4 -4
  131. package/.next/server/app/servers.segments/_index.segment.rsc +9 -9
  132. package/.next/server/app/servers.segments/_tree.segment.rsc +5 -4
  133. package/.next/server/app/servers.segments/servers/__PAGE__.segment.rsc +4 -4
  134. package/.next/server/app/servers.segments/servers.segment.rsc +3 -3
  135. package/.next/server/app/settings/networks/page/next-font-manifest.json +2 -1
  136. package/.next/server/app/settings/networks/page.js +1 -1
  137. package/.next/server/app/settings/networks/page.js.nft.json +1 -1
  138. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  139. package/.next/server/app/settings/networks.html +4 -4
  140. package/.next/server/app/settings/networks.rsc +20 -19
  141. package/.next/server/app/settings/networks.segments/_full.segment.rsc +20 -19
  142. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  143. package/.next/server/app/settings/networks.segments/_index.segment.rsc +9 -9
  144. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +5 -4
  145. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  146. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  147. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  148. package/.next/server/app/settings/page/next-font-manifest.json +2 -1
  149. package/.next/server/app/settings/page.js +1 -1
  150. package/.next/server/app/settings/page.js.nft.json +1 -1
  151. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  152. package/.next/server/app/settings/tokens/page/next-font-manifest.json +2 -1
  153. package/.next/server/app/settings/tokens/page.js +1 -1
  154. package/.next/server/app/settings/tokens/page.js.nft.json +1 -1
  155. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  156. package/.next/server/app/settings/tokens.html +4 -4
  157. package/.next/server/app/settings/tokens.rsc +20 -19
  158. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +20 -19
  159. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  160. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +9 -9
  161. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +5 -4
  162. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  163. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  164. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  165. package/.next/server/app/settings.html +4 -4
  166. package/.next/server/app/settings.rsc +20 -19
  167. package/.next/server/app/settings.segments/_full.segment.rsc +20 -19
  168. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  169. package/.next/server/app/settings.segments/_index.segment.rsc +9 -9
  170. package/.next/server/app/settings.segments/_tree.segment.rsc +5 -4
  171. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  172. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  173. package/.next/server/app/tasks/[id]/page/next-font-manifest.json +2 -1
  174. package/.next/server/app/tasks/[id]/page.js +1 -1
  175. package/.next/server/app/tasks/[id]/page.js.nft.json +1 -1
  176. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  177. package/.next/server/app/tasks/page/next-font-manifest.json +2 -1
  178. package/.next/server/app/tasks/page.js +1 -1
  179. package/.next/server/app/tasks/page.js.nft.json +1 -1
  180. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  181. package/.next/server/app/tasks.html +4 -4
  182. package/.next/server/app/tasks.rsc +20 -19
  183. package/.next/server/app/tasks.segments/_full.segment.rsc +20 -19
  184. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  185. package/.next/server/app/tasks.segments/_index.segment.rsc +9 -9
  186. package/.next/server/app/tasks.segments/_tree.segment.rsc +5 -4
  187. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  188. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  189. package/.next/server/app-paths-manifest.json +0 -1
  190. package/.next/server/chunks/00jm_next_dist_0ju_ux9._.js +1 -1
  191. package/.next/server/chunks/00jm_next_dist_0ju_ux9._.js.map +1 -1
  192. package/.next/server/chunks/ssr/[root-of-the-server]__030vg4n._.js +1 -1
  193. package/.next/server/chunks/ssr/[root-of-the-server]__030vg4n._.js.map +1 -1
  194. package/.next/server/chunks/ssr/[root-of-the-server]__04gz75y._.js +3 -0
  195. package/.next/server/chunks/ssr/[root-of-the-server]__04gz75y._.js.map +1 -0
  196. package/.next/server/chunks/ssr/[root-of-the-server]__05kf31s._.js +3 -0
  197. package/.next/server/chunks/ssr/[root-of-the-server]__05kf31s._.js.map +1 -0
  198. package/.next/server/chunks/ssr/[root-of-the-server]__096ytyk._.js +3 -0
  199. package/.next/server/chunks/ssr/[root-of-the-server]__096ytyk._.js.map +1 -0
  200. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js +1 -1
  201. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js.map +1 -1
  202. package/.next/server/chunks/ssr/[root-of-the-server]__0u4-66w._.js +8 -0
  203. package/.next/server/chunks/ssr/[root-of-the-server]__0u4-66w._.js.map +1 -0
  204. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
  205. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  206. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  207. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  208. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  209. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  210. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_870i8._.js +3 -0
  211. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_870i8._.js.map +1 -0
  212. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js +1 -1
  213. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js.map +1 -1
  214. package/.next/server/chunks/ssr/agent-network-dashboard_app_0fjlnh~._.js +3 -0
  215. package/.next/server/chunks/ssr/agent-network-dashboard_app_0fjlnh~._.js.map +1 -0
  216. package/.next/server/chunks/ssr/agent-network-dashboard_app_0gd.4pc._.js +9 -0
  217. package/.next/server/chunks/ssr/agent-network-dashboard_app_0gd.4pc._.js.map +1 -0
  218. package/.next/server/chunks/ssr/agent-network-dashboard_app_0wn4jc5._.js +3 -0
  219. package/.next/server/chunks/ssr/agent-network-dashboard_app_0wn4jc5._.js.map +1 -0
  220. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js +1 -1
  221. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js.map +1 -1
  222. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js +1 -1
  223. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js.map +1 -1
  224. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js +1 -1
  225. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js.map +1 -1
  226. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0r7kb.o._.js +9 -0
  227. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0r7kb.o._.js.map +1 -0
  228. package/.next/server/chunks/ssr/agent-network-dashboard_app_servers_page_tsx_0jib5qm._.js +1 -1
  229. package/.next/server/chunks/ssr/agent-network-dashboard_app_servers_page_tsx_0jib5qm._.js.map +1 -1
  230. package/.next/server/chunks/ssr/agent-network-dashboard_app_tasks_page_tsx_0mwxy4z._.js +1 -1
  231. package/.next/server/chunks/ssr/agent-network-dashboard_app_tasks_page_tsx_0mwxy4z._.js.map +1 -1
  232. package/.next/server/middleware-build-manifest.js +3 -3
  233. package/.next/server/next-font-manifest.js +1 -1
  234. package/.next/server/next-font-manifest.json +30 -15
  235. package/.next/server/pages/404.html +4 -4
  236. package/.next/server/pages/500.html +1 -1
  237. package/.next/server/server-reference-manifest.js +1 -1
  238. package/.next/server/server-reference-manifest.json +1 -1
  239. package/.next/static/chunks/0..h8s._z~uek.js +1 -0
  240. package/.next/static/chunks/0.mh8n0itrii5.js +1 -0
  241. package/.next/static/chunks/{0jp~cs9-zkmqa.js → 00b4y77vxfabl.js} +1 -1
  242. package/.next/static/chunks/049vx3qljs1tt.js +1 -0
  243. package/.next/static/chunks/066jf0nk75nic.css +2 -0
  244. package/.next/static/chunks/06vp7429lrzl7.js +1 -0
  245. package/.next/static/chunks/06xxn73qy0qiz.js +7 -0
  246. package/.next/static/chunks/0_bn~gcrgo.4n.js +1 -0
  247. package/.next/static/chunks/0_tvbie.c68h5.js +1 -0
  248. package/.next/static/chunks/0cp0cz3mxejl~.js +4 -0
  249. package/.next/static/chunks/0g4d-_fi-d9hg.js +1 -0
  250. package/.next/static/chunks/0scww97p6z5z..css +1 -0
  251. package/.next/static/chunks/0shtnff1p8hzw.js +1 -0
  252. package/.next/static/chunks/0wz0122ym_gr3.js +1 -0
  253. package/.next/static/chunks/0y5gol09tlu63.js +1 -0
  254. package/.next/static/chunks/13l-ngu707546.js +1 -0
  255. package/.next/static/chunks/14141xj5.1t3t.js +1 -0
  256. package/.next/static/chunks/149a4l50_3vw-.js +7 -0
  257. package/.next/static/chunks/152_p6jt7txp6.js +1 -0
  258. package/.next/static/chunks/15hos-r_t7doi.js +1 -0
  259. package/.next/static/media/4fa387ec64143e14-s.0wkzw~je483f-.woff2 +0 -0
  260. package/.next/static/media/53b9e256198e5412-s.0-wfv7uh4i7h9.woff2 +0 -0
  261. package/.next/static/media/5ce348bf30bf5439-s.0zgw-jeven.3w.woff2 +0 -0
  262. package/.next/static/media/6306c77e7c8268e4-s.0rhz0arwfsn~5.woff2 +0 -0
  263. package/.next/static/media/7178b3e590c64307-s.0nx0ww8fni_q3.woff2 +0 -0
  264. package/.next/static/media/797e433ab948586e-s.p.08e28id.o-okb.woff2 +0 -0
  265. package/.next/static/media/7d817b4c03b0c5f1-s.0l76wvqk9d84w.woff2 +0 -0
  266. package/.next/static/media/8a480f0b521d4e75-s.0jzbimsg8vl84.woff2 +0 -0
  267. package/.next/static/media/bbc41e54d2fcbd21-s.0k4k9394f2q-k.woff2 +0 -0
  268. package/.next/static/media/caa3a2e1cccd8315-s.p.09~u27dqhyhd6.woff2 +0 -0
  269. package/.next/static/media/fef07dbb0973bf53-s.12tyk43_3sh9u.woff2 +0 -0
  270. package/.next/trace +2 -2
  271. package/.next/trace-build +1 -1
  272. package/.next/types/routes.d.ts +1 -2
  273. package/.next/types/validator.ts +0 -9
  274. package/app/admin/page.tsx +53 -38
  275. package/app/components/AgentCard.tsx +36 -20
  276. package/app/components/AppShell.tsx +7 -1
  277. package/app/components/ChatPopover.tsx +1 -1
  278. package/app/components/CollapsibleSearch.tsx +127 -0
  279. package/app/components/CommandCenter.tsx +14 -4
  280. package/app/components/CommandPalette.tsx +6 -6
  281. package/app/components/DispatchPanel.tsx +13 -10
  282. package/app/components/EmptyState.tsx +19 -4
  283. package/app/components/HealthBanner.tsx +28 -4
  284. package/app/components/HelpOverlay.tsx +4 -7
  285. package/app/components/LoadingSkeleton.tsx +31 -21
  286. package/app/components/MobileNav.tsx +28 -21
  287. package/app/components/Sidebar.tsx +30 -23
  288. package/app/components/StatsBar.tsx +69 -42
  289. package/app/components/TaskChatPanel.tsx +19 -5
  290. package/app/components/TaskDrawer.tsx +9 -7
  291. package/app/components/ThemeSwitcher.tsx +15 -79
  292. package/app/components/TopoGraph.tsx +31 -21
  293. package/app/components/UserBar.tsx +5 -5
  294. package/app/globals.css +1757 -1776
  295. package/app/layout.tsx +37 -4
  296. package/app/lib/hooks.ts +0 -5
  297. package/app/lib/status.ts +24 -17
  298. package/app/login/page.tsx +7 -7
  299. package/app/logs/page.tsx +12 -6
  300. package/app/manifest.ts +2 -2
  301. package/app/messages/page.tsx +67 -47
  302. package/app/node/page.tsx +27 -17
  303. package/app/nodes/page.tsx +62 -49
  304. package/app/page.tsx +60 -282
  305. package/app/server-logs/page.tsx +40 -14
  306. package/app/servers/page.tsx +33 -12
  307. package/app/settings/networks/page.tsx +17 -15
  308. package/app/settings/page.tsx +102 -96
  309. package/app/settings/tokens/page.tsx +5 -5
  310. package/app/tasks/[id]/page.tsx +10 -10
  311. package/app/tasks/page.tsx +58 -34
  312. package/bin/start.js +0 -0
  313. package/package.json +1 -1
  314. package/public/favicon.svg +1 -1
  315. package/public/manifest.webmanifest +24 -0
  316. package/public/robots.txt +7 -0
  317. package/.next/server/app/api/hub/license/route/app-paths-manifest.json +0 -3
  318. package/.next/server/app/api/hub/license/route/build-manifest.json +0 -9
  319. package/.next/server/app/api/hub/license/route/server-reference-manifest.json +0 -4
  320. package/.next/server/app/api/hub/license/route.js +0 -7
  321. package/.next/server/app/api/hub/license/route.js.map +0 -5
  322. package/.next/server/app/api/hub/license/route.js.nft.json +0 -1
  323. package/.next/server/app/api/hub/license/route_client-reference-manifest.js +0 -3
  324. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_license_route_actions_0a4.fuh.js +0 -3
  325. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_license_route_actions_0a4.fuh.js.map +0 -1
  326. package/.next/server/chunks/[root-of-the-server]__0rovr5-._.js +0 -3
  327. package/.next/server/chunks/[root-of-the-server]__0rovr5-._.js.map +0 -1
  328. package/.next/server/chunks/ssr/[root-of-the-server]__0lu1wok._.js +0 -8
  329. package/.next/server/chunks/ssr/[root-of-the-server]__0lu1wok._.js.map +0 -1
  330. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js +0 -3
  331. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js.map +0 -1
  332. package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js +0 -3
  333. package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js.map +0 -1
  334. package/.next/server/chunks/ssr/[root-of-the-server]__11fu-5m._.js +0 -3
  335. package/.next/server/chunks/ssr/[root-of-the-server]__11fu-5m._.js.map +0 -1
  336. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js +0 -3
  337. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js.map +0 -1
  338. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js +0 -3
  339. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js.map +0 -1
  340. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js +0 -9
  341. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js.map +0 -1
  342. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0s5uqlp._.js +0 -9
  343. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0s5uqlp._.js.map +0 -1
  344. package/.next/server/chunks/ssr/agent-network-dashboard_app_server-logs_page_tsx_0dg.l_8._.js +0 -3
  345. package/.next/server/chunks/ssr/agent-network-dashboard_app_server-logs_page_tsx_0dg.l_8._.js.map +0 -1
  346. package/.next/static/chunks/0-mpa_947ipeq.js +0 -1
  347. package/.next/static/chunks/02to42x11p557.js +0 -7
  348. package/.next/static/chunks/03~5pxwbxxw-b.js +0 -1
  349. package/.next/static/chunks/04~fkia6-79k3.js +0 -1
  350. package/.next/static/chunks/0561vp5-q5.zp.js +0 -1
  351. package/.next/static/chunks/05uk96gc~9mni.js +0 -1
  352. package/.next/static/chunks/085rejlait1fs.js +0 -1
  353. package/.next/static/chunks/0a.9~-nf0gpec.js +0 -1
  354. package/.next/static/chunks/0im751o4n61c7.js +0 -1
  355. package/.next/static/chunks/0inql3s9ldyx5.js +0 -1
  356. package/.next/static/chunks/0ku0fjqlm9mca.js +0 -1
  357. package/.next/static/chunks/0mcamnu4w_x1r.js +0 -4
  358. package/.next/static/chunks/0ss8u23bnbyry.js +0 -1
  359. package/.next/static/chunks/0~rv5y.y5my9s.css +0 -1
  360. package/.next/static/chunks/13yktdzuatx3d.js +0 -1
  361. package/.next/static/chunks/15-ltfhot3b4n.js +0 -7
  362. package/.next/static/chunks/16ls93seuyj8a.js +0 -1
  363. package/.next/static/chunks/17sxlwlx5fhrp.css +0 -1
  364. package/.next/static/chunks/181u38qblp8lz.js +0 -1
  365. package/.next/static/media/4fa387ec64143e14-s.0.qu-9752pffj.woff2 +0 -0
  366. package/.next/static/media/5ce348bf30bf5439-s.0ee55_hj9qcer.woff2 +0 -0
  367. package/.next/static/media/6306c77e7c8268e4-s.0mao5jbfbduzp.woff2 +0 -0
  368. package/.next/static/media/797e433ab948586e-s.p.09zddjkbdep5a.woff2 +0 -0
  369. package/.next/static/media/7d817b4c03b0c5f1-s.0uzt.a6d44yda.woff2 +0 -0
  370. package/.next/static/media/bbc41e54d2fcbd21-s.0mvwgmnhv29no.woff2 +0 -0
  371. package/app/api/hub/license/route.ts +0 -33
  372. package/app/components/BroadcastBar.tsx +0 -84
  373. package/app/components/InboxPanel.tsx +0 -36
  374. /package/.next/static/{vv4Gz5yVhOzydMI2UlT1l → TDZA1Sx9EZPbGBOvEQtJ7}/_buildManifest.js +0 -0
  375. /package/.next/static/{vv4Gz5yVhOzydMI2UlT1l → TDZA1Sx9EZPbGBOvEQtJ7}/_clientMiddlewareManifest.js +0 -0
  376. /package/.next/static/{vv4Gz5yVhOzydMI2UlT1l → TDZA1Sx9EZPbGBOvEQtJ7}/_ssgManifest.js +0 -0
@@ -1,10 +1,11 @@
1
1
  'use client';
2
2
 
3
3
  import { useState } from 'react';
4
+ import Link from 'next/link';
4
5
  import { useSessions, useHealth, useStats } from '../lib/hooks';
5
6
  import { timeAgo } from '../components/utils';
6
7
  import { AliasAvatar } from '../components/AliasAvatar';
7
- import { STATUS_CHIP_CLASS, SESSION_STATUS_CHIP_CLASS } from '../lib/status';
8
+ import { SESSION_STATUS_CHIP_CLASS } from '../lib/status';
8
9
 
9
10
  export default function AdminPage() {
10
11
  const { sessions } = useSessions();
@@ -73,7 +74,7 @@ export default function AdminPage() {
73
74
  const offlineNodes = sessions.filter(s => !sseFor(s));
74
75
 
75
76
  return (
76
- <div className="min-h-screen bg-[#0a0a1a] text-gray-100 p-4 sm:p-6 font-mono">
77
+ <div className="min-h-screen bg-[#0b0b0d] text-gray-100 p-4 sm:p-6">
77
78
  <h1 className="text-2xl font-bold text-white mb-3 lg:ml-0 ml-10">Admin</h1>
78
79
 
79
80
  {/* Section anchor nav — same pattern as Settings r28; R2 of #190 mobile
@@ -82,14 +83,18 @@ export default function AdminPage() {
82
83
  read as inert labels rather than tappable jumps. Wrapping each in a
83
84
  visible bordered chip restores the affordance and lifts tap height
84
85
  to ~44px without changing the desktop hierarchy. */}
85
- <nav className="mb-8 flex flex-wrap gap-2 text-xs">
86
+ {/* #209 R31 (mobile vertical rhythm — same pattern as R28/R30):
87
+ chip-jump nav was mb-8 (32 px) — biggest single gap on /admin
88
+ above the fold. Drop to mb-4 sm:mb-8 — phones get 16 px, sm:
89
+ up keeps the original breathing room. */}
90
+ <nav className="mb-4 sm:mb-8 flex flex-wrap gap-2 text-xs">
86
91
  {[
87
92
  { href: '#status', label: 'Status' },
88
93
  { href: '#actions', label: 'Actions' },
89
94
  { href: '#users', label: 'Users' },
90
95
  ].map(a => (
91
96
  <a key={a.href} href={a.href}
92
- className="inline-flex min-h-[44px] items-center rounded-md border border-[#2a2a4a] bg-[#0a0a15]/60 px-3 py-2 text-gray-400 hover:border-cyan-500/50 hover:text-cyan-300 hover:bg-cyan-500/10 transition-colors">
97
+ className="inline-flex min-h-[44px] items-center rounded-md border border-[#26262b] bg-[#0e0e10]/60 px-3 py-2 text-gray-400 hover:border-cyan-500/50 hover:text-cyan-300 hover:bg-cyan-500/10 transition-colors">
93
98
  {a.label}
94
99
  </a>
95
100
  ))}
@@ -101,50 +106,60 @@ export default function AdminPage() {
101
106
  <div id="status" className="space-y-4 scroll-mt-6">
102
107
  <div className="flex items-center gap-2 px-1">
103
108
  <div className="text-[11px] uppercase tracking-[0.14em] text-gray-400 font-semibold">Status</div>
104
- <div className="flex-1 h-px bg-[#2a2a4a]" />
109
+ <div className="flex-1 h-px bg-[#26262b]" />
105
110
  </div>
106
111
  <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
107
112
 
108
- {/* Server Overview */}
109
- <section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
110
- <h2 className="text-sm font-semibold text-gray-300 mb-4">Server Overview</h2>
113
+ {/* Server Overview.
114
+ #209 R42: same mobile density pattern R39 brought to the
115
+ Overview StatsBar 4-card grid — p-5 → p-4 sm:p-5 and
116
+ text-3xl numbers → text-2xl sm:text-3xl on phones. The
117
+ 2×2 numeric grid shrinks ~25 % on mobile (~60 px of fold
118
+ reclaimed) while reading identically on desktop. mb-4 on
119
+ the section heading also drops to mb-3 sm:mb-4. */}
120
+ <section className="bg-[#161618] border border-[#26262b] rounded-xl p-4 sm:p-5">
121
+ <h2 className="text-sm font-semibold text-gray-300 mb-3 sm:mb-4">Server Overview</h2>
111
122
  <div className="grid grid-cols-2 gap-4 text-sm">
112
123
  <div>
113
- <div className="text-3xl font-bold text-green-400 tabular-nums">{onlineNodes.length}</div>
124
+ <div className="text-2xl sm:text-3xl font-bold text-green-400 tabular-nums">{onlineNodes.length}</div>
114
125
  <div className="text-xs text-gray-500">Online</div>
115
126
  </div>
116
127
  <div>
117
- <div className="text-3xl font-bold text-gray-500 tabular-nums">{offlineNodes.length}</div>
128
+ <div className="text-2xl sm:text-3xl font-bold text-gray-500 tabular-nums">{offlineNodes.length}</div>
118
129
  <div className="text-xs text-gray-500">Offline</div>
119
130
  </div>
120
131
  <div>
121
- <div className="text-3xl font-bold text-cyan-400 tabular-nums">{health?.sse_connections ?? '--'}</div>
132
+ <div className="text-2xl sm:text-3xl font-bold text-cyan-400 tabular-nums">{health?.sse_connections ?? '--'}</div>
122
133
  <div className="text-xs text-gray-500">
123
134
  SSE Streams <span className="text-gray-600">· server</span>
124
135
  </div>
125
136
  </div>
126
137
  <div>
127
- <div className="text-3xl font-bold text-white tabular-nums">{stats?.tasks?.total ?? '--'}</div>
138
+ <div className="text-2xl sm:text-3xl font-bold text-white tabular-nums">{stats?.tasks?.total ?? '--'}</div>
128
139
  <div className="text-xs text-gray-500">Total Tasks</div>
129
140
  </div>
130
141
  </div>
131
- {stats?.tasks?.by_status && stats.tasks.by_status.length > 0 && (
132
- /* Round 90: adopt the shared STATUS_CHIP_CLASS palette so
133
- `acked`/`replied`/`failed` here color-match the same
134
- chips on /tasks, Overview r69, and /tasks/[id] r68.
135
- Unknown statuses fall back to the previous flat gray. */
136
- <div className="mt-4 flex flex-wrap gap-2">
137
- {stats.tasks.by_status.map((s: { status: string; count: number }) => (
138
- <span key={s.status} className={`text-xs px-2 py-1 rounded border ${STATUS_CHIP_CLASS[s.status] || 'bg-[#0a0a15] border-[#1a1a2a] text-gray-400'}`}>
139
- {s.status}: {s.count}
140
- </span>
141
- ))}
142
- </div>
143
- )}
142
+ {stats?.tasks?.by_status && stats.tasks.by_status.length > 0 && (() => {
143
+ /* #217 D7 (less is more): the 6-9 chip per-status wall
144
+ duplicated the /tasks tab strip. Same one-line summary
145
+ idiom as the Overview task line — running/failed are the
146
+ only numbers that drive action, the rest is on /tasks. */
147
+ const byStatus = Object.fromEntries(stats.tasks.by_status.map((s: { status: string; count: number }) => [s.status, s.count]));
148
+ return (
149
+ <div className="mt-4 flex items-center justify-between border-t border-[#26262b] pt-3 text-xs">
150
+ <div className="text-gray-400 tabular-nums">
151
+ <span className={byStatus['running'] ? 'text-green-400' : 'text-gray-500'}>{byStatus['running'] || 0} running</span>
152
+ <span className="text-gray-600"> &middot; </span>
153
+ <span className={byStatus['failed'] ? 'text-red-400' : 'text-gray-500'}>{byStatus['failed'] || 0} failed</span>
154
+ </div>
155
+ <Link href="/tasks" prefetch={false} className="text-cyan-400 hover:text-cyan-300">View all &rarr;</Link>
156
+ </div>
157
+ );
158
+ })()}
144
159
  </section>
145
160
 
146
161
  {/* Online Sessions — pulled into Status group (was below Send Task) */}
147
- <section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
162
+ <section className="bg-[#161618] border border-[#26262b] rounded-xl p-5">
148
163
  <h2 className="text-sm font-semibold text-gray-300 mb-4">
149
164
  Online Sessions <span className="text-gray-600">({onlineNodes.length})</span>
150
165
  </h2>
@@ -152,7 +167,7 @@ export default function AdminPage() {
152
167
  {onlineNodes.length === 0 ? (
153
168
  <div className="text-xs text-gray-600 text-center py-4">No online sessions</div>
154
169
  ) : onlineNodes.map(s => (
155
- <div key={s.alias} className="flex items-center gap-3 bg-[#0a0a15] rounded-lg px-3 py-2 border border-[#1a1a2a]">
170
+ <div key={s.alias} className="flex items-center gap-3 bg-[#0e0e10] rounded-lg px-3 py-2 border border-[#1c1c1f]">
156
171
  <AliasAvatar alias={s.alias} size={20} />
157
172
  <div className="min-w-0 flex-1">
158
173
  <div className="text-sm text-white font-medium truncate">{s.alias}</div>
@@ -177,28 +192,28 @@ export default function AdminPage() {
177
192
  <div id="actions" className="space-y-4 scroll-mt-6">
178
193
  <div className="flex items-center gap-2 px-1">
179
194
  <div className="text-[11px] uppercase tracking-[0.14em] text-gray-400 font-semibold">Actions</div>
180
- <div className="flex-1 h-px bg-[#2a2a4a]" />
195
+ <div className="flex-1 h-px bg-[#26262b]" />
181
196
  </div>
182
197
  {sessions.length === 0 ? (
183
198
  /* Round 77: Broadcast and Send Task both require at least one
184
199
  registered agent. Surfacing the empty cards above an empty
185
200
  fleet is the same dead-control class as the Overview
186
201
  Dispatch button (r70). Replace with a single inline hint. */
187
- <div className="bg-[#111128] border border-[#2a2a4a] rounded-xl px-5 py-4 text-sm text-gray-500">
202
+ <div className="bg-[#161618] border border-[#26262b] rounded-xl px-5 py-4 text-sm text-gray-500">
188
203
  Broadcast and Send Task become available after the first agent registers.
189
204
  </div>
190
205
  ) : (
191
206
  <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
192
207
 
193
208
  {/* Broadcast */}
194
- <section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
209
+ <section className="bg-[#161618] border border-[#26262b] rounded-xl p-5">
195
210
  <h2 className="text-sm font-semibold text-gray-300 mb-4">Broadcast</h2>
196
211
  <textarea
197
212
  value={broadcastMsg}
198
213
  onChange={e => setBroadcastMsg(e.target.value)}
199
214
  placeholder="Message to all online nodes..."
200
215
  rows={3}
201
- className="w-full bg-[#0a0a15] border border-[#2a2a4a] rounded-lg px-3 py-2 text-sm text-white placeholder-gray-600 focus:border-cyan-500/50 focus:outline-none resize-none"
216
+ className="w-full bg-[#0e0e10] border border-[#26262b] rounded-lg px-3 py-2 text-sm text-white placeholder-gray-600 focus:border-cyan-500/50 focus:outline-none resize-none"
202
217
  />
203
218
  <div className="flex justify-between items-center mt-3">
204
219
  <span className="text-xs text-gray-600">{broadcastMsg.length}/500</span>
@@ -214,12 +229,12 @@ export default function AdminPage() {
214
229
  </section>
215
230
 
216
231
  {/* Send Task */}
217
- <section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
232
+ <section className="bg-[#161618] border border-[#26262b] rounded-xl p-5">
218
233
  <h2 className="text-sm font-semibold text-gray-300 mb-4">Send Task</h2>
219
234
  <select
220
235
  value={taskTarget}
221
236
  onChange={e => setTaskTarget(e.target.value)}
222
- className="w-full bg-[#0a0a15] border border-[#2a2a4a] rounded-lg px-3 py-2 text-sm text-white focus:border-cyan-500/50 focus:outline-none mb-3"
237
+ className="w-full bg-[#0e0e10] border border-[#26262b] rounded-lg px-3 py-2 text-base sm:text-sm text-white focus:border-cyan-500/50 focus:outline-none mb-3"
223
238
  >
224
239
  <option value="">Select target node...</option>
225
240
  {onlineNodes.map(s => (
@@ -236,7 +251,7 @@ export default function AdminPage() {
236
251
  onChange={e => setTaskContent(e.target.value)}
237
252
  placeholder="Task content..."
238
253
  rows={3}
239
- className="w-full bg-[#0a0a15] border border-[#2a2a4a] rounded-lg px-3 py-2 text-sm text-white placeholder-gray-600 focus:border-cyan-500/50 focus:outline-none resize-none"
254
+ className="w-full bg-[#0e0e10] border border-[#26262b] rounded-lg px-3 py-2 text-sm text-white placeholder-gray-600 focus:border-cyan-500/50 focus:outline-none resize-none"
240
255
  />
241
256
  <div className="flex justify-end mt-3">
242
257
  <button
@@ -258,12 +273,12 @@ export default function AdminPage() {
258
273
  <div id="users" className="space-y-4 scroll-mt-6">
259
274
  <div className="flex items-center gap-2 px-1">
260
275
  <div className="text-[11px] uppercase tracking-[0.14em] text-gray-400 font-semibold">Users</div>
261
- <div className="flex-1 h-px bg-[#2a2a4a]" />
276
+ <div className="flex-1 h-px bg-[#26262b]" />
262
277
  </div>
263
278
  <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
264
279
 
265
280
  {/* User Management (V3 Auth) */}
266
- <section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
281
+ <section className="bg-[#161618] border border-[#26262b] rounded-xl p-5">
267
282
  <h2 className="text-sm font-semibold text-gray-300 mb-4">Register User (V3)</h2>
268
283
  <div className="space-y-3">
269
284
  <input
@@ -271,14 +286,14 @@ export default function AdminPage() {
271
286
  value={regUser}
272
287
  onChange={e => setRegUser(e.target.value)}
273
288
  placeholder="Username"
274
- className="w-full bg-[#0a0a15] border border-[#2a2a4a] rounded-lg px-3 py-2 text-sm text-white placeholder-gray-600 focus:border-cyan-500/50 focus:outline-none"
289
+ className="w-full bg-[#0e0e10] border border-[#26262b] rounded-lg px-3 py-2 text-sm text-white placeholder-gray-600 focus:border-cyan-500/50 focus:outline-none"
275
290
  />
276
291
  <input
277
292
  type="password"
278
293
  value={regPass}
279
294
  onChange={e => setRegPass(e.target.value)}
280
295
  placeholder="Password"
281
- className="w-full bg-[#0a0a15] border border-[#2a2a4a] rounded-lg px-3 py-2 text-sm text-white placeholder-gray-600 focus:border-cyan-500/50 focus:outline-none"
296
+ className="w-full bg-[#0e0e10] border border-[#26262b] rounded-lg px-3 py-2 text-sm text-white placeholder-gray-600 focus:border-cyan-500/50 focus:outline-none"
282
297
  />
283
298
  <button
284
299
  onClick={registerUser}
@@ -28,10 +28,21 @@ export function AgentCard({ session: s, hasSse, sseCount, onChat }: AgentCardPro
28
28
  <Link
29
29
  href={`/node?alias=${encodeURIComponent(s.alias)}`}
30
30
  prefetch={false}
31
+ onClick={e => {
32
+ // #217 M2 (Vincent tg 646 "为什么还要留一个 chat 按钮,直接点击
33
+ // 不就行了"): tapping a reachable agent's card opens chat
34
+ // directly — the Chat button is gone. Offline/unreachable cards
35
+ // keep navigating to /node detail, and detail for reachable
36
+ // agents stays available via the Agents tab.
37
+ if (onChat && hasSse) {
38
+ e.preventDefault();
39
+ onChat(s.alias);
40
+ }
41
+ }}
31
42
  className={`anet-agent-card group relative block rounded-xl border p-3 sm:p-4 transition-all duration-300 cursor-pointer hover:-translate-y-0.5 ${
32
43
  hasSse
33
- ? `bg-[#111128] border-[#2a2a4a] hover:border-cyan-500/30 hover:shadow-lg ${cfg.glow}`
34
- : 'bg-[#0d0d1a] border-[#1a1a2a] opacity-40'
44
+ ? `bg-[#161618] border-[#26262b] hover:border-cyan-500/30 hover:shadow-lg ${cfg.glow}`
45
+ : 'bg-[#111113] border-[#1c1c1f] opacity-40'
35
46
  }`}
36
47
  >
37
48
  {/* Header: avatar + name + status. Avatar carries the alias→hue map
@@ -55,7 +66,7 @@ export function AgentCard({ session: s, hasSse, sseCount, onChat }: AgentCardPro
55
66
  chews ~28px per card × 99 sessions on Overview mobile. The
56
67
  agent type stays one tap away on /node detail. */}
57
68
  <div className="hidden sm:flex items-center gap-2 mb-3">
58
- <span className="text-xs text-gray-600 bg-[#0a0a15] px-2 py-0.5 rounded border border-[#1a1a2a]">
69
+ <span className="text-xs text-gray-600 bg-[#0e0e10] px-2 py-0.5 rounded border border-[#1c1c1f]">
59
70
  {s.agent || 'unknown'}
60
71
  </span>
61
72
  {hasSse && (
@@ -67,12 +78,14 @@ export function AgentCard({ session: s, hasSse, sseCount, onChat }: AgentCardPro
67
78
  single-line padding (px-2 py-1) so the task strip is ~28px
68
79
  rather than ~56px. The full task is still in the title
69
80
  tooltip and on /node detail. */}
70
- {s.task ? (
71
- <div className="text-xs text-gray-400 bg-[#0a0a15] rounded-lg px-2 sm:px-3 py-1 sm:py-2 border border-[#1a1a2a] line-clamp-1 sm:line-clamp-2" title={s.task}>
81
+ {/* #217 M3: the "No active task" italic placeholder is gone — the
82
+ idle status chip in the header already says it, and the line
83
+ cost ~30px on every idle card across a 150-node fleet. The
84
+ task strip renders only when there is a task. */}
85
+ {s.task && (
86
+ <div className="text-xs text-gray-400 bg-[#0e0e10] rounded-lg px-2 sm:px-3 py-1 sm:py-2 border border-[#1c1c1f] line-clamp-1 sm:line-clamp-2" title={s.task}>
72
87
  {s.task}
73
88
  </div>
74
- ) : (
75
- <div className="text-xs text-gray-700 italic">No active task</div>
76
89
  )}
77
90
 
78
91
  {/* Progress bar — hidden below sm so an empty 0% bar doesn't
@@ -96,22 +109,25 @@ export function AgentCard({ session: s, hasSse, sseCount, onChat }: AgentCardPro
96
109
  {/* Footer: time + chat + hover chevron affordance (round 44).
97
110
  The card is a <Link> so it's clickable everywhere, but with no
98
111
  visible cue users may not realise. Chevron appears on hover and
99
- slides right ~2px for a "drill in" hint. */}
100
- <div className="mt-3 flex justify-between items-center text-[10px] text-gray-600">
101
- <span className="truncate" title={s.server || ''}>{s.server || '--'}</span>
102
- <div className="flex items-center gap-2">
103
- {onChat && hasSse && (
104
- <button
105
- onClick={e => { e.preventDefault(); e.stopPropagation(); onChat(s.alias); }}
106
- className="text-cyan-400 hover:text-cyan-300 px-1.5 py-0.5 rounded border border-cyan-500/20 hover:bg-cyan-500/10 transition-colors"
107
- >
108
- Chat
109
- </button>
110
- )}
112
+ slides right ~2px for a "drill in" hint.
113
+ #209 R40: two small mobile tweaks
114
+ (1) the hover chevron is `hidden sm:inline-block` because
115
+ touch devices never trigger the :hover state, and the
116
+ 12 px wide reserved slot was just an empty void on
117
+ mobile — but flex with opacity-0 was still spending
118
+ 12 + 8 (gap) = 20 px on the right side of every card.
119
+ (2) mt-3 → mt-2 sm:mt-3 mirrors the same density pattern
120
+ R28 / R39 brought to the rest of the page. */}
121
+ <div className="mt-2 sm:mt-3 flex justify-between items-center text-[10px] text-gray-600">
122
+ {/* #217 D5 (Vincent: 乱七八糟的元素都可以删掉): the raw server
123
+ hostname (cloud instance IDs like iZrj93…) is noise on a
124
+ phone card — desktop keeps it, /node detail always has it. */}
125
+ <span className="hidden sm:inline truncate" title={s.server || ''}>{s.server || '--'}</span>
126
+ <div className="flex items-center gap-2 ml-auto">
111
127
  <span>{timeAgo(s.updated_at)}</span>
112
128
  <svg
113
129
  aria-hidden
114
- className="w-3 h-3 text-gray-700 opacity-0 -translate-x-1 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200"
130
+ className="hidden sm:inline-block w-3 h-3 text-gray-700 opacity-0 -translate-x-1 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200"
115
131
  fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
116
132
  >
117
133
  <path d="M9 18l6-6-6-6" />
@@ -20,7 +20,13 @@ export function AppShell({ children }: { children: React.ReactNode }) {
20
20
  return (
21
21
  <div className="flex min-h-[100dvh] max-w-full overflow-x-hidden">
22
22
  <Sidebar />
23
- <main className="flex-1 min-w-0 max-w-full overflow-x-hidden flex flex-col">
23
+ {/* R25 of #190: pair the existing bottom safe-area padding with a
24
+ top one so the HealthBanner clears the iOS status bar when
25
+ launched as an installed PWA (statusBarStyle: black-translucent
26
+ + viewportFit: cover, see app/layout.tsx). On non-PWA contexts
27
+ env(safe-area-inset-top) is 0, so this is a no-op for browser
28
+ tab visits. */}
29
+ <main className="flex-1 min-w-0 max-w-full overflow-x-hidden flex flex-col pt-[env(safe-area-inset-top)]">
24
30
  <HealthBanner />
25
31
  <div className="flex-1 min-w-0 max-w-full overflow-x-hidden pb-[calc(5rem+env(safe-area-inset-bottom))] lg:pb-0">{children}</div>
26
32
  </main>
@@ -191,7 +191,7 @@ export function ChatPopover({ alias, onClose }: ChatPopoverProps) {
191
191
  // Chromium retargets the click to the header — the button never fires.
192
192
  onPointerDown={(e) => e.stopPropagation()}
193
193
  aria-label="Close chat"
194
- className="text-[var(--fg-muted)] hover:text-[var(--fg)] p-1.5 rounded-lg hover:bg-[var(--bg-elevated)] shrink-0"
194
+ className="inline-flex min-h-[44px] min-w-[44px] items-center justify-center text-[var(--fg-muted)] hover:text-[var(--fg)] rounded-lg hover:bg-[var(--bg-elevated)] shrink-0"
195
195
  >
196
196
  <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
197
197
  <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
@@ -0,0 +1,127 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * CollapsibleSearch
5
+ * ──────────────────
6
+ * #209 R34 — Reusable WeChat-style search-toggle pattern.
7
+ *
8
+ * Two pieces of UI from one component:
9
+ * 1. A magnifier button (`w-9 h-9 rounded-full`) that lives in a page
10
+ * header — render it via `Button`.
11
+ * 2. A collapsible search row that reveals below the header on open —
12
+ * render it via `Row`.
13
+ *
14
+ * Used together they replicate WeChat's chat-list search affordance:
15
+ * the search field is hidden by default behind a small circle at the
16
+ * top-right of the page header. Tapping it slides the input open,
17
+ * autofocuses, and lets Escape clear + close. A cyan dot appears in
18
+ * the corner of the icon when there is an active search term but the
19
+ * row is collapsed, so the user always knows a filter is in effect.
20
+ *
21
+ * Owner of the search string is the parent (this component is
22
+ * uncontrolled w.r.t. value — pure UI). That keeps the filter logic
23
+ * with the page so it's easy to plumb additional behaviour like
24
+ * `from:alias` shortcuts or highlight rendering.
25
+ *
26
+ * Shipped as a single component with two named children so callers
27
+ * can place `<Button>` inside their existing header flex row and
28
+ * `<Row>` just after, without restructuring around a tighter API.
29
+ *
30
+ * First adopters: /nodes (R32), /messages (R33). R34 ships
31
+ * /nodes + /messages migrated to use this component; future pages
32
+ * can add search by importing it instead of copy-pasting the JSX.
33
+ */
34
+
35
+ import { useState, useCallback } from 'react';
36
+
37
+ interface CollapsibleSearchAPI {
38
+ /** Round magnifier button. Place in your page header (e.g. right edge). */
39
+ Button: () => React.JSX.Element;
40
+ /** Collapsible search input row. Place immediately after the header. */
41
+ Row: () => React.JSX.Element | null;
42
+ /** Current search value — pass to your filter logic. */
43
+ value: string;
44
+ /** Programmatically clear + close (useful for "no results" reset link). */
45
+ reset: () => void;
46
+ }
47
+
48
+ interface UseCollapsibleSearchProps {
49
+ /** Controlled value + setter from the parent — owns the search string. */
50
+ value: string;
51
+ onChange: (next: string) => void;
52
+ /** Input placeholder. */
53
+ placeholder?: string;
54
+ /** Accessible label for the button (also drives `title=`). */
55
+ label?: string;
56
+ /** Hide everything when false (e.g. when there is nothing to search). */
57
+ enabled?: boolean;
58
+ }
59
+
60
+ export function useCollapsibleSearch({
61
+ value,
62
+ onChange,
63
+ placeholder = 'Search…',
64
+ label = 'Search',
65
+ enabled = true,
66
+ }: UseCollapsibleSearchProps): CollapsibleSearchAPI {
67
+ const [open, setOpen] = useState(false);
68
+
69
+ const reset = useCallback(() => {
70
+ onChange('');
71
+ setOpen(false);
72
+ }, [onChange]);
73
+
74
+ const Button = useCallback(() => {
75
+ if (!enabled) return <></>;
76
+ const active = open || value;
77
+ return (
78
+ <button
79
+ type="button"
80
+ onClick={() => setOpen(v => !v)}
81
+ aria-label={open ? `Close ${label.toLowerCase()}` : `Open ${label.toLowerCase()}`}
82
+ aria-pressed={open}
83
+ title={open ? `Close ${label.toLowerCase()}` : label}
84
+ className={`relative shrink-0 inline-flex items-center justify-center rounded-full border w-9 h-9 transition-colors ${
85
+ active
86
+ ? 'border-cyan-500/40 bg-cyan-500/10 text-cyan-300'
87
+ : 'border-[#26262b] bg-[#161618] text-gray-400 hover:text-gray-200 hover:border-[#3a3a41]'
88
+ }`}
89
+ >
90
+ <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
91
+ <path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
92
+ </svg>
93
+ {value && !open && (
94
+ <span aria-hidden className="absolute -top-0.5 -right-0.5 w-2 h-2 rounded-full bg-cyan-400 ring-2 ring-[#0b0b0d]" />
95
+ )}
96
+ </button>
97
+ );
98
+ }, [open, value, label, enabled]);
99
+
100
+ const Row = useCallback(() => {
101
+ if (!enabled) return null;
102
+ if (!open && !value) return null;
103
+ return (
104
+ <div className="mb-3 sm:mb-4 flex items-center gap-2">
105
+ <input
106
+ type="text"
107
+ value={value}
108
+ onChange={e => onChange(e.target.value)}
109
+ onKeyDown={e => { if (e.key === 'Escape') reset(); }}
110
+ placeholder={placeholder}
111
+ autoFocus={open}
112
+ className="flex-1 bg-[#161618] border border-[#26262b] rounded-lg px-3 py-2 text-base sm:text-sm text-white placeholder-gray-600 focus:border-cyan-500/40 focus:outline-none"
113
+ />
114
+ <button
115
+ type="button"
116
+ onClick={reset}
117
+ className="shrink-0 text-xs text-gray-500 hover:text-gray-300 px-2 py-2"
118
+ aria-label="Clear and close search"
119
+ >
120
+ Cancel
121
+ </button>
122
+ </div>
123
+ );
124
+ }, [open, value, onChange, placeholder, reset, enabled]);
125
+
126
+ return { Button, Row, value, reset };
127
+ }
@@ -26,9 +26,9 @@ export function CommandCenter({ tabs, activeTab, onOpenTab, onCloseTab, onSetAct
26
26
  <div className="fixed inset-0 bg-black/30 z-40 lg:hidden" onClick={onClose} />
27
27
 
28
28
  {/* Panel */}
29
- <div className="fixed top-0 right-0 h-[100dvh] w-full lg:w-[500px] bg-[#0a0a1a] border-l border-[#2a2a4a] z-50 flex flex-col shadow-2xl shadow-black/60 animate-slide-in">
29
+ <div className="fixed top-0 right-0 h-[100dvh] w-full lg:w-[500px] bg-[#0b0b0d] border-l border-[#26262b] z-50 flex flex-col shadow-2xl shadow-black/60 animate-slide-in">
30
30
  {/* Tab bar */}
31
- <div className="flex items-center border-b border-[#2a2a4a] bg-[#0d0d1a] overflow-x-auto">
31
+ <div className="flex items-center border-b border-[#26262b] bg-[#111113] overflow-x-auto">
32
32
  <div className="flex-1 flex min-w-0">
33
33
  {tabs.map(alias => (
34
34
  <button
@@ -42,16 +42,26 @@ export function CommandCenter({ tabs, activeTab, onOpenTab, onCloseTab, onSetAct
42
42
  >
43
43
  <div className={`w-2 h-2 rounded-full ${activeTab === alias ? 'bg-cyan-400' : 'bg-gray-600'}`} />
44
44
  <span className="max-w-[80px] truncate">{alias}</span>
45
+ {/* R21 of #190 mobile a11y: nested close had no aria-label
46
+ (screen-readers spoke just "button") and an ~8 px
47
+ tap target (p-0.5 + text "×"). Both fixed; nested
48
+ <button> inside <button> remains invalid HTML but
49
+ is browser-tolerated and refactoring to role="tab"
50
+ is out of scope for this round. */}
45
51
  <button
46
52
  onClick={e => { e.stopPropagation(); onCloseTab(alias); }}
47
- className="ml-1 text-gray-600 hover:text-gray-300 p-0.5"
53
+ aria-label={`Close ${alias} chat tab`}
54
+ className="ml-1 inline-flex h-7 w-7 items-center justify-center text-gray-600 hover:text-gray-300 rounded-md hover:bg-white/5"
48
55
  >
49
56
  ×
50
57
  </button>
51
58
  </button>
52
59
  ))}
53
60
  </div>
54
- <button onClick={onClose} className="text-gray-500 hover:text-white px-3 py-2.5 shrink-0 border-l border-[#2a2a4a]">
61
+ {/* R21 mobile a11y: outer command-center close was SVG-only
62
+ and screen-reader silent; also bumped to 44 x 44 hit zone
63
+ from 32 x ~36. */}
64
+ <button onClick={onClose} aria-label="Close command center" className="inline-flex min-h-[44px] min-w-[44px] items-center justify-center text-gray-500 hover:text-white shrink-0 border-l border-[#26262b]">
55
65
  <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
56
66
  <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
57
67
  </svg>
@@ -426,10 +426,10 @@ export function CommandPalette() {
426
426
  >
427
427
  <div className="absolute inset-0 bg-black/50 backdrop-blur-sm" aria-hidden />
428
428
  <div
429
- className="relative w-full max-w-xl rounded-xl border border-[#2a2a4a] bg-[#0d0d1a] shadow-2xl shadow-black/40 overflow-hidden"
429
+ className="relative w-full max-w-xl rounded-xl border border-[#26262b] bg-[#111113] shadow-2xl shadow-black/40 overflow-hidden"
430
430
  onClick={e => e.stopPropagation()}
431
431
  >
432
- <div className="flex items-center gap-2 border-b border-[#2a2a4a] px-3 py-2.5">
432
+ <div className="flex items-center gap-2 border-b border-[#26262b] px-3 py-2.5">
433
433
  <svg width="14" height="14" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="1.5" className="text-gray-500 shrink-0">
434
434
  <path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
435
435
  </svg>
@@ -438,9 +438,9 @@ export function CommandPalette() {
438
438
  value={query}
439
439
  onChange={e => { setQuery(e.target.value); setSelected(0); }}
440
440
  placeholder="Type a command or search…"
441
- className="flex-1 bg-transparent text-sm text-gray-200 placeholder-gray-600 focus:outline-none"
441
+ className="flex-1 bg-transparent text-base sm:text-sm text-gray-200 placeholder-gray-600 focus:outline-none"
442
442
  />
443
- <kbd className="text-[10px] text-gray-600 border border-[#2a2a4a] rounded px-1.5 py-0.5 font-mono">esc</kbd>
443
+ <kbd className="text-[10px] text-gray-600 border border-[#26262b] rounded px-1.5 py-0.5 font-mono">esc</kbd>
444
444
  </div>
445
445
 
446
446
  <div className="max-h-[60vh] overflow-y-auto py-2">
@@ -461,7 +461,7 @@ export function CommandPalette() {
461
461
  onMouseEnter={() => setSelected(idx)}
462
462
  onClick={() => { pushRecent(c.id); c.perform(router); setOpen(false); }}
463
463
  className={`anet-cmdk-row w-full flex items-center gap-3 px-3 py-2 text-left text-sm ${
464
- isActive ? 'bg-cyan-500/10 text-cyan-300' : 'text-gray-300 hover:bg-[#1a1a2a]/50'
464
+ isActive ? 'bg-cyan-500/10 text-cyan-300' : 'text-gray-300 hover:bg-[#1c1c1f]/50'
465
465
  }`}
466
466
  >
467
467
  <span className={isActive ? 'text-cyan-400' : 'text-gray-500'}>{c.icon}</span>
@@ -484,7 +484,7 @@ export function CommandPalette() {
484
484
  )}
485
485
  </div>
486
486
 
487
- <div className="border-t border-[#2a2a4a] px-3 py-1.5 flex items-center justify-between text-[10px] text-gray-600">
487
+ <div className="border-t border-[#26262b] px-3 py-1.5 flex items-center justify-between text-[10px] text-gray-600">
488
488
  <span className="flex items-center gap-3">
489
489
  <span><kbd className="font-mono">↑↓</kbd> navigate</span>
490
490
  <span><kbd className="font-mono">↵</kbd> select</span>
@@ -73,14 +73,17 @@ export function DispatchPanel({ sessions, onClose }: DispatchPanelProps) {
73
73
  return (
74
74
  <>
75
75
  <div className="fixed inset-0 bg-black/50 z-40 anet-fade-in" onClick={onClose} />
76
- <div className="fixed inset-x-3 inset-y-3 max-h-[calc(100dvh-1.5rem)] lg:inset-x-[15%] lg:inset-y-[5%] lg:max-h-none bg-[#0a0a1a] border border-[#2a2a4a] rounded-2xl z-50 flex flex-col shadow-2xl shadow-black/70 overflow-hidden anet-fade-in">
76
+ <div className="fixed inset-x-3 inset-y-3 max-h-[calc(100dvh-1.5rem)] lg:inset-x-[15%] lg:inset-y-[5%] lg:max-h-none bg-[#0b0b0d] border border-[#26262b] rounded-2xl z-50 flex flex-col shadow-2xl shadow-black/70 overflow-hidden anet-fade-in">
77
77
  {/* Header */}
78
- <div className="flex items-center justify-between px-6 py-4 border-b border-[#2a2a4a] bg-[#0d0d1a]">
78
+ <div className="flex items-center justify-between px-6 py-4 border-b border-[#26262b] bg-[#111113]">
79
79
  <div>
80
80
  <h2 className="text-lg font-bold text-white">Dispatch Task</h2>
81
81
  <p className="text-xs text-gray-500 mt-0.5">Send a task to one or more agents</p>
82
82
  </div>
83
- <button onClick={onClose} className="text-gray-500 hover:text-white p-1.5 rounded-lg hover:bg-[#1a1a2a]">
83
+ {/* R22 of #190: same SVG-only-no-aria-label pattern that R16
84
+ fixed on TaskChatPanel/TaskDrawer/ChatPopover; DispatchPanel
85
+ missed that pass. ~32 px hit zone + silent on screen-reader. */}
86
+ <button onClick={onClose} aria-label="Close dispatch panel" className="inline-flex min-h-[44px] min-w-[44px] items-center justify-center text-gray-500 hover:text-white rounded-lg hover:bg-[#1c1c1f]">
84
87
  <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
85
88
  <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
86
89
  </svg>
@@ -89,12 +92,12 @@ export function DispatchPanel({ sessions, onClose }: DispatchPanelProps) {
89
92
 
90
93
  <div className="flex-1 flex flex-col lg:flex-row overflow-hidden">
91
94
  {/* Left: Node selection */}
92
- <div className="lg:w-[280px] border-b lg:border-b-0 lg:border-r border-[#2a2a4a] flex flex-col">
93
- <div className="px-4 py-3 border-b border-[#2a2a4a]">
95
+ <div className="lg:w-[280px] border-b lg:border-b-0 lg:border-r border-[#26262b] flex flex-col">
96
+ <div className="px-4 py-3 border-b border-[#26262b]">
94
97
  <input
95
98
  type="text" value={filter} onChange={e => setFilter(e.target.value)}
96
99
  placeholder="Filter agents..."
97
- className="w-full bg-[#111128] border border-[#2a2a4a] rounded-lg px-3 py-2 text-xs text-white placeholder-gray-600 focus:border-cyan-500/40 focus:outline-none"
100
+ className="w-full bg-[#161618] border border-[#26262b] rounded-lg px-3 py-2 text-base sm:text-xs text-white placeholder-gray-600 focus:border-cyan-500/40 focus:outline-none"
98
101
  />
99
102
  <div className="flex items-center justify-between mt-2">
100
103
  <button onClick={selectAll} className="text-[10px] text-cyan-400 hover:text-cyan-300">
@@ -107,7 +110,7 @@ export function DispatchPanel({ sessions, onClose }: DispatchPanelProps) {
107
110
  {filtered.map(s => (
108
111
  <button key={s.alias} onClick={() => toggleNode(s.alias)}
109
112
  className={`w-full flex items-center gap-2 px-3 py-2 rounded-lg text-xs text-left transition-colors ${
110
- selected.has(s.alias) ? 'bg-cyan-500/10 text-cyan-300 border border-cyan-500/20' : 'text-gray-400 hover:bg-[#1a1a2a]'
113
+ selected.has(s.alias) ? 'bg-cyan-500/10 text-cyan-300 border border-cyan-500/20' : 'text-gray-400 hover:bg-[#1c1c1f]'
111
114
  }`}>
112
115
  <AliasAvatar alias={s.alias} size={16} />
113
116
  <div className={`w-1.5 h-1.5 rounded-full shrink-0 ${s.status === 'working' ? 'bg-green-400' : s.status === 'idle' ? 'bg-cyan-400' : 'bg-gray-500'}`} />
@@ -126,12 +129,12 @@ export function DispatchPanel({ sessions, onClose }: DispatchPanelProps) {
126
129
  <textarea
127
130
  value={prompt} onChange={e => setPrompt(e.target.value)}
128
131
  placeholder="Enter the task you want to dispatch..."
129
- className="flex-1 min-h-[120px] bg-[#111128] border border-[#2a2a4a] rounded-xl px-4 py-3 text-sm text-white placeholder-gray-600 focus:border-cyan-500/40 focus:outline-none resize-none"
132
+ className="flex-1 min-h-[120px] bg-[#161618] border border-[#26262b] rounded-xl px-4 py-3 text-base sm:text-sm text-white placeholder-gray-600 focus:border-cyan-500/40 focus:outline-none resize-none"
130
133
  />
131
134
 
132
135
  <div className="flex items-center gap-3 mt-4">
133
136
  <select value={priority} onChange={e => setPriority(e.target.value)}
134
- className="bg-[#111128] border border-[#2a2a4a] rounded-lg px-3 py-2 text-xs text-white focus:outline-none">
137
+ className="bg-[#161618] border border-[#26262b] rounded-lg px-3 py-2 text-base sm:text-xs text-white focus:outline-none">
135
138
  <option value="normal">Normal priority</option>
136
139
  <option value="high">High priority</option>
137
140
  <option value="low">Low priority</option>
@@ -155,7 +158,7 @@ export function DispatchPanel({ sessions, onClose }: DispatchPanelProps) {
155
158
 
156
159
  {/* Results */}
157
160
  {results.length > 0 && (
158
- <div className="px-6 py-3 border-t border-[#2a2a4a] bg-[#0d0d1a]">
161
+ <div className="px-6 py-3 border-t border-[#26262b] bg-[#111113]">
159
162
  <div className="text-xs text-gray-500 mb-2">
160
163
  {successCount}/{results.length} dispatched successfully
161
164
  </div>