@sleep2agi/agent-network-dashboard 0.5.7-preview.3 → 0.5.7-preview.31

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 (273) 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 +48 -48
  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.js.nft.json +1 -1
  16. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  17. package/.next/server/app/_not-found.html +3 -3
  18. package/.next/server/app/_not-found.rsc +15 -15
  19. package/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  20. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  21. package/.next/server/app/_not-found.segments/_index.segment.rsc +8 -8
  22. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  23. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  24. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  25. package/.next/server/app/admin/page.js.nft.json +1 -1
  26. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  27. package/.next/server/app/admin.html +3 -3
  28. package/.next/server/app/admin.rsc +17 -17
  29. package/.next/server/app/admin.segments/_full.segment.rsc +17 -17
  30. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  31. package/.next/server/app/admin.segments/_index.segment.rsc +8 -8
  32. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  33. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  34. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  35. package/.next/server/app/index.html +3 -3
  36. package/.next/server/app/index.rsc +17 -17
  37. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  38. package/.next/server/app/index.segments/_full.segment.rsc +17 -17
  39. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  40. package/.next/server/app/index.segments/_index.segment.rsc +8 -8
  41. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  42. package/.next/server/app/login/page.js.nft.json +1 -1
  43. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  44. package/.next/server/app/login.html +2 -2
  45. package/.next/server/app/login.rsc +17 -17
  46. package/.next/server/app/login.segments/_full.segment.rsc +17 -17
  47. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  48. package/.next/server/app/login.segments/_index.segment.rsc +8 -8
  49. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  50. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  51. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  52. package/.next/server/app/logs/page.js.nft.json +1 -1
  53. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  54. package/.next/server/app/logs.html +3 -3
  55. package/.next/server/app/logs.rsc +17 -17
  56. package/.next/server/app/logs.segments/_full.segment.rsc +17 -17
  57. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  58. package/.next/server/app/logs.segments/_index.segment.rsc +8 -8
  59. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  60. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  61. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  62. package/.next/server/app/messages/page.js.nft.json +1 -1
  63. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  64. package/.next/server/app/messages.html +3 -3
  65. package/.next/server/app/messages.rsc +17 -17
  66. package/.next/server/app/messages.segments/_full.segment.rsc +17 -17
  67. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  68. package/.next/server/app/messages.segments/_index.segment.rsc +8 -8
  69. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  70. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  71. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  72. package/.next/server/app/node/page.js.nft.json +1 -1
  73. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  74. package/.next/server/app/node.html +3 -3
  75. package/.next/server/app/node.rsc +17 -17
  76. package/.next/server/app/node.segments/_full.segment.rsc +17 -17
  77. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  78. package/.next/server/app/node.segments/_index.segment.rsc +8 -8
  79. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  80. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  81. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  82. package/.next/server/app/nodes/page.js.nft.json +1 -1
  83. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  84. package/.next/server/app/nodes.html +3 -3
  85. package/.next/server/app/nodes.rsc +17 -17
  86. package/.next/server/app/nodes.segments/_full.segment.rsc +17 -17
  87. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  88. package/.next/server/app/nodes.segments/_index.segment.rsc +8 -8
  89. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  90. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  91. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  92. package/.next/server/app/page.js.nft.json +1 -1
  93. package/.next/server/app/page_client-reference-manifest.js +1 -1
  94. package/.next/server/app/server-logs/page.js.nft.json +1 -1
  95. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  96. package/.next/server/app/server-logs.html +3 -3
  97. package/.next/server/app/server-logs.rsc +17 -17
  98. package/.next/server/app/server-logs.segments/_full.segment.rsc +17 -17
  99. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  100. package/.next/server/app/server-logs.segments/_index.segment.rsc +8 -8
  101. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  102. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  103. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  104. package/.next/server/app/servers/page.js.nft.json +1 -1
  105. package/.next/server/app/servers/page_client-reference-manifest.js +1 -1
  106. package/.next/server/app/servers.html +3 -3
  107. package/.next/server/app/servers.rsc +17 -17
  108. package/.next/server/app/servers.segments/_full.segment.rsc +17 -17
  109. package/.next/server/app/servers.segments/_head.segment.rsc +4 -4
  110. package/.next/server/app/servers.segments/_index.segment.rsc +8 -8
  111. package/.next/server/app/servers.segments/_tree.segment.rsc +2 -2
  112. package/.next/server/app/servers.segments/servers/__PAGE__.segment.rsc +4 -4
  113. package/.next/server/app/servers.segments/servers.segment.rsc +3 -3
  114. package/.next/server/app/settings/networks/page.js.nft.json +1 -1
  115. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  116. package/.next/server/app/settings/networks.html +3 -3
  117. package/.next/server/app/settings/networks.rsc +17 -17
  118. package/.next/server/app/settings/networks.segments/_full.segment.rsc +17 -17
  119. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  120. package/.next/server/app/settings/networks.segments/_index.segment.rsc +8 -8
  121. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  122. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  123. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  124. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  125. package/.next/server/app/settings/page.js.nft.json +1 -1
  126. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  127. package/.next/server/app/settings/tokens/page.js.nft.json +1 -1
  128. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  129. package/.next/server/app/settings/tokens.html +3 -3
  130. package/.next/server/app/settings/tokens.rsc +17 -17
  131. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +17 -17
  132. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  133. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +8 -8
  134. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  135. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  136. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  137. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  138. package/.next/server/app/settings.html +3 -3
  139. package/.next/server/app/settings.rsc +17 -17
  140. package/.next/server/app/settings.segments/_full.segment.rsc +17 -17
  141. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  142. package/.next/server/app/settings.segments/_index.segment.rsc +8 -8
  143. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  144. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  145. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  146. package/.next/server/app/tasks/[id]/page.js.nft.json +1 -1
  147. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  148. package/.next/server/app/tasks/page.js.nft.json +1 -1
  149. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  150. package/.next/server/app/tasks.html +3 -3
  151. package/.next/server/app/tasks.rsc +17 -17
  152. package/.next/server/app/tasks.segments/_full.segment.rsc +17 -17
  153. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  154. package/.next/server/app/tasks.segments/_index.segment.rsc +8 -8
  155. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  156. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  157. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  158. package/.next/server/app-paths-manifest.json +0 -1
  159. package/.next/server/chunks/ssr/[root-of-the-server]__030vg4n._.js +1 -1
  160. package/.next/server/chunks/ssr/[root-of-the-server]__030vg4n._.js.map +1 -1
  161. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js +1 -1
  162. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js.map +1 -1
  163. package/.next/server/chunks/ssr/[root-of-the-server]__0lu1wok._.js +2 -2
  164. package/.next/server/chunks/ssr/[root-of-the-server]__0lu1wok._.js.map +1 -1
  165. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js +1 -1
  166. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js.map +1 -1
  167. package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js +1 -1
  168. package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js.map +1 -1
  169. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
  170. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  171. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  172. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  173. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  174. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  175. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js +1 -1
  176. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js.map +1 -1
  177. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js +1 -1
  178. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js.map +1 -1
  179. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js +1 -1
  180. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js.map +1 -1
  181. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js +2 -2
  182. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js.map +1 -1
  183. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js +1 -1
  184. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js.map +1 -1
  185. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0r7kb.o._.js +9 -0
  186. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0r7kb.o._.js.map +1 -0
  187. package/.next/server/chunks/ssr/agent-network-dashboard_app_server-logs_page_tsx_0dg.l_8._.js +1 -1
  188. package/.next/server/chunks/ssr/agent-network-dashboard_app_server-logs_page_tsx_0dg.l_8._.js.map +1 -1
  189. package/.next/server/chunks/ssr/agent-network-dashboard_app_tasks_page_tsx_0mwxy4z._.js +1 -1
  190. package/.next/server/chunks/ssr/agent-network-dashboard_app_tasks_page_tsx_0mwxy4z._.js.map +1 -1
  191. package/.next/server/middleware-build-manifest.js +3 -3
  192. package/.next/server/pages/404.html +3 -3
  193. package/.next/server/pages/500.html +1 -1
  194. package/.next/server/server-reference-manifest.js +1 -1
  195. package/.next/server/server-reference-manifest.json +1 -1
  196. package/.next/static/chunks/0.054rbp43q.y.js +1 -0
  197. package/.next/static/chunks/{181u38qblp8lz.js → 00b-ysl~m~dr~.js} +1 -1
  198. package/.next/static/chunks/{0jp~cs9-zkmqa.js → 00b4y77vxfabl.js} +1 -1
  199. package/.next/static/chunks/0188imvuz-~kd.js +1 -0
  200. package/.next/static/chunks/{15qxef.ilfysw.js → 03g.reu.n2vy-.js} +3 -3
  201. package/.next/static/chunks/{0a.9~-nf0gpec.js → 0_eddxjio~tei.js} +1 -1
  202. package/.next/static/chunks/0fkd-56.k2ts..js +1 -0
  203. package/.next/static/chunks/0fvus2k1~nj8..js +1 -0
  204. package/.next/static/chunks/{0im751o4n61c7.js → 0hv6izw.g6cnm.js} +1 -1
  205. package/.next/static/chunks/0miet1u_5tj06.js +7 -0
  206. package/.next/static/chunks/{05uk96gc~9mni.js → 0ti3v67ixu43p.js} +1 -1
  207. package/.next/static/chunks/{0xqeurx5nvu76.js → 0xa~3.e0_hqfk.js} +1 -1
  208. package/.next/static/chunks/{0561vp5-q5.zp.js → 0ymogrg8t82.8.js} +1 -1
  209. package/.next/static/chunks/{0nqm.7w9_inwd.js → 0~eyvw9.ips4y.js} +2 -2
  210. package/.next/static/chunks/10u21fv4dvd...css +1 -0
  211. package/.next/static/chunks/{0.66f3.rtcybb.js → 136r0ae9ihgvo.js} +1 -1
  212. package/.next/trace +2 -2
  213. package/.next/trace-build +1 -1
  214. package/.next/types/routes.d.ts +1 -2
  215. package/.next/types/validator.ts +0 -9
  216. package/app/admin/page.tsx +1 -1
  217. package/app/components/AgentCard.tsx +19 -9
  218. package/app/components/AppShell.tsx +7 -1
  219. package/app/components/ChatPopover.tsx +1 -1
  220. package/app/components/CommandCenter.tsx +12 -2
  221. package/app/components/CommandPalette.tsx +1 -1
  222. package/app/components/DispatchPanel.tsx +7 -4
  223. package/app/components/HealthBanner.tsx +10 -2
  224. package/app/components/HelpOverlay.tsx +0 -3
  225. package/app/components/MobileNav.tsx +26 -19
  226. package/app/components/Sidebar.tsx +19 -12
  227. package/app/components/TaskChatPanel.tsx +19 -5
  228. package/app/components/TaskDrawer.tsx +3 -1
  229. package/app/components/ThemeSwitcher.tsx +10 -79
  230. package/app/components/UserBar.tsx +3 -3
  231. package/app/globals.css +76 -707
  232. package/app/layout.tsx +27 -1
  233. package/app/lib/hooks.ts +0 -5
  234. package/app/login/page.tsx +3 -3
  235. package/app/logs/page.tsx +6 -2
  236. package/app/messages/page.tsx +19 -12
  237. package/app/node/page.tsx +2 -2
  238. package/app/nodes/page.tsx +12 -6
  239. package/app/page.tsx +0 -24
  240. package/app/server-logs/page.tsx +10 -3
  241. package/app/settings/networks/page.tsx +3 -3
  242. package/app/settings/page.tsx +31 -83
  243. package/app/settings/tokens/page.tsx +1 -1
  244. package/app/tasks/page.tsx +13 -9
  245. package/bin/start.js +0 -0
  246. package/package.json +1 -1
  247. package/public/manifest.webmanifest +24 -0
  248. package/public/robots.txt +7 -0
  249. package/.next/server/app/api/hub/license/route/app-paths-manifest.json +0 -3
  250. package/.next/server/app/api/hub/license/route/build-manifest.json +0 -9
  251. package/.next/server/app/api/hub/license/route/server-reference-manifest.json +0 -4
  252. package/.next/server/app/api/hub/license/route.js +0 -7
  253. package/.next/server/app/api/hub/license/route.js.map +0 -5
  254. package/.next/server/app/api/hub/license/route.js.nft.json +0 -1
  255. package/.next/server/app/api/hub/license/route_client-reference-manifest.js +0 -3
  256. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_license_route_actions_0a4.fuh.js +0 -3
  257. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_license_route_actions_0a4.fuh.js.map +0 -1
  258. package/.next/server/chunks/[root-of-the-server]__0rovr5-._.js +0 -3
  259. package/.next/server/chunks/[root-of-the-server]__0rovr5-._.js.map +0 -1
  260. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0s5uqlp._.js +0 -9
  261. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0s5uqlp._.js.map +0 -1
  262. package/.next/static/chunks/03~5pxwbxxw-b.js +0 -1
  263. package/.next/static/chunks/0eggl7f36vd8m.js +0 -1
  264. package/.next/static/chunks/0h_gdeiy~s92j.css +0 -1
  265. package/.next/static/chunks/0inql3s9ldyx5.js +0 -1
  266. package/.next/static/chunks/0xze0l75jjpwr.js +0 -1
  267. package/.next/static/chunks/15-ltfhot3b4n.js +0 -7
  268. package/app/api/hub/license/route.ts +0 -33
  269. package/app/components/BroadcastBar.tsx +0 -84
  270. package/app/components/InboxPanel.tsx +0 -36
  271. /package/.next/static/{cssa52keEzN4TEvVJY7V3 → 1jvl1LpPAXW-Ul4xR7NzO}/_buildManifest.js +0 -0
  272. /package/.next/static/{cssa52keEzN4TEvVJY7V3 → 1jvl1LpPAXW-Ul4xR7NzO}/_clientMiddlewareManifest.js +0 -0
  273. /package/.next/static/{cssa52keEzN4TEvVJY7V3 → 1jvl1LpPAXW-Ul4xR7NzO}/_ssgManifest.js +0 -0
