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

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 +2 -2
  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]__05kf31s._.js +3 -0
  195. package/.next/server/chunks/ssr/[root-of-the-server]__05kf31s._.js.map +1 -0
  196. package/.next/server/chunks/ssr/[root-of-the-server]__096ytyk._.js +3 -0
  197. package/.next/server/chunks/ssr/[root-of-the-server]__096ytyk._.js.map +1 -0
  198. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js +1 -1
  199. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js.map +1 -1
  200. package/.next/server/chunks/ssr/[root-of-the-server]__0nw1f-j._.js +3 -0
  201. package/.next/server/chunks/ssr/[root-of-the-server]__0nw1f-j._.js.map +1 -0
  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_012oyw5._.js +3 -0
  207. package/.next/server/chunks/ssr/agent-network-dashboard_app_012oyw5._.js.map +1 -0
  208. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  209. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  210. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  211. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  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.mh8n0itrii5.js +1 -0
  240. package/.next/static/chunks/049vx3qljs1tt.js +1 -0
  241. package/.next/static/chunks/04uju~n5s9g7..css +1 -0
  242. package/.next/static/chunks/066jf0nk75nic.css +2 -0
  243. package/.next/static/chunks/06vp7429lrzl7.js +1 -0
  244. package/.next/static/chunks/{0jp~cs9-zkmqa.js → 07h2umbpc5lqy.js} +2 -2
  245. package/.next/static/chunks/0_bn~gcrgo.4n.js +1 -0
  246. package/.next/static/chunks/0_cm~9rtqil56.js +1 -0
  247. package/.next/static/chunks/0cp0cz3mxejl~.js +4 -0
  248. package/.next/static/chunks/0g1o7c8fbafn7.js +1 -0
  249. package/.next/static/chunks/0g4d-_fi-d9hg.js +1 -0
  250. package/.next/static/chunks/0iv.0p9_5b36e.js +1 -0
  251. package/.next/static/chunks/0tor7h4q5_rz..js +1 -0
  252. package/.next/static/chunks/0vun~4ljcrj3p.js +7 -0
  253. package/.next/static/chunks/0wz0122ym_gr3.js +1 -0
  254. package/.next/static/chunks/0y5gol09tlu63.js +1 -0
  255. package/.next/static/chunks/114o05335p68v.js +1 -0
  256. package/.next/static/chunks/14141xj5.1t3t.js +1 -0
  257. package/.next/static/chunks/149a4l50_3vw-.js +7 -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 +105 -12
  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 +4 -6
  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 +108 -50
  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 → nPhsSMLrgn8H0RsXNP4ku}/_buildManifest.js +0 -0
  375. /package/.next/static/{vv4Gz5yVhOzydMI2UlT1l → nPhsSMLrgn8H0RsXNP4ku}/_clientMiddlewareManifest.js +0 -0
  376. /package/.next/static/{vv4Gz5yVhOzydMI2UlT1l → nPhsSMLrgn8H0RsXNP4ku}/_ssgManifest.js +0 -0
@@ -5,19 +5,19 @@ import { usePathname } from 'next/navigation';
5
5
  import { useState } from 'react';
6
6
  import useSWR from 'swr';
7
7
  import { useNetworkId } from '../lib/network-context';
8
- import { ThemeSwitcher } from './ThemeSwitcher';
9
8
 
10
9
  const networkFetcher = (url: string) => fetch(url).then(r => r.ok ? r.json() : { networks: [] });
11
10
 
11
+ // Cleanup (issue #4): sidebar collapsed to the 6 core destinations. The
12
+ // low-frequency / unverified entries (Messages, Networks, Audit Log,
13
+ // Server Logs) were removed from primary nav — those pages still exist
14
+ // and stay reachable via direct URL and the command palette (⌘K); they
15
+ // were just cluttering the main rail. Restore here if usage warrants.
12
16
  const NAV_ITEMS = [
13
17
  { href: '/', label: 'Overview', icon: 'M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6' },
14
18
  { href: '/tasks', label: 'Tasks', icon: 'M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4' },
15
19
  { href: '/nodes', label: 'Nodes', icon: 'M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01' },
16
20
  { href: '/servers', label: 'Servers', icon: 'M4 6.5A2.5 2.5 0 016.5 4h11A2.5 2.5 0 0120 6.5v1A2.5 2.5 0 0117.5 10h-11A2.5 2.5 0 014 7.5v-1zM4 16.5A2.5 2.5 0 016.5 14h11a2.5 2.5 0 012.5 2.5v1a2.5 2.5 0 01-2.5 2.5h-11A2.5 2.5 0 014 17.5v-1zM7 7h.01M7 17h.01' },
17
- { href: '/messages', label: 'Messages', icon: 'M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z' },
18
- { href: '/settings/networks', label: 'Networks', icon: 'M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9' },
19
- { href: '/logs', label: 'Audit Log', icon: 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z' },
20
- { href: '/server-logs', label: 'Server Logs', icon: 'M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z' },
21
21
  { href: '/admin', label: 'Admin', icon: 'M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z' },
22
22
  { href: '/settings', label: 'Settings', icon: 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z M15 12a3 3 0 11-6 0 3 3 0 016 0z' },
23
23
  ];
@@ -74,10 +74,10 @@ export function Sidebar() {
74
74
  prefetch={false}
75
75
  onClick={() => setMobileOpen(false)}
76
76
  title={collapsed ? item.label : undefined}
77
- className={`relative flex items-center gap-3 px-3 py-3 rounded-lg text-sm transition-colors active:bg-[#1a1a3a] ${
77
+ className={`relative flex items-center gap-3 px-3 py-3 rounded-lg text-sm transition-colors active:bg-[#232327] ${
78
78
  isActive(item.href)
79
79
  ? 'bg-cyan-500/10 text-cyan-300 border border-cyan-500/20 anet-nav-active'
80
- : 'text-gray-400 hover:text-gray-200 hover:bg-[#1a1a2a]'
80
+ : 'text-gray-400 hover:text-gray-200 hover:bg-[#1c1c1f]'
81
81
  } ${collapsed ? 'justify-center px-0' : ''}`}
82
82
  >
83
83
  <svg className="w-5 h-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
@@ -91,10 +91,12 @@ export function Sidebar() {
91
91
 
92
92
  return (
93
93
  <>
94
- {/* Mobile hamburger */}
94
+ {/* Mobile hamburger — R13 of #190: was p-2.5 = ~40px tap target,
95
+ just below the iOS 44px guideline. Bump padding and add an
96
+ explicit min-w/min-h so it can never be miss-tapped. */}
95
97
  <button
96
98
  onClick={() => setMobileOpen(!mobileOpen)}
97
- className="fixed top-4 left-3 z-50 lg:hidden bg-[#111128] border border-[#2a2a4a] rounded-lg p-2.5 text-gray-400 hover:text-white active:bg-[#1a1a3a]"
99
+ className="fixed top-3 left-3 z-50 lg:hidden bg-[#161618] border border-[#26262b] rounded-lg p-3 min-h-[44px] min-w-[44px] inline-flex items-center justify-center text-gray-400 hover:text-white active:bg-[#232327]"
98
100
  aria-label="Toggle menu"
99
101
  >
100
102
  <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
@@ -113,15 +115,15 @@ export function Sidebar() {
113
115
  />
114
116
  )}
115
117
 
116
- {/* Sidebar — `bg-[#0d0d1a]` resolves to var(--bg) in dark themes,
118
+ {/* Sidebar — `bg-[#111113]` resolves to var(--bg) in dark themes,
117
119
  but in light/mint we want a distinct surface, so we layer
118
- `lg:bg-white` / `lg:dark:bg-[#0d0d1a]` via the theme attribute.
120
+ `lg:bg-white` / `lg:dark:bg-[#111113]` via the theme attribute.
119
121
  The CSS shim in globals.css upgrades sidebar bg to bg-secondary
120
122
  in light themes so the sidebar reads as its own card.
121
123
  Round 47: explicit ease-out curve + slight shadow so the drawer
122
124
  edge "leaves a trail" as it slides in. */}
123
125
  <aside data-anet-sidebar="true" className={`
124
- fixed top-0 left-0 h-full z-40 bg-[#0d0d1a] border-r border-[#2a2a4a]
126
+ fixed top-0 left-0 h-full z-40 bg-[#111113] border-r border-[#26262b]
125
127
  transition-transform duration-200 ease-out
126
128
  ${collapsed ? 'w-16' : 'w-52'}
127
129
  ${mobileOpen ? 'translate-x-0 shadow-2xl shadow-black/40 lg:shadow-none' : '-translate-x-full'}
@@ -131,11 +133,11 @@ export function Sidebar() {
131
133
  with an inline live "online" pulse so every page surfaces
132
134
  fleet health without leaving for /nodes. */}
133
135
  <SidebarBrand collapsed={collapsed} />
134
- <div className={`border-b border-[#2a2a4a]`} />
136
+ <div className={`border-b border-[#26262b]`} />
135
137
 
136
138
  {/* Network list */}
137
139
  {!collapsed && networks.length > 0 && (
138
- <div className="px-2 py-3 border-b border-[#2a2a4a]">
140
+ <div className="px-2 py-3 border-b border-[#26262b]">
139
141
  <div className="px-3 text-[10px] text-gray-600 uppercase mb-2">Networks</div>
140
142
  <div className="space-y-0.5 max-h-32 overflow-y-auto">
141
143
  {networks.map((n: SidebarNetwork) => (
@@ -143,10 +145,10 @@ export function Sidebar() {
143
145
  key={n.network_id}
144
146
  onClick={() => { setNetworkId(n.network_id); setMobileOpen(false); }}
145
147
  title={n.network_id}
146
- className={`w-full flex items-center gap-2 px-3 py-1.5 rounded-md text-xs transition-colors text-left ${
148
+ className={`w-full flex items-center gap-2 px-3 py-2.5 lg:py-1.5 rounded-md text-xs transition-colors text-left ${
147
149
  networkId === n.network_id
148
150
  ? 'bg-cyan-500/10 text-cyan-300'
149
- : 'text-gray-500 hover:text-gray-300 hover:bg-[#1a1a2a]'
151
+ : 'text-gray-500 hover:text-gray-300 hover:bg-[#1c1c1f]'
150
152
  }`}
151
153
  >
152
154
  <span>{ROLE_ICON[n.role || 'member'] || '👤'}</span>
@@ -164,21 +166,27 @@ export function Sidebar() {
164
166
  </div>
165
167
  )}
166
168
 
167
- <div className="pb-20">
169
+ {/* #209 R26 (Vincent msg 540 screenshot — "设置页面没展示全"): the
170
+ absolute-bottom footer below stacks 3 rows (Quick search /
171
+ Sign out / collapse) ≈ 92-100px tall, but this spacer was
172
+ pb-20 (80px), so the last nav entry (Settings on /settings)
173
+ was being eaten by the footer overlay. Bump to pb-28 (112px)
174
+ to clear the actual footer height. */}
175
+ <div className="pb-28">
168
176
  {nav}
169
177
  </div>
170
178
 
171
179
  {/* Sign out + collapse — round 27: collapsed-state gets icon-only
172
180
  variants so users still have Sign out / Quick search access at
173
181
  56px width, plus title= tooltips. */}
174
- <div className="absolute bottom-0 left-0 right-0 border-t border-[#2a2a4a] bg-[#0d0d1a]">
182
+ <div className="absolute bottom-0 left-0 right-0 border-t border-[#26262b] bg-[#111113]">
175
183
  <button
176
184
  onClick={() => {
177
185
  window.dispatchEvent(new KeyboardEvent('keydown', { key: 'k', metaKey: true, ctrlKey: true }));
178
186
  }}
179
187
  title={collapsed ? 'Quick search (⌘K)' : undefined}
180
- className={`w-full flex items-center text-[11px] text-gray-600 hover:text-gray-400 hover:bg-[#1a1a2a] transition-colors ${
181
- collapsed ? 'justify-center px-0 py-2.5' : 'justify-between gap-2 px-5 py-2'
188
+ className={`w-full flex items-center text-[11px] text-gray-600 hover:text-gray-400 hover:bg-[#1c1c1f] transition-colors ${
189
+ collapsed ? 'justify-center px-0 py-2.5' : 'justify-between gap-2 px-5 py-3 lg:py-2'
182
190
  }`}
183
191
  aria-label="Open command palette"
184
192
  >
@@ -206,8 +214,7 @@ export function Sidebar() {
206
214
  </svg>
207
215
  {!collapsed && 'Sign out'}
208
216
  </button>
209
- <div className={`flex items-center gap-2 ${collapsed ? 'flex-col px-0 py-2' : 'px-3 py-2 justify-between'}`}>
210
- <ThemeSwitcher compact={collapsed} />
217
+ <div className={`flex items-center ${collapsed ? 'flex-col px-0 py-2' : 'px-3 py-2 justify-end'}`}>
211
218
  <button
212
219
  onClick={() => setCollapsed(!collapsed)}
213
220
  title={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
@@ -255,7 +262,7 @@ function SidebarBrand({ collapsed }: { collapsed: boolean }) {
255
262
  return (
256
263
  <Link
257
264
  href="/"
258
- className="block px-4 py-4 flex items-center gap-3 hover:bg-[#11112a]/40 transition-colors rounded-r-xl"
265
+ className="block px-4 py-4 flex items-center gap-3 hover:bg-[#161618]/40 transition-colors rounded-r-xl"
259
266
  aria-label="Agent Network — home"
260
267
  >
261
268
  <BrandMark size={32} />
@@ -4,29 +4,35 @@ interface StatsBarProps {
4
4
  online: number;
5
5
  working: number;
6
6
  total: number;
7
- version: string;
8
- uptime: string;
9
7
  }
10
8
 
11
- export function StatsBar({ online, working, total, version, uptime }: StatsBarProps) {
9
+ export function StatsBar({ online, working, total }: StatsBarProps) {
12
10
  const onlinePercent = total > 0 ? Math.round((online / total) * 100) : 0;
13
11
  const fleetEmpty = total === 0;
14
12
 
13
+ // #209 R29 (mobile vertical rhythm — goal "大幅提升移动端体验"):
14
+ // populated branch previously left mb-8 (32 px) below the 4-card grid,
15
+ // the single biggest gap on the Overview page. Drop to mb-4 (16 px)
16
+ // on phones, restore mb-8 from sm: up. Pairs with R28's section-gap
17
+ // tighten — together they reclaim ~48 px of pure scroll waste before
18
+ // the agent grid. The empty-fleet branch already used mb-4 since R72,
19
+ // so this only touches the populated case.
15
20
  return (
16
- <div className={fleetEmpty ? 'mb-4' : 'mb-8'}>
17
- {/* Title row */}
18
- <div className="flex flex-wrap items-center gap-3 mb-4">
19
- <h1 className="text-2xl font-bold text-white tracking-tight">Agent Network</h1>
20
- <span className="text-xs text-gray-500">
21
- CommHub {version} &middot; {uptime}
22
- </span>
21
+ <div className={fleetEmpty ? 'mb-4' : 'mb-4 sm:mb-8'}>
22
+ {/* Title row. #217 S3 dropped the version/uptime subtitle (lives in
23
+ Settings CommHub Connection). S9 (Vincent tg 613 "没居中"):
24
+ WeChat-style centered title on phones — centering also clears
25
+ the fixed hamburger symmetrically, replacing the old ml-10
26
+ indent that read as misalignment. Left-aligned from lg: up. */}
27
+ <div className="mb-4">
28
+ <h1 className="text-2xl font-bold text-white tracking-tight text-center lg:text-left">Agent Network</h1>
23
29
  </div>
24
30
 
25
31
  {fleetEmpty ? (
26
32
  /* Round 72: thin status strip replaces the 4-card grid when fleet
27
33
  is empty. Saves ~280px on mobile (CTA y=650 → ~370) and keeps
28
34
  the same data visible in a single inline row. */
29
- <div className="anet-stat-strip flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-gray-500 border-t border-b border-[#2a2a4a] py-2">
35
+ <div className="anet-stat-strip flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-gray-500 border-t border-b border-[#26262b] py-2">
30
36
  <span className="inline-flex items-center gap-1.5">
31
37
  <span aria-hidden className="inline-block w-1.5 h-1.5 rounded-full bg-gray-600" />
32
38
  <span className="text-gray-300 tabular-nums">0</span> online
@@ -43,63 +49,84 @@ export function StatsBar({ online, working, total, version, uptime }: StatsBarPr
43
49
  </span>
44
50
  </div>
45
51
  ) : (
46
- /* Populated state — full 4-card grid as before */
47
- <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
52
+ <>
53
+ {/* #217 M1 (Vincent: "再简洁点你自己理解一下"): on phones the
54
+ populated state reuses the round-72 thin strip instead of
55
+ the card grid — one quiet line of numbers, ~70px reclaimed.
56
+ Cards return from sm: up where they have room to breathe. */}
57
+ <div className="sm:hidden anet-stat-strip flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-gray-500 border-t border-b border-[#26262b] py-2">
58
+ <span className="inline-flex items-center gap-1.5">
59
+ <span aria-hidden className="inline-block w-1.5 h-1.5 rounded-full bg-green-400" />
60
+ <span className="text-gray-200 tabular-nums">{online}</span> online
61
+ </span>
62
+ {working > 0 && (
63
+ <>
64
+ <span className="text-gray-700">·</span>
65
+ <span className="inline-flex items-center gap-1.5">
66
+ <span aria-hidden className="inline-block w-1.5 h-1.5 rounded-full bg-green-400 anet-brand-pulse" />
67
+ <span className="text-gray-200 tabular-nums">{working}</span> working
68
+ </span>
69
+ </>
70
+ )}
71
+ <span className="text-gray-700">·</span>
72
+ <span className="inline-flex items-center gap-1.5">
73
+ <span aria-hidden className="inline-block w-1.5 h-1.5 rounded-full bg-gray-600" />
74
+ <span className="text-gray-200 tabular-nums">{total - online}</span> offline
75
+ </span>
76
+ <span className="text-gray-700">·</span>
77
+ <span className="inline-flex items-center gap-1.5">
78
+ <span className="text-gray-200 tabular-nums">{total}</span> total
79
+ </span>
80
+ </div>
81
+ {/* Populated state, sm: up. #217 S2 (less is more): the Working
82
+ card only earns its grid cell when something is actually
83
+ working. */}
84
+ <div className={`hidden sm:grid gap-3 ${working > 0 ? 'sm:grid-cols-4' : 'sm:grid-cols-3'}`}>
48
85
  <StatCard
49
86
  value={online}
50
87
  label="Online"
51
88
  sub={`${onlinePercent}% of fleet`}
52
89
  color="text-green-400"
53
- accent="from-green-500/20 to-green-500/0"
54
- border="border-green-500/15"
55
- />
56
- <StatCard
57
- value={working}
58
- label="Working"
59
- sub={online > 0 ? `${Math.round((working / online) * 100)}% utilization` : '--'}
60
- color="text-cyan-400"
61
- accent="from-cyan-500/20 to-cyan-500/0"
62
- border="border-cyan-500/15"
63
90
  />
91
+ {working > 0 && (
92
+ <StatCard
93
+ value={working}
94
+ label="Working"
95
+ sub={online > 0 ? `${Math.round((working / online) * 100)}% utilization` : '--'}
96
+ color="text-cyan-400"
97
+ />
98
+ )}
64
99
  <StatCard
65
100
  value={total - online}
66
101
  label="Offline"
67
102
  sub={total - online === 0 ? 'All systems go' : `${total - online} disconnected`}
68
103
  color="text-gray-400"
69
- accent="from-gray-500/10 to-gray-500/0"
70
- border="border-gray-500/15"
71
104
  />
72
105
  <StatCard
73
106
  value={total}
74
107
  label="Total"
75
108
  sub="Registered nodes"
76
109
  color="text-white"
77
- accent="from-blue-500/15 to-blue-500/0"
78
- border="border-blue-500/15"
79
110
  />
80
111
  </div>
112
+ </>
81
113
  )}
82
114
  </div>
83
115
  );
84
116
  }
85
117
 
86
- function StatCard({ value, label, sub, color, accent, border }: {
87
- value: number; label: string; sub: string; color: string; accent: string; border: string;
118
+ function StatCard({ value, label, sub, color }: {
119
+ value: number; label: string; sub: string; color: string;
88
120
  }) {
89
- // Extract the color family (green/cyan/gray/blue/white) from `color` prop
90
- // so the light-theme top-strip CSS can pick the right accent.
91
- const accentKey = color.replace('text-', '').split('-')[0];
121
+ // #217 D2 (OpenWebUI-style color restraint): the per-color gradient
122
+ // wash + tinted borders made the KPI row read as four neon billboards.
123
+ // Surfaces are now neutral (shared border + bg); color survives only
124
+ // on the number itself. Mobile density values are #209 R39.
92
125
  return (
93
- <div
94
- data-anet-stat-card={accentKey}
95
- className={`anet-stat-card relative overflow-hidden rounded-xl border ${border} bg-[#111128] px-4 py-3 transition-all`}
96
- >
97
- <div className={`absolute inset-0 bg-gradient-to-br ${accent} pointer-events-none`} />
98
- <div className="relative">
99
- <div className={`text-3xl font-bold ${color} tabular-nums leading-tight`}>{value}</div>
100
- <div className="text-sm text-gray-300 mt-0.5">{label}</div>
101
- <div className="text-xs text-gray-600 mt-1">{sub}</div>
102
- </div>
126
+ <div className="anet-stat-card rounded-xl border border-[#26262b] bg-[#161618] px-3 sm:px-4 py-2.5 sm:py-3 transition-all">
127
+ <div className={`text-2xl sm:text-3xl font-bold ${color} tabular-nums leading-tight`}>{value}</div>
128
+ <div className="text-xs sm:text-sm text-gray-300 mt-0.5">{label}</div>
129
+ <div className="text-[10px] sm:text-xs text-gray-600 mt-1">{sub}</div>
103
130
  </div>
104
131
  );
105
132
  }
@@ -213,6 +213,18 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
213
213
  const [mentionNodes, setMentionNodes] = useState<string[]>([]);
214
214
  const messagesEndRef = useRef<HTMLDivElement>(null);
215
215
  const textareaRef = useRef<HTMLTextAreaElement>(null);
216
+ // #217 M4 (Vincent: 倒序加载, 上滑再拉旧的): history is paged. We load
217
+ // only the newest PAGE on open and grow the window when the user
218
+ // scrolls to the top. The hub API has no `before` cursor (offset is
219
+ // ignored), so "older" = refetch with a larger limit and prepend the
220
+ // delta — each step is on-demand and tiny vs one full pull.
221
+ const HISTORY_PAGE = 20;
222
+ const scrollRef = useRef<HTMLDivElement>(null);
223
+ const histLimitRef = useRef(HISTORY_PAGE);
224
+ const skipAutoScrollRef = useRef(false);
225
+ const anchoredRef = useRef(false);
226
+ const [hasOlder, setHasOlder] = useState(true);
227
+ const [loadingOlder, setLoadingOlder] = useState(false);
216
228
 
217
229
  // Load available nodes for @ mention
218
230
  useEffect(() => {
@@ -231,14 +243,22 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
231
243
  markChatRead(alias, networkId);
232
244
  }, [alias, networkId]);
233
245
 
234
- // Load task history for this node
235
- const loadHistory = useCallback(async () => {
246
+ // Load task history for this node — newest `limit` only (M4).
247
+ const loadHistory = useCallback(async (limit: number) => {
236
248
  try {
237
- const res = await fetch(`/api/hub/tasks?to_name=${encodeURIComponent(alias)}&limit=30`);
249
+ const res = await fetch(`/api/hub/tasks?to_name=${encodeURIComponent(alias)}&limit=${limit}`);
238
250
  const data = await res.json();
239
251
  if (data.tasks) {
240
- // Show oldest first
241
- setMessages(data.tasks.reverse());
252
+ const fetched = data.tasks.reverse(); // oldest first for display
253
+ if (data.tasks.length < limit) setHasOlder(false);
254
+ // Merge: keep current messages the fetch doesn't know about yet
255
+ // (optimistic sends / SSE patches) so growing the window never
256
+ // drops an in-flight bubble.
257
+ setMessages(prev => {
258
+ const ids = new Set(fetched.map((t: { task_id?: string }) => t.task_id));
259
+ const extras = prev.filter(t => t.task_id && !ids.has(t.task_id));
260
+ return [...fetched, ...extras];
261
+ });
242
262
  }
243
263
  } catch {} finally {
244
264
  setHistoryLoaded(true);
@@ -248,13 +268,52 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
248
268
  useEffect(() => {
249
269
  setMessages([]);
250
270
  setHistoryLoaded(false);
251
- loadHistory();
271
+ histLimitRef.current = HISTORY_PAGE;
272
+ anchoredRef.current = false;
273
+ setHasOlder(true);
274
+ loadHistory(HISTORY_PAGE);
252
275
  // Focus textarea
253
276
  setTimeout(() => textareaRef.current?.focus(), 300);
254
277
  }, [alias, loadHistory]);
255
278
 
256
- // Auto-scroll to bottom
279
+ // M4: user scrolled to the top → grow the history window, keeping the
280
+ // viewport anchored on the message they were looking at.
281
+ const loadOlder = useCallback(async () => {
282
+ if (loadingOlder || !hasOlder) return;
283
+ setLoadingOlder(true);
284
+ const el = scrollRef.current;
285
+ const prevHeight = el?.scrollHeight ?? 0;
286
+ skipAutoScrollRef.current = true;
287
+ histLimitRef.current += HISTORY_PAGE;
288
+ await loadHistory(histLimitRef.current);
289
+ requestAnimationFrame(() => {
290
+ if (el) el.scrollTop += el.scrollHeight - prevHeight;
291
+ setLoadingOlder(false);
292
+ });
293
+ }, [loadingOlder, hasOlder, loadHistory]);
294
+
295
+ const onMessagesScroll = useCallback(() => {
296
+ const el = scrollRef.current;
297
+ if (!el) return;
298
+ // The initial bottom-anchor is a smooth scroll, so its own scroll
299
+ // events pass through scrollTop≈0 territory. Arm the older-page
300
+ // trigger only after the container has actually reached the bottom
301
+ // once — everything before that is layout/auto-scroll noise.
302
+ if (!anchoredRef.current) {
303
+ if (el.scrollHeight - el.scrollTop - el.clientHeight < 80) anchoredRef.current = true;
304
+ return;
305
+ }
306
+ if (el.scrollTop < 40) loadOlder();
307
+ }, [loadOlder]);
308
+
309
+ // Auto-scroll to bottom on new messages — but not when we just
310
+ // prepended older history (that would yank the user away from what
311
+ // they scrolled up to read).
257
312
  useEffect(() => {
313
+ if (skipAutoScrollRef.current) {
314
+ skipAutoScrollRef.current = false;
315
+ return;
316
+ }
258
317
  messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
259
318
  }, [messages]);
260
319
 
@@ -483,12 +542,39 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
483
542
  const chatContent = (
484
543
  <>
485
544
  {/* Messages area */}
486
- <div className="flex-1 overflow-y-auto px-3 py-3 sm:px-4 sm:py-4 space-y-3 sm:space-y-4">
545
+ {/* R12 of #190 mobile polish: the chat scroll surface used
546
+ space-y-3 between task+reply pairs at mobile = 12 px, which
547
+ in a long thread (the panel's bread-and-butter use case)
548
+ adds up to a significant scroll length. Drop to space-y-2 at
549
+ mobile and the per-pair grouping (line 540) from space-y-2
550
+ to space-y-1.5 so messages read denser without losing the
551
+ speaker-turn rhythm. Desktop unchanged at sm: and up. */}
552
+ <div ref={scrollRef} onScroll={onMessagesScroll} className="flex-1 overflow-y-auto px-3 py-3 sm:px-4 sm:py-4 space-y-2 sm:space-y-4">
487
553
  {!historyLoaded && (
488
554
  <div className="flex justify-center py-8">
489
555
  <div className="w-5 h-5 border-2 border-cyan-500/30 border-t-cyan-500 rounded-full animate-spin" />
490
556
  </div>
491
557
  )}
558
+ {/* M4: top-of-thread row — scroll-to-top auto-loads older, and
559
+ the row is also tappable for short threads that don't
560
+ overflow (nothing to scroll). Quiet "beginning" marker
561
+ once history is exhausted. */}
562
+ {historyLoaded && hasOlder && messages.length > 0 && (
563
+ <button
564
+ type="button"
565
+ onClick={loadOlder}
566
+ className="w-full flex justify-center py-2 text-[11px] text-[var(--fg-dim)] hover:text-[var(--fg-muted)]"
567
+ >
568
+ {loadingOlder ? (
569
+ <span className="w-4 h-4 border-2 border-cyan-500/30 border-t-cyan-500 rounded-full animate-spin" />
570
+ ) : (
571
+ <span>↑ Load earlier messages</span>
572
+ )}
573
+ </button>
574
+ )}
575
+ {historyLoaded && !hasOlder && messages.length > 0 && (
576
+ <div className="text-center text-[10px] text-[var(--fg-dim)] py-1">— beginning of history —</div>
577
+ )}
492
578
  {historyLoaded && messages.length === 0 && (
493
579
  <div className="text-center py-12">
494
580
  <div className="text-3xl mb-3">💬</div>
@@ -537,7 +623,7 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
537
623
  );
538
624
  }
539
625
  return (
540
- <div key={`${m.task_id}:task`} className="space-y-2">
626
+ <div key={`${m.task_id}:task`} className="space-y-1.5 sm:space-y-2">
541
627
  {/* Outgoing task — labeled with origin so peer-forwarded tasks are obvious */}
542
628
  <div className="flex justify-end">
543
629
  <div className="max-w-[92%] sm:max-w-[85%] bg-cyan-500/8 border border-cyan-500/15 rounded-2xl rounded-br-md px-3 py-2.5 sm:px-4 shadow-sm">
@@ -626,8 +712,11 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
626
712
  </select>
627
713
  </div>
628
714
  </div>
629
- <button onClick={send} disabled={sending || (!input.trim() && attachedFiles.length === 0)}
630
- className="p-2.5 bg-cyan-600 hover:bg-cyan-500 disabled:bg-[var(--border)] disabled:text-[var(--fg-dim)] text-[var(--fg)] rounded-xl transition-all shrink-0 active:scale-95">
715
+ {/* R17 of #190: send button was p-2.5 + w-5 icon = ~40 x 40
716
+ hit zone, 4 px short of the iOS 44 px guideline on
717
+ the short axis. Bump to inline-flex + min-h/w 44. */}
718
+ <button onClick={send} aria-label="Send message" disabled={sending || (!input.trim() && attachedFiles.length === 0)}
719
+ className="inline-flex min-h-[44px] min-w-[44px] items-center justify-center bg-cyan-600 hover:bg-cyan-500 disabled:bg-[var(--border)] disabled:text-[var(--fg-dim)] text-[var(--fg)] rounded-xl transition-all shrink-0 active:scale-95">
631
720
  {sending ? (
632
721
  <div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
633
722
  ) : (
@@ -673,7 +762,11 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
673
762
  <div className="text-[10px] text-[var(--fg-muted)]">{pollingIds.size > 0 ? 'Processing...' : 'Ready'}</div>
674
763
  </div>
675
764
  </div>
676
- <button onClick={onClose} className="text-[var(--fg-muted)] hover:text-[var(--fg)] p-1.5 rounded-lg hover:bg-[var(--bg-elevated)]">
765
+ {/* R16 of #190: was p-1.5 + w-5 h-5 svg = ~32 px tap target.
766
+ The chat panel close is high-frequency on mobile (user
767
+ dismisses to scroll the underlying page); bump to a
768
+ uniform 44 x 44 hit zone via inline-flex + min-h/w. */}
769
+ <button onClick={onClose} aria-label="Close chat" 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)]">
677
770
  <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
678
771
  <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
679
772
  </svg>
@@ -81,14 +81,16 @@ export function TaskDrawer({ taskId, onClose }: TaskDrawerProps) {
81
81
  return (
82
82
  <>
83
83
  <div className="fixed inset-0 bg-black/30 z-40 anet-fade-in" onClick={onClose} />
84
- <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 overflow-y-auto animate-slide-in">
84
+ <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 overflow-y-auto animate-slide-in">
85
85
  {/* Header */}
86
- <div className="flex items-center justify-between px-5 py-4 border-b border-[#2a2a4a] bg-[#0d0d1a] sticky top-0">
86
+ <div className="flex items-center justify-between px-5 py-4 border-b border-[#26262b] bg-[#111113] sticky top-0">
87
87
  <div>
88
88
  <div className="text-sm font-semibold text-white">Task Detail</div>
89
89
  <div className="text-[10px] text-gray-500 mt-0.5">{taskId.slice(0, 16)}...</div>
90
90
  </div>
91
- <button onClick={onClose} className="text-gray-500 hover:text-white p-1.5 rounded-lg hover:bg-[#1a1a2a]">
91
+ {/* R16 of #190: same chat-panel close pattern — was ~32 px
92
+ tap target; lift to a uniform 44 x 44 hit zone. */}
93
+ <button onClick={onClose} aria-label="Close task drawer" className="inline-flex min-h-[44px] min-w-[44px] items-center justify-center text-gray-500 hover:text-white rounded-lg hover:bg-[#1c1c1f]">
92
94
  <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
93
95
  <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
94
96
  </svg>
@@ -127,7 +129,7 @@ export function TaskDrawer({ taskId, onClose }: TaskDrawerProps) {
127
129
 
128
130
  {/* Timeline — round 36 polish: relative timestamps, current-step
129
131
  pulse if task is still in flight, full ISO in title=. */}
130
- <div className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-4">
132
+ <div className="bg-[#161618] border border-[#26262b] rounded-xl p-4">
131
133
  <div className="flex items-center justify-between mb-3">
132
134
  <div className="text-xs text-gray-500 uppercase tracking-wide">Timeline</div>
133
135
  {duration !== null && (
@@ -180,14 +182,14 @@ export function TaskDrawer({ taskId, onClose }: TaskDrawerProps) {
180
182
  </div>
181
183
 
182
184
  {/* Content */}
183
- <div className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-4">
185
+ <div className="bg-[#161618] border border-[#26262b] rounded-xl p-4">
184
186
  <div className="text-xs text-gray-500 uppercase mb-2">Input</div>
185
187
  <div className="text-sm text-gray-300 whitespace-pre-wrap">{task.content || '--'}</div>
186
188
  </div>
187
189
 
188
190
  {/* Result */}
189
191
  {task.result && (
190
- <div className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-4">
192
+ <div className="bg-[#161618] border border-[#26262b] rounded-xl p-4">
191
193
  <div className="text-xs text-gray-500 uppercase mb-2">Output</div>
192
194
  <div className="text-sm text-gray-300 whitespace-pre-wrap max-h-64 overflow-y-auto">{task.result}</div>
193
195
  </div>
@@ -195,7 +197,7 @@ export function TaskDrawer({ taskId, onClose }: TaskDrawerProps) {
195
197
 
196
198
  {/* Events */}
197
199
  {events.length > 0 && (
198
- <div className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-4">
200
+ <div className="bg-[#161618] border border-[#26262b] rounded-xl p-4">
199
201
  <div className="text-xs text-gray-500 uppercase mb-2">Events ({events.length})</div>
200
202
  <div className="space-y-1.5 max-h-40 overflow-y-auto">
201
203
  {events.map(e => (