package/app/layout.tsx CHANGED
@@ -20,7 +20,26 @@ export const metadata: Metadata = {
20
20
  statusBarStyle: "black-translucent",
21
21
  title: "Agent Network",
22
22
  },
23
- icons: { icon: '/favicon.svg' },
23
+ // R26 of #190: Next.js 16 renders `mobile-web-app-capable` (the modern
24
+ // standardized meta) but NOT the legacy `apple-mobile-web-app-capable`.
25
+ // iOS Safari < 16.4 only checks the apple-prefixed name; without it,
26
+ // the dashboard won't install as a web app on those iOS versions and
27
+ // the statusBarStyle + apple-touch-icon (R24) are silently ignored.
28
+ // Emit the legacy alias via metadata.other for back-compat.
29
+ other: {
30
+ 'apple-mobile-web-app-capable': 'yes',
31
+ },
32
+ // R24 of #190: appleWebApp.capable above declares this is an iOS web
33
+ // app, but only `icon` was specified, leaving iOS Safari to fall back
34
+ // to a generic icon on Add-to-Home-Screen. iOS 15+ accepts SVG via
35
+ // `rel="apple-touch-icon" type="image/svg+xml"`; older iOS will fall
36
+ // back to the manifest icons (added R23) and then the generic. Reuse
37
+ // the existing sleep2agi-logo.svg — no new asset shipped.
38
+ icons: {
39
+ icon: [{ url: '/favicon.svg', type: 'image/svg+xml' }],
40
+ apple: [{ url: '/sleep2agi-logo.svg', type: 'image/svg+xml' }],
41
+ shortcut: '/favicon.svg',
42
+ },
24
43
  openGraph: {
25
44
  title: "Agent Network Dashboard",
26
45
  description: "Real-time monitoring dashboard for Agent Network nodes",
@@ -34,6 +53,13 @@ export const viewport: Viewport = {
34
53
  maximumScale: 5,
35
54
  userScalable: true,
36
55
  themeColor: "#0a0a1a",
56
+ // R25 of #190: metadata.appleWebApp.statusBarStyle above is
57
+ // "black-translucent", which renders the iOS status bar as a
58
+ // transparent overlay above the page (rather than reserving its own
59
+ // strip). Without viewportFit: "cover" the resulting safe-area-inset
60
+ // env() values resolve to 0, so the HealthBanner gets occluded by
61
+ // the clock/battery row when the user opens the installed PWA.
62
+ viewportFit: "cover",
37
63
  };
38
64
 
39
65
  // Inline pre-paint script to apply persisted theme before React hydrates,
package/app/lib/hooks.ts CHANGED
@@ -63,11 +63,6 @@ export function useStats() {
63
63
  return { stats: data?.ok ? data : null, error };
64
64
  }
65
65
 
66
- export function useLicense() {
67
- const { data, error } = useSWR('/api/hub/license', fetcher, { refreshInterval: 60000 });
68
- return { license: data?.ok ? data : null, error };
69
- }
70
-
71
66
  export function useMessages(limit = 100) {
72
67
  const { networkId } = useNetworkId();
73
68
  const { data, error, isLoading } = useSWR(
@@ -92,7 +92,7 @@ export default function LoginPage() {
92
92
  </label>
93
93
  <input id="username" type="text" value={username} onChange={e => setUsername(e.target.value)} autoFocus
94
94
  placeholder="Enter username"
95
- className="w-full bg-[#0a0a15] border border-[#2a2a4a] rounded-lg px-4 py-3 text-sm text-white placeholder-gray-600 focus:border-cyan-500/50 focus:ring-1 focus:ring-cyan-500/20 focus:outline-none transition-all mb-4" />
95
+ className="w-full bg-[#0a0a15] border border-[#2a2a4a] rounded-lg px-4 py-3 text-base sm:text-sm text-white placeholder-gray-600 focus:border-cyan-500/50 focus:ring-1 focus:ring-cyan-500/20 focus:outline-none transition-all mb-4" />
96
96
 
97
97
  <label htmlFor="password" className="block text-xs text-gray-500 mb-2 uppercase tracking-wider">
98
98
  Password
@@ -100,13 +100,13 @@ export default function LoginPage() {
100
100
  <div className="relative">
101
101
  <input id="password" type={showPassword ? 'text' : 'password'} value={password} onChange={e => setPassword(e.target.value)}
102
102
  placeholder="Enter password"
103
- className="w-full bg-[#0a0a15] border border-[#2a2a4a] rounded-lg px-4 pr-11 py-3 text-sm text-white placeholder-gray-600 focus:border-cyan-500/50 focus:ring-1 focus:ring-cyan-500/20 focus:outline-none transition-all" />
103
+ className="w-full bg-[#0a0a15] border border-[#2a2a4a] rounded-lg px-4 pr-11 py-3 text-base sm:text-sm text-white placeholder-gray-600 focus:border-cyan-500/50 focus:ring-1 focus:ring-cyan-500/20 focus:outline-none transition-all" />
104
104
  <button
105
105
  type="button"
106
106
  onClick={() => setShowPassword(s => !s)}
107
107
  tabIndex={-1}
108
108
  aria-label={showPassword ? 'Hide password' : 'Show password'}
109
- className="absolute inset-y-0 right-0 flex items-center px-3 text-gray-600 hover:text-gray-300 transition-colors"
109
+ className="absolute inset-y-0 right-0 flex items-center justify-center min-w-[44px] px-3 text-gray-600 hover:text-gray-300 transition-colors"
110
110
  >
111
111
  {showPassword ? (
112
112
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
package/app/logs/page.tsx CHANGED
@@ -162,11 +162,15 @@ export default function LogsPage() {
162
162
  ) : logs.length === 0 ? (
163
163
  <EmptyState variant="logs" />
164
164
  ) : (
165
- <div className="space-y-2">
165
+ /* R18 of #190 mobile polish: /logs was 28,791 px on 390 px
166
+ mobile; same dense-card pattern that R7/R8 hit on /nodes
167
+ and /tasks. Tighten card padding and inter-card gap on
168
+ mobile, sm:-gated so desktop stays comfortable. */
169
+ <div className="space-y-1 sm:space-y-2">
166
170
  {logs.map(log => {
167
171
  const userName = log.username || log.user_id;
168
172
  return (
169
- <div key={log.id} className="relative bg-[#111128] border border-[#2a2a4a] rounded-lg pl-4 pr-4 py-3 hover:border-[#3a3a5a] transition-colors overflow-hidden">
173
+ <div key={log.id} className="relative bg-[#111128] border border-[#2a2a4a] rounded-lg pl-3 pr-3 py-2 sm:pl-4 sm:pr-4 sm:py-3 hover:border-[#3a3a5a] transition-colors overflow-hidden">
170
174
  {/* 2px left rail per action (round 33) — failed logins, token
171
175
  rotations spike out of a wall of register/login rows. */}
172
176
  <span
@@ -148,12 +148,12 @@ export default function MessagesPage() {
148
148
  value={search}
149
149
  onChange={e => setSearch(e.target.value)}
150
150
  placeholder="Search from/to/content or use from:alias"
151
- className="bg-[#111128] border border-[#2a2a4a] rounded-lg px-3 py-2 text-sm text-white placeholder-gray-600 focus:border-blue-500/50 focus:outline-none w-full sm:w-72"
151
+ className="bg-[#111128] border border-[#2a2a4a] rounded-lg px-3 py-2 text-base sm:text-sm text-white placeholder-gray-600 focus:border-blue-500/50 focus:outline-none w-full sm:w-72"
152
152
  />
153
153
  <select
154
154
  value={filterType}
155
155
  onChange={e => setFilterType(e.target.value)}
156
- className="bg-[#111128] border border-[#2a2a4a] rounded-lg px-3 py-2 text-sm text-white focus:border-blue-500/50 focus:outline-none"
156
+ className="bg-[#111128] border border-[#2a2a4a] rounded-lg px-3 py-2 text-base sm:text-sm text-white focus:border-blue-500/50 focus:outline-none"
157
157
  >
158
158
  <option value="">All types</option>
159
159
  <option value="task">task</option>
@@ -260,19 +260,26 @@ export default function MessagesPage() {
260
260
 
261
261
  return (
262
262
  <div key={message.id}>
263
+ {/* R6 of #190 mobile polish: gap divider was my-4 (=16px
264
+ each side), and with multi-hour message lulls it
265
+ fired several times per session, eating ~32px each.
266
+ Halve it on mobile. */}
263
267
  {gapExceeded && (
264
- <div className="my-4 flex items-center gap-3">
268
+ <div className="my-2 sm:my-4 flex items-center gap-3">
265
269
  <div className="h-px flex-1 bg-[#2a2a4a]" />
266
270
  <div className="text-[11px] text-gray-600">{formatDividerLabel(message.created_at)}</div>
267
271
  <div className="h-px flex-1 bg-[#2a2a4a]" />
268
272
  </div>
269
273
  )}
270
274
 
271
- {/* Broadcasts span full width — no avatar gutter. */}
275
+ {/* Broadcasts span full width — no avatar gutter. R6 mobile:
276
+ tighter padding + tighter header margin + snug leading
277
+ on the content so each bubble is ~25-30% shorter at
278
+ 390px. Desktop unchanged from sm: up. */}
272
279
  {variant === 'broadcast' ? (
273
- <div className={samePrev ? 'mt-1' : 'mt-3'}>
274
- <div className="rounded-2xl border border-purple-500/20 bg-purple-500/10 px-4 py-3 shadow-sm w-full">
275
- <div className="mb-2 flex flex-wrap items-center gap-2">
280
+ <div className={samePrev ? 'mt-0.5 sm:mt-1' : 'mt-2 sm:mt-3'}>
281
+ <div className="rounded-2xl border border-purple-500/20 bg-purple-500/10 px-3 py-2 sm:px-4 sm:py-3 shadow-sm w-full">
282
+ <div className="mb-1 sm:mb-2 flex flex-wrap items-center gap-2">
276
283
  <span className={`text-xs px-2 py-0.5 rounded-md border ${TYPE_COLORS[message.type || ''] || 'bg-gray-500/10 text-gray-400 border-gray-500/20'}`}>
277
284
  {message.type || 'unknown'}
278
285
  </span>
@@ -285,24 +292,24 @@ export default function MessagesPage() {
285
292
  long unbroken runs (URLs, ASCII rules like
286
293
  `═══════════════`) wrap instead of pushing the
287
294
  chat bubble past the mobile viewport. */}
288
- <div className="whitespace-pre-wrap break-words [overflow-wrap:anywhere] text-sm leading-relaxed text-gray-200">
295
+ <div className="whitespace-pre-wrap break-words [overflow-wrap:anywhere] text-sm leading-snug sm:leading-relaxed text-gray-200">
289
296
  {renderHighlighted(message.content, search)}
290
297
  </div>
291
298
  </div>
292
299
  </div>
293
300
  ) : (
294
- <div className={`${samePrev ? 'mt-1' : 'mt-3'} flex gap-2 ${variant === 'outgoing' ? 'flex-row-reverse' : 'flex-row'}`}>
301
+ <div className={`${samePrev ? 'mt-0.5 sm:mt-1' : 'mt-2 sm:mt-3'} flex gap-2 ${variant === 'outgoing' ? 'flex-row-reverse' : 'flex-row'}`}>
295
302
  {/* Avatar gutter — fixed width keeps bubble columns aligned even on streaks */}
296
303
  <div className="w-8 shrink-0 pt-1">
297
304
  {!samePrev && <AliasAvatar alias={fromAlias} size={32} />}
298
305
  </div>
299
- <div className={`min-w-0 max-w-3xl rounded-2xl border px-4 py-3 shadow-sm ${
306
+ <div className={`min-w-0 max-w-3xl rounded-2xl border px-3 py-2 sm:px-4 sm:py-3 shadow-sm ${
300
307
  variant === 'outgoing'
301
308
  ? 'border-green-500/20 bg-green-500/10'
302
309
  : 'border-blue-500/20 bg-blue-500/10'
303
310
  }`}>
304
311
  {!samePrev && (
305
- <div className="mb-2 flex flex-wrap items-center gap-2">
312
+ <div className="mb-1 sm:mb-2 flex flex-wrap items-center gap-2">
306
313
  <span className={`text-xs px-2 py-0.5 rounded-md border ${TYPE_COLORS[message.type || ''] || 'bg-gray-500/10 text-gray-400 border-gray-500/20'}`}>
307
314
  {message.type || 'unknown'}
308
315
  </span>
@@ -320,7 +327,7 @@ export default function MessagesPage() {
320
327
  long unbroken runs (URLs, ASCII rules like
321
328
  `═══════════════`) wrap instead of pushing the
322
329
  chat bubble past the mobile viewport. */}
323
- <div className="whitespace-pre-wrap break-words [overflow-wrap:anywhere] text-sm leading-relaxed text-gray-200">
330
+ <div className="whitespace-pre-wrap break-words [overflow-wrap:anywhere] text-sm leading-snug sm:leading-relaxed text-gray-200">
324
331
  {renderHighlighted(message.content, search)}
325
332
  </div>
326
333
 
package/app/node/page.tsx CHANGED
@@ -302,12 +302,12 @@ function NodeFullPanel({ alias, session, sse, sendMsg, setSendMsg, sending, send
302
302
  onChange={e => setSendMsg(e.target.value)}
303
303
  onKeyDown={e => e.key === 'Enter' && sendTask()}
304
304
  placeholder={`Send task to ${alias}...`}
305
- className="flex-1 bg-[#0a0a15] border border-[#2a2a4a] rounded px-3 py-2 text-xs text-white placeholder-gray-600 focus:border-blue-500 focus:outline-none"
305
+ className="flex-1 bg-[#0a0a15] border border-[#2a2a4a] rounded px-3 py-2 text-base sm:text-xs text-white placeholder-gray-600 focus:border-blue-500 focus:outline-none"
306
306
  />
307
307
  <button
308
308
  onClick={sendTask}
309
309
  disabled={sending || !sendMsg.trim()}
310
- className="px-3 py-2 bg-blue-600 hover:bg-blue-500 disabled:bg-gray-700 text-white text-xs rounded transition-colors"
310
+ className="inline-flex min-h-[44px] items-center justify-center px-4 py-2 bg-blue-600 hover:bg-blue-500 disabled:bg-gray-700 text-white text-xs rounded transition-colors"
311
311
  >
312
312
  {sending ? '...' : 'Send'}
313
313
  </button>
@@ -94,12 +94,12 @@ export default function NodesPage() {
94
94
  value={search}
95
95
  onChange={e => setSearch(e.target.value)}
96
96
  placeholder="Search nodes..."
97
- className="bg-[#111128] border border-[#2a2a4a] rounded-lg px-3 py-2 text-sm text-white placeholder-gray-600 focus:border-blue-500/50 focus:outline-none w-48"
97
+ className="bg-[#111128] border border-[#2a2a4a] rounded-lg px-3 py-2 text-base sm:text-sm text-white placeholder-gray-600 focus:border-blue-500/50 focus:outline-none w-full sm:w-48"
98
98
  />
99
99
  <select
100
100
  value={filterStatus}
101
101
  onChange={e => setFilterStatus(e.target.value)}
102
- className="bg-[#111128] border border-[#2a2a4a] rounded-lg px-3 py-2 text-sm text-white focus:border-blue-500/50 focus:outline-none"
102
+ className="bg-[#111128] border border-[#2a2a4a] rounded-lg px-3 py-2 text-base sm:text-sm text-white focus:border-blue-500/50 focus:outline-none"
103
103
  >
104
104
  <option value="">All</option>
105
105
  <option value="online">Online</option>
@@ -215,7 +215,7 @@ export default function NodesPage() {
215
215
  })}
216
216
  </div>
217
217
  ) : (
218
- <div className="space-y-2">
218
+ <div className="space-y-1 sm:space-y-2">
219
219
  {/* Round 94: AGENT + SERVER merged into one `agent · server`
220
220
  cell. Round mobile-command: node row itself opens chat, so
221
221
  the old Chat / Send Task action column is gone. */}
@@ -237,7 +237,7 @@ export default function NodesPage() {
237
237
  tabIndex={0}
238
238
  onClick={() => setChatAlias(s.alias)}
239
239
  onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setChatAlias(s.alias); } }}
240
- className={`rounded-lg border border-[#2a2a4a] bg-[#111128] px-4 py-3 transition-colors hover:border-cyan-500/40 cursor-pointer ${!s.online ? 'opacity-50' : ''}`}
240
+ className={`rounded-lg border border-[#2a2a4a] bg-[#111128] px-3 py-2 sm:px-4 sm:py-3 transition-colors hover:border-cyan-500/40 cursor-pointer ${!s.online ? 'opacity-50' : ''}`}
241
241
  >
242
242
  <div className="hidden sm:grid sm:grid-cols-10 gap-2 items-center">
243
243
  <div className="col-span-1">
@@ -261,7 +261,14 @@ export default function NodesPage() {
261
261
  <div className="col-span-4 truncate text-xs text-gray-500" title={s.task || ''}>{s.task || '--'}</div>
262
262
  <div className="col-span-1 text-xs text-gray-500">{timeAgo(s.last_seen_at || s.updated_at)}</div>
263
263
  </div>
264
- <div className="sm:hidden space-y-2">
264
+ {/* R7 of #190: mobile node row was ~340px tall × ~150
265
+ rows = the 51k page. Wins this round, in priority
266
+ order: (1) drop the per-row "Tap anywhere to chat"
267
+ hint — useful once, redundant 149 times; the cyan
268
+ border on hover/focus still teaches it. (2) tighten
269
+ space-y-2 → space-y-1 so the avatar/task gap is
270
+ 4px tighter on every row. */}
271
+ <div className="sm:hidden space-y-1">
265
272
  <div className="flex items-center gap-2.5">
266
273
  <div className="relative shrink-0">
267
274
  <AliasAvatar alias={s.alias} size={28} />
@@ -279,7 +286,6 @@ export default function NodesPage() {
279
286
  </span>
280
287
  </div>
281
288
  {s.task && <div className="truncate text-xs text-gray-500">{s.task}</div>}
282
- <div className="text-[10px] text-cyan-300/70">Tap anywhere to chat</div>
283
289
  </div>
284
290
  </div>
285
291
  );
package/app/page.tsx CHANGED
@@ -4,10 +4,8 @@ import { useEffect, useState } from 'react';
4
4
  import Link from 'next/link';
5
5
  import { formatUptime, previewContent } from './components/utils';
6
6
  import { StatsBar } from './components/StatsBar';
7
- import { BroadcastBar } from './components/BroadcastBar';
8
7
  import { TopoGraph } from './components/TopoGraph';
9
8
  import { AgentCard } from './components/AgentCard';
10
- import { InboxPanel } from './components/InboxPanel';
11
9
  import { LoadingSkeleton } from './components/LoadingSkeleton';
12
10
  import { NodesEmptyState as EmptyState } from './components/EmptyState';
13
11
  import { AliasAvatar } from './components/AliasAvatar';
@@ -17,7 +15,6 @@ import { CommandCenter, useCommandCenter } from './components/CommandCenter';
17
15
  import { DispatchPanel } from './components/DispatchPanel';
18
16
  import { useSessions, useHealth, useAnetConfig, useTasks, useStats } from './lib/hooks';
19
17
  import { useSSE } from './lib/useSSE';
20
- import { InboxMessage } from './components/types';
21
18
  import { useSWRConfig } from 'swr';
22
19
 
23
20
  export default function Dashboard() {
@@ -40,7 +37,6 @@ export default function Dashboard() {
40
37
  const [showConfig, setShowConfig] = useState(false);
41
38
  const cmd = useCommandCenter();
42
39
  const [showDispatch, setShowDispatch] = useState(false);
43
- const [inbox, setInbox] = useState<InboxMessage[]>([]);
44
40
  const [agentFilter, setAgentFilter] = useState<'all' | 'working' | 'idle' | 'offline'>('all');
45
41
  // #84: last node.renamed event — passed to TopoGraph so an open chat
46
42
  // popover follows the rename instead of pointing at a dead alias. `ts`
@@ -75,22 +71,6 @@ export default function Dashboard() {
75
71
  },
76
72
  });
77
73
 
78
- // Fetch inbox (not in SWR since it accumulates)
79
- useEffect(() => {
80
- const fetchInbox = () => {
81
- fetch('/api/hub/inbox').then(r => r.json()).then(data => {
82
- if (data.messages?.length) setInbox(prev => {
83
- const ids = new Set(prev.map(m => m.id));
84
- const newMsgs = data.messages.filter((m: { id: string }) => !ids.has(m.id));
85
- return [...newMsgs, ...prev].slice(0, 100);
86
- });
87
- }).catch(() => {});
88
- };
89
- fetchInbox();
90
- const interval = setInterval(fetchInbox, 10000);
91
- return () => clearInterval(interval);
92
- }, []);
93
-
94
74
  if (isLoading) return <LoadingSkeleton />;
95
75
 
96
76
  const sseSessions = health?.sse_sessions || {};
@@ -328,8 +308,6 @@ export default function Dashboard() {
328
308
  ))}
329
309
  </section>
330
310
 
331
- <BroadcastBar />
332
-
333
311
  {/* Recent Activity */}
334
312
  {tasks.length > 0 && (
335
313
  <section className="mb-6 bg-[#111128] border border-[#2a2a4a] rounded-xl p-4">
@@ -454,8 +432,6 @@ export default function Dashboard() {
454
432
  );
455
433
  })()}
456
434
 
457
- <InboxPanel messages={inbox} />
458
-
459
435
  {/* Round 111 (issue #82): dropped the license badge — "trial (12d
460
436
  left)" read like a paywall countdown on an open-source dashboard
461
437
  and Vincent flagged it as misleading more than once. The SSE /
@@ -196,7 +196,7 @@ export default function ServerLogsPage() {
196
196
  value={search}
197
197
  onChange={e => setSearch(e.target.value)}
198
198
  placeholder="搜索关键字 (alias / task_id / error message)"
199
- className="flex-1 min-w-[140px] basis-full sm:basis-0 px-3 py-1.5 text-xs bg-[#11111c] border border-[#2a2a4a] rounded text-gray-200 focus:outline-none focus:border-cyan-500/40"
199
+ className="flex-1 min-w-[140px] basis-full sm:basis-0 px-3 py-1.5 text-base sm:text-xs bg-[#11111c] border border-[#2a2a4a] rounded text-gray-200 focus:outline-none focus:border-cyan-500/40"
200
200
  />
201
201
  <span className="text-[10px] text-gray-600">
202
202
  {filtered.length} / {logs.length}
@@ -252,8 +252,15 @@ export default function ServerLogsPage() {
252
252
  className="absolute left-0 top-0 bottom-0 w-0.5"
253
253
  style={{ backgroundColor: LEVEL_STRIPE[l.level] }}
254
254
  />
255
- <span className="text-gray-600 shrink-0 w-[100px] text-[10px] tabular-nums">{shortTime(l.ts)}</span>
256
- <span className={`shrink-0 px-1.5 rounded border text-[9px] uppercase ${LEVEL_BADGE[l.level]}`}>
255
+ {/* R4 of #190 mobile polish: the 100px timestamp column +
256
+ 32px LEVEL badge ate ~45% of a 375px row, squeezing log
257
+ content into a 3-line wrap. Drop the ts column to 60px
258
+ on mobile (HH:MM:SS still fits at 9px), restore 100px
259
+ at sm. The 2px left rail (LEVEL_STRIPE) already
260
+ encodes level visually for warn/error, so the LOG badge
261
+ is redundant on mobile — hide it below sm. */}
262
+ <span className="text-gray-600 shrink-0 w-[60px] sm:w-[100px] text-[9px] sm:text-[10px] tabular-nums">{shortTime(l.ts)}</span>
263
+ <span className={`hidden sm:inline shrink-0 px-1.5 rounded border text-[9px] uppercase ${LEVEL_BADGE[l.level]}`}>
257
264
  {l.level}
258
265
  </span>
259
266
  {/* Round 85: CommHub stamps each log line with a `[HH:MM:SS]`
@@ -111,9 +111,9 @@ export default function NetworksPage() {
111
111
  <h2 className="text-sm font-semibold text-gray-300 mb-3">Create Network</h2>
112
112
  <div className="space-y-3">
113
113
  <input type="text" value={newName} onChange={e => setNewName(e.target.value)} placeholder="Network name"
114
- 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" />
114
+ className="w-full bg-[#0a0a15] border border-[#2a2a4a] rounded-lg px-3 py-2 text-base sm:text-sm text-white placeholder-gray-600 focus:border-cyan-500/50 focus:outline-none" />
115
115
  <input type="text" value={newDesc} onChange={e => setNewDesc(e.target.value)} placeholder="Description (optional)"
116
- 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" />
116
+ className="w-full bg-[#0a0a15] border border-[#2a2a4a] rounded-lg px-3 py-2 text-base sm:text-sm text-white placeholder-gray-600 focus:border-cyan-500/50 focus:outline-none" />
117
117
  <button onClick={createNetwork} disabled={!newName.trim()}
118
118
  className="px-4 py-2 bg-green-600 hover:bg-green-500 disabled:bg-gray-800 text-white text-sm rounded-lg transition-colors">
119
119
  Create
@@ -199,7 +199,7 @@ export default function NetworksPage() {
199
199
  <div className="text-xs text-gray-500 mb-2">Invite</div>
200
200
  <div className="flex gap-2">
201
201
  <select value={inviteRole} onChange={e => setInviteRole(e.target.value)}
202
- className="bg-[#0a0a15] border border-[#2a2a4a] rounded px-2 py-1 text-xs text-white focus:outline-none">
202
+ className="bg-[#0a0a15] border border-[#2a2a4a] rounded px-2 py-1 text-base sm:text-xs text-white focus:outline-none">
203
203
  <option value="member">member</option>
204
204
  <option value="admin">admin</option>
205
205
  <option value="viewer">viewer</option>
@@ -2,15 +2,12 @@
2
2
 
3
3
  import { useState } from 'react';
4
4
  import Link from 'next/link';
5
- import { useAnetConfig, useHealth, useLicense } from '../lib/hooks';
5
+ import { useAnetConfig, useHealth } from '../lib/hooks';
6
6
  import { DASHBOARD_VERSION } from '../lib/version';
7
7
 
8
8
  export default function SettingsPage() {
9
9
  const { config } = useAnetConfig();
10
10
  const { health } = useHealth();
11
- const { license: licData } = useLicense();
12
- const [licKey, setLicKey] = useState('');
13
- const [licResult, setLicResult] = useState('');
14
11
  const [oldPwd, setOldPwd] = useState('');
15
12
  const [newPwd, setNewPwd] = useState('');
16
13
  const [pwdResult, setPwdResult] = useState('');
@@ -23,15 +20,18 @@ export default function SettingsPage() {
23
20
 
24
21
  {/* Section anchor nav (round 28) — jump to a group instead of scrolling
25
22
  through the whole page. Mirrors the section headers below; pages
26
- with 7+ cards benefit from a visible table-of-contents. */}
27
- <nav className="mb-8 flex flex-wrap gap-1 text-xs">
23
+ with 7+ cards benefit from a visible table-of-contents.
24
+ R3 of #190 mobile polish: matches the /admin chip treatment
25
+ (preview.3) — 44px tap-target + visible border so the chips read
26
+ as tappable rather than as inert headings on 375–390px. */}
27
+ <nav className="mb-8 flex flex-wrap gap-2 text-xs">
28
28
  {[
29
29
  { href: '#connection', label: 'Connection' },
30
30
  { href: '#account', label: 'Account' },
31
31
  { href: '#resources', label: 'Resources' },
32
32
  ].map(a => (
33
33
  <a key={a.href} href={a.href}
34
- className="rounded-md px-2.5 py-1 text-gray-500 hover:text-cyan-300 hover:bg-cyan-500/10 transition-colors">
34
+ 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">
35
35
  {a.label}
36
36
  </a>
37
37
  ))}
@@ -131,87 +131,14 @@ export default function SettingsPage() {
131
131
  <div className="flex-1 h-px bg-[#2a2a4a]" />
132
132
  </div>
133
133
 
134
- {/* License */}
135
- <section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
136
- <h2 className="text-sm font-semibold text-gray-300 mb-4 flex items-center gap-2">
137
- License
138
- {licData?.license && (
139
- <span
140
- className={`inline-flex items-center gap-1.5 text-[10px] font-medium px-2 py-0.5 rounded-full border ${
141
- licData.license.type === 'pro'
142
- ? 'text-green-300 bg-green-500/10 border-green-500/30'
143
- : licData.license.days_left <= 7
144
- ? 'text-red-300 bg-red-500/10 border-red-500/30'
145
- : 'text-amber-300 bg-amber-500/10 border-amber-500/30'
146
- }`}
147
- >
148
- <span aria-hidden className="w-1.5 h-1.5 rounded-full bg-current" />
149
- {licData.license.type}{licData.license.days_left ? ` · ${licData.license.days_left}d left` : ''}
150
- </span>
151
- )}
152
- </h2>
153
- {licData?.license ? (
154
- <div className="space-y-3 text-sm">
155
- {/* Type + Days Left are summarized in the inline chip in the
156
- header. Surface "expiring soon" only when relevant. */}
157
- {licData.license.days_left <= 7 && (
158
- <div className={rowClass}>
159
- <span className="text-red-400 font-medium">⚠ Expiring soon</span>
160
- <span className="text-red-400">{licData.license.days_left} days left</span>
161
- </div>
162
- )}
163
- <div className={rowClass}>
164
- <span className="text-gray-500">Expires</span>
165
- <span className={`text-gray-300 ${valueClass}`}>{licData.license.expires_at}</span>
166
- </div>
167
- {licData.limits && (
168
- <>
169
- <div className={rowClass}>
170
- <span className="text-gray-500">Max Agents</span>
171
- <span className={`text-gray-300 ${valueClass}`}>{licData.limits.max_agents}</span>
172
- </div>
173
- <div className={rowClass}>
174
- <span className="text-gray-500">Max Networks</span>
175
- <span className={`text-gray-300 ${valueClass}`}>{licData.limits.max_networks}</span>
176
- </div>
177
- <div className={rowClass}>
178
- <span className="text-gray-500">Tasks/Day</span>
179
- <span className={`text-gray-300 ${valueClass}`}>{licData.limits.max_tasks_day}</span>
180
- </div>
181
- </>
182
- )}
183
- <div className="pt-3 border-t border-[#2a2a4a]">
184
- <div className="flex gap-2">
185
- <input type="text" value={licKey} onChange={e => setLicKey(e.target.value)}
186
- placeholder="anet-XXXX-XXXX-XXXX-XXXX"
187
- className="flex-1 bg-[#0a0a15] border border-[#2a2a4a] rounded px-3 py-2 text-xs text-white placeholder-gray-600 focus:outline-none" />
188
- <button onClick={async () => {
189
- if (!licKey.trim()) return;
190
- const res = await fetch('/api/hub/license', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key: licKey }) });
191
- const data = await res.json();
192
- setLicResult(data.ok ? `Activated: ${data.type}` : `Failed: ${data.error}`);
193
- if (data.ok) setLicKey('');
194
- setTimeout(() => setLicResult(''), 5000);
195
- }} className="px-3 py-2 bg-cyan-600 hover:bg-cyan-500 text-white text-xs rounded transition-colors">
196
- Activate
197
- </button>
198
- </div>
199
- {licResult && <div className={`mt-2 text-xs ${licResult.startsWith('Failed') ? 'text-red-400' : 'text-green-400'}`}>{licResult}</div>}
200
- </div>
201
- </div>
202
- ) : (
203
- <div className="text-xs text-gray-600">License info not available</div>
204
- )}
205
- </section>
206
-
207
134
  {/* Change Password */}
208
135
  <section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
209
136
  <h2 className="text-sm font-semibold text-gray-300 mb-4">Change Password</h2>
210
137
  <div className="space-y-3">
211
138
  <input type="password" value={oldPwd} onChange={e => setOldPwd(e.target.value)} placeholder="Current password"
212
- 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" />
139
+ className="w-full bg-[#0a0a15] border border-[#2a2a4a] rounded-lg px-3 py-2 text-base sm:text-sm text-white placeholder-gray-600 focus:border-cyan-500/50 focus:outline-none" />
213
140
  <input type="password" value={newPwd} onChange={e => setNewPwd(e.target.value)} placeholder="New password"
214
- 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" />
141
+ className="w-full bg-[#0a0a15] border border-[#2a2a4a] rounded-lg px-3 py-2 text-base sm:text-sm text-white placeholder-gray-600 focus:border-cyan-500/50 focus:outline-none" />
215
142
  <button onClick={async () => {
216
143
  if (!oldPwd || !newPwd) return;
217
144
  const saved = sessionStorage.getItem('anet_v3_auth');
@@ -263,7 +190,13 @@ export default function SettingsPage() {
263
190
  <div className="flex-1 h-px bg-[#2a2a4a]" />
264
191
  </div>
265
192
 
266
- {/* API Tokens + Networks */}
193
+ {/* #209 (per Vincent 521/522): the low-frequency pages 通信龙
194
+ moved out of the sidebar (Messages, Audit Log, Server Logs)
195
+ need a discovery surface that isn't the primary nav — folded
196
+ into the Settings/Resources grid alongside Tokens + Networks
197
+ so the page tree stays the same (pages NOT deleted), the
198
+ sidebar stays at 6, and users still have a one-tap way to
199
+ reach them without remembering URLs or opening Cmd+K. */}
267
200
  <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
268
201
  <Link href="/settings/tokens" className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5 hover:border-cyan-500/30 transition-colors">
269
202
  <h2 className="text-sm font-semibold text-gray-300">API Tokens</h2>
@@ -275,6 +208,21 @@ export default function SettingsPage() {
275
208
  <p className="text-xs text-gray-500 mt-2">Create, manage, and delete agent networks.</p>
276
209
  <span className="text-xs text-cyan-400 mt-3 inline-block">Manage &rarr;</span>
277
210
  </Link>
211
+ <Link href="/messages" className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5 hover:border-cyan-500/30 transition-colors">
212
+ <h2 className="text-sm font-semibold text-gray-300">Messages</h2>
213
+ <p className="text-xs text-gray-500 mt-2">Global timeline of CommHub messages across all agents.</p>
214
+ <span className="text-xs text-cyan-400 mt-3 inline-block">Open &rarr;</span>
215
+ </Link>
216
+ <Link href="/logs" className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5 hover:border-cyan-500/30 transition-colors">
217
+ <h2 className="text-sm font-semibold text-gray-300">Audit Log</h2>
218
+ <p className="text-xs text-gray-500 mt-2">Authentication, token rotations, and admin actions.</p>
219
+ <span className="text-xs text-cyan-400 mt-3 inline-block">Open &rarr;</span>
220
+ </Link>
221
+ <Link href="/server-logs" className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5 hover:border-cyan-500/30 transition-colors">
222
+ <h2 className="text-sm font-semibold text-gray-300">Server Logs</h2>
223
+ <p className="text-xs text-gray-500 mt-2">Live stdout / stderr from the CommHub server.</p>
224
+ <span className="text-xs text-cyan-400 mt-3 inline-block">Open &rarr;</span>
225
+ </Link>
278
226
  </div>
279
227
  </div>
280
228
 
@@ -80,7 +80,7 @@ export default function TokensPage() {
80
80
  <div className="flex gap-2">
81
81
  <input type="text" value={newName} onChange={e => setNewName(e.target.value)}
82
82
  placeholder="Token name (e.g. my-cli)"
83
- className="flex-1 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" />
83
+ className="flex-1 bg-[#0a0a15] border border-[#2a2a4a] rounded-lg px-3 py-2 text-base sm:text-sm text-white placeholder-gray-600 focus:border-cyan-500/50 focus:outline-none" />
84
84
  <button onClick={createToken} disabled={!newName.trim()}
85
85
  className="px-4 py-2 bg-cyan-600 hover:bg-cyan-500 disabled:bg-gray-800 text-white text-sm rounded-lg transition-colors">
86
86
  Create
@@ -180,7 +180,7 @@ function TasksContent() {
180
180
  value={filterFrom}
181
181
  onChange={e => setFilterFrom(e.target.value)}
182
182
  placeholder="any node"
183
- className="w-28 bg-transparent text-sm text-white placeholder-gray-700 focus:outline-none"
183
+ className="w-28 bg-transparent text-base sm:text-sm text-white placeholder-gray-700 focus:outline-none"
184
184
  />
185
185
  </div>
186
186
  <div className="flex items-center gap-1.5 rounded-lg border border-[#2a2a4a] bg-[#111128] px-2.5 py-1.5 focus-within:border-blue-500/40">
@@ -190,7 +190,7 @@ function TasksContent() {
190
190
  value={filterTo}
191
191
  onChange={e => setFilterTo(e.target.value)}
192
192
  placeholder="any node"
193
- className="w-28 bg-transparent text-sm text-white placeholder-gray-700 focus:outline-none"
193
+ className="w-28 bg-transparent text-base sm:text-sm text-white placeholder-gray-700 focus:outline-none"
194
194
  />
195
195
  </div>
196
196
  {(filterStatus || filterFrom || filterTo) && (
@@ -248,7 +248,7 @@ function TasksContent() {
248
248
  : 'Tasks will appear here when agents send them via CommHub.'}
249
249
  />
250
250
  ) : (
251
- <div className="space-y-2">
251
+ <div className="space-y-1 sm:space-y-2">
252
252
  {/* Table header */}
253
253
  <div className="hidden sm:grid sm:grid-cols-12 gap-2 px-4 py-2 text-xs text-gray-600 uppercase">
254
254
  <div className="col-span-1">Status</div>
@@ -264,7 +264,7 @@ function TasksContent() {
264
264
  return (
265
265
  <div
266
266
  key={t.task_id}
267
- className={`anet-task-row group bg-[#111128] border rounded-lg px-4 py-3 transition-all duration-200 cursor-pointer ${
267
+ className={`anet-task-row group bg-[#111128] border rounded-lg px-3 py-2 sm:px-4 sm:py-3 transition-all duration-200 cursor-pointer ${
268
268
  isOpen
269
269
  ? 'border-[#3a3a5a] shadow-lg shadow-black/20'
270
270
  : 'border-[#2a2a4a] hover:border-[#3a3a5a] hover:bg-[#15152e]'
@@ -311,8 +311,12 @@ function TasksContent() {
311
311
  </div>
312
312
  </div>
313
313
 
314
- {/* Mobile layout */}
315
- <div className="sm:hidden space-y-2">
314
+ {/* Mobile layout — R8 of #190 mobile polish: 4-row stack
315
+ 3-row by inlining timeAgo onto the same row as the
316
+ from→to alias header (it's already 4 small atoms, has
317
+ room), plus space-y-2 → space-y-1 to trim ~4px per
318
+ row × 200 tasks. */}
319
+ <div className="sm:hidden space-y-1">
316
320
  <div className="flex items-center justify-between">
317
321
  <span className={statusBadge(t.status)}>{t.status}</span>
318
322
  <div className="flex items-center gap-2">
@@ -328,13 +332,13 @@ function TasksContent() {
328
332
  </div>
329
333
  <div className="flex items-center gap-1.5 text-xs text-gray-300 min-w-0">
330
334
  {t.from_name && <AliasAvatar alias={t.from_name} size={16} />}
331
- <span className="truncate max-w-[40%]">{t.from_name || '--'}</span>
335
+ <span className="truncate max-w-[35%]">{t.from_name || '--'}</span>
332
336
  <span className="text-gray-600">&rarr;</span>
333
337
  {t.to_name && <AliasAvatar alias={t.to_name} size={16} />}
334
- <span className="truncate max-w-[40%]">{t.to_name || '--'}</span>
338
+ <span className="truncate max-w-[35%]">{t.to_name || '--'}</span>
339
+ <span className="ml-auto shrink-0 text-[10px] text-gray-600">{timeAgo(t.created_at)}</span>
335
340
  </div>
336
341
  <div className="text-xs text-gray-400 line-clamp-1" title={t.content}>{previewContent(t.content)}</div>
337
- <div className="text-xs text-gray-600">{timeAgo(t.created_at)}</div>
338
342
  </div>
339
343
 
340
344
  {/* Expanded detail — always mounted; grid-rows 0fr↔1fr trick gives
package/bin/start.js CHANGED
File without changes