@xenon-device-management/xenon 2.14.0 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/README.md +44 -29
  2. package/lib/package.json +6 -5
  3. package/lib/public/assets/{AnnotationOverlay-B_VNtKBH.js → AnnotationOverlay-2W4JvEyZ.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-BwuaMjkX.js → ApiKeyGate-GmckX_7r.js} +1 -1
  5. package/lib/public/assets/DeviceMosaicView-DqODzkyo.js +2 -0
  6. package/lib/public/assets/{EmptyState-BSjhk_IV.js → EmptyState-7hETKEWb.js} +1 -1
  7. package/lib/public/assets/{FieldGroup-Cb5QZsE1.js → FieldGroup-GOgssFEP.js} +1 -1
  8. package/lib/public/assets/{FilterMenu-BAaSlWbW.js → FilterMenu-Yvf6RGMd.js} +1 -1
  9. package/lib/public/assets/{Menu-JGGYrSQQ.js → Menu-KrPnfdsf.js} +1 -1
  10. package/lib/public/assets/{Modal-CFCp3n9d.js → Modal-C6WjZFeI.js} +1 -1
  11. package/lib/public/assets/{RecordingPage-CbJj_EBT.js → RecordingPage-CSRIhZEh.js} +1 -1
  12. package/lib/public/assets/{RecordingsPage-BJeYnygR.js → RecordingsPage-CB8ST3nE.js} +1 -1
  13. package/lib/public/assets/{SegmentedControl-Bg42Atjy.js → SegmentedControl-C9o90KId.js} +1 -1
  14. package/lib/public/assets/{SettingCard-Njz1pUIJ.js → SettingCard-CPm3w9l2.js} +1 -1
  15. package/lib/public/assets/{Table-BcTm5xV2.js → Table-DwIgERRt.js} +1 -1
  16. package/lib/public/assets/{activity-DOVdsfMQ.js → activity-DrLbHifn.js} +1 -1
  17. package/lib/public/assets/ai-settings-DU_Ls06P.js +16 -0
  18. package/lib/public/assets/{api-keys-BKnNSTw9.js → api-keys-CWYdEzJ_.js} +1 -1
  19. package/lib/public/assets/{apps-D0SC6uw6.js → apps-BqNVdln7.js} +1 -1
  20. package/lib/public/assets/{arrow-left-MMbTmgoE.js → arrow-left-BtB0_Jg_.js} +1 -1
  21. package/lib/public/assets/{arrow-right-BMfnGmxW.js → arrow-right-C8NhdtyN.js} +1 -1
  22. package/lib/public/assets/{arrow-up-right-DuAftMWB.js → arrow-up-right-gMg0ffvv.js} +1 -1
  23. package/lib/public/assets/{auth-shell-nZC2JNDO.js → auth-shell-CiYZ3N4M.js} +2 -2
  24. package/lib/public/assets/{builds-page-hDCgTwkS.js → builds-page-EaaqDmLN.js} +1 -1
  25. package/lib/public/assets/{button-Dda4irn_.js → button-DDau4Axv.js} +1 -1
  26. package/lib/public/assets/{calendar-BMr0Q62v.js → calendar-DQ83B1ed.js} +1 -1
  27. package/lib/public/assets/{check-CZ9iw9-e.js → check-K1ovTQam.js} +1 -1
  28. package/lib/public/assets/{chevron-right-BRYJJVC9.js → chevron-right-Biq00pMt.js} +1 -1
  29. package/lib/public/assets/{circle-check-CqAJjQ5O.js → circle-check-CBv88cH7.js} +1 -1
  30. package/lib/public/assets/{circle-x-DlDJtnVz.js → circle-x-Bmltailn.js} +1 -1
  31. package/lib/public/assets/{clock-CpuDKysU.js → clock-BPrld-3h.js} +1 -1
  32. package/lib/public/assets/{copy-CAQydNFe.js → copy-t0QB14JI.js} +1 -1
  33. package/lib/public/assets/derive-UnLRp4r3.js +1 -0
  34. package/lib/public/assets/device-explorer-BgGZ8AvV.js +282 -0
  35. package/lib/public/assets/{download-C9XG6lLv.js → download-BroHxNOh.js} +1 -1
  36. package/lib/public/assets/forgot-password-Bjmf49i6.js +1 -0
  37. package/lib/public/assets/{h264Stream-VQBphYhg.js → h264Stream-DeD__6wa.js} +6 -6
  38. package/lib/public/assets/{index-DJGc1M2f.js → index-72oF89LJ.js} +1 -1
  39. package/lib/public/assets/index-B0JeuZbg.js +15 -0
  40. package/lib/public/assets/index-B0gQkEg0.js +219 -0
  41. package/lib/public/assets/{input-Cm0tmSoc.js → input-DIc4MSC7.js} +1 -1
  42. package/lib/public/assets/{line-chart-D2DyJCml.js → line-chart-BcWm2vW8.js} +1 -1
  43. package/lib/public/assets/{list-checks-UWf1GY99.js → list-checks-B06IJJad.js} +1 -1
  44. package/lib/public/assets/{lock-CCJv9m_I.js → lock-pVLvnN30.js} +1 -1
  45. package/lib/public/assets/{login-f8vOVW-U.js → login-CN77vy--.js} +1 -1
  46. package/lib/public/assets/{maintenance-settings-C1PrkuAH.js → maintenance-settings-CSdzQSkY.js} +1 -1
  47. package/lib/public/assets/{monitor-B2jDgwm7.js → monitor-BtrN5IkI.js} +1 -1
  48. package/lib/public/assets/{mouse-pointer-2-DLI1RtzU.js → mouse-pointer-2-CNsQfo6w.js} +1 -1
  49. package/lib/public/assets/{network-ebT86qoi.js → network-BSfA8ZxG.js} +1 -1
  50. package/lib/public/assets/{overview-C0aykVrd.js → overview-BfTebCK5.js} +1 -1
  51. package/lib/public/assets/{page-header-BjzgWz-T.js → page-header-D8KUX6Qg.js} +1 -1
  52. package/lib/public/assets/{play-DLG9t8FU.js → play-Cvc8xzic.js} +1 -1
  53. package/lib/public/assets/{plus-nBFSMu_X.js → plus-C9Zj84UF.js} +1 -1
  54. package/lib/public/assets/{profile-page-CSl0UYlv.js → profile-page-Bi27STgs.js} +1 -1
  55. package/lib/public/assets/{reset-password-LeOuHqmB.js → reset-password-CPsOuwSu.js} +1 -1
  56. package/lib/public/assets/runbook-page-DEQ-DBOX.js +304 -0
  57. package/lib/public/assets/{select-88jHcl-Z.js → select-BWpnhWXj.js} +1 -1
  58. package/lib/public/assets/{selector-detail-redirect-BbXKJjZM.js → selector-detail-redirect-BefSCSpe.js} +1 -1
  59. package/lib/public/assets/{selector-health-page-B4rA1Xkv.js → selector-health-page-MSk96Zz7.js} +1 -1
  60. package/lib/public/assets/session-detail-page-DPyBNef_.js +4 -0
  61. package/lib/public/assets/{settings-Dep_3kbA.css → settings-K8UHWMdI.css} +1 -1
  62. package/lib/public/assets/{settings-GF6ZMSQ6.js → settings-V6x-cXwy.js} +1 -1
  63. package/lib/public/assets/{stat-tile-BV98e_Sv.js → stat-tile-Duex-GZD.js} +1 -1
  64. package/lib/public/assets/{tablet-BiAsnFDj.js → tablet-BBcoqBmL.js} +1 -1
  65. package/lib/public/assets/{teams-B4-jrr-1.js → teams-ts8p5rEh.js} +1 -1
  66. package/lib/public/assets/{trash-2-C5RSuX0e.js → trash-2-CZ2ew6AZ.js} +1 -1
  67. package/lib/public/assets/{upload-BG7_rMXK.js → upload-Dv99y_le.js} +1 -1
  68. package/lib/public/assets/{use-builds-data-J-riQYyC.js → use-builds-data-DrYC8JCF.js} +1 -1
  69. package/lib/public/assets/{use-password-reset-mode-C8-eH-UG.js → use-password-reset-mode-C7QkBNkP.js} +1 -1
  70. package/lib/public/assets/{users-CFba5TSy.js → users-D7tKMESH.js} +1 -1
  71. package/lib/public/assets/{users-Cp0beJm3.js → users-gJ5Y01yo.js} +1 -1
  72. package/lib/public/assets/{video-off-DDsEISjV.js → video-off-CqZVuZGs.js} +1 -1
  73. package/lib/public/assets/{webhook-settings-BGu32vSB.js → webhook-settings-DI5DSW99.js} +1 -1
  74. package/lib/public/assets/{zap-l4VaXzq8.js → zap-ClcHFxfo.js} +1 -1
  75. package/lib/public/index.html +1 -1
  76. package/lib/schema.json +16 -12
  77. package/lib/src/InternalHttpClient.js +27 -5
  78. package/lib/src/XenonCapabilityManager.js +10 -3
  79. package/lib/src/app/dashboardPluginLink.js +117 -0
  80. package/lib/src/app/index.js +13 -34
  81. package/lib/src/app/openapi/control.yaml +6 -4
  82. package/lib/src/app/openapi/grid.yaml +8 -2
  83. package/lib/src/app/openapi/identity.yaml +103 -31
  84. package/lib/src/app/openapi/platform.yaml +33 -15
  85. package/lib/src/app/openapi/sessions.yaml +12 -3
  86. package/lib/src/app/routers/apikeys.js +14 -2
  87. package/lib/src/app/routers/auth.js +31 -6
  88. package/lib/src/app/routers/control.js +13 -5
  89. package/lib/src/app/routers/dashboard.js +42 -27
  90. package/lib/src/app/routers/grid.js +5 -3
  91. package/lib/src/app/routers/profile.js +21 -0
  92. package/lib/src/app/routers/users.js +12 -6
  93. package/lib/src/app/swagger.js +2 -0
  94. package/lib/src/app/ws/h264StreamWs.js +10 -1
  95. package/lib/src/app/ws/nodeSocketRelay.js +77 -11
  96. package/lib/src/config.js +1 -0
  97. package/lib/src/dashboard/commandLogFields.js +42 -0
  98. package/lib/src/dashboard/commands.js +5 -5
  99. package/lib/src/dashboard/event-manager.js +64 -204
  100. package/lib/src/dashboard/services/failure-analysis-service.js +118 -105
  101. package/lib/src/dashboard/services/failureCategories.js +264 -0
  102. package/lib/src/dashboard/sessionCommandSummary.js +39 -0
  103. package/lib/src/data-service/device-store.js +14 -1
  104. package/lib/src/data-service/prisma-store.js +19 -7
  105. package/lib/src/data-service/web-config-service.js +7 -0
  106. package/lib/src/device-managers/AndroidDeviceManager.js +61 -23
  107. package/lib/src/device-managers/IOSDeviceManager.js +10 -0
  108. package/lib/src/device-managers/android/AndroidH264StreamService.js +69 -20
  109. package/lib/src/device-managers/android/H264Multiplexer.js +38 -4
  110. package/lib/src/device-utils.js +29 -3
  111. package/lib/src/gateway/defaultGateway.js +1 -1
  112. package/lib/src/gateway/forwardToNode.js +6 -11
  113. package/lib/src/gateway/healReport.js +87 -0
  114. package/lib/src/gateway/hubSessionToken.js +27 -4
  115. package/lib/src/gateway/nodeWebDriverUrl.js +3 -5
  116. package/lib/src/gateway/sessionGateway.js +12 -2
  117. package/lib/src/generated/client/edge.js +4 -3
  118. package/lib/src/generated/client/index-browser.js +1 -0
  119. package/lib/src/generated/client/index.d.ts +32 -0
  120. package/lib/src/generated/client/index.js +4 -3
  121. package/lib/src/generated/client/package.json +1 -1
  122. package/lib/src/generated/client/schema.prisma +3 -0
  123. package/lib/src/generated/client/wasm.js +1 -0
  124. package/lib/src/helpers/outboundProxy.js +219 -0
  125. package/lib/src/index.js +21 -0
  126. package/lib/src/interceptors/CommandInterceptor.js +256 -196
  127. package/lib/src/interceptors/xenonScripts.js +28 -7
  128. package/lib/src/logger.js +3 -1
  129. package/lib/src/middleware/authMiddleware.js +15 -3
  130. package/lib/src/middleware/commandCaller.js +3 -3
  131. package/lib/src/middleware/verifyCredential.js +58 -1
  132. package/lib/src/plugin.js +16 -6
  133. package/lib/src/scripts/database-check.js +3 -3
  134. package/lib/src/scripts/initialize-database.js +35 -33
  135. package/lib/src/scripts/run-migrations.js +1079 -27
  136. package/lib/src/services/AICommandService.js +9 -2
  137. package/lib/src/services/AIService.js +192 -42
  138. package/lib/src/services/ApiKeyService.js +16 -5
  139. package/lib/src/services/EmailService.js +29 -0
  140. package/lib/src/services/InterceptorService.js +6 -2
  141. package/lib/src/services/OrphanSweeper.js +8 -0
  142. package/lib/src/services/SelectorStateService.js +11 -7
  143. package/lib/src/services/SelectorVerificationJob.js +21 -6
  144. package/lib/src/services/ServerManager.js +11 -1
  145. package/lib/src/services/SessionLifecycleService.js +118 -44
  146. package/lib/src/services/SocketClient.js +11 -0
  147. package/lib/src/services/SocketServer.js +380 -45
  148. package/lib/src/services/TeamService.js +5 -1
  149. package/lib/src/services/TracingService.js +171 -60
  150. package/lib/src/services/UserService.js +14 -3
  151. package/lib/src/services/UserSessionService.js +16 -4
  152. package/lib/src/services/bug-report/video-slice.js +4 -1
  153. package/lib/src/services/device-access/leaseOverride.js +7 -5
  154. package/lib/src/services/healing/FuzzyXmlHealingProvider.js +51 -0
  155. package/lib/src/services/healing/HealEtalonService.js +3 -2
  156. package/lib/src/services/healing/HealingOrchestrator.js +57 -72
  157. package/lib/src/services/healing/OcrHealingProvider.js +41 -64
  158. package/lib/src/services/healing/ResilioTreeHealingProvider.js +41 -50
  159. package/lib/src/services/healing/VisualAiHealingProvider.js +1 -1
  160. package/lib/src/services/healing/fingerprintIdentity.js +126 -0
  161. package/lib/src/services/healing/resilioPath.js +274 -0
  162. package/lib/src/services/identity/identityChanges.js +41 -0
  163. package/lib/src/services/logcat/SessionDeviceLogs.js +358 -0
  164. package/lib/src/services/logcat/deviceLogBook.js +164 -0
  165. package/lib/src/services/logcat/iosDriverLog.js +178 -0
  166. package/lib/src/services/ocr/ocrData.js +118 -0
  167. package/lib/src/services/ocr/vendor/LICENSE-tessdata.txt +202 -0
  168. package/lib/src/services/ocr/vendor/eng.traineddata.gz +0 -0
  169. package/lib/src/services/omni-vision/OmniVisionService.js +83 -56
  170. package/lib/src/services/passwordResetLink.js +50 -9
  171. package/lib/src/services/passwordResetMode.js +3 -1
  172. package/lib/src/services/publicUrl.js +45 -0
  173. package/lib/src/services/selector-health/SelectorVisibilityResolver.js +121 -0
  174. package/lib/src/services/session/sessionIdentity.js +5 -3
  175. package/lib/src/services/session/shutdownReason.js +26 -0
  176. package/lib/src/services/settings/aiEngineSettings.js +219 -0
  177. package/lib/src/services/token/JwtKeyService.js +8 -2
  178. package/lib/src/services/token/mcpTokenTtl.js +27 -0
  179. package/lib/src/services/token/mintedLifetime.js +51 -0
  180. package/lib/src/sessions/LocalSession.js +29 -1
  181. package/lib/src/sessions/RemoteSession.js +12 -2
  182. package/lib/src/sessions/SessionManager.js +4 -1
  183. package/lib/src/sessions/sessionMemory.js +25 -0
  184. package/lib/test/helpers/driver-log.js +38 -0
  185. package/lib/test/helpers/fake-ai-provider.js +188 -0
  186. package/lib/test/helpers/fake-http-proxy.js +106 -0
  187. package/lib/test/helpers/loki-stores.js +33 -0
  188. package/lib/test/helpers/otlp-probe.js +107 -0
  189. package/lib/test/helpers/page-sources.js +164 -0
  190. package/lib/test/helpers/scratch-database.js +57 -13
  191. package/lib/test/integration/androidDevices.spec.js +1 -1
  192. package/lib/test/integration/audit-ingest.spec.js +10 -10
  193. package/lib/test/integration/auth-flow.spec.js +9 -14
  194. package/lib/test/integration/auth-rate-limit.spec.js +6 -3
  195. package/lib/test/integration/bug-report-route.spec.js +22 -2
  196. package/lib/test/integration/forgot-password-rate-limit.spec.js +6 -3
  197. package/lib/test/integration/forgot-password.spec.js +17 -18
  198. package/lib/test/integration/ios/01iOSSimulator.spec.js +1 -1
  199. package/lib/test/integration/ios/02iOSDevices.spec.js +1 -1
  200. package/lib/test/integration/legacy-deprecation-headers.spec.js +5 -2
  201. package/lib/test/integration/migration.spec.js +6 -1
  202. package/lib/test/integration/node-pair-auth-rest.spec.js +5 -9
  203. package/lib/test/integration/ports-router.spec.js +4 -4
  204. package/lib/test/integration/prisma-device-store-add-devices.spec.js +2 -3
  205. package/lib/test/integration/prisma-device-store-team-filter.spec.js +2 -5
  206. package/lib/test/integration/profile-tokens.spec.js +10 -16
  207. package/lib/test/integration/recordings-router.spec.js +16 -16
  208. package/lib/test/integration/reset-revokes-sessions.spec.js +4 -9
  209. package/lib/test/integration/role-matrix.spec.js +4 -9
  210. package/lib/test/integration/sdk-leases-router.spec.js +9 -9
  211. package/lib/test/integration/sdk-version.spec.js +2 -2
  212. package/lib/test/integration/selector-health-endpoint.spec.js +5 -5
  213. package/lib/test/integration/selector-state-flow.spec.js +5 -5
  214. package/lib/test/integration/team-membership-migration.spec.js +73 -12
  215. package/lib/test/integration/team-visibility-apps.spec.js +15 -22
  216. package/lib/test/integration/team-visibility-control.spec.js +96 -69
  217. package/lib/test/integration/team-visibility-dashboard.spec.js +7 -23
  218. package/lib/test/integration/team-visibility-grid.spec.js +16 -15
  219. package/lib/test/integration/team-visibility-leases.spec.js +8 -11
  220. package/lib/test/integration/team-visibility-recordings.spec.js +6 -19
  221. package/lib/test/integration/team-visibility-sessions.spec.js +6 -19
  222. package/lib/test/integration/users-flow.spec.js +7 -12
  223. package/lib/test/integration/users-lockout.spec.js +5 -3
  224. package/lib/test/unit/EmailService.test.js +66 -0
  225. package/lib/test/unit/ResilioTreeHealingProvider.test.js +46 -19
  226. package/lib/test/unit/XenonCapabilityManager.spec.js +23 -0
  227. package/lib/test/unit/ai-call-time-limit.spec.js +130 -0
  228. package/lib/test/unit/ai-engine-settings.spec.js +338 -0
  229. package/lib/test/unit/ai-provider-switch.spec.js +130 -0
  230. package/lib/test/unit/ai-rate-limit.spec.js +126 -0
  231. package/lib/test/unit/android-adb-retry.spec.js +112 -0
  232. package/lib/test/unit/android-h264-viewers.spec.js +272 -0
  233. package/lib/test/unit/android-preview-hold.spec.js +3 -2
  234. package/lib/test/unit/apikeys-router.spec.js +84 -0
  235. package/lib/test/unit/authMiddleware.bearer.spec.js +9 -0
  236. package/lib/test/unit/authMiddleware.credentialLifetime.spec.js +149 -0
  237. package/lib/test/unit/bug-report/video-slice.spec.js +19 -0
  238. package/lib/test/unit/command-log-network-capture.spec.js +191 -0
  239. package/lib/test/unit/dashboard-events-device-logs.spec.js +255 -0
  240. package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
  241. package/lib/test/unit/dashboard-plugin-link.spec.js +213 -0
  242. package/lib/test/unit/database-check.spec.js +1 -0
  243. package/lib/test/unit/device-log-book.spec.js +180 -0
  244. package/lib/test/unit/device-utils.spec.js +13 -4
  245. package/lib/test/unit/enable-dashboard-option.spec.js +57 -0
  246. package/lib/test/unit/event-log-session-data.spec.js +251 -0
  247. package/lib/test/unit/failure-analysis.spec.js +196 -0
  248. package/lib/test/unit/failure-categories.spec.js +130 -0
  249. package/lib/test/unit/failure-categorization.spec.js +393 -0
  250. package/lib/test/unit/fingerprint-identity.spec.js +433 -0
  251. package/lib/test/unit/h264-multiplexer.spec.js +46 -0
  252. package/lib/test/unit/heal-report.spec.js +71 -0
  253. package/lib/test/unit/healing-instrumentation.spec.js +6 -1
  254. package/lib/test/unit/healing-tier-gate.spec.js +74 -29
  255. package/lib/test/unit/healing-tiers-every-session.spec.js +219 -0
  256. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +243 -1
  257. package/lib/test/unit/interceptor-visual-elements.spec.js +192 -0
  258. package/lib/test/unit/ios-driver-log.spec.js +168 -0
  259. package/lib/test/unit/ios-simulator-session-readiness.spec.js +187 -0
  260. package/lib/test/unit/ios-simulator-session-teardown.spec.js +158 -0
  261. package/lib/test/unit/lease/lease-override.spec.js +23 -6
  262. package/lib/test/unit/lease/session-lease-proof.spec.js +16 -4
  263. package/lib/test/unit/logcat-ws.spec.js +78 -37
  264. package/lib/test/unit/logger-json-levels.spec.js +44 -0
  265. package/lib/test/unit/mcp-token-ttl.spec.js +38 -0
  266. package/lib/test/unit/observability-example-loki.spec.js +25 -0
  267. package/lib/test/unit/ocr-healing-tier-real-ocr.spec.js +113 -0
  268. package/lib/test/unit/ocr-healing-tier.spec.js +122 -0
  269. package/lib/test/unit/ocr-offline.spec.js +213 -0
  270. package/lib/test/unit/omni-virtual-element-store.spec.js +301 -0
  271. package/lib/test/unit/omni-vision-ios-points.spec.js +0 -2
  272. package/lib/test/unit/omni-vision.spec.js +5 -7
  273. package/lib/test/unit/openapi-for-site.spec.js +80 -0
  274. package/lib/test/unit/outbound-proxy.spec.js +320 -0
  275. package/lib/test/unit/package-engines.spec.js +27 -0
  276. package/lib/test/unit/passwordResetMode.test.js +10 -2
  277. package/lib/test/unit/port-exhaustion.spec.js +2 -1
  278. package/lib/test/unit/profile-router.test.js +122 -1
  279. package/lib/test/unit/proxy-option.spec.js +358 -0
  280. package/lib/test/unit/queue-requester.spec.js +1 -1
  281. package/lib/test/unit/reset-link-address.spec.js +266 -0
  282. package/lib/test/unit/reset-link.test.js +10 -4
  283. package/lib/test/unit/resilio-path-healing.spec.js +270 -0
  284. package/lib/test/unit/run-migrations-database.spec.js +807 -0
  285. package/lib/test/unit/run-migrations-fail-fast.test.js +41 -27
  286. package/lib/test/unit/run-migrations.test.js +864 -0
  287. package/lib/test/unit/selector-health-actions.spec.js +3 -1
  288. package/lib/test/unit/selector-learning-every-session.spec.js +179 -0
  289. package/lib/test/unit/selector-state-service.spec.js +72 -23
  290. package/lib/test/unit/selector-verification-clean-build.spec.js +130 -0
  291. package/lib/test/unit/selector-verification-job.spec.js +28 -19
  292. package/lib/test/unit/selector-verification-missed-regression.spec.js +2 -2
  293. package/lib/test/unit/selector-visibility-resolver.spec.js +209 -0
  294. package/lib/test/unit/self-healing-boot.spec.js +10 -5
  295. package/lib/test/unit/self-healing-setting.spec.js +7 -6
  296. package/lib/test/unit/session-app-resolution.spec.js +5 -1
  297. package/lib/test/unit/session-attribution-wiring.spec.js +41 -13
  298. package/lib/test/unit/session-create-credential-owner.spec.js +242 -0
  299. package/lib/test/unit/session-credential-strip.spec.js +1 -1
  300. package/lib/test/unit/session-device-logs-ios.spec.js +180 -0
  301. package/lib/test/unit/session-device-logs.spec.js +356 -0
  302. package/lib/test/unit/session-end-failure-analysis.spec.js +133 -0
  303. package/lib/test/unit/session-log-indexes.spec.js +64 -3
  304. package/lib/test/unit/socket-team-scope.integration.spec.js +749 -0
  305. package/lib/test/unit/socketServer.scopedEmit.spec.js +237 -5
  306. package/lib/test/unit/socketServer.sessionCookie.spec.js +2 -1
  307. package/lib/test/unit/stream-start-pipelines.spec.js +72 -18
  308. package/lib/test/unit/token-mint-limits.spec.js +165 -0
  309. package/lib/test/unit/tracing-export-routing.spec.js +137 -0
  310. package/lib/test/unit/tracing-session-spans.spec.js +337 -0
  311. package/lib/test/unit/unexpected-shutdown-reason.spec.js +163 -0
  312. package/lib/test/unit/verify-credential.spec.js +33 -0
  313. package/lib/test/unit/vision-assertion.spec.js +3 -2
  314. package/lib/test/unit/web-config-service.spec.js +19 -0
  315. package/lib/tsconfig.tsbuildinfo +1 -1
  316. package/package.json +6 -5
  317. package/prisma/migrations/20261004174413_locator_etalon_path/migration.sql +2 -0
  318. package/prisma/schema.prisma +3 -0
  319. package/schema.json +16 -12
  320. package/scripts/dev/readme-screenshots.js +276 -23
  321. package/scripts/dev/screenshot-fixtures/logcat.js +157 -0
  322. package/scripts/dev/screenshot-fixtures/phone-screen.html +469 -0
  323. package/scripts/dev/screenshot-fixtures/session.js +242 -0
  324. package/scripts/export-openapi.js +32 -0
  325. package/scripts/lib/openapi-for-site.js +79 -0
  326. package/lib/public/assets/BugReportButton-BAt0_pzq.js +0 -15
  327. package/lib/public/assets/DeviceMosaicView-BYaQHhTf.js +0 -2
  328. package/lib/public/assets/ai-settings-CsuJroRf.js +0 -21
  329. package/lib/public/assets/device-explorer-CdO6_nrn.js +0 -282
  330. package/lib/public/assets/forgot-password-CGnkRgE9.js +0 -1
  331. package/lib/public/assets/index-BbKEXUFH.js +0 -219
  332. package/lib/public/assets/runbook-page-CMMeQml9.js +0 -124
  333. package/lib/public/assets/session-detail-page-COL_RZxw.js +0 -4
  334. package/lib/src/scripts/prepare-prisma.js +0 -89
  335. /package/lib/public/assets/{BugReportButton-CPBjfiv4.css → index-CPBjfiv4.css} +0 -0
@@ -0,0 +1,4 @@
1
+ import{r as x,X as S,j as e,e as te,f as Ae,t as H,y as Ee,R as De,p as Le,i as Me,u as Oe}from"./index-B0gQkEg0.js";import{C as Fe,d as Pe,p as Ne,e as Ie,r as ye,f as He,j as we,k as X,c as Be,h as re,u as Ue,b as Ve,q as ze}from"./use-builds-data-DrYC8JCF.js";import{A as Je}from"./arrow-left-BtB0_Jg_.js";import{C as K}from"./chevron-right-Biq00pMt.js";import{s as We}from"./labels-C21SA4JG.js";import{B as Ke,S as ke,u as qe,P as $}from"./index-B0JeuZbg.js";import{C as Xe}from"./circle-x-Bmltailn.js";import{C as Ge}from"./circle-check-CBv88cH7.js";import{S as F}from"./stat-tile-Duex-GZD.js";import{h as G,p as Ye,a as se,b as ae}from"./derive-UnLRp4r3.js";import{C as Y}from"./copy-t0QB14JI.js";import{A as Qe}from"./arrow-up-right-gMg0ffvv.js";import{A as Ze}from"./arrow-right-C8NhdtyN.js";import{f as et,a as ne,C as Se,b as tt,s as rt,L as st,n as ie}from"./line-chart-BcWm2vW8.js";import{E as M}from"./EmptyState-7hETKEWb.js";import{A as at}from"./activity-DrLbHifn.js";import{V as nt}from"./video-off-CqZVuZGs.js";import{D as _e}from"./download-BroHxNOh.js";import{T as it,a as ot,b as oe,c as E,d as lt,e as D}from"./Table-DwIgERRt.js";import{M as ct}from"./Modal-C6WjZFeI.js";import{N as dt}from"./network-BSfA8ZxG.js";import"./time-D-gjmE1o.js";import"./index-72oF89LJ.js";const xt=4e3,le=t=>!!t&&typeof t=="object"&&typeof t.id=="string"&&typeof t.status=="string";function mt(t){const[r,s]=x.useState(null),[a,n]=x.useState([]),[l,o]=x.useState([]),[i,c]=x.useState([]),[m,b]=x.useState([]),[f,j]=x.useState(!0),[p,g]=x.useState(null),[y,w]=x.useState(!1),[R,A]=x.useState(0),_=x.useRef(null);x.useEffect(()=>{if(!t){s(null),n([]),o([]),c([]),b([]),w(!1),j(!1);return}let v=!0;return _.current!==t&&j(!0),Promise.all([S.getSession(t),S.getSessionLogs(t),S.getDeviceLogs(t),S.getDebugLogs(t),S.getProfilingData(t).catch(()=>[])]).then(([h,C,d,N,L])=>{if(!v)return;const O=le(h);s(O?h:null),O?(_.current=t,w(!1),g(null)):(w(!!h&&typeof h=="object"&&(h.error===!0||typeof h.message=="string")),g(typeof(h==null?void 0:h.message)=="string"?h.message:"Session not found")),n(Array.isArray(C)?C:[]),o(Array.isArray(d)?d:[]),c(Array.isArray(N)?N:[]),b(Array.isArray(L)?L:[])}).catch(h=>{v&&g(h instanceof Error?h.message:String(h))}).finally(()=>{v&&j(!1)}),()=>{v=!1}},[t,R]);const u=(r==null?void 0:r.status)==="running";return x.useEffect(()=>{if(!t||!u)return;let v=!0;const h=setInterval(async()=>{try{const[C,d]=await Promise.all([S.getSession(t),S.getSessionLogs(t)]);if(!v||!le(C))return;s(C),Array.isArray(d)&&n(d),C.status!=="running"&&A(N=>N+1)}catch{}},xt);return()=>{v=!1,clearInterval(h)}},[t,u]),{session:r,sessionLogs:a,deviceLogs:l,debugLogs:i,profiling:m,loading:f,error:p,notFound:y,refresh:()=>A(v=>v+1)}}const ce=({buildId:t,buildName:r,sessionId:s})=>e.jsxs("nav",{"aria-label":"Breadcrumb",className:"flex items-center gap-2 px-4 py-2 border-b border-[var(--border)] bg-[var(--surface)]",children:[e.jsxs(te,{to:"/builds",className:"inline-flex items-center gap-1.5 text-xs text-[var(--text-muted)] hover:text-[var(--text)] transition-colors",children:[e.jsx(Je,{className:"h-3.5 w-3.5"}),"Sessions"]}),e.jsx(K,{className:"h-3 w-3 text-[var(--text-dim)]"}),r&&e.jsxs(e.Fragment,{children:[e.jsx(te,{to:`/builds/${t}`,className:"text-[11px] text-[var(--color-accent)] hover:underline",children:r}),e.jsx(K,{className:"h-3 w-3 text-[var(--text-dim)]"})]}),e.jsxs("span",{className:"font-mono text-[11px] text-[var(--text-muted)]",title:s,children:["#",s.slice(0,8)]})]}),ut={passed:{label:"Passed",Icon:Ge,cls:"text-[var(--color-success)]"},failed:{label:"Failed",Icon:Xe,cls:"text-[var(--color-danger)]"},running:{label:"Running",Icon:Ae,cls:"text-[var(--color-warning)]"},other:{Icon:Fe,cls:"text-[var(--text-dim)]"}};function Q(t){const r=Be(t),s=ut[r];return{bucket:r,...s,label:s.label??We(t||"unknown")}}const ht=({session:t})=>{var l,o;const r=Q(t.status),s=Pe(t),a=[Ne(t),t.device_version].filter(i=>i&&i!=="—").join(" "),n=[[Ie(t),a].filter(Boolean).join(" · "),ye(t.ranOn),(l=t.owner)==null?void 0:l.name,He(t.startTime),we(X(t))].filter(i=>i&&i!=="—");return e.jsxs("header",{className:"flex items-start justify-between gap-4 px-4 py-4 border-b border-[var(--border)] bg-[var(--surface)]",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2 min-w-0",children:[e.jsxs("span",{className:`inline-flex items-center gap-1.5 text-xs font-medium shrink-0 ${r.cls}`,children:[e.jsx(r.Icon,{className:`h-3.5 w-3.5 ${r.bucket==="running"?"animate-spin":""}`,"aria-hidden":"true"}),r.label]}),e.jsx("h1",{className:`text-lg font-semibold truncate ${s.source==="name"?"text-[var(--text)]":"text-[var(--text-muted)]"}`,title:s.source==="name"?s.text:`${s.text}
2
+ Unnamed session. Set xe:options.name in its capabilities to name it.`,children:s.text})]}),e.jsx("p",{"data-testid":"outcome-meta",className:"mt-1 text-xs text-[var(--text-muted)] tabular-nums truncate",title:((o=t.owner)==null?void 0:o.email)??void 0,children:n.join(" · ")})]}),e.jsx("div",{className:"shrink-0",children:e.jsx(Ke,{sessionId:t.id,mode:"full",variant:"inline"})})]})},B=t=>t.command_name||t.title||"command",q=t=>{const r=t.createdAt?Date.parse(t.createdAt):NaN;return Number.isFinite(r)?r:0},Z=t=>[...t].sort((r,s)=>q(r)-q(s));function U(t){return t.is_success===!1||t.is_error===!0}function Ce(t){const r=[];let s=null;for(const a of Z(t))a.is_healed&&a.healing_tier&&!r.includes(a.healing_tier)&&r.push(a.healing_tier),typeof a.duration=="number"&&(!s||a.duration>s.ms)&&(s={name:B(a),ms:a.duration});return{total:t.length,failed:t.filter(U).length,healed:t.filter(a=>a.is_healed).length,healedTiers:r,slowest:s,screenshots:t.filter(a=>a.screenshot).length}}const de=(t,r)=>r?t?`${t}=${r}`:r:null;function ft(t){return Z(t.filter(r=>r.is_healed)).map(r=>({id:r.id,command:B(r),original:de(r.original_strategy,r.original_selector),healed:de(r.healed_strategy,r.healed_selector),tier:r.healing_tier??null,confidence:typeof r.healing_confidence=="number"?Math.round(r.healing_confidence*100):null,at:r.createdAt}))}function pt(t){var r;if(!t.response)return null;try{const s=(r=JSON.parse(t.response))==null?void 0:r.value;if(!s||typeof s!="object")return null;const a=typeof s.message=="string"?s.message.trim():"";return a||(typeof s.error=="string"&&s.error?s.error:null)}catch{return null}}function vt(t){if(t.length===0)return{spanMs:0,bars:[]};const r=t.map(n=>{const l=q(n),o=typeof n.duration=="number"&&n.duration>0?n.duration:0;return{l:n,start:l-o,end:l,durationMs:o}}),s=Math.min(...r.map(n=>n.start)),a=Math.max(...r.map(n=>n.end));return{spanMs:Math.max(1,a-s),bars:r.sort((n,l)=>n.start-l.start).map(({l:n,start:l,durationMs:o})=>({id:n.id,label:B(n),startMs:l-s,durationMs:o,failed:U(n),healed:!!n.is_healed}))}}function bt(t){return Z(t.filter(r=>r.screenshot)).map(r=>({id:r.id,path:r.screenshot,command:B(r),at:r.createdAt,failed:U(r)}))}function I(t){if(t==null||!Number.isFinite(t)||t<0)return"—";if(t<1e3)return`${Math.round(t)}ms`;if(t<6e4)return`${(t/1e3).toFixed(1)}s`;const r=Math.round(t/1e3);return`${Math.floor(r/60)}m ${r%60}s`}const gt=({session:t,commands:r})=>{var o,i;const s=Q(t.status),a=Ce(r),n=we(X(t)),l=s.bucket==="failed"?G(t.failure_category)||"No category recorded":s.bucket==="running"?`Running for ${n}`:`Ran ${n}`;return e.jsxs("div",{className:"grid grid-cols-4 gap-3",children:[e.jsx(F,{label:"Result",value:s.label,valueCls:s.cls,note:l,noteTone:s.bucket==="failed"?"bad":"neutral"}),e.jsx(F,{label:"Commands",value:a.total,note:a.failed>0?`${a.failed} failed`:"None failed",noteTone:a.failed>0?"bad":"neutral"}),e.jsx(F,{label:"Self-healing",value:a.healed>0?`${a.healed} healed`:"None",note:a.healed>0?a.healedTiers.join(", ")||"Tier not recorded":"No selector needed healing"}),e.jsx(F,{label:"Slowest command",value:I((o=a.slowest)==null?void 0:o.ms),note:((i=a.slowest)==null?void 0:i.name)??"No timings recorded"})]})};function jt(t){const r=t.filter(U),s=r[r.length-1];return s?{command:s.command_name||s.title||"command",message:pt(s)}:null}function Nt(t,r,s,a,n){var m;const l=G(t.failure_category),o=((m=t.device_name)==null?void 0:m.trim())||"Unknown Device",i=t.device_version?` ${t.device_version}`:"",c=n?`${n.command}${n.message?`: ${n.message}`:""}`:"(no failed command recorded)";return[`**Session:** ${t.id}`,`**Build:** ${r} (#${s})`,`**Device:** ${o} (${t.node_id||"unknown-node"}) · ${t.device_platform||"unknown"}${i}`,`**Duration:** ${a}`,`**Failure category:** ${l||"(uncategorized)"}`,`**Failure reason:** ${t.failure_reason||"(not set)"}`,`**First failed command:** ${c}`,...t.ai_analysis?[`**AI analysis:** ${t.ai_analysis}`]:[]].join(`
3
+ `)}function yt({text:t}){return e.jsx(e.Fragment,{children:t.split(/(\*\*[^*]+\*\*|`[^`]+`)/g).map((r,s)=>/^\*\*[^*]+\*\*$/.test(r)?e.jsx("strong",{children:r.slice(2,-2)},s):/^`[^`]+`$/.test(r)?e.jsx("code",{className:"font-mono text-[11px] px-1 rounded bg-[var(--surface-2)]",children:r.slice(1,-1)},s):e.jsx(De.Fragment,{children:r},s))})}const P=({children:t})=>e.jsx("div",{className:"text-[11px] font-semibold text-[var(--text-dim)] mb-1",children:t}),wt=({session:t,buildName:r,buildId:s,commands:a,durationText:n})=>{var y;const{toast:l}=H(),[o,i]=x.useState(!1),c=G(t.failure_category),m=jt(a),b=Ye(t.failure_reason),f=(y=t.ai_analysis)==null?void 0:y.trim(),j=!!f&&f.length>480,p=async()=>{const w=Nt(t,r,s,n,m);try{await navigator.clipboard.writeText(w),l("Failure report copied","success")}catch{l("Clipboard unavailable","error")}},g=t.failure_category?`/xenon/runbooks/${encodeURIComponent(t.failure_category.toLowerCase())}`:void 0;return e.jsxs("section",{className:"rounded-lg border border-[var(--red)]/30 bg-[var(--surface)] overflow-hidden",children:[e.jsxs("header",{className:"flex items-center justify-between gap-4 px-4 py-3 border-b border-[var(--border)] bg-[var(--red)]/5",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(Ee,{className:"h-4 w-4 text-[var(--red)]","aria-hidden":"true"}),e.jsx("h2",{className:"text-sm font-semibold text-[var(--red)]",children:"Why it failed"}),c&&e.jsx("span",{className:"text-[11px] text-[var(--text-dim)] ml-2",children:c})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs("button",{type:"button",onClick:p,className:"inline-flex items-center gap-1.5 h-7 px-2.5 rounded-md border border-[var(--border)] text-xs text-[var(--text)] hover:border-[var(--border-strong)] hover:bg-[var(--surface-2)] transition-colors",children:[e.jsx(Y,{className:"h-3 w-3"}),"Copy"]}),e.jsxs("a",{...g?{href:g,target:"_blank",rel:"noreferrer"}:{"aria-disabled":!0,onClick:w=>w.preventDefault()},className:`inline-flex items-center gap-1.5 h-7 px-2.5 rounded-md border border-[var(--border)] text-xs text-[var(--text)] hover:border-[var(--border-strong)] hover:bg-[var(--surface-2)] transition-colors ${g?"":"opacity-50 cursor-not-allowed"}`,title:g?"Open runbook in new tab":"No runbook — failure category not set",children:[e.jsx(Qe,{className:"h-3 w-3"}),"Open runbook"]})]})]}),e.jsxs("div",{className:"px-4 py-4 space-y-4",children:[e.jsxs("div",{children:[e.jsx(P,{children:"Reason"}),e.jsx("div",{className:"text-sm text-[var(--text)] break-words",children:t.failure_reason||e.jsx("span",{className:"text-[var(--text-dim)]",children:"(not set)"})})]}),e.jsxs("div",{children:[e.jsx(P,{children:"First failed command"}),m?e.jsxs("div",{className:"flex items-baseline gap-2 min-w-0 text-sm",children:[e.jsx("span",{className:"font-mono text-xs text-[var(--color-danger)] shrink-0",children:m.command}),m.message&&e.jsx("span",{className:"text-[var(--text-muted)] break-words min-w-0",children:m.message})]}):e.jsx("span",{className:"text-sm text-[var(--text-dim)]",children:"No failed command recorded."})]}),f&&e.jsxs("div",{"data-testid":"ai-analysis",children:[e.jsx(P,{children:e.jsxs("span",{className:"inline-flex items-center gap-1.5",children:[e.jsx(ke,{className:"h-3 w-3","aria-hidden":"true"}),"AI analysis"]})}),e.jsx("div",{className:`text-sm text-[var(--text)] whitespace-pre-wrap break-words ${j&&!o?"line-clamp-6":""}`,children:e.jsx(yt,{text:f})}),j&&e.jsx("button",{type:"button",onClick:()=>i(w=>!w),"aria-expanded":o,className:"mt-1 text-xs text-[var(--color-accent)] hover:underline",children:o?"Show less":"Show all"})]}),b.length>0&&e.jsxs("div",{children:[e.jsx(P,{children:"Stack trace"}),e.jsx("pre",{className:"rounded-md border border-[var(--border)] bg-[var(--bg)] p-3 font-mono text-[11px] leading-relaxed text-[var(--color-accent)] overflow-x-auto",children:b.join(`
4
+ `)})]})]})]})};function kt(t){return t>=1e3?`${(t/1e3).toFixed(1).replace(/\.0$/,"")}k`:String(t)}function V(t){const r=t;return typeof r.message=="string"&&!r.command_name&&!r.title}const St=/^\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\.\d+\s+\d+\s+\d+\s+([VDIWEF])\s/,_t=/^([VDIWEF])\/[^(:]*[(:]/,Ct=/<(Error|Fault|Warning)>/,Rt=/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+\s+(Df|Db|E|F|I|A)\s/;function Re(t){var n,l,o,i;const r=((n=St.exec(t))==null?void 0:n[1])??((l=_t.exec(t))==null?void 0:l[1]);if(r)return r==="E"||r==="F"?"error":r==="W"?"warn":null;const s=(o=Ct.exec(t))==null?void 0:o[1];if(s)return s==="Warning"?"warn":"error";const a=(i=Rt.exec(t))==null?void 0:i[1];return a==="E"||a==="F"?"error":null}function z(t){const r=t.timestamp;if(!r){const o=t.createdAt;return o?z({timestamp:o}):"--:--:--"}const s=typeof r=="number"?new Date(r):new Date(String(r));if(Number.isNaN(s.getTime()))return"--:--:--";const a=String(s.getHours()).padStart(2,"0"),n=String(s.getMinutes()).padStart(2,"0"),l=String(s.getSeconds()).padStart(2,"0");return`${a}:${n}:${l}`}function Tt(t){if(V(t)){const s=Re(t.message);return s==="error"?{label:"error",tone:"red"}:s==="warn"?{label:"warn",tone:"amber"}:{label:"",tone:"neutral"}}if(t.is_healed===!0)return{label:String(t.command_name||"healed"),tone:"amber"};if(t.is_success===!1||t.is_error===!0)return{label:String(t.command_name||t.title||"error"),tone:"red"};if(t.is_success===!0)return{label:String(t.command_name||t.title||"ok"),tone:"green"};const r=t.title||t.event||t.command_name;return typeof r=="string"?/stop|fail|error/i.test(r)?{label:r,tone:"red"}:/start|begin|init/i.test(r)?{label:r,tone:"green"}:{label:r,tone:"blue"}:{label:"log",tone:"neutral"}}function $t(t){if(t==null)return"";if(typeof t=="string")try{return JSON.stringify(JSON.parse(t),null,2)}catch{return t}try{return JSON.stringify(t,null,2)}catch{return String(t)}}function At(t){if(!t||typeof t!="string")return[{text:String(t??""),kind:"plain"}];const r=[],s=/("(?:\\.|[^"\\])*")(\s*:)|("(?:\\.|[^"\\])*")|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|\b(true|false)\b|\b(null)\b|([{}[\],])/g;let a=0,n;for(;(n=s.exec(t))!==null;)n.index>a&&r.push({text:t.slice(a,n.index),kind:"plain"}),n[1]&&n[2]!==void 0?(r.push({text:n[1],kind:"key"}),r.push({text:n[2],kind:"punct"})):n[3]?r.push({text:n[3],kind:"string"}):n[4]?r.push({text:n[4],kind:"number"}):n[5]?r.push({text:n[5],kind:"boolean"}):n[6]?r.push({text:n[6],kind:"null"}):n[7]&&r.push({text:n[7],kind:"punct"}),a=s.lastIndex;return a<t.length&&r.push({text:t.slice(a),kind:"plain"}),r}function Et(t){if(V(t))return t.message.split(/\r?\n/,1)[0].trimEnd();const r=t,s=typeof r.title=="string"&&r.title.trim()?r.title.trim():null,a=typeof r.command_name=="string"&&r.command_name.trim()?r.command_name.trim():null;return s??a??"Command"}function xe(t){const r=t.subtitle;return typeof r=="string"&&r.trim()?r.trim():null}function Dt(t,r){return r?t.filter(s=>s.is_success===!1||s.is_error===!0||V(s)&&Re(s.message)==="error"):t}const Lt=({commands:t})=>{const r=ft(t);return r.length===0?null:e.jsxs("section",{className:"rounded-lg border border-[var(--border)] bg-[var(--surface)] overflow-hidden",children:[e.jsxs("header",{className:"flex items-center gap-2 px-4 py-3 border-b border-[var(--border)]",children:[e.jsx(ke,{className:"h-4 w-4 text-[var(--color-warning)]","aria-hidden":"true"}),e.jsx("h2",{className:"text-sm font-semibold text-[var(--text)]",children:"Self-healing"}),e.jsxs("span",{className:"text-[11px] text-[var(--text-muted)]",children:[r.length," selector",r.length===1?"":"s"," healed. Update the test so the next run doesn't need to."]})]}),e.jsxs("table",{className:"w-full table-fixed text-left",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-[var(--border)] text-[11px] text-[var(--text-dim)]",children:[e.jsx("th",{className:"px-4 py-2 font-medium w-[88px]",children:"Time"}),e.jsx("th",{className:"px-3 py-2 font-medium w-[128px]",children:"Command"}),e.jsx("th",{className:"px-3 py-2 font-medium",children:"Asked for"}),e.jsx("th",{className:"w-[28px]",children:e.jsx("span",{className:"sr-only",children:"healed to"})}),e.jsx("th",{className:"px-3 py-2 font-medium",children:"Healed to"}),e.jsx("th",{className:"px-3 py-2 font-medium w-[112px]",children:"Tier"}),e.jsx("th",{className:"px-4 py-2 font-medium w-[96px] text-right",children:"Confidence"})]})}),e.jsx("tbody",{children:r.map((s,a)=>e.jsxs("tr",{className:"border-b border-[var(--border)] last:border-b-0 align-top",children:[e.jsx("td",{className:"px-4 py-2 text-xs text-[var(--text-muted)] tabular-nums",children:z({timestamp:s.at??null})}),e.jsx("td",{className:"px-3 py-2 text-xs text-[var(--text)] truncate",children:s.command}),e.jsx("td",{className:"px-3 py-2 font-mono text-[11px] text-[var(--text-muted)] break-all",children:s.original??"—"}),e.jsx("td",{className:"py-2 text-[var(--text-dim)]",children:e.jsx(Ze,{className:"h-3.5 w-3.5","aria-hidden":"true"})}),e.jsx("td",{className:"px-3 py-2 font-mono text-[11px] text-[var(--text)] break-all",children:s.healed??"—"}),e.jsx("td",{className:"px-3 py-2 text-xs text-[var(--text)]",children:s.tier??"—"}),e.jsx("td",{className:"px-4 py-2 text-xs text-[var(--text)] tabular-nums text-right",children:s.confidence===null?"—":`${s.confidence}%`})]},s.id??a))})]})]})},Mt=1e4,me="var(--color-info)",ue="var(--color-accent)",Ot="var(--border-strong)";function he(t){return t>=1024?ie(t/1024)*1024:ie(t)}const fe=t=>{let r=0;for(const s of t)for(const a of s.values)a!==null&&a>r&&(r=a);return r},W=({title:t,series:r,times:s,yMax:a,format:n,ariaLabel:l})=>e.jsxs("section",{"aria-label":t,children:[e.jsxs("div",{className:"mb-2 flex flex-wrap items-baseline gap-x-4 gap-y-1",children:[e.jsx("h3",{className:"text-xs font-semibold text-[var(--text)]",children:t}),r.map(o=>{const i=rt(o.values);return e.jsxs("span",{className:"flex min-w-0 items-center gap-1.5 text-[11px] tabular-nums text-[var(--text-muted)]",children:[e.jsx("span",{className:"inline-block h-0.5 w-3 shrink-0",style:{background:o.color}}),e.jsx("span",{className:"max-w-[16rem] truncate",title:o.label,children:o.label}),i&&(o.dashed?e.jsx("span",{children:n(i.peak)}):e.jsxs("span",{children:["peak ",n(i.peak)," · avg ",n(i.average)]}))]},o.key)})]}),e.jsx(st,{times:s,series:r,yMax:a,formatValue:n,ariaLabel:l})]}),Ft=({sessionId:t,running:r,hasTrace:s})=>{const[a,n]=x.useState(null),[l,o]=x.useState(!1);x.useEffect(()=>{let u=!0;const v=()=>S.getSessionMetrics(t).then(C=>{u&&(n(tt(C)),o(!0))}).catch(()=>{u&&o(!0)});if(v(),!r)return()=>{u=!1};const h=setInterval(()=>void v(),Mt);return()=>{u=!1,clearInterval(h)}},[t,r]);const i=(a==null?void 0:a.samples)??[],c=x.useMemo(()=>i.map(u=>{var v;return u.t-(((v=i[0])==null?void 0:v.t)??0)}),[i]),m=new Set(i.map(u=>u.app).filter(u=>!!u)),b=m.size>1?"Foreground app":Array.from(m)[0]??(a==null?void 0:a.appId)??"App",f=i.map(u=>u.app??null),j=u=>u>0&&!!f[u]&&!!f[u-1]&&f[u]!==f[u-1],p=[],g=[],y=[];a&&(a.series.deviceCpu&&p.push({key:"deviceCpu",label:"Device",color:me,values:i.map(u=>u.deviceCpu)}),a.series.appCpu&&p.push({key:"appCpu",label:b,color:ue,values:i.map((u,v)=>j(v)?null:u.appCpu),pointLabels:f}),a.series.appMem&&g.push({key:"appMem",label:b,color:ue,values:i.map((u,v)=>j(v)?null:u.appMemMb),pointLabels:f}),a.series.deviceMem&&(y.push({key:"deviceMem",label:"Device used",color:me,values:i.map(u=>u.deviceMemMb)}),y.push({key:"deviceTotal",label:"Device total",color:Ot,values:i.map(u=>u.deviceMemTotalMb),dashed:!0})));const w=r&&(a==null?void 0:a.recording)==="off",R=(a==null?void 0:a.recording)==="stopped",A="Recording stopped: the device stopped responding.";let _;return l?w?_=e.jsx(M,{title:"Performance isn't recorded for this session",description:"CPU and memory are recorded for Android devices and iPhones. This session runs on an iOS simulator or on a machine that needs updating, or recording is turned off."}):i.length===0&&!r?_=e.jsx(M,{title:"No performance data for this session",description:"CPU and memory are recorded for Android devices and iPhones. This session ran on an iOS simulator or on a machine that needed updating, before recording was available, or with recording turned off."}):i.length<2?_=e.jsx("div",{className:"text-xs text-[var(--text-dim)]",children:R?A:r?"Collecting… The first figures appear within 10 seconds.":"Too few figures were recorded to draw a chart."}):_=e.jsxs("div",{className:"space-y-4",children:[R&&e.jsxs("p",{className:"text-xs text-[var(--color-warning)]",children:[A," The data ends there."]}),(a==null?void 0:a.platform)==="ios"&&e.jsxs("p",{className:"text-xs text-[var(--text-dim)]",children:["On iPhones, only the device's overall CPU is recorded.",s?" For more detail, download the Performance trace under Details.":""]}),p.length>0&&e.jsx(W,{title:"CPU",series:p,times:c,yMax:100,format:et,ariaLabel:"CPU over the session"}),g.length>0&&e.jsx(W,{title:"App memory",series:g,times:c,yMax:he(fe(g)),format:ne,ariaLabel:"App memory over the session"}),y.length>0&&e.jsx(W,{title:"Device memory",series:y,times:c,yMax:he(fe(y)),format:ne,ariaLabel:"Device memory over the session"})]}):_=e.jsx("div",{className:"text-xs text-[var(--text-dim)]",children:"Loading performance…"}),e.jsx(Se,{header:e.jsxs("span",{className:"flex items-center gap-2",children:[e.jsx(at,{size:14})," Performance"]}),children:_})};function Pt(t){const r=t.status==="running";return t.video_recording_enabled===!1?r?"Recording is off for this session.":"Recording was off for this session.":r?"Any video appears here after the session ends.":"No video was saved for this session."}const It=({session:t})=>{const r=t.status==="running",s=!!t.video_recording;return e.jsxs("section",{className:"rounded-lg border border-[var(--border)] bg-[var(--surface)] overflow-hidden",children:[e.jsxs("header",{className:"px-3 py-2 border-b border-[var(--border)] flex items-center justify-between",children:[e.jsx("span",{className:"text-[11px] font-semibold text-[var(--text-dim)]",children:"Recording"}),r&&e.jsxs("span",{className:"inline-flex items-center gap-1.5 text-[11px] text-[var(--red)]",children:[e.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-[var(--red)] pulse-dot"}),"Live"]})]}),e.jsx("div",{className:"aspect-video bg-[var(--bg)] flex items-center justify-center",children:r&&t.has_live_video?e.jsx("img",{src:S.getSessionLiveVideoUrl(t.id),alt:`Live view of session ${t.id}`,className:"w-full h-full object-contain"}):s?e.jsx("video",{controls:!0,src:S.getAssetUrl(t.video_recording),className:"w-full h-full object-contain"}):e.jsxs("div",{className:"flex flex-col items-center text-center px-6 py-8 gap-2",children:[e.jsx("div",{className:"h-10 w-10 rounded-full bg-[var(--surface-2)] border border-[var(--border)] flex items-center justify-center",children:e.jsx(nt,{className:"h-5 w-5 text-[var(--text-dim)]"})}),e.jsx("div",{className:"text-sm text-[var(--text)] font-medium",children:"No video available"}),e.jsx("div",{className:"text-xs text-[var(--text-dim)] max-w-xs",children:Pt(t)})]})})]})},T=({label:t,children:r,title:s,mono:a})=>e.jsxs("div",{className:"flex items-baseline justify-between gap-3 px-3 py-2",children:[e.jsx("dt",{className:"text-xs text-[var(--text-muted)] shrink-0",children:t}),e.jsx("dd",{className:`text-xs text-[var(--text)] text-right min-w-0 break-all ${a?"font-mono text-[11px]":""}`,title:s,children:r})]}),Ht=({session:t,buildName:r})=>{var o,i;const{toast:s}=H(),a=ye(t.ranOn),n=t.performance_trace?S.getAssetUrl(t.performance_trace):"",l=async()=>{try{await navigator.clipboard.writeText(t.id),s("Session ID copied","success")}catch{s("Clipboard unavailable","error")}};return e.jsxs("section",{className:"rounded-lg border border-[var(--border)] bg-[var(--surface)] overflow-hidden",children:[e.jsx("header",{className:"px-3 py-2 border-b border-[var(--border)]",children:e.jsx("h2",{className:"text-[11px] font-semibold text-[var(--text-dim)]",children:"Details"})}),e.jsxs("dl",{className:"divide-y divide-[var(--border)]",children:[e.jsx(T,{label:"Session ID",mono:!0,children:e.jsxs("span",{className:"inline-flex items-center gap-1",children:[e.jsx("span",{children:t.id}),e.jsx("button",{type:"button",onClick:l,"aria-label":"Copy session ID",className:"p-0.5 rounded text-[var(--text-dim)] hover:text-[var(--text)]",children:e.jsx(Y,{className:"h-3 w-3"})})]})}),r&&e.jsx(T,{label:"Build",children:r}),e.jsx(T,{label:"Device UDID",mono:!0,children:t.device_udid||"—"}),e.jsx(T,{label:"Platform",children:[Ne(t),t.device_version].filter(c=>c&&c!=="—").join(" ")||"—"}),e.jsx(T,{label:"Ran on",title:t.node_id?`Node ${t.node_id}`:void 0,children:a??"Not known"}),e.jsx(T,{label:"Run by",title:(o=t.owner)==null?void 0:o.email,children:((i=t.owner)==null?void 0:i.name)??"Not recorded"}),e.jsx(T,{label:"Started",children:re(t.startTime)}),e.jsx(T,{label:"Ended",children:t.endTime?re(t.endTime):"—"}),t.tags&&e.jsx(T,{label:"Tags",children:t.tags}),n&&e.jsx(T,{label:"iOS trace",children:e.jsxs("a",{href:n,className:"inline-flex items-center gap-1 text-[var(--color-accent)] hover:underline",children:[e.jsx(_e,{className:"h-3 w-3","aria-hidden":"true"}),"Performance trace"]})})]})]})},pe={desired:"Requested",session:"Actual"},Bt=({session:t})=>{const[r,s]=x.useState("desired"),a=se(t.desired_capabilities),n=se(t.session_capabilities),o=Object.entries(r==="desired"?a:n);return e.jsxs("section",{className:"rounded-lg border border-[var(--border)] bg-[var(--surface)] overflow-hidden",children:[e.jsx("header",{className:"flex items-center border-b border-[var(--border)]",children:["desired","session"].map(i=>e.jsx("button",{type:"button",onClick:()=>s(i),"aria-pressed":r===i,className:`flex-1 h-9 text-[11px] font-semibold transition-colors border-b-2 ${r===i?"border-[var(--color-accent)] text-[var(--text)]":"border-transparent text-[var(--text-muted)] hover:text-[var(--text)]"}`,children:pe[i]},i))}),o.length===0?e.jsxs("div",{className:"px-3 py-6 text-center text-xs text-[var(--text-dim)]",children:["No ",pe[r].toLowerCase()," capabilities recorded for this session."]}):e.jsx("dl",{className:"divide-y divide-[var(--border)]",children:o.map(([i,c])=>e.jsxs("div",{className:"grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)] items-baseline gap-3 px-3 py-2",children:[e.jsx("dt",{className:"text-xs text-[var(--text-muted)] break-all",children:i}),e.jsx("dd",{className:"font-mono text-xs text-[var(--text)] truncate text-right min-w-0",title:ae(c,500),children:ae(c)})]},i))})]})},Ut=({tabs:t,active:r,onChange:s,errorsOnly:a,onErrorsOnlyChange:n,showErrorsOnly:l=!0})=>{const o=t.filter(i=>!(i.hideWhenEmpty&&i.count===0));return e.jsxs("div",{className:"flex items-center justify-between gap-4 px-3 border-b border-[var(--border)] bg-[var(--surface)]",children:[e.jsx("div",{className:"flex items-center gap-1 overflow-x-auto",children:o.map(i=>{const c=i.key===r;return e.jsxs("button",{type:"button",onClick:()=>s(i.key),"aria-pressed":c,"data-empty":i.count===0?"true":"false",className:`relative inline-flex items-center gap-1.5 h-9 px-3 text-xs whitespace-nowrap transition-colors ${c?"text-[var(--text)]":i.count===0?"text-[var(--text-dim)] hover:text-[var(--text-muted)]":"text-[var(--text-muted)] hover:text-[var(--text)]"}`,children:[e.jsx("span",{children:i.label}),i.count>0&&e.jsx("span",{className:`text-[10px] tabular-nums ${c?"text-[var(--text-muted)]":"text-[var(--text-dim)]"}`,children:kt(i.count)}),c&&e.jsx("span",{className:"absolute left-2 right-2 bottom-0 h-[2px] bg-[var(--color-accent)] rounded-t"})]},i.key)})}),l&&e.jsxs("label",{className:"inline-flex items-center gap-2 text-[11px] text-[var(--text-muted)] cursor-pointer select-none whitespace-nowrap",children:[e.jsx("input",{type:"checkbox",checked:a,onChange:i=>n(i.target.checked),"aria-label":"Show only error rows"}),"Errors only"]})]})},Vt={key:"text-[var(--blue)]",string:"text-[var(--color-accent)]",number:"text-[var(--amber)]",boolean:"text-[var(--amber)]",null:"text-[var(--text-dim)] italic",punct:"text-[var(--text-muted)]",plain:"text-[var(--text)]"},zt=({value:t,maxHeightPx:r})=>{const s=$t(t);if(!s)return e.jsx("span",{className:"text-[var(--text-dim)] text-xs",children:"(empty)"});let a;try{a=At(s)}catch{a=[{text:s,kind:"plain"}]}return e.jsx("pre",{className:"font-mono text-[11px] leading-relaxed bg-[var(--bg)] border border-[var(--border)] rounded-md p-3 overflow-x-auto whitespace-pre",style:r?{maxHeight:r,overflowY:"auto"}:void 0,children:a.map((n,l)=>e.jsx("span",{className:Vt[n.kind],children:n.text},l))})},Jt={green:"bg-[var(--color-success)]",red:"bg-[var(--red)]",amber:"bg-[var(--amber)]",blue:"bg-[var(--blue)]",neutral:"bg-[var(--text-dim)]"},Wt={green:"text-[var(--color-success)]",red:"text-[var(--red)]",amber:"text-[var(--amber)]",blue:"text-[var(--blue)]",neutral:"text-[var(--text-muted)]"},Te=({log:t})=>{const[r,s]=x.useState(!1),{toast:a}=H(),n=z(t),l=Tt(t),o=V(t),i=o?String(t.message):"",c=async m=>{m.stopPropagation();try{const b=o?i:t.body??t,f=typeof b=="string"?b:JSON.stringify(b,null,2);await navigator.clipboard.writeText(f),a("Log entry copied","success")}catch{a("Clipboard unavailable","error")}};return e.jsxs("div",{className:"border-b border-[var(--border)] last:border-b-0",children:[e.jsxs("button",{type:"button",onClick:()=>s(m=>!m),"aria-expanded":r,className:"group w-full flex items-center gap-2 px-3 py-2 text-left hover:bg-[var(--surface-2)]/60 transition-colors",children:[e.jsx(K,{className:`h-3 w-3 text-[var(--text-dim)] transition-transform shrink-0 ${r?"rotate-90":""}`}),e.jsx("span",{className:"font-mono text-[11px] text-[var(--text-muted)] w-[64px] shrink-0",children:n}),e.jsx("span",{className:`h-1.5 w-1.5 rounded-full shrink-0 ${Jt[l.tone]}`}),l.label&&e.jsx("span",{className:`font-mono text-[11px] font-medium shrink-0 ${Wt[l.tone]}`,children:l.label}),e.jsxs("span",{className:"min-w-0 flex-1 flex items-baseline gap-2 truncate",children:[e.jsx("span",{className:o?"font-mono text-[11px] text-[var(--text)] truncate min-w-0":"text-[12px] text-[var(--text)] shrink-0",children:Et(t)}),xe(t)&&e.jsx("span",{className:"font-mono text-[11px] text-[var(--text-dim)] truncate min-w-0",children:xe(t)})]}),typeof t.duration=="number"&&e.jsx("span",{className:"text-[11px] tabular-nums text-[var(--text-muted)] shrink-0",children:I(t.duration)}),e.jsx("span",{role:"button",tabIndex:0,onClick:c,onKeyDown:m=>{(m.key==="Enter"||m.key===" ")&&c(m)},"aria-label":"Copy log entry",className:"opacity-0 group-hover:opacity-100 p-1 rounded text-[var(--text-dim)] hover:text-[var(--text)] transition-opacity shrink-0",children:e.jsx(Y,{className:"h-3 w-3"})})]}),r&&e.jsx("div",{className:"px-3 pb-3 pl-9",children:o?e.jsx("pre",{className:"font-mono text-[11px] leading-relaxed text-[var(--text)] bg-[var(--bg)] border border-[var(--border)] rounded-md p-3 max-h-[400px] overflow-auto whitespace-pre-wrap break-words",children:i}):e.jsx(zt,{value:t.body??t,maxHeightPx:400})})]})},Kt=(t,r)=>t?"bg-[var(--color-danger)]":r?"bg-[var(--color-warning)]":"bg-[var(--color-success)]",qt=({commands:t})=>{const{spanMs:r,bars:s}=vt(t);return s.length===0?e.jsx("div",{className:"px-4 py-10 text-center text-xs text-[var(--text-dim)]",children:"No commands recorded for this session."}):e.jsxs("div",{className:"px-3 py-3",children:[e.jsxs("div",{className:"flex items-center justify-between pb-2 text-[11px] text-[var(--text-dim)] tabular-nums",children:[e.jsxs("span",{children:[s.length," command",s.length===1?"":"s"," over ",I(r)]}),e.jsxs("span",{className:"inline-flex items-center gap-3",children:[e.jsxs("span",{className:"inline-flex items-center gap-1",children:[e.jsx("span",{className:"h-2 w-2 rounded-sm bg-[var(--color-danger)]"})," Failed"]}),e.jsxs("span",{className:"inline-flex items-center gap-1",children:[e.jsx("span",{className:"h-2 w-2 rounded-sm bg-[var(--color-warning)]"})," Healed"]})]})]}),e.jsx("ol",{className:"space-y-1",children:s.map((a,n)=>e.jsxs("li",{"data-testid":"timeline-bar","data-command":a.label,"data-failed":a.failed?"true":"false",className:"grid grid-cols-[128px_minmax(0,1fr)_64px] items-center gap-2",children:[e.jsx("span",{className:"truncate text-[11px] font-mono text-[var(--text-muted)]",title:a.label,children:a.label}),e.jsx("span",{className:"relative h-3 rounded-sm bg-[rgb(var(--rgb-fg)/0.05)]",children:e.jsx("span",{className:`absolute inset-y-0 rounded-sm ${Kt(a.failed,a.healed)}`,style:{left:`${a.startMs/r*100}%`,width:`max(2px, ${a.durationMs/r*100}%)`}})}),e.jsx("span",{className:"text-right text-[11px] tabular-nums text-[var(--text-muted)]",children:a.durationMs>0?I(a.durationMs):"—"})]},a.id??n))})]})},Xt=({commands:t})=>{const r=bt(t);return r.length===0?e.jsxs("div",{className:"px-4 py-10 text-center text-xs text-[var(--text-dim)]",children:["No screenshots were kept. A failed command keeps one unless"," ",e.jsx("code",{className:"font-mono",children:"xe:options.screenshotOnFailure"})," is false; set"," ",e.jsx("code",{className:"font-mono",children:"xe:options.screenshotOnEveryCommand"})," to keep one for every command."]}):e.jsx("ul",{className:"grid grid-cols-[repeat(auto-fill,minmax(140px,1fr))] gap-3 p-3",children:r.map((s,a)=>{const n=S.getAssetUrl(s.path),l=z({timestamp:s.at??null});return e.jsx("li",{children:e.jsxs("a",{href:n,target:"_blank",rel:"noreferrer",className:`block rounded-md border overflow-hidden hover:border-[var(--border-strong)] ${s.failed?"border-[var(--color-danger)]":"border-[var(--border)]"}`,children:[e.jsx("img",{src:n,alt:`${s.command} at ${l}`,loading:"lazy",className:"block w-full aspect-[9/16] object-contain bg-[rgb(var(--rgb-fg)/0.03)]"}),e.jsxs("span",{className:"flex items-center justify-between gap-2 px-2 py-1 text-[11px]",children:[e.jsx("span",{className:`truncate font-mono ${s.failed?"text-[var(--color-danger)]":"text-[var(--text)]"}`,children:s.command}),e.jsx("span",{className:"tabular-nums text-[var(--text-dim)] shrink-0",children:l})]})]})},s.id??a)})})},ve=["commands","device","debug","profiling"],Gt=500,Yt=33,Qt=({rows:t,scroller:r})=>{const s=qe({count:t.length,getScrollElement:()=>r.current,estimateSize:()=>Yt,getItemKey:a=>t[a].id??a,overscan:20});return e.jsx("div",{className:"relative",style:{height:s.getTotalSize()},children:s.getVirtualItems().map(a=>e.jsx("div",{"data-index":a.index,ref:s.measureElement,className:`absolute left-0 top-0 w-full${a.index<t.length-1?" border-b border-[var(--border)]":""}`,style:{transform:`translateY(${a.start}px)`},children:e.jsx(Te,{log:t[a.index]})},String(a.key)))})},Zt=({sessionLogs:t,deviceLogs:r,debugLogs:s,profiling:a})=>{const[n,l]=x.useState("commands"),[o,i]=x.useState(!1),c=x.useRef(null),m=t,b=x.useMemo(()=>Ce(m).screenshots,[m]),f=x.useMemo(()=>[{key:"commands",label:"Commands",count:t.length},{key:"timeline",label:"Timeline",count:t.length},{key:"screenshots",label:"Screenshots",count:b},{key:"device",label:"Device logs",count:r.length},{key:"debug",label:"Debug logs",count:s.length},{key:"profiling",label:"System profiling",count:a.length,hideWhenEmpty:!0}],[t.length,b,r.length,s.length,a.length]),j=x.useMemo(()=>{let p=[];return n==="commands"?p=t:n==="device"?p=r:n==="debug"?p=s:n==="profiling"&&(p=a),Dt(p,o)},[n,t,r,s,a,o]);return e.jsxs("section",{className:"rounded-lg border border-[var(--border)] bg-[var(--surface)] flex flex-col min-h-[280px] max-h-[640px]",children:[e.jsx(Ut,{tabs:f,active:n,onChange:l,errorsOnly:o,onErrorsOnlyChange:i,showErrorsOnly:ve.includes(n)}),e.jsxs("div",{ref:c,className:"flex-1 overflow-y-auto","data-log-scroller":"",children:[n==="timeline"&&e.jsx(qt,{commands:m}),n==="screenshots"&&e.jsx(Xt,{commands:m}),ve.includes(n)&&(j.length===0?e.jsx("div",{className:"px-4 py-10 text-center text-xs text-[var(--text-dim)]",children:o?"No error rows in this tab.":"No log entries in this tab."}):j.length>=Gt?e.jsx(Qt,{rows:j,scroller:c},n):j.map((p,g)=>e.jsx(Te,{log:p},p.id??g)))]})]})};function $e(t){if(!t)return"failed";switch(t){case"HTTPS_CLIENT_ERROR":case"HTTPS_SERVER_ERROR":case"OPEN_HTTPS_SERVER_ERROR":case"ON_CONNECT_ERROR":return"tls";case"PROXY_TO_SERVER_REQUEST_ERROR":return"net";default:return"failed"}}const ee="/xenon/api/interceptor";async function be(t){const r=await fetch(`${ee}/sessions/${encodeURIComponent(t)}/requests`);if(r.status===403)return{active:!1,forbidden:!0};if(r.status===404){const a=await r.json().catch(()=>({}));return{active:!1,error:a==null?void 0:a.error}}return r.ok?{active:!0,requests:(await r.json()).requests||[]}:{active:!1,error:`request failed (${r.status})`}}async function er(t,r){const s=await fetch(`${ee}/sessions/${encodeURIComponent(t)}/requests/${encodeURIComponent(r)}`);return s.ok?s.json():null}function tr(t){return`${ee}/sessions/${encodeURIComponent(t)}/har`}function rr(t){return t>=200&&t<300?"ready":t>=300&&t<400?"neutral":t>=400&&t<500?"busy":t>=500?"error":"neutral"}const ge=({headers:t})=>{const r=Object.entries(t||{});return r.length===0?e.jsx("div",{className:"text-xs text-[var(--text-dim)]",children:"No headers"}):e.jsx("div",{className:"space-y-0.5 font-mono text-xs",children:r.map(([s,a])=>e.jsxs("div",{className:"flex gap-2",children:[e.jsxs("span",{className:"text-[var(--text-dim)] shrink-0",children:[s,":"]}),e.jsx("span",{className:"break-all",children:a})]},s))})},je=({body:t})=>{if(t==null||t==="")return e.jsx("div",{className:"text-xs text-[var(--text-dim)]",children:"No body"});let r=t;try{r=JSON.stringify(JSON.parse(t),null,2)}catch{}return e.jsx("pre",{className:"font-mono text-xs whitespace-pre-wrap break-all bg-[var(--surface-2,var(--surface))] rounded p-2 max-h-64 overflow-auto",children:r})},sr=({request:t,onClose:r})=>{if(!t)return null;const s=e.jsxs("div",{className:"flex items-center gap-2 text-sm",children:[e.jsx($,{tone:"accent",children:t.method}),t.failed?e.jsx($,{tone:"error",children:$e(t.failureKind)}):e.jsx($,{tone:rr(t.resStatus),children:t.resStatus||"—"}),e.jsx("span",{className:"font-mono text-xs truncate",title:t.url,children:t.url})]});return e.jsx(ct,{open:!!t,title:s,onClose:r,width:720,children:e.jsxs("div",{className:"space-y-4 text-xs",children:[t.failed&&e.jsxs("div",{className:"border border-[var(--red)] rounded p-2 bg-[rgb(var(--rgb-red-600)/0.08)]",children:[e.jsx("div",{className:"text-[var(--red)] text-[11px] mb-1",children:"Failure"}),e.jsx("div",{className:"text-xs",children:t.failureReason||"Request failed"}),t.failureKind&&e.jsx("div",{className:"font-mono text-[10px] text-[var(--text-dim)] mt-1",children:t.failureKind})]}),!t.failed&&e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-[var(--text-dim)] text-[11px] mb-1",children:"Duration"}),e.jsxs("div",{className:"font-mono",children:[t.durationMs," ms"]})]}),e.jsxs("div",{children:[e.jsx("div",{className:"text-[var(--text-dim)] text-[11px] mb-1",children:"Flags"}),e.jsxs("div",{className:"flex gap-1",children:[t.mocked&&e.jsx($,{tone:"accent",children:"mocked"}),t.modified&&e.jsx($,{tone:"busy",children:"modified"}),!t.mocked&&!t.modified&&e.jsx("span",{className:"text-[var(--text-dim)]",children:"passthrough"})]})]})]}),t.commandHint&&e.jsxs("div",{children:[e.jsx("div",{className:"text-[var(--text-dim)] text-[11px] mb-1",children:"Triggered by"}),e.jsx("div",{className:"font-mono",children:t.commandHint.commandName})]}),!t.failed&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-[var(--text-dim)] text-[11px] mb-1",children:"Request Headers"}),e.jsx(ge,{headers:t.reqHeaders})]}),e.jsxs("div",{children:[e.jsx("div",{className:"text-[var(--text-dim)] text-[11px] mb-1",children:"Request Body"}),e.jsx(je,{body:t.reqBody})]}),e.jsxs("div",{children:[e.jsx("div",{className:"text-[var(--text-dim)] text-[11px] mb-1",children:"Response Headers"}),e.jsx(ge,{headers:t.resHeaders})]}),e.jsxs("div",{children:[e.jsx("div",{className:"text-[var(--text-dim)] text-[11px] mb-1",children:"Response Body"}),e.jsx(je,{body:t.resBody})]})]})]})})};function ar(t){return t?new Date(t).toLocaleTimeString(void 0,{hour12:!1}):"—"}function nr(t){return t>=200&&t<300?"ready":t>=300&&t<400?"neutral":t>=400&&t<500?"busy":t>=500?"error":"neutral"}const ir=({sessionId:t,sessionEnded:r})=>{const[s,a]=x.useState(null),[n,l]=x.useState(!1),[o,i]=x.useState(null),[c,m]=x.useState([]),[b,f]=x.useState(!0),[j,p]=x.useState(null),g=x.useRef(new Set),{on:y,isConnected:w}=Le(),R=x.useCallback(d=>{l(!!d.forbidden),a(d.active),i(d.error||null);const N=d.requests||[];m(N),g.current=new Set(N.map(L=>L.id))},[]);x.useEffect(()=>{let d=!0;return f(!0),be(t).then(N=>{d&&R(N)}).catch(()=>{d&&R({active:!1,error:"the server could not be reached"})}).finally(()=>{d&&f(!1)}),()=>{d=!1}},[t,R]),x.useEffect(()=>{if(r)return;let d=!0;const N=y("interceptor_request",k=>{!k||k.sessionId!==t||g.current.has(k.id)||(g.current.add(k.id),m(J=>[k,...J]),a(!0))}),L=y("interceptor_session_started",k=>{(k==null?void 0:k.sessionId)===t&&a(!0)}),O=y("interceptor_session_stopped",k=>{(k==null?void 0:k.sessionId)===t&&be(t).then(J=>{d&&R(J)}).catch(()=>{})});return()=>{d=!1,N(),L(),O()}},[y,t,r,w,R]);const A=x.useMemo(()=>[...c].sort((d,N)=>N.ts-d.ts),[c]),_=async d=>{if(p(d),s){const N=await er(t,d.id);N&&p(N)}},u=s&&c.length>0?e.jsxs("a",{href:tr(t),className:"inline-flex items-center gap-1 text-xs text-[var(--text-dim)] hover:text-[var(--text)]",title:"Download HAR",children:[e.jsx(_e,{className:"h-3 w-3"}),"HAR"]}):null,v=e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(dt,{className:"h-3.5 w-3.5 text-[var(--text-dim)]"}),e.jsx("span",{children:"Network"}),s&&c.length>0&&e.jsxs("span",{className:"text-xs text-[var(--text-dim)]",children:["(",c.length,")"]})]});let h;const C=s===!1&&(!o||o==="interceptor inactive");return b?h=e.jsx("div",{className:"p-4 text-xs text-[var(--text-dim)]",children:"Loading…"}):n?h=e.jsx(M,{title:"Only admins can see network requests",description:"Captured requests can carry sign-in details and personal data, so only admins can open them. Ask an admin to look at this session's requests."}):C&&r?h=e.jsx(M,{title:"No network capture",description:"This session didn't capture its network requests."}):C?h=e.jsx(M,{title:"Network capture is off",description:"This session isn't capturing its network requests. Android sessions capture them when the test turns network capture on in its capabilities, or when it is on for every session in the server settings."}):o?h=e.jsxs("div",{className:"p-4 text-xs text-[var(--red)]",children:["Failed to load requests: ",o]}):A.length===0?h=e.jsx(M,{title:"Waiting for requests",description:r?"No HTTP traffic was captured during this session.":"Captured requests will appear here as the device makes calls."}):h=e.jsx("div",{className:"overflow-auto max-h-96",children:e.jsxs(it,{children:[e.jsx(ot,{children:e.jsxs(oe,{children:[e.jsx(E,{style:{width:80},children:"Time"}),e.jsx(E,{style:{width:70},children:"Method"}),e.jsx(E,{style:{width:70},children:"Status"}),e.jsx(E,{children:"Host"}),e.jsx(E,{children:"Path"}),e.jsx(E,{style:{width:80},children:"Duration"}),e.jsx(E,{style:{width:90},children:"Flags"})]})}),e.jsx(lt,{children:A.map(d=>e.jsxs(oe,{onClick:()=>_(d),style:{cursor:"pointer"},title:d.failed?d.failureReason:void 0,children:[e.jsx(D,{className:"font-mono text-[11px]",children:ar(d.ts)}),e.jsx(D,{children:e.jsx($,{tone:"accent",children:d.method})}),e.jsx(D,{children:d.failed?e.jsx($,{tone:"error",children:$e(d.failureKind)}):e.jsx($,{tone:nr(d.resStatus),children:d.resStatus||"—"})}),e.jsx(D,{className:"font-mono text-[11px] truncate",title:d.host,children:d.host}),e.jsx(D,{className:"font-mono text-[11px] truncate",title:d.path,children:d.failed?"—":d.path}),e.jsx(D,{className:"font-mono text-[11px]",children:d.failed?"—":`${d.durationMs}ms`}),e.jsx(D,{children:e.jsxs("div",{className:"flex gap-1",children:[d.mocked&&e.jsx($,{tone:"accent",children:"mock"}),d.modified&&e.jsx($,{tone:"busy",children:"mod"})]})})]},d.id))})]})}),e.jsxs(e.Fragment,{children:[e.jsx(Se,{header:v,action:u,padded:!1,children:h}),e.jsx(sr,{request:j,onClose:()=>p(null)})]})},$r=()=>{const{buildId:t="",sessionId:r=""}=Me(),{toast:s}=H(),a=Oe(),n=Ue(),l=mt(r||null),o=x.useMemo(()=>n.builds.find(f=>f.id===t)??null,[n.builds,t]),i=o?Ve(o):"Build";if(x.useEffect(()=>{!l.loading&&l.notFound&&(s("Session not available — it may belong to a team you are not on.","info"),a("/builds",{replace:!0}))},[l.loading,l.notFound,a,s]),l.loading)return e.jsx("div",{className:"p-6 text-xs text-[var(--text-dim)]",children:"Loading session…"});if(l.error||!l.session)return e.jsxs("div",{className:"p-6 space-y-2",children:[e.jsx(ce,{buildId:t,buildName:o?i:null,sessionId:r}),e.jsx("div",{className:"mt-6 rounded-md border border-[var(--red)]/30 bg-[var(--surface)] p-4 text-xs text-[var(--red)]",children:l.error||"Session not found."})]});const c=l.session,m=l.sessionLogs,b=Q(c.status).bucket==="failed";return e.jsxs("div",{className:"flex flex-col h-full min-h-0",children:[e.jsx(ce,{buildId:t,buildName:o?i:null,sessionId:c.id}),e.jsx(ht,{session:c}),e.jsx("div",{className:"flex-1 overflow-y-auto overflow-x-hidden",children:e.jsxs("div",{className:"px-4 py-4 space-y-4",children:[e.jsx(gt,{session:c,commands:m}),b&&e.jsx(wt,{session:c,buildName:i,buildId:t,commands:m,durationText:ze(X(c))}),e.jsx(Lt,{commands:m}),e.jsx(Ft,{sessionId:c.id,running:c.status==="running",hasTrace:!!c.performance_trace}),e.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-[minmax(0,1fr)_340px] gap-4",children:[e.jsx("div",{className:"min-w-0",children:e.jsx(Zt,{sessionLogs:l.sessionLogs,deviceLogs:l.deviceLogs,debugLogs:l.debugLogs,profiling:l.profiling})}),e.jsxs("div",{className:"flex flex-col gap-4 min-w-0",children:[e.jsx(It,{session:c}),e.jsx(Ht,{session:c,buildName:o?i:null}),e.jsx(Bt,{session:c})]})]}),e.jsx(ir,{sessionId:c.id,sessionEnded:c.status!=="running"})]})})]})};export{$r as SessionDetailPage,$r as default};
@@ -1 +1 @@
1
- .settings-container{padding:24px 32px;max-width:1440px;margin:0 auto;color:var(--text);animation:fadeIn .3s ease;min-height:calc(100vh - 40px);display:flex;flex-direction:column;font-family:Inter,-apple-system,sans-serif;position:relative;background:var(--bg);width:100%}.settings-container>header{padding-left:0;padding-right:0}.settings-header{margin-bottom:24px;padding-bottom:20px;border-bottom:1px solid var(--border)}.settings-header h2{margin:0;font-size:1.75rem;font-weight:700;font-family:Outfit,sans-serif;color:var(--text)}.settings-title-group{display:flex;align-items:center;gap:12px}.settings-title-group .settings-icon{color:var(--text-muted);flex-shrink:0}.settings-subtitle{color:var(--text-muted);font-size:.85rem;margin-top:6px;line-height:1.5}.settings-subtitle span{font-family:JetBrains Mono,monospace;font-size:.75rem;color:var(--text-dim)}.badge-elite{font-size:11px;padding:3px 8px;border-radius:4px;background:rgb(var(--rgb-accent) / .08);color:var(--color-accent);border:1px solid rgb(var(--rgb-accent) / .25);font-weight:500}.status-pill{display:inline-flex;align-items:center;gap:8px;padding:4px 12px;border-radius:6px;font-size:11px;font-weight:800;background:rgb(var(--rgb-well) / calc(.4 * var(--well-k)));border:1px solid var(--border)}.status-pill.success{border-color:rgb(var(--rgb-success) / .3);color:var(--color-success)}.status-pill.error{border-color:#ff44444d;color:var(--status-error-fg)}.locked-indicator{display:flex;align-items:center;gap:8px;font-size:.75rem;color:var(--text-dim);font-family:JetBrains Mono,monospace;margin-top:var(--space-4)}.locked-indicator code{background:rgb(var(--rgb-fg) / .05);color:var(--text-muted);padding:2px 6px;border-radius:4px}.settings-content{display:flex;flex-direction:column;gap:var(--space-8);flex-grow:1}.settings-section h3{display:flex;align-items:center;gap:var(--space-2);font-size:1.125rem;color:var(--text);font-weight:700;margin-bottom:var(--space-2)}.settings-content{display:flex;flex-direction:column;gap:var(--space-8);flex-grow:1;position:relative}.settings-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--space-6);margin-bottom:var(--space-6)}.settings-grid>*{grid-column:span 12}@media (min-width: 1024px){.settings-grid{grid-template-columns:repeat(3,1fr)}.settings-grid>*{grid-column:span 1}}.settings-grid--three{display:grid;grid-template-columns:1fr;gap:var(--space-5);margin-bottom:var(--space-6);align-items:stretch}.settings-grid--three>*{grid-column:auto}@media (min-width: 1024px){.settings-grid--three{grid-template-columns:repeat(3,minmax(0,1fr))}}.settings-grid--two-equal{display:grid;grid-template-columns:1fr;gap:var(--space-5);margin-bottom:var(--space-6);align-items:stretch}.settings-grid--two-equal>*{grid-column:auto}@media (min-width: 1024px){.settings-grid--two-equal{grid-template-columns:repeat(2,minmax(0,1fr))}}.card-spacer{flex-grow:1;min-height:24px}.section-description{color:var(--text-muted);font-size:.875rem;margin-bottom:var(--space-6);max-width:640px;line-height:1.6}.input-group{display:flex;align-items:center;gap:8px;background:var(--bg);border-radius:6px;padding:0 12px;height:42px;border:1px solid var(--border);transition:all .2s ease;position:relative}.input-group:hover{border-color:var(--border-strong)}.mesh-gradient-infra .input-group:focus-within,.mesh-gradient-ai .input-group:focus-within{border-color:var(--color-accent);background:rgb(var(--rgb-accent) / .03);box-shadow:0 0 0 1px var(--color-accent)}.input-group input,.input-group select{background:transparent;border:none;color:var(--text);font-size:.95rem;width:100%;height:100%;outline:none;font-family:JetBrains Mono,monospace;font-weight:500}.input-group input::placeholder{color:var(--text-dim);opacity:.5}.input-group select{cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 0 center;padding-right:2rem}.input-group .code-font{color:var(--text-dim);font-size:.7rem;font-weight:500;letter-spacing:1px;font-family:JetBrains Mono,monospace}.health-monitor-alert{display:flex;align-items:center;gap:12px;background:rgb(var(--rgb-amber-alt) / .06);border:1px solid rgb(var(--rgb-amber-alt) / .2);padding:12px 16px;border-radius:6px;color:var(--status-busy-fg);font-size:11px;font-weight:500}.status-banner{display:flex;align-items:center;gap:10px;padding:12px 16px;border-radius:6px;font-size:11px;animation:slideIn .3s ease;margin-top:16px;font-weight:500}@keyframes slideIn{0%{opacity:0;transform:translate(-20px)}to{opacity:1;transform:translate(0)}}.status-banner.success{background:rgb(var(--rgb-success) / .06);border:1px solid rgb(var(--rgb-success) / .25);color:var(--color-success)}.status-banner.error{background:#ff44440f;border:1px solid rgba(255,68,68,.25);color:var(--status-error-fg)}.cron-presets{margin-top:24px;padding-top:16px;border-top:1px solid var(--border)}.presets-label{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:500;color:var(--text-dim);margin-bottom:10px}.preset-chip{padding:5px 12px;margin:2px;background:var(--bg);border:1px solid var(--border);color:var(--text-muted);font-size:11px;font-weight:500;border-radius:4px;cursor:pointer;transition:all .2s ease;white-space:nowrap}.preset-chip:hover{background:var(--surface-2);color:var(--text);border-color:var(--border-strong)}.preset-chip.active{background:rgb(var(--rgb-accent) / .08);color:var(--color-accent);border-color:rgb(var(--rgb-accent) / .3)}.settings-footer{position:sticky;bottom:0;background:var(--surface);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border-top:1px solid var(--border-strong);padding:16px 32px;display:flex;justify-content:space-between;align-items:center;margin-top:auto;z-index:1000}.footer-left{display:flex;align-items:center;gap:16px}.footer-right{display:flex;gap:16px}.footer-dirty{display:inline-flex;align-items:center;gap:8px;color:var(--amber);font-family:Inter,sans-serif;font-size:13px;font-weight:500}.footer-dirty__dot{width:8px;height:8px;border-radius:50%;background:var(--amber);box-shadow:none;animation:dirty-pulse 2s ease-in-out infinite}@keyframes dirty-pulse{0%,to{opacity:1}50%{opacity:.55}}.footer-divider{width:1px;height:18px;background:var(--border-strong)}.save-btn{background:var(--color-accent);color:var(--bg);padding:10px 24px;border-radius:6px;font-weight:600;font-size:11px;border:none;transition:all .2s;display:flex;align-items:center;gap:6px}.save-btn:hover:not(:disabled){background:var(--color-accent-strong);transform:translateY(-1px);box-shadow:none}.save-btn:active:not(:disabled){transform:translateY(0)}.reset-btn{background:transparent;border:1px solid var(--border);color:var(--text-muted);padding:9px 20px;border-radius:6px;font-weight:500;font-size:11px;transition:all .2s}.reset-btn:hover:not(:disabled){background:var(--surface-2);color:var(--text);border-color:var(--border-strong)}.reset-to-defaults-btn{background:transparent;border:1px solid rgba(255,68,68,.2);color:#f44;padding:9px 20px;border-radius:6px;font-size:11px;font-weight:500;display:flex;align-items:center;gap:6px;transition:all .2s}.reset-to-defaults-btn:hover:not(:disabled){background:rgb(var(--rgb-red-alt) / .05);border-color:var(--red-alt)}.save-btn:disabled,.reset-btn:disabled,.reset-to-defaults-btn:disabled{opacity:.5;cursor:not-allowed;box-shadow:none;filter:grayscale(.2)}.setting-field{display:flex;flex-direction:column;gap:var(--space-2);width:100%}.setting-input-wrapper{display:flex;align-items:center;gap:var(--space-4);background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:8px 12px;transition:border-color .15s,box-shadow .15s}.setting-input-wrapper:focus-within{border-color:var(--color-accent);box-shadow:0 0 0 2px #3fb95066}.setting-input-wrapper input,.setting-input-wrapper select{background:transparent;border:none;color:var(--text);font-size:.875rem;width:100%;outline:none}.settings-loading{height:400px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--space-4);color:var(--text-muted)}.animate-spin{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@media (max-width: 640px){.settings-container{padding:var(--space-6)}.settings-footer{flex-direction:column;gap:var(--space-3);padding:var(--space-6)}.footer-left,.footer-right{width:100%;display:flex;flex-direction:column;gap:var(--space-2)}.reset-btn,.save-btn,.reset-to-defaults-btn{width:100%;justify-content:center;white-space:nowrap}}.save-btn,.reset-btn,.reset-to-defaults-btn{white-space:nowrap}.glow-error{box-shadow:none;border-color:rgb(var(--rgb-red) / .4)!important;animation:pulse-error 2s infinite}@keyframes pulse-error{0%{box-shadow:none}50%{box-shadow:none}to{box-shadow:none}}.critical-alert-card{background:#7f1d1d4d;border:2px solid var(--status-error-fg);border-radius:var(--radius-lg);overflow:hidden;margin-top:var(--space-8);box-shadow:none;animation:shake-entry .4s ease-out}@keyframes shake-entry{0%{transform:translate(0)}25%{transform:translate(-5px)}50%{transform:translate(5px)}75%{transform:translate(-5px)}to{transform:translate(0)}}.critical-header{background:var(--status-error-fg);color:var(--text);padding:16px 24px;display:flex;align-items:center;gap:12px;font-weight:700;font-size:1.1rem;letter-spacing:.02em}.setting-input-wrapper select{background:transparent;color:var(--text);font-family:Inter,sans-serif;font-weight:500;cursor:pointer;padding-right:32px;-moz-appearance:none;appearance:none;-webkit-appearance:none;color-scheme:dark}.setting-input-wrapper{position:relative}.setting-input-wrapper:after{content:"";position:absolute;right:12px;top:50%;transform:translateY(-50%);width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid var(--text-dim);pointer-events:none}.setting-input-wrapper:hover:after{border-top-color:var(--color-accent)}.setting-input-wrapper select option{background-color:var(--surface);color:var(--text);padding:12px}.critical-body{padding:24px;color:#fca5a5}.critical-body p{margin:0 0 16px;line-height:1.6;font-size:.95rem}.terminal-block-critical{background:var(--bg);border:1px solid rgba(127,29,29,.6);border-radius:8px;padding:16px;font-family:JetBrains Mono,monospace;font-size:.85rem;color:var(--white);display:flex;align-items:center;justify-content:space-between;gap:16px;margin-top:16px}.terminal-block-critical code{color:#a5f3fc;font-weight:600}.copy-btn-critical{background:rgb(var(--rgb-fg) / .1);border:1px solid rgb(var(--rgb-fg) / .2);color:var(--white);width:32px;height:32px;border-radius:6px;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .2s}.copy-btn-critical:hover{background:rgb(var(--rgb-fg) / .2);transform:scale(1.05)}.setting-input-wrapper:focus-within{border-color:var(--color-accent);background:rgb(var(--rgb-accent) / .03);box-shadow:0 0 0 1px var(--color-accent),inset 0 0 0 1px rgb(var(--rgb-accent) / .1);transform:translateY(-1px)}.live-signal{width:6px;height:6px;background-color:var(--color-success);border-radius:50%;margin-right:6px;position:relative}.live-signal:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;border-radius:50%;background:var(--color-success);animation:ping-signal 1.5s cubic-bezier(0,0,.2,1) infinite}@keyframes ping-signal{75%,to{transform:scale(2.5);opacity:0}}.input-container{background:rgb(var(--rgb-fg) / .05);border:1px solid rgb(var(--rgb-fg) / .1);height:42px}.status-pill{height:42px;background:rgb(var(--rgb-fg) / .05);border:1px solid rgb(var(--rgb-fg) / .1)}.card-header-dense{border-bottom:1px solid rgb(var(--rgb-fg) / .05);padding-bottom:16px;margin-bottom:24px}.form-field{display:flex;flex-direction:column;gap:10px}.field-label{font-size:.75rem;font-weight:700;color:var(--text-muted);padding-left:2px}.status-badge{display:flex;align-items:center;gap:8px;padding:0 16px;border-radius:6px;font-size:.8rem;font-weight:600;font-family:JetBrains Mono,monospace;height:42px;width:100%;transition:all .2s}.status-badge.success-filled{background:rgb(var(--rgb-success) / .15);color:var(--color-success-soft);border:1px solid rgb(var(--rgb-success) / .2)}.status-badge.error-filled{background:rgb(var(--rgb-red) / .15);color:var(--red-400);border:1px solid rgb(var(--rgb-red) / .2)}.ghost-btn{background:transparent;border:1px solid rgb(var(--rgb-fg) / .1);color:var(--text-muted);padding:0 20px;height:40px;border-radius:6px;font-weight:600;font-size:.85rem;transition:all .2s;display:flex;align-items:center;gap:8px}.ghost-btn:hover{background:rgb(var(--rgb-fg) / .05);color:var(--text);border-color:rgb(var(--rgb-fg) / .2)}.save-btn.primary{height:40px;box-shadow:none}.ai-provider-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:var(--space-4)}.ai-provider-card{display:flex;flex-direction:column;gap:12px;padding:16px 20px;border-radius:8px;border:1px solid rgb(var(--rgb-fg) / .08);background:rgb(var(--rgb-fg) / .03);cursor:pointer;transition:all .2s ease;text-align:left;color:var(--text)}.ai-provider-card:hover:not(.disabled){border-color:rgb(var(--rgb-fg) / .2);background:rgb(var(--rgb-fg) / .06)}.ai-provider-card.active{border-color:var(--color-accent);background:rgb(var(--rgb-accent) / .06);box-shadow:0 0 0 1px var(--color-accent)}.ai-provider-card.disabled{opacity:.4;cursor:not-allowed;filter:grayscale(.5)}.ai-provider-card-header{display:flex;align-items:center;gap:12px}.ai-provider-icon{width:36px;height:36px;border-radius:8px;background:rgb(var(--rgb-fg) / .06);display:flex;align-items:center;justify-content:center;color:var(--text-muted);flex-shrink:0}.ai-provider-card.active .ai-provider-icon{background:rgb(var(--rgb-accent) / .15);color:var(--color-accent)}.ai-provider-info{display:flex;flex-direction:column;gap:2px;min-width:0}.ai-provider-name{font-weight:700;font-size:.95rem;color:var(--text)}.ai-provider-desc{font-size:.75rem;color:var(--text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ai-provider-status{display:flex;align-items:center}.ai-config-display{display:flex;flex-direction:column;gap:1px;background:rgb(var(--rgb-fg) / .04);border-radius:8px;overflow:hidden;border:1px solid rgb(var(--rgb-fg) / .06)}.ai-config-row{display:flex;align-items:center;justify-content:space-between;padding:14px 20px;background:#02061780}.ai-config-row+.ai-config-row{border-top:1px solid rgb(var(--rgb-fg) / .04)}.ai-config-label{font-size:.8rem;font-weight:600;color:var(--text-muted)}.ai-config-value{font-size:.9rem;font-weight:500;color:var(--text);display:flex;align-items:center;gap:8px}.ai-config-value.mono{font-family:JetBrains Mono,monospace;font-size:.85rem;color:var(--text)}.ai-config-default{font-size:11px;background:rgb(var(--rgb-fg) / .06);color:var(--text-dim);padding:2px 6px;border-radius:4px}.toggle-group{display:flex;align-items:center;gap:12px}.switch{position:relative;display:inline-block;width:38px;height:22px;flex-shrink:0}.switch input{opacity:0;width:0;height:0}.slider{position:absolute;top:0;right:0;bottom:0;left:0;background:rgb(var(--rgb-fg) / .08);border:1px solid var(--border);transition:all .2s ease;cursor:pointer;border-radius:999px}.slider:before{content:"";position:absolute;width:16px;height:16px;left:2px;top:50%;transform:translateY(-50%);background:var(--text-muted);border-radius:50%;transition:all .2s ease}.switch input:checked+.slider{background:rgb(var(--rgb-accent) / .15);border-color:var(--color-accent);box-shadow:none}.switch input:checked+.slider:before{left:calc(100% - 18px);background:var(--color-accent)}.switch input:focus-visible+.slider{outline:2px solid var(--color-accent);outline-offset:2px}.toggle-label{font-size:11px;font-weight:700;color:var(--text-muted)}.cron-preview{margin-top:12px;padding:10px 12px;background:rgb(var(--rgb-well) / calc(.25 * var(--well-k)));border:1px solid var(--border);border-radius:6px;display:flex;flex-wrap:wrap;align-items:baseline;gap:8px}.cron-preview .preview-label{font-size:11px;font-weight:700;color:var(--text-dim)}.cron-preview .preview-value{font-size:13px;color:var(--text);font-weight:500}.cron-presets{margin-top:12px}.presets-label{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:600;color:var(--text-dim);margin-bottom:8px}.presets-grid{display:flex;flex-wrap:wrap;gap:8px}.preset-chip{padding:6px 12px;border-radius:6px;border:1px solid var(--border);background:rgb(var(--rgb-fg) / .03);color:var(--text-muted);font-size:11px;font-weight:600;cursor:pointer;transition:all .2s ease}.preset-chip:hover{border-color:var(--border);color:var(--text)}.preset-chip.active{background:rgb(var(--rgb-accent) / .18);border-color:var(--color-accent);color:var(--color-accent);box-shadow:inset 0 0 0 1px rgb(var(--rgb-accent) / .35)}.preset-chip__check{color:var(--color-accent)}.active-pill{display:inline-flex;align-items:center;gap:5px;margin-left:auto;padding:4px 10px;border-radius:999px;font-family:Inter,sans-serif;font-size:11px;font-weight:600;color:var(--color-accent);background:rgb(var(--rgb-accent) / .1);border:1px solid rgb(var(--rgb-accent) / .3);letter-spacing:.01em}.card-title-row{display:inline-flex;align-items:center;gap:6px}.modified-dot{width:6px;height:6px;border-radius:50%;background:var(--amber);box-shadow:none;display:inline-block}.input-group.has-error{border-color:rgb(var(--rgb-red) / .55);background:rgb(var(--rgb-red) / .04)}.input-group.has-error:focus-within{border-color:var(--red);box-shadow:0 0 0 1px var(--red);background:rgb(var(--rgb-red) / .06)}.input-status{display:flex;align-items:center;gap:6px;margin-top:4px;font-family:Inter,sans-serif;font-size:12px;font-weight:500;letter-spacing:.01em}.input-status--ok{color:var(--color-success)}.input-status--error{color:var(--status-error-fg)}.presets-grid--stacked{display:flex;flex-direction:column;gap:8px;align-items:stretch}.presets-grid--stacked .preset-chip{justify-content:flex-start;width:100%}.switch--header{margin-left:auto}.healing-state{font-size:11px;font-weight:700;margin-top:-4px}.healing-state.is-on{color:var(--color-accent)}.healing-state.is-off{color:var(--text-dim)}.healing-events{margin-top:auto;padding:12px;border-radius:var(--radius-md);background:rgb(var(--rgb-fg) / .025);border:1px solid var(--border);display:flex;flex-direction:column;gap:10px}.healing-events__link{margin-left:auto;display:inline-flex;align-items:center;gap:3px;text-decoration:none;color:var(--color-accent);font-family:JetBrains Mono,monospace;font-size:9.5px;font-weight:600;letter-spacing:.06em;padding:2px 6px;border-radius:4px;border:1px solid rgb(var(--rgb-accent) / .25);background:rgb(var(--rgb-accent) / .06);transition:all .15s ease;text-transform:none}.healing-events__link:hover{background:rgb(var(--rgb-accent) / .12);border-color:rgb(var(--rgb-accent) / .4)}.healing-events__header{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:700;color:var(--text-dim)}.healing-events__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px}.healing-events__empty{font-size:11.5px;color:var(--text-dim);line-height:1.45;padding:6px 2px}.healing-event{display:flex;gap:10px;align-items:flex-start}.healing-event__icon{color:var(--color-accent);flex-shrink:0;margin-top:1px}.healing-event__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.healing-event__row{display:flex;justify-content:space-between;align-items:baseline;gap:8px}.healing-event__kind{font-size:12px;font-weight:700;color:var(--text)}.healing-event__when{font-family:JetBrains Mono,monospace;font-size:10px;color:var(--text-dim);white-space:nowrap}.healing-event__message{font-size:11.5px;color:var(--text-muted);line-height:1.4}.settings-footer{animation:dirty-slide-up .2s cubic-bezier(.2,0,0,1)}@keyframes dirty-slide-up{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.page-header-subnote{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--text-dim);margin-left:8px}.provider-count-pill{display:inline-flex;align-items:center;margin-left:auto;padding:4px 10px;border-radius:6px;font-size:11px;font-weight:700}.provider-count-pill.is-ok{background:rgb(var(--rgb-success) / .1);color:var(--color-success);border:1px solid rgb(var(--rgb-success) / .3)}.provider-count-pill.is-empty{background:#8a938f14;color:var(--text-muted);border:1px solid var(--border-strong)}.provider-list{display:flex;flex-direction:column;gap:10px}.provider-row{display:flex;align-items:flex-start;gap:12px;padding:14px 16px;border-radius:var(--radius-md);border:1px solid var(--border);background:rgb(var(--rgb-fg) / .02);color:var(--text);text-align:left;cursor:pointer;transition:border-color .15s,background .15s,box-shadow .15s;width:100%}.provider-row:hover:not(:disabled):not(.is-active){border-color:var(--border-strong);background:rgb(var(--rgb-fg) / .04)}.provider-row.is-active{border-color:var(--color-accent);background:rgb(var(--rgb-accent) / .06);box-shadow:inset 0 0 0 1px rgb(var(--rgb-accent) / .4)}.provider-row.is-disabled{cursor:not-allowed;opacity:.55}.provider-row__icon{width:36px;height:36px;border-radius:var(--radius-md);display:flex;align-items:center;justify-content:center;background:rgb(var(--rgb-fg) / .05);color:var(--text-muted);flex-shrink:0;font-family:Outfit,sans-serif;font-weight:700;font-size:16px}.provider-row.is-active .provider-row__icon{background:rgb(var(--rgb-accent) / .18);color:var(--color-accent)}.ai-provider-glyph{font-family:Outfit,sans-serif;font-weight:700;font-size:18px;line-height:1}.provider-row__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.provider-row__name{font-weight:700;font-size:14px;color:var(--text)}.provider-row__desc{font-size:12px;color:var(--text-muted);line-height:1.4}.provider-row__status{margin-top:6px}.provider-status{display:inline-flex;align-items:center;gap:5px;padding:3px 8px;border-radius:4px;font-size:11px;font-weight:700;border:1px solid transparent}.provider-status--active{color:var(--color-accent);background:rgb(var(--rgb-accent) / .12);border-color:rgb(var(--rgb-accent) / .35)}.provider-status--off{color:var(--text-dim);background:rgb(var(--rgb-fg) / .04);border-color:var(--border-strong)}.provider-status--ready{color:var(--text-muted);background:rgb(var(--rgb-fg) / .04);border-color:var(--border)}.model-params{display:flex;flex-direction:column;gap:14px;margin-top:8px;padding:14px;border-radius:var(--radius-md);background:rgb(var(--rgb-fg) / .025);border:1px solid var(--border)}.model-params__header{display:flex;align-items:center;gap:8px;color:var(--text);font-family:Outfit,sans-serif;font-size:13px;font-weight:700;letter-spacing:.01em}.model-param{display:flex;flex-direction:column;gap:6px}.model-param__label-row{display:flex;justify-content:space-between;align-items:baseline}.model-param__label-row label{font-size:11px;font-weight:700;color:var(--text-dim)}.model-param__value{font-family:JetBrains Mono,monospace;font-size:13px;font-weight:700;color:var(--text)}.model-param__slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;height:4px;background:rgb(var(--rgb-fg) / .08);border-radius:999px;outline:none;cursor:pointer}.model-param__slider::-webkit-slider-runnable-track{height:4px;background:linear-gradient(to right,var(--color-accent) 0%,var(--color-accent) var(--slider-pct, 50%),rgb(var(--rgb-fg) / .08) var(--slider-pct, 50%),rgb(var(--rgb-fg) / .08) 100%);border-radius:999px}.model-param__slider::-moz-range-track{height:4px;background:rgb(var(--rgb-fg) / .08);border-radius:999px}.model-param__slider::-moz-range-progress{height:4px;background:var(--color-accent);border-radius:999px}.model-param__slider::-webkit-slider-thumb{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:14px;height:14px;border-radius:50%;background:var(--color-accent);cursor:pointer;margin-top:-5px;box-shadow:none;border:2px solid var(--bg)}.model-param__slider::-moz-range-thumb{width:14px;height:14px;border-radius:50%;background:var(--color-accent);cursor:pointer;box-shadow:none;border:2px solid var(--bg)}.test-connection-btn{margin-top:auto;display:flex;align-items:center;justify-content:center;gap:8px;padding:12px 16px;border-radius:var(--radius-md);background:rgb(var(--rgb-fg) / .04);border:1px solid var(--border-strong);color:var(--text);font-family:Inter,sans-serif;font-size:13px;font-weight:600;cursor:pointer;transition:background .15s,border-color .15s;width:100%}.test-connection-btn:hover:not(:disabled){background:rgb(var(--rgb-accent) / .06);border-color:rgb(var(--rgb-accent) / .4);color:var(--color-accent)}.test-connection-btn:disabled{opacity:.5;cursor:not-allowed}.ai-config-row .ai-config-default{margin-left:auto}.maintenance-notice{background:rgb(var(--rgb-blue) / .05)!important;border-color:rgb(var(--rgb-blue) / .25)!important;color:var(--blue)!important;text-transform:none!important;letter-spacing:normal!important}.stat-strip{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:var(--space-4);margin-bottom:var(--space-5)}.stat-strip__cell{background:var(--surface);border:1px solid var(--border-strong);border-radius:var(--radius-lg);padding:16px 20px;box-shadow:var(--shadow-sm);display:flex;flex-direction:column;gap:4px}.stat-strip__value{font-family:Outfit,sans-serif;font-size:28px;font-weight:700;line-height:1.1;color:var(--text);letter-spacing:-.02em}.stat-strip__value--accent{color:var(--color-accent)}.stat-strip__label{font-size:11px;font-weight:600;color:var(--text-dim)}.data-card{background:var(--surface);border:1px solid var(--border-strong);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);overflow:hidden}.data-card .table-row:hover{background:rgb(var(--rgb-fg) / .03)}.pill-chip{display:inline-flex;align-items:center;padding:3px 9px;border-radius:999px;font-family:JetBrains Mono,monospace;font-size:10.5px;font-weight:600;letter-spacing:.04em;border:1px solid transparent;line-height:1.4}.pill-chip--shared{background:rgb(var(--rgb-fg) / .04);color:var(--text-muted);border-color:var(--border-strong)}.pill-chip--team{background:rgb(var(--rgb-accent) / .1);color:var(--color-accent);border-color:rgb(var(--rgb-accent) / .3)}.pill-chip--scope{background:rgb(var(--rgb-blue) / .1);color:var(--blue);border-color:rgb(var(--rgb-blue) / .3)}.pill-chip--admin{background:rgb(var(--rgb-amber) / .12);color:var(--amber);border-color:rgb(var(--rgb-amber) / .35)}.scope-chips{display:inline-flex;flex-wrap:wrap;gap:4px}.row-meta-mono{font-family:JetBrains Mono,monospace;font-size:11px;color:var(--text-dim);margin-top:2px}.row-mono{font-family:JetBrains Mono,monospace;font-size:12px;color:var(--text-muted)}.row-action{display:inline-flex;align-items:center;gap:5px;padding:5px 10px;border-radius:var(--radius-md);background:transparent;border:1px solid var(--border-strong);color:var(--text-muted);font-size:11px;font-weight:600;cursor:pointer;transition:all .15s ease}.row-action:hover:not(:disabled){background:rgb(var(--rgb-fg) / .04);border-color:rgb(var(--rgb-fg) / .2);color:var(--text)}.row-action--danger{color:var(--status-error-fg);border-color:rgb(var(--rgb-red) / .25)}.row-action--danger:hover:not(:disabled){background:rgb(var(--rgb-red) / .06);border-color:rgb(var(--rgb-red) / .5);color:#fca5a5}.row-action:disabled{opacity:.4;cursor:not-allowed}.empty-state{background:var(--surface);border:1px dashed var(--border-strong);border-radius:var(--radius-lg);padding:56px 32px;text-align:center;display:flex;flex-direction:column;align-items:center;gap:12px}.empty-state__icon{width:56px;height:56px;border-radius:50%;background:rgb(var(--rgb-accent) / .08);color:var(--color-accent);display:flex;align-items:center;justify-content:center;margin-bottom:4px;border:1px solid rgb(var(--rgb-accent) / .18)}.empty-state__title{margin:0;font-family:Outfit,sans-serif;font-size:18px;font-weight:700;color:var(--text);letter-spacing:-.005em}.empty-state__copy{margin:0;font-size:13px;color:var(--text-muted);line-height:1.55;max-width:480px}.empty-state__action{margin-top:12px}.team-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:var(--space-4)}.team-card{background:var(--surface);border:1px solid var(--border-strong);border-radius:var(--radius-lg);padding:18px;box-shadow:var(--shadow-sm);display:flex;flex-direction:column;gap:14px;text-align:left;cursor:pointer;transition:border-color .15s,transform .15s,box-shadow .15s;color:var(--text);width:100%}.team-card:hover{border-color:rgb(var(--rgb-accent) / .4);transform:translateY(-1px);box-shadow:var(--shadow-md)}.team-card__header{display:flex;align-items:flex-start;gap:12px}.team-card__icon{width:36px;height:36px;border-radius:var(--radius-md);background:rgb(var(--rgb-accent) / .1);color:var(--color-accent);display:flex;align-items:center;justify-content:center;flex-shrink:0}.team-card__title{flex:1;min-width:0}.team-card__name{font-family:Outfit,sans-serif;font-size:15px;font-weight:700;color:var(--text);letter-spacing:-.005em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.team-card__id{font-family:JetBrains Mono,monospace;font-size:11px;color:var(--text-dim);margin-top:2px}.team-card__delete{flex-shrink:0}.team-card__metrics{display:flex;gap:20px;border-top:1px dashed var(--border);padding-top:12px}.team-card__metric{display:flex;align-items:center;gap:6px;color:var(--text-muted);font-size:12px}.team-card__metric svg{color:var(--text-dim)}.team-card__metric-value{font-family:Outfit,sans-serif;font-size:16px;font-weight:700;color:var(--text)}.team-card__metric-label{color:var(--text-dim);font-size:11px;letter-spacing:.02em}.team-card__footer{margin-top:auto;font-family:JetBrains Mono,monospace;font-size:10.5px;color:var(--text-dim);letter-spacing:.04em}:root[data-theme=light] .ai-config-row{background:var(--surface-2)}:root[data-theme=light] .setting-input-wrapper select{color-scheme:light}:root[data-theme=light] .reset-to-defaults-btn{border-color:rgb(var(--rgb-red) / .3);color:var(--red)}:root[data-theme=light] .row-action--danger:hover:not(:disabled){color:var(--red-100)}
1
+ .settings-container{padding:24px 32px;max-width:1440px;margin:0 auto;color:var(--text);animation:fadeIn .3s ease;min-height:calc(100vh - 40px);display:flex;flex-direction:column;font-family:Inter,-apple-system,sans-serif;position:relative;background:var(--bg);width:100%}.settings-container>header{padding-left:0;padding-right:0}.settings-header{margin-bottom:24px;padding-bottom:20px;border-bottom:1px solid var(--border)}.settings-header h2{margin:0;font-size:1.75rem;font-weight:700;font-family:Outfit,sans-serif;color:var(--text)}.settings-title-group{display:flex;align-items:center;gap:12px}.settings-title-group .settings-icon{color:var(--text-muted);flex-shrink:0}.settings-subtitle{color:var(--text-muted);font-size:.85rem;margin-top:6px;line-height:1.5}.settings-subtitle span{font-family:JetBrains Mono,monospace;font-size:.75rem;color:var(--text-dim)}.badge-elite{font-size:11px;padding:3px 8px;border-radius:4px;background:rgb(var(--rgb-accent) / .08);color:var(--color-accent);border:1px solid rgb(var(--rgb-accent) / .25);font-weight:500}.status-pill{display:inline-flex;align-items:center;gap:8px;padding:4px 12px;border-radius:6px;font-size:11px;font-weight:800;background:rgb(var(--rgb-well) / calc(.4 * var(--well-k)));border:1px solid var(--border)}.status-pill.success{border-color:rgb(var(--rgb-success) / .3);color:var(--color-success)}.status-pill.error{border-color:#ff44444d;color:var(--status-error-fg)}.locked-indicator{display:flex;align-items:center;gap:8px;font-size:.75rem;color:var(--text-dim);font-family:JetBrains Mono,monospace;margin-top:var(--space-4)}.locked-indicator code{background:rgb(var(--rgb-fg) / .05);color:var(--text-muted);padding:2px 6px;border-radius:4px}.settings-content{display:flex;flex-direction:column;gap:var(--space-8);flex-grow:1}.settings-section h3{display:flex;align-items:center;gap:var(--space-2);font-size:1.125rem;color:var(--text);font-weight:700;margin-bottom:var(--space-2)}.settings-content{display:flex;flex-direction:column;gap:var(--space-8);flex-grow:1;position:relative}.settings-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--space-6);margin-bottom:var(--space-6)}.settings-grid>*{grid-column:span 12}@media (min-width: 1024px){.settings-grid{grid-template-columns:repeat(3,1fr)}.settings-grid>*{grid-column:span 1}}.settings-grid--three{display:grid;grid-template-columns:1fr;gap:var(--space-5);margin-bottom:var(--space-6);align-items:stretch}.settings-grid--three>*{grid-column:auto}@media (min-width: 1024px){.settings-grid--three{grid-template-columns:repeat(3,minmax(0,1fr))}}.settings-grid--two-equal{display:grid;grid-template-columns:1fr;gap:var(--space-5);margin-bottom:var(--space-6);align-items:stretch}.settings-grid--two-equal>*{grid-column:auto}@media (min-width: 1024px){.settings-grid--two-equal{grid-template-columns:repeat(2,minmax(0,1fr))}}.card-spacer{flex-grow:1;min-height:24px}.section-description{color:var(--text-muted);font-size:.875rem;margin-bottom:var(--space-6);max-width:640px;line-height:1.6}.input-group{display:flex;align-items:center;gap:8px;background:var(--bg);border-radius:6px;padding:0 12px;height:42px;border:1px solid var(--border);transition:all .2s ease;position:relative}.input-group:hover{border-color:var(--border-strong)}.mesh-gradient-infra .input-group:focus-within,.mesh-gradient-ai .input-group:focus-within{border-color:var(--color-accent);background:rgb(var(--rgb-accent) / .03);box-shadow:0 0 0 1px var(--color-accent)}.input-group input,.input-group select{background:transparent;border:none;color:var(--text);font-size:.95rem;width:100%;height:100%;outline:none;font-family:JetBrains Mono,monospace;font-weight:500}.input-group input::placeholder{color:var(--text-dim);opacity:.5}.input-group select{cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 0 center;padding-right:2rem}.input-group .code-font{color:var(--text-dim);font-size:.7rem;font-weight:500;letter-spacing:1px;font-family:JetBrains Mono,monospace}.health-monitor-alert{display:flex;align-items:center;gap:12px;background:rgb(var(--rgb-amber-alt) / .06);border:1px solid rgb(var(--rgb-amber-alt) / .2);padding:12px 16px;border-radius:6px;color:var(--status-busy-fg);font-size:11px;font-weight:500}.status-banner{display:flex;align-items:center;gap:10px;padding:12px 16px;border-radius:6px;font-size:11px;animation:slideIn .3s ease;margin-top:16px;font-weight:500}@keyframes slideIn{0%{opacity:0;transform:translate(-20px)}to{opacity:1;transform:translate(0)}}.status-banner.success{background:rgb(var(--rgb-success) / .06);border:1px solid rgb(var(--rgb-success) / .25);color:var(--color-success)}.status-banner.error{background:#ff44440f;border:1px solid rgba(255,68,68,.25);color:var(--status-error-fg)}.cron-presets{margin-top:24px;padding-top:16px;border-top:1px solid var(--border)}.presets-label{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:500;color:var(--text-dim);margin-bottom:10px}.preset-chip{padding:5px 12px;margin:2px;background:var(--bg);border:1px solid var(--border);color:var(--text-muted);font-size:11px;font-weight:500;border-radius:4px;cursor:pointer;transition:all .2s ease;white-space:nowrap}.preset-chip:hover{background:var(--surface-2);color:var(--text);border-color:var(--border-strong)}.preset-chip.active{background:rgb(var(--rgb-accent) / .08);color:var(--color-accent);border-color:rgb(var(--rgb-accent) / .3)}.settings-footer{position:sticky;bottom:0;background:var(--surface);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border-top:1px solid var(--border-strong);padding:16px 32px;display:flex;justify-content:space-between;align-items:center;margin-top:auto;z-index:1000}.footer-left{display:flex;align-items:center;gap:16px}.footer-right{display:flex;gap:16px}.footer-dirty{display:inline-flex;align-items:center;gap:8px;color:var(--amber);font-family:Inter,sans-serif;font-size:13px;font-weight:500}.footer-dirty__dot{width:8px;height:8px;border-radius:50%;background:var(--amber);box-shadow:none;animation:dirty-pulse 2s ease-in-out infinite}@keyframes dirty-pulse{0%,to{opacity:1}50%{opacity:.55}}.footer-divider{width:1px;height:18px;background:var(--border-strong)}.save-btn{background:var(--color-accent);color:var(--bg);padding:10px 24px;border-radius:6px;font-weight:600;font-size:11px;border:none;transition:all .2s;display:flex;align-items:center;gap:6px}.save-btn:hover:not(:disabled){background:var(--color-accent-strong);transform:translateY(-1px);box-shadow:none}.save-btn:active:not(:disabled){transform:translateY(0)}.reset-btn{background:transparent;border:1px solid var(--border);color:var(--text-muted);padding:9px 20px;border-radius:6px;font-weight:500;font-size:11px;transition:all .2s}.reset-btn:hover:not(:disabled){background:var(--surface-2);color:var(--text);border-color:var(--border-strong)}.reset-to-defaults-btn{background:transparent;border:1px solid rgba(255,68,68,.2);color:#f44;padding:9px 20px;border-radius:6px;font-size:11px;font-weight:500;display:flex;align-items:center;gap:6px;transition:all .2s}.reset-to-defaults-btn:hover:not(:disabled){background:rgb(var(--rgb-red-alt) / .05);border-color:var(--red-alt)}.save-btn:disabled,.reset-btn:disabled,.reset-to-defaults-btn:disabled{opacity:.5;cursor:not-allowed;box-shadow:none;filter:grayscale(.2)}.setting-field{display:flex;flex-direction:column;gap:var(--space-2);width:100%}.setting-input-wrapper{display:flex;align-items:center;gap:var(--space-4);background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:8px 12px;transition:border-color .15s,box-shadow .15s}.setting-input-wrapper:focus-within{border-color:var(--color-accent);box-shadow:0 0 0 2px #3fb95066}.setting-input-wrapper input,.setting-input-wrapper select{background:transparent;border:none;color:var(--text);font-size:.875rem;width:100%;outline:none}.settings-loading{height:400px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--space-4);color:var(--text-muted)}.animate-spin{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@media (max-width: 640px){.settings-container{padding:var(--space-6)}.settings-footer{flex-direction:column;gap:var(--space-3);padding:var(--space-6)}.footer-left,.footer-right{width:100%;display:flex;flex-direction:column;gap:var(--space-2)}.reset-btn,.save-btn,.reset-to-defaults-btn{width:100%;justify-content:center;white-space:nowrap}}.save-btn,.reset-btn,.reset-to-defaults-btn{white-space:nowrap}.glow-error{box-shadow:none;border-color:rgb(var(--rgb-red) / .4)!important;animation:pulse-error 2s infinite}@keyframes pulse-error{0%{box-shadow:none}50%{box-shadow:none}to{box-shadow:none}}.critical-alert-card{background:#7f1d1d4d;border:2px solid var(--status-error-fg);border-radius:var(--radius-lg);overflow:hidden;margin-top:var(--space-8);box-shadow:none;animation:shake-entry .4s ease-out}@keyframes shake-entry{0%{transform:translate(0)}25%{transform:translate(-5px)}50%{transform:translate(5px)}75%{transform:translate(-5px)}to{transform:translate(0)}}.critical-header{background:var(--status-error-fg);color:var(--text);padding:16px 24px;display:flex;align-items:center;gap:12px;font-weight:700;font-size:1.1rem;letter-spacing:.02em}.setting-input-wrapper select{background:transparent;color:var(--text);font-family:Inter,sans-serif;font-weight:500;cursor:pointer;padding-right:32px;-moz-appearance:none;appearance:none;-webkit-appearance:none;color-scheme:dark}.setting-input-wrapper{position:relative}.setting-input-wrapper:after{content:"";position:absolute;right:12px;top:50%;transform:translateY(-50%);width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid var(--text-dim);pointer-events:none}.setting-input-wrapper:hover:after{border-top-color:var(--color-accent)}.setting-input-wrapper select option{background-color:var(--surface);color:var(--text);padding:12px}.critical-body{padding:24px;color:#fca5a5}.critical-body p{margin:0 0 16px;line-height:1.6;font-size:.95rem}.terminal-block-critical{background:var(--bg);border:1px solid rgba(127,29,29,.6);border-radius:8px;padding:16px;font-family:JetBrains Mono,monospace;font-size:.85rem;color:var(--white);display:flex;align-items:center;justify-content:space-between;gap:16px;margin-top:16px}.terminal-block-critical code{color:#a5f3fc;font-weight:600}.copy-btn-critical{background:rgb(var(--rgb-fg) / .1);border:1px solid rgb(var(--rgb-fg) / .2);color:var(--white);width:32px;height:32px;border-radius:6px;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .2s}.copy-btn-critical:hover{background:rgb(var(--rgb-fg) / .2);transform:scale(1.05)}.setting-input-wrapper:focus-within{border-color:var(--color-accent);background:rgb(var(--rgb-accent) / .03);box-shadow:0 0 0 1px var(--color-accent),inset 0 0 0 1px rgb(var(--rgb-accent) / .1);transform:translateY(-1px)}.live-signal{width:6px;height:6px;background-color:var(--color-success);border-radius:50%;margin-right:6px;position:relative}.live-signal:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;border-radius:50%;background:var(--color-success);animation:ping-signal 1.5s cubic-bezier(0,0,.2,1) infinite}@keyframes ping-signal{75%,to{transform:scale(2.5);opacity:0}}.input-container{background:rgb(var(--rgb-fg) / .05);border:1px solid rgb(var(--rgb-fg) / .1);height:42px}.status-pill{height:42px;background:rgb(var(--rgb-fg) / .05);border:1px solid rgb(var(--rgb-fg) / .1)}.card-header-dense{border-bottom:1px solid rgb(var(--rgb-fg) / .05);padding-bottom:16px;margin-bottom:24px}.form-field{display:flex;flex-direction:column;gap:10px}.field-label{font-size:.75rem;font-weight:700;color:var(--text-muted);padding-left:2px}.status-badge{display:flex;align-items:center;gap:8px;padding:0 16px;border-radius:6px;font-size:.8rem;font-weight:600;font-family:JetBrains Mono,monospace;height:42px;width:100%;transition:all .2s}.status-badge.success-filled{background:rgb(var(--rgb-success) / .15);color:var(--color-success-soft);border:1px solid rgb(var(--rgb-success) / .2)}.status-badge.error-filled{background:rgb(var(--rgb-red) / .15);color:var(--red-400);border:1px solid rgb(var(--rgb-red) / .2)}.ghost-btn{background:transparent;border:1px solid rgb(var(--rgb-fg) / .1);color:var(--text-muted);padding:0 20px;height:40px;border-radius:6px;font-weight:600;font-size:.85rem;transition:all .2s;display:flex;align-items:center;gap:8px}.ghost-btn:hover{background:rgb(var(--rgb-fg) / .05);color:var(--text);border-color:rgb(var(--rgb-fg) / .2)}.save-btn.primary{height:40px;box-shadow:none}.ai-provider-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:var(--space-4)}.ai-provider-card{display:flex;flex-direction:column;gap:12px;padding:16px 20px;border-radius:8px;border:1px solid rgb(var(--rgb-fg) / .08);background:rgb(var(--rgb-fg) / .03);cursor:pointer;transition:all .2s ease;text-align:left;color:var(--text)}.ai-provider-card:hover:not(.disabled){border-color:rgb(var(--rgb-fg) / .2);background:rgb(var(--rgb-fg) / .06)}.ai-provider-card.active{border-color:var(--color-accent);background:rgb(var(--rgb-accent) / .06);box-shadow:0 0 0 1px var(--color-accent)}.ai-provider-card.disabled{opacity:.4;cursor:not-allowed;filter:grayscale(.5)}.ai-provider-card-header{display:flex;align-items:center;gap:12px}.ai-provider-icon{width:36px;height:36px;border-radius:8px;background:rgb(var(--rgb-fg) / .06);display:flex;align-items:center;justify-content:center;color:var(--text-muted);flex-shrink:0}.ai-provider-card.active .ai-provider-icon{background:rgb(var(--rgb-accent) / .15);color:var(--color-accent)}.ai-provider-info{display:flex;flex-direction:column;gap:2px;min-width:0}.ai-provider-name{font-weight:700;font-size:.95rem;color:var(--text)}.ai-provider-desc{font-size:.75rem;color:var(--text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ai-provider-status{display:flex;align-items:center}.ai-config-display{display:flex;flex-direction:column;gap:1px;background:rgb(var(--rgb-fg) / .04);border-radius:8px;overflow:hidden;border:1px solid rgb(var(--rgb-fg) / .06)}.ai-config-row{display:flex;align-items:center;justify-content:space-between;padding:14px 20px;background:#02061780}.ai-config-row+.ai-config-row{border-top:1px solid rgb(var(--rgb-fg) / .04)}.ai-config-label{font-size:.8rem;font-weight:600;color:var(--text-muted)}.ai-config-value{font-size:.9rem;font-weight:500;color:var(--text);display:flex;align-items:center;gap:8px}.ai-config-value.mono{font-family:JetBrains Mono,monospace;font-size:.85rem;color:var(--text)}.ai-config-default{font-size:11px;background:rgb(var(--rgb-fg) / .06);color:var(--text-dim);padding:2px 6px;border-radius:4px}.toggle-group{display:flex;align-items:center;gap:12px}.switch{position:relative;display:inline-block;width:38px;height:22px;flex-shrink:0}.switch input{opacity:0;width:0;height:0}.slider{position:absolute;top:0;right:0;bottom:0;left:0;background:rgb(var(--rgb-fg) / .08);border:1px solid var(--border);transition:all .2s ease;cursor:pointer;border-radius:999px}.slider:before{content:"";position:absolute;width:16px;height:16px;left:2px;top:50%;transform:translateY(-50%);background:var(--text-muted);border-radius:50%;transition:all .2s ease}.switch input:checked+.slider{background:rgb(var(--rgb-accent) / .15);border-color:var(--color-accent);box-shadow:none}.switch input:checked+.slider:before{left:calc(100% - 18px);background:var(--color-accent)}.switch input:focus-visible+.slider{outline:2px solid var(--color-accent);outline-offset:2px}.toggle-label{font-size:11px;font-weight:700;color:var(--text-muted)}.cron-preview{margin-top:12px;padding:10px 12px;background:rgb(var(--rgb-well) / calc(.25 * var(--well-k)));border:1px solid var(--border);border-radius:6px;display:flex;flex-wrap:wrap;align-items:baseline;gap:8px}.cron-preview .preview-label{font-size:11px;font-weight:700;color:var(--text-dim)}.cron-preview .preview-value{font-size:13px;color:var(--text);font-weight:500}.cron-presets{margin-top:12px}.presets-label{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:600;color:var(--text-dim);margin-bottom:8px}.presets-grid{display:flex;flex-wrap:wrap;gap:8px}.preset-chip{padding:6px 12px;border-radius:6px;border:1px solid var(--border);background:rgb(var(--rgb-fg) / .03);color:var(--text-muted);font-size:11px;font-weight:600;cursor:pointer;transition:all .2s ease}.preset-chip:hover{border-color:var(--border);color:var(--text)}.preset-chip.active{background:rgb(var(--rgb-accent) / .18);border-color:var(--color-accent);color:var(--color-accent);box-shadow:inset 0 0 0 1px rgb(var(--rgb-accent) / .35)}.preset-chip__check{color:var(--color-accent)}.active-pill{display:inline-flex;align-items:center;gap:5px;margin-left:auto;padding:4px 10px;border-radius:999px;font-family:Inter,sans-serif;font-size:11px;font-weight:600;color:var(--color-accent);background:rgb(var(--rgb-accent) / .1);border:1px solid rgb(var(--rgb-accent) / .3);letter-spacing:.01em}.card-title-row{display:inline-flex;align-items:center;gap:6px}.modified-dot{width:6px;height:6px;border-radius:50%;background:var(--amber);box-shadow:none;display:inline-block}.input-group.has-error{border-color:rgb(var(--rgb-red) / .55);background:rgb(var(--rgb-red) / .04)}.input-group.has-error:focus-within{border-color:var(--red);box-shadow:0 0 0 1px var(--red);background:rgb(var(--rgb-red) / .06)}.input-status{display:flex;align-items:center;gap:6px;margin-top:4px;font-family:Inter,sans-serif;font-size:12px;font-weight:500;letter-spacing:.01em}.input-status--ok{color:var(--color-success)}.input-status--error{color:var(--status-error-fg)}.presets-grid--stacked{display:flex;flex-direction:column;gap:8px;align-items:stretch}.presets-grid--stacked .preset-chip{justify-content:flex-start;width:100%}.switch--header{margin-left:auto}.healing-state{font-size:11px;font-weight:700;margin-top:-4px}.healing-state.is-on{color:var(--color-accent)}.healing-state.is-off{color:var(--text-dim)}.healing-events{margin-top:auto;padding:12px;border-radius:var(--radius-md);background:rgb(var(--rgb-fg) / .025);border:1px solid var(--border);display:flex;flex-direction:column;gap:10px}.healing-events__link{margin-left:auto;display:inline-flex;align-items:center;gap:3px;text-decoration:none;color:var(--color-accent);font-family:JetBrains Mono,monospace;font-size:9.5px;font-weight:600;letter-spacing:.06em;padding:2px 6px;border-radius:4px;border:1px solid rgb(var(--rgb-accent) / .25);background:rgb(var(--rgb-accent) / .06);transition:all .15s ease;text-transform:none}.healing-events__link:hover{background:rgb(var(--rgb-accent) / .12);border-color:rgb(var(--rgb-accent) / .4)}.healing-events__header{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:700;color:var(--text-dim)}.healing-events__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px}.healing-events__empty{font-size:11.5px;color:var(--text-dim);line-height:1.45;padding:6px 2px}.healing-event{display:flex;gap:10px;align-items:flex-start}.healing-event__icon{color:var(--color-accent);flex-shrink:0;margin-top:1px}.healing-event__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.healing-event__row{display:flex;justify-content:space-between;align-items:baseline;gap:8px}.healing-event__kind{font-size:12px;font-weight:700;color:var(--text)}.healing-event__when{font-family:JetBrains Mono,monospace;font-size:10px;color:var(--text-dim);white-space:nowrap}.healing-event__message{font-size:11.5px;color:var(--text-muted);line-height:1.4}.settings-footer{animation:dirty-slide-up .2s cubic-bezier(.2,0,0,1)}@keyframes dirty-slide-up{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.page-header-subnote{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--text-dim);margin-left:8px}.provider-count-pill{display:inline-flex;align-items:center;margin-left:auto;padding:4px 10px;border-radius:6px;font-size:11px;font-weight:700}.provider-count-pill.is-ok{background:rgb(var(--rgb-success) / .1);color:var(--color-success);border:1px solid rgb(var(--rgb-success) / .3)}.provider-count-pill.is-empty{background:#8a938f14;color:var(--text-muted);border:1px solid var(--border-strong)}.provider-list{display:flex;flex-direction:column;gap:10px}.provider-row{display:flex;align-items:flex-start;gap:12px;padding:14px 16px;border-radius:var(--radius-md);border:1px solid var(--border);background:rgb(var(--rgb-fg) / .02);color:var(--text);text-align:left;cursor:pointer;transition:border-color .15s,background .15s,box-shadow .15s;width:100%}.provider-row:hover:not(:disabled):not(.is-active){border-color:var(--border-strong);background:rgb(var(--rgb-fg) / .04)}.provider-row.is-active{border-color:var(--color-accent);background:rgb(var(--rgb-accent) / .06);box-shadow:inset 0 0 0 1px rgb(var(--rgb-accent) / .4)}.provider-row.is-disabled{cursor:not-allowed;opacity:.55}.provider-row__icon{width:36px;height:36px;border-radius:var(--radius-md);display:flex;align-items:center;justify-content:center;background:rgb(var(--rgb-fg) / .05);color:var(--text-muted);flex-shrink:0;font-family:Outfit,sans-serif;font-weight:700;font-size:16px}.provider-row.is-active .provider-row__icon{background:rgb(var(--rgb-accent) / .18);color:var(--color-accent)}.ai-provider-glyph{font-family:Outfit,sans-serif;font-weight:700;font-size:18px;line-height:1}.provider-row__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.provider-row__name{font-weight:700;font-size:14px;color:var(--text)}.provider-row__desc{font-size:12px;color:var(--text-muted);line-height:1.4}.provider-row__status{margin-top:6px}.provider-status{display:inline-flex;align-items:center;gap:5px;padding:3px 8px;border-radius:4px;font-size:11px;font-weight:700;border:1px solid transparent}.provider-status--active{color:var(--color-accent);background:rgb(var(--rgb-accent) / .12);border-color:rgb(var(--rgb-accent) / .35)}.provider-status--off{color:var(--text-dim);background:rgb(var(--rgb-fg) / .04);border-color:var(--border-strong)}.provider-status--ready{color:var(--text-muted);background:rgb(var(--rgb-fg) / .04);border-color:var(--border)}.test-connection-btn{margin-top:auto;display:flex;align-items:center;justify-content:center;gap:8px;padding:12px 16px;border-radius:var(--radius-md);background:rgb(var(--rgb-fg) / .04);border:1px solid var(--border-strong);color:var(--text);font-family:Inter,sans-serif;font-size:13px;font-weight:600;cursor:pointer;transition:background .15s,border-color .15s;width:100%}.test-connection-btn:hover:not(:disabled){background:rgb(var(--rgb-accent) / .06);border-color:rgb(var(--rgb-accent) / .4);color:var(--color-accent)}.test-connection-btn:disabled{opacity:.5;cursor:not-allowed}.ai-config-row .ai-config-default{margin-left:auto}.maintenance-notice{background:rgb(var(--rgb-blue) / .05)!important;border-color:rgb(var(--rgb-blue) / .25)!important;color:var(--blue)!important;text-transform:none!important;letter-spacing:normal!important}.stat-strip{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:var(--space-4);margin-bottom:var(--space-5)}.stat-strip__cell{background:var(--surface);border:1px solid var(--border-strong);border-radius:var(--radius-lg);padding:16px 20px;box-shadow:var(--shadow-sm);display:flex;flex-direction:column;gap:4px}.stat-strip__value{font-family:Outfit,sans-serif;font-size:28px;font-weight:700;line-height:1.1;color:var(--text);letter-spacing:-.02em}.stat-strip__value--accent{color:var(--color-accent)}.stat-strip__label{font-size:11px;font-weight:600;color:var(--text-dim)}.data-card{background:var(--surface);border:1px solid var(--border-strong);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);overflow:hidden}.data-card .table-row:hover{background:rgb(var(--rgb-fg) / .03)}.pill-chip{display:inline-flex;align-items:center;padding:3px 9px;border-radius:999px;font-family:JetBrains Mono,monospace;font-size:10.5px;font-weight:600;letter-spacing:.04em;border:1px solid transparent;line-height:1.4}.pill-chip--shared{background:rgb(var(--rgb-fg) / .04);color:var(--text-muted);border-color:var(--border-strong)}.pill-chip--team{background:rgb(var(--rgb-accent) / .1);color:var(--color-accent);border-color:rgb(var(--rgb-accent) / .3)}.pill-chip--scope{background:rgb(var(--rgb-blue) / .1);color:var(--blue);border-color:rgb(var(--rgb-blue) / .3)}.pill-chip--admin{background:rgb(var(--rgb-amber) / .12);color:var(--amber);border-color:rgb(var(--rgb-amber) / .35)}.scope-chips{display:inline-flex;flex-wrap:wrap;gap:4px}.row-meta-mono{font-family:JetBrains Mono,monospace;font-size:11px;color:var(--text-dim);margin-top:2px}.row-mono{font-family:JetBrains Mono,monospace;font-size:12px;color:var(--text-muted)}.row-action{display:inline-flex;align-items:center;gap:5px;padding:5px 10px;border-radius:var(--radius-md);background:transparent;border:1px solid var(--border-strong);color:var(--text-muted);font-size:11px;font-weight:600;cursor:pointer;transition:all .15s ease}.row-action:hover:not(:disabled){background:rgb(var(--rgb-fg) / .04);border-color:rgb(var(--rgb-fg) / .2);color:var(--text)}.row-action--danger{color:var(--status-error-fg);border-color:rgb(var(--rgb-red) / .25)}.row-action--danger:hover:not(:disabled){background:rgb(var(--rgb-red) / .06);border-color:rgb(var(--rgb-red) / .5);color:#fca5a5}.row-action:disabled{opacity:.4;cursor:not-allowed}.empty-state{background:var(--surface);border:1px dashed var(--border-strong);border-radius:var(--radius-lg);padding:56px 32px;text-align:center;display:flex;flex-direction:column;align-items:center;gap:12px}.empty-state__icon{width:56px;height:56px;border-radius:50%;background:rgb(var(--rgb-accent) / .08);color:var(--color-accent);display:flex;align-items:center;justify-content:center;margin-bottom:4px;border:1px solid rgb(var(--rgb-accent) / .18)}.empty-state__title{margin:0;font-family:Outfit,sans-serif;font-size:18px;font-weight:700;color:var(--text);letter-spacing:-.005em}.empty-state__copy{margin:0;font-size:13px;color:var(--text-muted);line-height:1.55;max-width:480px}.empty-state__action{margin-top:12px}.team-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:var(--space-4)}.team-card{background:var(--surface);border:1px solid var(--border-strong);border-radius:var(--radius-lg);padding:18px;box-shadow:var(--shadow-sm);display:flex;flex-direction:column;gap:14px;text-align:left;cursor:pointer;transition:border-color .15s,transform .15s,box-shadow .15s;color:var(--text);width:100%}.team-card:hover{border-color:rgb(var(--rgb-accent) / .4);transform:translateY(-1px);box-shadow:var(--shadow-md)}.team-card__header{display:flex;align-items:flex-start;gap:12px}.team-card__icon{width:36px;height:36px;border-radius:var(--radius-md);background:rgb(var(--rgb-accent) / .1);color:var(--color-accent);display:flex;align-items:center;justify-content:center;flex-shrink:0}.team-card__title{flex:1;min-width:0}.team-card__name{font-family:Outfit,sans-serif;font-size:15px;font-weight:700;color:var(--text);letter-spacing:-.005em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.team-card__id{font-family:JetBrains Mono,monospace;font-size:11px;color:var(--text-dim);margin-top:2px}.team-card__delete{flex-shrink:0}.team-card__metrics{display:flex;gap:20px;border-top:1px dashed var(--border);padding-top:12px}.team-card__metric{display:flex;align-items:center;gap:6px;color:var(--text-muted);font-size:12px}.team-card__metric svg{color:var(--text-dim)}.team-card__metric-value{font-family:Outfit,sans-serif;font-size:16px;font-weight:700;color:var(--text)}.team-card__metric-label{color:var(--text-dim);font-size:11px;letter-spacing:.02em}.team-card__footer{margin-top:auto;font-family:JetBrains Mono,monospace;font-size:10.5px;color:var(--text-dim);letter-spacing:.04em}:root[data-theme=light] .ai-config-row{background:var(--surface-2)}:root[data-theme=light] .setting-input-wrapper select{color-scheme:light}:root[data-theme=light] .reset-to-defaults-btn{border-color:rgb(var(--rgb-red) / .3);color:var(--red)}:root[data-theme=light] .row-action--danger:hover:not(:disabled){color:var(--red-100)}
@@ -1,4 +1,4 @@
1
- import{c as X,t as G,r as h,p as O,X as g,j as e,A as W,C as E,V as K,e as U,W as Y,Y as J}from"./index-BbKEXUFH.js";/* empty css */import{S as x,A as Q}from"./SettingCard-Njz1pUIJ.js";import{M as Z}from"./Modal-CFCp3n9d.js";import{B as j}from"./button-Dda4irn_.js";import{P as ee}from"./page-header-BjzgWz-T.js";import{C as w}from"./circle-check-CqAJjQ5O.js";import{C as ae}from"./clock-CpuDKysU.js";import{M as se}from"./mouse-pointer-2-DLI1RtzU.js";import{C as te}from"./check-CZ9iw9-e.js";import{A as le}from"./activity-DOVdsfMQ.js";import{C as ne}from"./calendar-BMr0Q62v.js";import{A as re}from"./arrow-up-right-DuAftMWB.js";import"./index-DJGc1M2f.js";import"./utils-CDN07tui.js";/**
1
+ import{c as X,t as G,r as h,p as O,X as g,j as e,A as W,C as E,V as K,e as U,W as Y,Y as J}from"./index-B0gQkEg0.js";/* empty css */import{S as x,A as Q}from"./SettingCard-CPm3w9l2.js";import{M as Z}from"./Modal-C6WjZFeI.js";import{B as j}from"./button-DDau4Axv.js";import{P as ee}from"./page-header-D8KUX6Qg.js";import{C as w}from"./circle-check-CBv88cH7.js";import{C as ae}from"./clock-BPrld-3h.js";import{M as se}from"./mouse-pointer-2-CNsQfo6w.js";import{C as te}from"./check-K1ovTQam.js";import{A as le}from"./activity-DrLbHifn.js";import{C as ne}from"./calendar-DQ83B1ed.js";import{A as re}from"./arrow-up-right-gMg0ffvv.js";import"./index-72oF89LJ.js";import"./utils-CDN07tui.js";/**
2
2
  * @license lucide-react v0.555.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1 +1 @@
1
- import{j as t}from"./index-BbKEXUFH.js";const d={neutral:"text-[var(--text-muted)]",good:"text-[var(--color-success)]",bad:"text-[var(--color-danger)]"},o=({label:e,value:r,valueCls:a="text-[var(--text)]",note:s,noteTone:x="neutral"})=>t.jsxs("div",{className:"min-w-0 rounded-lg border border-[var(--border)] bg-[var(--surface)] px-4 py-3",children:[t.jsx("div",{className:"text-xs text-[var(--text-muted)]",children:e}),t.jsx("div",{className:`mt-1 text-2xl font-semibold tabular-nums leading-tight truncate ${a}`,children:r}),t.jsx("div",{className:`mt-0.5 text-[11px] truncate ${d[x]}`,children:s??" "})]});export{o as S};
1
+ import{j as t}from"./index-B0gQkEg0.js";const d={neutral:"text-[var(--text-muted)]",good:"text-[var(--color-success)]",bad:"text-[var(--color-danger)]"},o=({label:e,value:r,valueCls:a="text-[var(--text)]",note:s,noteTone:x="neutral"})=>t.jsxs("div",{className:"min-w-0 rounded-lg border border-[var(--border)] bg-[var(--surface)] px-4 py-3",children:[t.jsx("div",{className:"text-xs text-[var(--text-muted)]",children:e}),t.jsx("div",{className:`mt-1 text-2xl font-semibold tabular-nums leading-tight truncate ${a}`,children:r}),t.jsx("div",{className:`mt-0.5 text-[11px] truncate ${d[x]}`,children:s??" "})]});export{o as S};
@@ -1,4 +1,4 @@
1
- import{c as e}from"./index-BbKEXUFH.js";/**
1
+ import{c as e}from"./index-B0gQkEg0.js";/**
2
2
  * @license lucide-react v0.555.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1 +1 @@
1
- import{t as D,r as n,j as e,A as I,_ as f,q as $}from"./index-BbKEXUFH.js";/* empty css */import{a as F}from"./time-D-gjmE1o.js";import{T as E,a as M,b as T,c as u,d as P,e as p}from"./Table-BcTm5xV2.js";import{F as K}from"./FieldGroup-Cb5QZsE1.js";import{P as R}from"./page-header-BjzgWz-T.js";import{M as Q}from"./Modal-CFCp3n9d.js";import{B as A}from"./button-Dda4irn_.js";import{I as V}from"./input-Cm0tmSoc.js";import{S as U}from"./select-88jHcl-Z.js";import{l as W}from"./users-CFba5TSy.js";import{P as N}from"./plus-nBFSMu_X.js";import{A as X}from"./arrow-left-MMbTmgoE.js";import{T as L}from"./trash-2-C5RSuX0e.js";import"./index-DJGc1M2f.js";import"./utils-CDN07tui.js";const B={SUPER_ADMIN:"Super admin",ADMIN:"Admin",MEMBER:"Member"};async function j(i,r){const t=await fetch(i,{credentials:"include",...r});if(!t.ok){const c=await t.json().catch(()=>({}));throw Object.assign(new Error(c.error||`HTTP ${t.status}`),{status:t.status})}return t.json()}const ve=()=>{const{toast:i}=D(),[r,t]=n.useState([]),[c,m]=n.useState(!0),[l,h]=n.useState(!1),[x,d]=n.useState(!1),[_,C]=n.useState(null),v=async()=>{m(!0);try{const a=await j("/xenon/api/teams");t(a),h(!1)}catch(a){a.status===403?h(!0):i(`Failed to load teams: ${a.message}`,"error")}finally{m(!1)}};if(n.useEffect(()=>{v()},[]),c)return e.jsxs("div",{className:"settings-loading",children:[e.jsx(I,{className:"animate-spin",size:32}),e.jsx("span",{children:"Loading teams…"})]});if(l)return e.jsx("div",{className:"settings-container",children:e.jsx(R,{icon:f,title:"Teams",subtitle:e.jsxs(e.Fragment,{children:["Your key lacks the ",e.jsx("code",{children:"admin"})," scope. Ask an administrator to promote your key before you can manage teams."]})})});if(_)return e.jsx(se,{team:_,onBack:()=>{C(null),v()}});const S=r.reduce((a,o)=>a+o.deviceCount,0),y=r.reduce((a,o)=>a+o.memberCount,0);return e.jsxs("div",{className:"settings-container",children:[e.jsx(R,{icon:f,title:"Teams",subtitle:"Group devices and members by team. Devices with no team assignment stay in the shared pool — visible to every authenticated user.",action:r.length>0?e.jsxs("button",{type:"button",className:"page-header-action",onClick:()=>d(!0),children:[e.jsx(N,{size:16}),e.jsx("span",{children:"New team"})]}):void 0}),e.jsxs("div",{className:"settings-content",children:[r.length>0&&e.jsxs("div",{className:"stat-strip",children:[e.jsxs("div",{className:"stat-strip__cell",children:[e.jsx("div",{className:"stat-strip__value stat-strip__value--accent",children:r.length}),e.jsx("div",{className:"stat-strip__label",children:"Teams"})]}),e.jsxs("div",{className:"stat-strip__cell",children:[e.jsx("div",{className:"stat-strip__value",children:y}),e.jsx("div",{className:"stat-strip__label",children:"Members"})]}),e.jsxs("div",{className:"stat-strip__cell",children:[e.jsx("div",{className:"stat-strip__value",children:S}),e.jsx("div",{className:"stat-strip__label",children:"Devices assigned"})]})]}),r.length===0?e.jsxs("div",{className:"empty-state",children:[e.jsx("div",{className:"empty-state__icon",children:e.jsx(f,{size:28})}),e.jsx("h3",{className:"empty-state__title",children:"No teams yet"}),e.jsx("p",{className:"empty-state__copy",children:"Every device is in the shared pool until you create a team and assign it. Teams let you scope an API key to a subset of devices — useful for separating CI runners, staging fleets, or per-team device pools."}),e.jsxs("button",{type:"button",className:"page-header-action empty-state__action",onClick:()=>d(!0),children:[e.jsx(N,{size:16}),e.jsx("span",{children:"Create your first team"})]})]}):e.jsx("div",{className:"team-grid",children:r.map(a=>e.jsxs("button",{type:"button",className:"team-card",onClick:()=>C(a),children:[e.jsxs("div",{className:"team-card__header",children:[e.jsx("div",{className:"team-card__icon",children:e.jsx(f,{size:16})}),e.jsxs("div",{className:"team-card__title",children:[e.jsx("div",{className:"team-card__name",children:a.name}),e.jsx("div",{className:"team-card__id",children:a.id.slice(0,8)})]}),e.jsx("span",{className:"team-card__delete",role:"button",tabIndex:0,onClick:o=>o.stopPropagation(),children:e.jsx(ee,{team:a,onDeleted:v})})]}),e.jsxs("div",{className:"team-card__metrics",children:[e.jsxs("div",{className:"team-card__metric",children:[e.jsx($,{size:13}),e.jsx("span",{className:"team-card__metric-value",children:a.deviceCount}),e.jsx("span",{className:"team-card__metric-label",children:"devices"})]}),e.jsxs("div",{className:"team-card__metric",children:[e.jsx(f,{size:13}),e.jsx("span",{className:"team-card__metric-value",children:a.memberCount}),e.jsx("span",{className:"team-card__metric-label",children:"members"})]})]}),e.jsxs("div",{className:"team-card__footer",children:["Created ",F(a.createdAt)]})]},a.id))})]}),x&&e.jsx(Z,{onClose:()=>d(!1),onCreated:()=>{d(!1),v()}})]})},Z=({onClose:i,onCreated:r})=>{const{toast:t}=D(),[c,m]=n.useState(""),[l,h]=n.useState(!1),x=async()=>{if(!c.trim())return t("Name required","error");h(!0);try{await j("/xenon/api/teams",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:c.trim()})}),t("Team created","success"),r()}catch(d){t(d.message,"error")}finally{h(!1)}};return e.jsx(Q,{open:!0,onClose:i,title:"New team",footer:e.jsxs(e.Fragment,{children:[e.jsx(A,{variant:"secondary",onClick:i,disabled:l,children:"Cancel"}),e.jsxs(A,{variant:"primary",onClick:x,disabled:l,children:[l?e.jsx(I,{className:"animate-spin",size:18}):e.jsx(N,{size:18}),l?"Creating…":"Create"]})]}),children:e.jsx("div",{style:{display:"flex",flexDirection:"column"},children:e.jsx(K,{label:"Name",htmlFor:"team-name",children:e.jsx(V,{id:"team-name",type:"text",value:c,onChange:d=>m(d.target.value),placeholder:"android-team, qa-ios, …",className:"w-full",autoFocus:!0})})})})},ee=({team:i,onDeleted:r})=>{const{toast:t}=D(),[c,m]=n.useState(!1),l=async h=>{if(h.stopPropagation(),!!window.confirm(`Delete team "${i.name}"? It must have no devices, members or apps.`)){m(!0);try{await j(`/xenon/api/teams/${i.id}`,{method:"DELETE"}),t("Team deleted","success"),r()}catch(x){t(x.message,"error")}finally{m(!1)}}};return e.jsxs("button",{className:"reset-btn",disabled:c,onClick:l,style:{color:"var(--status-error-fg)"},children:[e.jsx(L,{size:14}),c?"Deleting…":"Delete"]})},se=({team:i,onBack:r})=>{const{toast:t}=D(),[c,m]=n.useState([]),[l,h]=n.useState([]),[x,d]=n.useState([]),[_,C]=n.useState([]),[v,S]=n.useState(!0),[y,a]=n.useState(""),[o,k]=n.useState(""),b=async()=>{S(!0);try{const[s,g,Y]=await Promise.all([j(`/xenon/api/teams/${i.id}/members`),W(),j("/xenon/api/device")]);m(s),h(g);const z=Y;d(z.filter(w=>w.teamId===i.id)),C(z.filter(w=>!w.teamId))}catch(s){t(`Failed to load team detail: ${s.message}`,"error")}finally{S(!1)}};n.useEffect(()=>{b()},[i.id]);const O=async()=>{if(y)try{await j(`/xenon/api/teams/${i.id}/members`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userId:y})}),t("Member added","success"),a(""),b()}catch(s){t(s.message,"error")}},H=async s=>{if(window.confirm("Remove this member from the team?"))try{await j(`/xenon/api/teams/${i.id}/members/${encodeURIComponent(s)}`,{method:"DELETE"}),t("Member removed","success"),b()}catch(g){t(g.message,"error")}},J=async()=>{if(o)try{await j(`/xenon/api/device/${encodeURIComponent(o)}/team`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({teamId:i.id})}),t("Device assigned","success"),k(""),b()}catch(s){t(s.message,"error")}},q=async s=>{if(window.confirm("Return this device to the shared pool?"))try{await j(`/xenon/api/device/${encodeURIComponent(s)}/team`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({teamId:null})}),t("Device unassigned","success"),b()}catch(g){t(g.message,"error")}},G=l.filter(s=>!c.some(g=>g.userId===s.id));return e.jsxs("div",{className:"settings-container mesh-gradient-infra",children:[e.jsxs("div",{className:"settings-header",children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:12},children:[e.jsx("button",{onClick:r,className:"reset-btn",style:{padding:"4px 8px"},"aria-label":"Back",children:e.jsx(X,{size:18})}),e.jsxs("div",{className:"settings-title-group",children:[e.jsx(f,{className:"settings-icon infra-icon",size:28}),e.jsx("h2",{children:i.name})]})]}),e.jsx("p",{className:"settings-subtitle",children:`Manage members and devices assigned to this team. Add a shared device below, or choose "Assign team…" in a device's ⋯ menu on the Devices page.`})]}),v?e.jsx("div",{className:"settings-loading",children:e.jsx(I,{className:"animate-spin",size:32})}):e.jsxs("div",{className:"settings-content",style:{display:"flex",flexDirection:"column",gap:24},children:[e.jsxs("section",{children:[e.jsxs("h3",{style:{marginBottom:12},children:["Members (",c.length,")"]}),e.jsxs("div",{className:"surface-card",style:{padding:12},children:[e.jsxs("div",{style:{display:"flex",gap:8,marginBottom:12},children:[e.jsxs(U,{value:y,onChange:s=>a(s.target.value),className:"flex-1",children:[e.jsx("option",{value:"",children:"Select a user to add…"}),G.map(s=>e.jsxs("option",{value:s.id,children:[s.name," (",s.email,") — ",B[s.role]]},s.id))]}),e.jsxs(A,{variant:"primary",onClick:O,disabled:!y,children:[e.jsx(N,{size:16})," Add"]})]}),c.length===0?e.jsx("p",{style:{opacity:.6,textAlign:"center",padding:24},children:"No members yet. Add a user above."}):e.jsxs(E,{children:[e.jsx(M,{children:e.jsxs(T,{children:[e.jsx(u,{children:"Name"}),e.jsx(u,{children:"Email"}),e.jsx(u,{children:"Role"}),e.jsx(u,{children:"Added"}),e.jsx(u,{style:{textAlign:"right"},children:"Action"})]})}),e.jsx(P,{children:c.map(s=>e.jsxs(T,{children:[e.jsx(p,{children:s.name}),e.jsx(p,{children:e.jsx("code",{style:{fontSize:"11px",color:"var(--text-muted)"},children:s.email})}),e.jsx(p,{children:B[s.role]}),e.jsx(p,{children:F(s.addedAt)}),e.jsx(p,{style:{textAlign:"right"},children:e.jsx("button",{className:"reset-btn",onClick:()=>H(s.userId),style:{color:"var(--status-error-fg)"},"aria-label":"Remove member",title:"Remove member",children:e.jsx(L,{size:14})})})]},s.userId))})]})]})]}),e.jsxs("section",{children:[e.jsxs("h3",{style:{marginBottom:12},children:["Devices (",x.length,")"]}),e.jsxs("div",{className:"surface-card",style:{padding:12},children:[e.jsxs("div",{style:{display:"flex",gap:8,marginBottom:12},children:[e.jsxs(U,{value:o,onChange:s=>k(s.target.value),className:"flex-1",children:[e.jsx("option",{value:"",children:"Select a shared-pool device to assign…"}),_.map(s=>e.jsxs("option",{value:s.udid,children:[s.name," (",s.platform,") — ",s.udid.slice(0,12),"…"]},s.udid))]}),e.jsxs(A,{variant:"primary",onClick:J,disabled:!o,children:[e.jsx(N,{size:16})," Assign"]})]}),x.length===0?e.jsx("p",{style:{opacity:.6,textAlign:"center",padding:24},children:"No devices assigned. Pick a shared device above to add one."}):e.jsxs(E,{children:[e.jsx(M,{children:e.jsxs(T,{children:[e.jsx(u,{children:"Name"}),e.jsx(u,{children:"Platform"}),e.jsx(u,{children:"UDID"}),e.jsx(u,{style:{textAlign:"right"},children:"Actions"})]})}),e.jsx(P,{children:x.map(s=>e.jsxs(T,{children:[e.jsxs(p,{children:[e.jsx($,{size:14,style:{marginRight:6,verticalAlign:"middle"}}),s.name]}),e.jsx(p,{children:s.platform}),e.jsx(p,{children:e.jsxs("code",{style:{fontSize:"11px"},children:[s.udid.slice(0,14),"…"]})}),e.jsx(p,{style:{textAlign:"right"},children:e.jsx("button",{className:"reset-btn",onClick:()=>q(s.udid),children:"Return to shared pool"})})]},s.udid))})]})]})]})]})]})};export{ve as Teams};
1
+ import{t as D,r as n,j as e,A as I,_ as f,q as $}from"./index-B0gQkEg0.js";/* empty css */import{a as F}from"./time-D-gjmE1o.js";import{T as E,a as M,b as T,c as u,d as P,e as p}from"./Table-DwIgERRt.js";import{F as K}from"./FieldGroup-GOgssFEP.js";import{P as R}from"./page-header-D8KUX6Qg.js";import{M as Q}from"./Modal-C6WjZFeI.js";import{B as A}from"./button-DDau4Axv.js";import{I as V}from"./input-DIc4MSC7.js";import{S as U}from"./select-BWpnhWXj.js";import{l as W}from"./users-D7tKMESH.js";import{P as N}from"./plus-C9Zj84UF.js";import{A as X}from"./arrow-left-BtB0_Jg_.js";import{T as L}from"./trash-2-CZ2ew6AZ.js";import"./index-72oF89LJ.js";import"./utils-CDN07tui.js";const B={SUPER_ADMIN:"Super admin",ADMIN:"Admin",MEMBER:"Member"};async function j(i,r){const t=await fetch(i,{credentials:"include",...r});if(!t.ok){const c=await t.json().catch(()=>({}));throw Object.assign(new Error(c.error||`HTTP ${t.status}`),{status:t.status})}return t.json()}const ve=()=>{const{toast:i}=D(),[r,t]=n.useState([]),[c,m]=n.useState(!0),[l,h]=n.useState(!1),[x,d]=n.useState(!1),[_,C]=n.useState(null),v=async()=>{m(!0);try{const a=await j("/xenon/api/teams");t(a),h(!1)}catch(a){a.status===403?h(!0):i(`Failed to load teams: ${a.message}`,"error")}finally{m(!1)}};if(n.useEffect(()=>{v()},[]),c)return e.jsxs("div",{className:"settings-loading",children:[e.jsx(I,{className:"animate-spin",size:32}),e.jsx("span",{children:"Loading teams…"})]});if(l)return e.jsx("div",{className:"settings-container",children:e.jsx(R,{icon:f,title:"Teams",subtitle:e.jsxs(e.Fragment,{children:["Your key lacks the ",e.jsx("code",{children:"admin"})," scope. Ask an administrator to promote your key before you can manage teams."]})})});if(_)return e.jsx(se,{team:_,onBack:()=>{C(null),v()}});const S=r.reduce((a,o)=>a+o.deviceCount,0),y=r.reduce((a,o)=>a+o.memberCount,0);return e.jsxs("div",{className:"settings-container",children:[e.jsx(R,{icon:f,title:"Teams",subtitle:"Group devices and members by team. Devices with no team assignment stay in the shared pool — visible to every authenticated user.",action:r.length>0?e.jsxs("button",{type:"button",className:"page-header-action",onClick:()=>d(!0),children:[e.jsx(N,{size:16}),e.jsx("span",{children:"New team"})]}):void 0}),e.jsxs("div",{className:"settings-content",children:[r.length>0&&e.jsxs("div",{className:"stat-strip",children:[e.jsxs("div",{className:"stat-strip__cell",children:[e.jsx("div",{className:"stat-strip__value stat-strip__value--accent",children:r.length}),e.jsx("div",{className:"stat-strip__label",children:"Teams"})]}),e.jsxs("div",{className:"stat-strip__cell",children:[e.jsx("div",{className:"stat-strip__value",children:y}),e.jsx("div",{className:"stat-strip__label",children:"Members"})]}),e.jsxs("div",{className:"stat-strip__cell",children:[e.jsx("div",{className:"stat-strip__value",children:S}),e.jsx("div",{className:"stat-strip__label",children:"Devices assigned"})]})]}),r.length===0?e.jsxs("div",{className:"empty-state",children:[e.jsx("div",{className:"empty-state__icon",children:e.jsx(f,{size:28})}),e.jsx("h3",{className:"empty-state__title",children:"No teams yet"}),e.jsx("p",{className:"empty-state__copy",children:"Every device is in the shared pool until you create a team and assign it. Teams let you scope an API key to a subset of devices — useful for separating CI runners, staging fleets, or per-team device pools."}),e.jsxs("button",{type:"button",className:"page-header-action empty-state__action",onClick:()=>d(!0),children:[e.jsx(N,{size:16}),e.jsx("span",{children:"Create your first team"})]})]}):e.jsx("div",{className:"team-grid",children:r.map(a=>e.jsxs("button",{type:"button",className:"team-card",onClick:()=>C(a),children:[e.jsxs("div",{className:"team-card__header",children:[e.jsx("div",{className:"team-card__icon",children:e.jsx(f,{size:16})}),e.jsxs("div",{className:"team-card__title",children:[e.jsx("div",{className:"team-card__name",children:a.name}),e.jsx("div",{className:"team-card__id",children:a.id.slice(0,8)})]}),e.jsx("span",{className:"team-card__delete",role:"button",tabIndex:0,onClick:o=>o.stopPropagation(),children:e.jsx(ee,{team:a,onDeleted:v})})]}),e.jsxs("div",{className:"team-card__metrics",children:[e.jsxs("div",{className:"team-card__metric",children:[e.jsx($,{size:13}),e.jsx("span",{className:"team-card__metric-value",children:a.deviceCount}),e.jsx("span",{className:"team-card__metric-label",children:"devices"})]}),e.jsxs("div",{className:"team-card__metric",children:[e.jsx(f,{size:13}),e.jsx("span",{className:"team-card__metric-value",children:a.memberCount}),e.jsx("span",{className:"team-card__metric-label",children:"members"})]})]}),e.jsxs("div",{className:"team-card__footer",children:["Created ",F(a.createdAt)]})]},a.id))})]}),x&&e.jsx(Z,{onClose:()=>d(!1),onCreated:()=>{d(!1),v()}})]})},Z=({onClose:i,onCreated:r})=>{const{toast:t}=D(),[c,m]=n.useState(""),[l,h]=n.useState(!1),x=async()=>{if(!c.trim())return t("Name required","error");h(!0);try{await j("/xenon/api/teams",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:c.trim()})}),t("Team created","success"),r()}catch(d){t(d.message,"error")}finally{h(!1)}};return e.jsx(Q,{open:!0,onClose:i,title:"New team",footer:e.jsxs(e.Fragment,{children:[e.jsx(A,{variant:"secondary",onClick:i,disabled:l,children:"Cancel"}),e.jsxs(A,{variant:"primary",onClick:x,disabled:l,children:[l?e.jsx(I,{className:"animate-spin",size:18}):e.jsx(N,{size:18}),l?"Creating…":"Create"]})]}),children:e.jsx("div",{style:{display:"flex",flexDirection:"column"},children:e.jsx(K,{label:"Name",htmlFor:"team-name",children:e.jsx(V,{id:"team-name",type:"text",value:c,onChange:d=>m(d.target.value),placeholder:"android-team, qa-ios, …",className:"w-full",autoFocus:!0})})})})},ee=({team:i,onDeleted:r})=>{const{toast:t}=D(),[c,m]=n.useState(!1),l=async h=>{if(h.stopPropagation(),!!window.confirm(`Delete team "${i.name}"? It must have no devices, members or apps.`)){m(!0);try{await j(`/xenon/api/teams/${i.id}`,{method:"DELETE"}),t("Team deleted","success"),r()}catch(x){t(x.message,"error")}finally{m(!1)}}};return e.jsxs("button",{className:"reset-btn",disabled:c,onClick:l,style:{color:"var(--status-error-fg)"},children:[e.jsx(L,{size:14}),c?"Deleting…":"Delete"]})},se=({team:i,onBack:r})=>{const{toast:t}=D(),[c,m]=n.useState([]),[l,h]=n.useState([]),[x,d]=n.useState([]),[_,C]=n.useState([]),[v,S]=n.useState(!0),[y,a]=n.useState(""),[o,k]=n.useState(""),b=async()=>{S(!0);try{const[s,g,Y]=await Promise.all([j(`/xenon/api/teams/${i.id}/members`),W(),j("/xenon/api/device")]);m(s),h(g);const z=Y;d(z.filter(w=>w.teamId===i.id)),C(z.filter(w=>!w.teamId))}catch(s){t(`Failed to load team detail: ${s.message}`,"error")}finally{S(!1)}};n.useEffect(()=>{b()},[i.id]);const O=async()=>{if(y)try{await j(`/xenon/api/teams/${i.id}/members`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userId:y})}),t("Member added","success"),a(""),b()}catch(s){t(s.message,"error")}},H=async s=>{if(window.confirm("Remove this member from the team?"))try{await j(`/xenon/api/teams/${i.id}/members/${encodeURIComponent(s)}`,{method:"DELETE"}),t("Member removed","success"),b()}catch(g){t(g.message,"error")}},J=async()=>{if(o)try{await j(`/xenon/api/device/${encodeURIComponent(o)}/team`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({teamId:i.id})}),t("Device assigned","success"),k(""),b()}catch(s){t(s.message,"error")}},q=async s=>{if(window.confirm("Return this device to the shared pool?"))try{await j(`/xenon/api/device/${encodeURIComponent(s)}/team`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({teamId:null})}),t("Device unassigned","success"),b()}catch(g){t(g.message,"error")}},G=l.filter(s=>!c.some(g=>g.userId===s.id));return e.jsxs("div",{className:"settings-container mesh-gradient-infra",children:[e.jsxs("div",{className:"settings-header",children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:12},children:[e.jsx("button",{onClick:r,className:"reset-btn",style:{padding:"4px 8px"},"aria-label":"Back",children:e.jsx(X,{size:18})}),e.jsxs("div",{className:"settings-title-group",children:[e.jsx(f,{className:"settings-icon infra-icon",size:28}),e.jsx("h2",{children:i.name})]})]}),e.jsx("p",{className:"settings-subtitle",children:`Manage members and devices assigned to this team. Add a shared device below, or choose "Assign team…" in a device's ⋯ menu on the Devices page.`})]}),v?e.jsx("div",{className:"settings-loading",children:e.jsx(I,{className:"animate-spin",size:32})}):e.jsxs("div",{className:"settings-content",style:{display:"flex",flexDirection:"column",gap:24},children:[e.jsxs("section",{children:[e.jsxs("h3",{style:{marginBottom:12},children:["Members (",c.length,")"]}),e.jsxs("div",{className:"surface-card",style:{padding:12},children:[e.jsxs("div",{style:{display:"flex",gap:8,marginBottom:12},children:[e.jsxs(U,{value:y,onChange:s=>a(s.target.value),className:"flex-1",children:[e.jsx("option",{value:"",children:"Select a user to add…"}),G.map(s=>e.jsxs("option",{value:s.id,children:[s.name," (",s.email,") — ",B[s.role]]},s.id))]}),e.jsxs(A,{variant:"primary",onClick:O,disabled:!y,children:[e.jsx(N,{size:16})," Add"]})]}),c.length===0?e.jsx("p",{style:{opacity:.6,textAlign:"center",padding:24},children:"No members yet. Add a user above."}):e.jsxs(E,{children:[e.jsx(M,{children:e.jsxs(T,{children:[e.jsx(u,{children:"Name"}),e.jsx(u,{children:"Email"}),e.jsx(u,{children:"Role"}),e.jsx(u,{children:"Added"}),e.jsx(u,{style:{textAlign:"right"},children:"Action"})]})}),e.jsx(P,{children:c.map(s=>e.jsxs(T,{children:[e.jsx(p,{children:s.name}),e.jsx(p,{children:e.jsx("code",{style:{fontSize:"11px",color:"var(--text-muted)"},children:s.email})}),e.jsx(p,{children:B[s.role]}),e.jsx(p,{children:F(s.addedAt)}),e.jsx(p,{style:{textAlign:"right"},children:e.jsx("button",{className:"reset-btn",onClick:()=>H(s.userId),style:{color:"var(--status-error-fg)"},"aria-label":"Remove member",title:"Remove member",children:e.jsx(L,{size:14})})})]},s.userId))})]})]})]}),e.jsxs("section",{children:[e.jsxs("h3",{style:{marginBottom:12},children:["Devices (",x.length,")"]}),e.jsxs("div",{className:"surface-card",style:{padding:12},children:[e.jsxs("div",{style:{display:"flex",gap:8,marginBottom:12},children:[e.jsxs(U,{value:o,onChange:s=>k(s.target.value),className:"flex-1",children:[e.jsx("option",{value:"",children:"Select a shared-pool device to assign…"}),_.map(s=>e.jsxs("option",{value:s.udid,children:[s.name," (",s.platform,") — ",s.udid.slice(0,12),"…"]},s.udid))]}),e.jsxs(A,{variant:"primary",onClick:J,disabled:!o,children:[e.jsx(N,{size:16})," Assign"]})]}),x.length===0?e.jsx("p",{style:{opacity:.6,textAlign:"center",padding:24},children:"No devices assigned. Pick a shared device above to add one."}):e.jsxs(E,{children:[e.jsx(M,{children:e.jsxs(T,{children:[e.jsx(u,{children:"Name"}),e.jsx(u,{children:"Platform"}),e.jsx(u,{children:"UDID"}),e.jsx(u,{style:{textAlign:"right"},children:"Actions"})]})}),e.jsx(P,{children:x.map(s=>e.jsxs(T,{children:[e.jsxs(p,{children:[e.jsx($,{size:14,style:{marginRight:6,verticalAlign:"middle"}}),s.name]}),e.jsx(p,{children:s.platform}),e.jsx(p,{children:e.jsxs("code",{style:{fontSize:"11px"},children:[s.udid.slice(0,14),"…"]})}),e.jsx(p,{style:{textAlign:"right"},children:e.jsx("button",{className:"reset-btn",onClick:()=>q(s.udid),children:"Return to shared pool"})})]},s.udid))})]})]})]})]})]})};export{ve as Teams};
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-BbKEXUFH.js";/**
1
+ import{c as a}from"./index-B0gQkEg0.js";/**
2
2
  * @license lucide-react v0.555.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-BbKEXUFH.js";/**
1
+ import{c as a}from"./index-B0gQkEg0.js";/**
2
2
  * @license lucide-react v0.555.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as G,r as o,p as V,X as v}from"./index-BbKEXUFH.js";import{f as Z}from"./time-D-gjmE1o.js";/**
1
+ import{c as G,r as o,p as V,X as v}from"./index-B0gQkEg0.js";import{f as Z}from"./time-D-gjmE1o.js";/**
2
2
  * @license lucide-react v0.555.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1 +1 @@
1
- import{r as t,h as u}from"./index-BbKEXUFH.js";function n(){const[s,r]=t.useState(null);return t.useEffect(()=>{let e=!0;return u().then(o=>e&&r(o.passwordReset)),()=>{e=!1}},[]),s}export{n as u};
1
+ import{r as t,h as u}from"./index-B0gQkEg0.js";function n(){const[s,r]=t.useState(null);return t.useEffect(()=>{let e=!0;return u().then(o=>e&&r(o.passwordReset)),()=>{e=!1}},[]),s}export{n as u};
@@ -1 +1 @@
1
- import{o as s}from"./index-BbKEXUFH.js";const o="/xenon/api/users";async function c(){const t=await fetch(`${o}/`,{credentials:"include"});if(!t.ok)throw new Error(`listUsers failed (${t.status})`);return t.json()}async function i(t){const e=await fetch(`${o}/`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",...s},body:JSON.stringify(t)});if(!e.ok){const n=await e.json().catch(()=>({}));throw new Error(n.error||`createUser failed (${e.status})`)}return e.json()}async function d(t,e){const n=await fetch(`${o}/${encodeURIComponent(t)}`,{method:"PATCH",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!n.ok){const r=await n.json().catch(()=>({}));throw new Error(r.error||`updateUser failed (${n.status})`)}return n.json()}async function l(t){const e=await fetch(`${o}/${encodeURIComponent(t)}`,{method:"DELETE",credentials:"include"});if(!e.ok&&e.status!==204){const n=await e.json().catch(()=>({}));throw new Error(n.error||`deleteUser failed (${e.status})`)}}async function u(t){const e=await fetch(`${o}/${encodeURIComponent(t)}/reset-link`,{method:"POST",credentials:"include"}),n=await e.json().catch(()=>({}));if(!e.ok)throw new Error(n.error||`Reset link failed (${e.status})`);return n}export{i as a,u as c,l as d,c as l,d as u};
1
+ import{o as s}from"./index-B0gQkEg0.js";const o="/xenon/api/users";async function c(){const t=await fetch(`${o}/`,{credentials:"include"});if(!t.ok)throw new Error(`listUsers failed (${t.status})`);return t.json()}async function i(t){const e=await fetch(`${o}/`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",...s},body:JSON.stringify(t)});if(!e.ok){const n=await e.json().catch(()=>({}));throw new Error(n.error||`createUser failed (${e.status})`)}return e.json()}async function d(t,e){const n=await fetch(`${o}/${encodeURIComponent(t)}`,{method:"PATCH",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!n.ok){const r=await n.json().catch(()=>({}));throw new Error(r.error||`updateUser failed (${n.status})`)}return n.json()}async function l(t){const e=await fetch(`${o}/${encodeURIComponent(t)}`,{method:"DELETE",credentials:"include"});if(!e.ok&&e.status!==204){const n=await e.json().catch(()=>({}));throw new Error(n.error||`deleteUser failed (${e.status})`)}}async function u(t){const e=await fetch(`${o}/${encodeURIComponent(t)}/reset-link`,{method:"POST",credentials:"include"}),n=await e.json().catch(()=>({}));if(!e.ok)throw new Error(n.error||`Reset link failed (${e.status})`);return n}export{i as a,u as c,l as d,c as l,d as u};
@@ -1,4 +1,4 @@
1
- import{c as w,b as k,r as a,j as e,a2 as C}from"./index-BbKEXUFH.js";import{l as M,d as R,c as I,a as A,u as D}from"./users-CFba5TSy.js";import{f as U}from"./time-D-gjmE1o.js";import{P}from"./page-header-BjzgWz-T.js";import{B as y}from"./button-Dda4irn_.js";import{I as N}from"./input-Cm0tmSoc.js";import{S as g}from"./select-88jHcl-Z.js";import{P as B}from"./plus-nBFSMu_X.js";import{T as L}from"./trash-2-C5RSuX0e.js";import"./utils-CDN07tui.js";/**
1
+ import{c as w,b as k,r as a,j as e,a2 as C}from"./index-B0gQkEg0.js";import{l as M,d as R,c as I,a as A,u as D}from"./users-D7tKMESH.js";import{f as U}from"./time-D-gjmE1o.js";import{P}from"./page-header-D8KUX6Qg.js";import{B as y}from"./button-DDau4Axv.js";import{I as N}from"./input-DIc4MSC7.js";import{S as g}from"./select-BWpnhWXj.js";import{P as B}from"./plus-C9Zj84UF.js";import{T as L}from"./trash-2-CZ2ew6AZ.js";import"./utils-CDN07tui.js";/**
2
2
  * @license lucide-react v0.555.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as o}from"./index-BbKEXUFH.js";/**
1
+ import{c as o}from"./index-B0gQkEg0.js";/**
2
2
  * @license lucide-react v0.555.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1 +1 @@
1
- import{t as P,r as l,X as p,j as e,P as w,A as x,C as V,Q as I}from"./index-BbKEXUFH.js";/* empty css */import{F as k}from"./FieldGroup-Cb5QZsE1.js";import{S as U}from"./select-88jHcl-Z.js";import{P as B}from"./page-header-BjzgWz-T.js";import{T as D}from"./trash-2-C5RSuX0e.js";import{P as _}from"./plus-nBFSMu_X.js";import{C as J}from"./circle-x-DlDJtnVz.js";import{C as K}from"./circle-check-CqAJjQ5O.js";import{Z as M}from"./zap-l4VaXzq8.js";import{A as X}from"./activity-DOVdsfMQ.js";import"./utils-CDN07tui.js";const Z=["udid","name","host","platform"],q=["udid","name","host","platform"],G=["sessionId","sessionName","failureReason","udid","deviceName","platform","osVersion","startTime","endTime"],Q=["windowDays","totalHeals","distinctSelectors"],Y={device_offline:Z,device_new:q,session_failed:G,selector_health_digest:Q},ee=Y,se={eventType:"The event: device_offline, session_failed, ...",udid:"The device's id",name:"The device's name",host:"The machine it is plugged into",platform:"android or ios",sessionId:"The session's id",sessionName:"The session's name, if the test gave it one",failureReason:"Why it failed",deviceName:"The device's name",osVersion:"The device's OS version",startTime:"When it started (ISO 8601)",endTime:"When it ended (ISO 8601)",windowDays:"How many days the digest covers",totalHeals:"Heals in that time",distinctSelectors:"Selectors that were healed"};function te(o){const c=["eventType"];for(const u of o)for(const h of ee[u]??[])c.includes(h)||c.push(h);return c}const ae=[{id:"slack",label:"Slack message"},{id:"webhook",label:"JSON (event and payload)"}];function oe(o){return o.payloadTemplate?"CUSTOM":o.type==="slack"?"SLACK":"JSON"}const j=[{id:"device_offline",label:"Device offline",icon:V,tone:"red"},{id:"device_new",label:"New device",icon:_,tone:"green"},{id:"session_failed",label:"Session failed",icon:J,tone:"amber"},{id:"selector_health_digest",label:"Selector health digest",icon:I,tone:"green"}],be=()=>{const{toast:o}=P(),[c,u]=l.useState([]),[h,N]=l.useState(!0),[r,y]=l.useState(""),[i,S]=l.useState(["device_offline","session_failed"]),[f,T]=l.useState(!1),[C,A]=l.useState(!1),[d,E]=l.useState(!1),[m,g]=l.useState(""),[b,O]=l.useState("slack");l.useEffect(()=>{v()},[]);const v=async()=>{N(!0);try{const s=await p.getWebhookConfigs();u(s||[])}catch(s){console.error("Failed to load webhook configs",s),o("Failed to load webhooks.","error")}finally{N(!1)}},W=async()=>{if(!r.trim()){o("Webhook URL is required.","error");return}if(i.length===0){o("Select at least one trigger event.","error");return}T(!0);try{await p.addWebhookConfig(r.trim(),i,b,m||void 0),y(""),g(""),E(!1),O("slack"),S(["device_offline","session_failed"]),await v(),o("Webhook saved.","success")}catch(s){console.error("Failed to add webhook",s),o(`Failed to add webhook: ${s.message||"unknown error"}`,"error")}finally{T(!1)}},$=async(s,t)=>{if(window.confirm(`Remove webhook for "${t}"?`))try{await p.deleteWebhookConfig(s),await v(),o("Webhook removed.","success")}catch(a){console.error("Failed to delete webhook",a),o(`Failed to delete: ${a.message||"unknown error"}`,"error")}},L=async()=>{if(!r.trim()||i.length===0)return;A(!0);const s=t=>{var a;return((a=j.find(n=>n.id===t))==null?void 0:a.label)??t};try{for(const t of i)try{await p.testWebhook(r.trim(),b,m||void 0,t)}catch(a){o(`Test failed for ${s(t)}: ${a.message||"check the URL"}`,"error");return}o(i.length===1?`Test message delivered: ${s(i[0])}.`:`Test messages delivered: ${i.map(s).join(", ")}.`,"success")}finally{A(!1)}},R=s=>{S(t=>t.includes(s)?t.filter(a=>a!==s):[...t,s])},z=s=>{g(t=>t+`{{${s}}} `)};return e.jsxs("div",{className:"settings-container",children:[e.jsx(B,{icon:w,title:"Notifications",subtitle:"Configure Slack or generic webhooks to receive alerts for critical infrastructure events."}),e.jsxs("div",{className:"settings-content",children:[h?e.jsxs("div",{className:"settings-loading",style:{height:200},children:[e.jsx(x,{className:"animate-spin",size:28}),e.jsx("span",{children:"Loading webhooks…"})]}):c.length===0?e.jsxs("div",{className:"empty-state",children:[e.jsx("div",{className:"empty-state__icon",children:e.jsx(w,{size:28})}),e.jsx("h3",{className:"empty-state__title",children:"No webhooks configured"}),e.jsx("p",{className:"empty-state__copy",children:"Wire up a Slack incoming webhook or any HTTPS endpoint to receive alerts when devices go offline, new devices register, or sessions fail. Configure your first webhook below."})]}):e.jsx("div",{className:"webhook-list-grid",children:c.map(s=>{const t=(()=>{try{return JSON.parse(s.events)}catch{return[]}})();return e.jsxs("div",{className:"webhook-row-card",children:[e.jsxs("div",{className:"webhook-row-card__header",children:[e.jsx("span",{className:`pill-chip ${s.payloadTemplate?"pill-chip--admin":"pill-chip--scope"}`,children:oe(s)}),e.jsx("span",{className:"webhook-row-card__url",title:s.url,children:s.url}),e.jsxs("button",{type:"button",className:"row-action row-action--danger",onClick:()=>$(s.id,s.url),"aria-label":`Remove webhook for ${s.url}`,children:[e.jsx(D,{size:13}),e.jsx("span",{children:"Remove"})]})]}),e.jsx("div",{className:"webhook-row-card__events",children:t.length===0?e.jsx("span",{className:"webhook-row-card__no-events",children:"No triggers"}):t.map(a=>{const n=j.find(H=>H.id===a),F=(n==null?void 0:n.icon)??w;return e.jsxs("span",{className:`event-pill event-pill--${(n==null?void 0:n.tone)||"gray"}`,children:[e.jsx(F,{size:11}),(n==null?void 0:n.label)||a]},a)})})]},s.id)})}),e.jsxs("div",{className:"add-webhook-card",children:[e.jsxs("div",{className:"add-webhook-card__header",children:[e.jsx(_,{size:14}),e.jsx("span",{children:"Add a new webhook"})]}),e.jsxs("div",{className:"add-webhook-card__body",children:[e.jsx(k,{label:"Webhook URL",description:"The endpoint we POST webhook events to.",htmlFor:"webhook-url",children:e.jsx("div",{className:"setting-input-wrapper",children:e.jsx("input",{id:"webhook-url",type:"url",placeholder:"https://hooks.slack.com/services/...",value:r,onChange:s=>y(s.target.value)})})}),e.jsx(k,{label:"Message format",description:m.trim()?"Your custom payload is sent as written, whatever the format.":"Slack message for a Slack incoming webhook; JSON for anything else.",htmlFor:"webhook-format",children:e.jsx(U,{id:"webhook-format",value:b,onChange:s=>O(s.target.value),children:ae.map(s=>e.jsx("option",{value:s.id,children:s.label},s.id))})}),e.jsx(k,{label:"Trigger events",children:e.jsx("div",{className:"event-toggle-grid",children:j.map(s=>{const t=i.includes(s.id),a=s.icon;return e.jsxs("button",{type:"button",role:"checkbox","aria-checked":t,className:`event-toggle event-toggle--${s.tone} ${t?"is-selected":""}`,onClick:()=>R(s.id),children:[e.jsx(a,{size:14,className:"event-toggle__lead"}),e.jsx("span",{children:s.label}),t&&e.jsx(K,{size:14,className:"event-toggle__check"})]},s.id)})})}),e.jsxs("div",{className:"template-section",children:[e.jsxs("button",{type:"button",className:"template-section__toggle",onClick:()=>E(!d),"aria-expanded":d,children:[e.jsx(M,{size:14,className:d?"tone-amber":"tone-dim"}),e.jsx("span",{children:"Use custom payload (optional)"}),e.jsx("span",{className:"template-section__indicator",children:d?"−":"+"})]}),d&&e.jsxs("div",{className:"template-editor",children:[e.jsx("p",{className:"template-editor__hint",children:"Define a JSON or text template. Click a name below to insert it; the names are the ones your selected events carry."}),e.jsx("div",{className:"template-editor__chips",children:te(i).map(s=>e.jsx("button",{type:"button",className:"template-editor__chip","aria-label":`{{${s}}}`,title:se[s],onClick:()=>z(s),children:`{{${s}}}`},s))}),e.jsx("textarea",{className:"template-editor__textarea",placeholder:'Example: { "text": "Alert: Device {{udid}} is offline!" }',value:m,onChange:s=>g(s.target.value),rows:4})]})]})]}),e.jsxs("div",{className:"add-webhook-card__footer",children:[e.jsxs("button",{type:"button",className:"page-header-action page-header-action--ghost",onClick:L,disabled:!r.trim()||i.length===0||C,"aria-label":"Send test",title:i.length===0?"Select a trigger event to send a sample of":"Sends a sample message for each selected event",children:[C?e.jsx(x,{size:14,className:"animate-spin"}):e.jsx(X,{size:14}),e.jsx("span",{children:"Send test"})]}),e.jsxs("button",{type:"button",className:"page-header-action",onClick:W,disabled:f||!r.trim(),children:[f?e.jsx(x,{size:14,className:"animate-spin"}):e.jsx(_,{size:14}),e.jsx("span",{children:f?"Saving…":"Save webhook"})]})]})]})]})]})};export{be as WebhookSettings};
1
+ import{t as P,r as l,X as p,j as e,P as w,A as x,C as V,Q as I}from"./index-B0gQkEg0.js";/* empty css */import{F as k}from"./FieldGroup-GOgssFEP.js";import{S as U}from"./select-BWpnhWXj.js";import{P as B}from"./page-header-D8KUX6Qg.js";import{T as D}from"./trash-2-CZ2ew6AZ.js";import{P as _}from"./plus-C9Zj84UF.js";import{C as J}from"./circle-x-Bmltailn.js";import{C as K}from"./circle-check-CBv88cH7.js";import{Z as M}from"./zap-ClcHFxfo.js";import{A as X}from"./activity-DrLbHifn.js";import"./utils-CDN07tui.js";const Z=["udid","name","host","platform"],q=["udid","name","host","platform"],G=["sessionId","sessionName","failureReason","udid","deviceName","platform","osVersion","startTime","endTime"],Q=["windowDays","totalHeals","distinctSelectors"],Y={device_offline:Z,device_new:q,session_failed:G,selector_health_digest:Q},ee=Y,se={eventType:"The event: device_offline, session_failed, ...",udid:"The device's id",name:"The device's name",host:"The machine it is plugged into",platform:"android or ios",sessionId:"The session's id",sessionName:"The session's name, if the test gave it one",failureReason:"Why it failed",deviceName:"The device's name",osVersion:"The device's OS version",startTime:"When it started (ISO 8601)",endTime:"When it ended (ISO 8601)",windowDays:"How many days the digest covers",totalHeals:"Heals in that time",distinctSelectors:"Selectors that were healed"};function te(o){const c=["eventType"];for(const u of o)for(const h of ee[u]??[])c.includes(h)||c.push(h);return c}const ae=[{id:"slack",label:"Slack message"},{id:"webhook",label:"JSON (event and payload)"}];function oe(o){return o.payloadTemplate?"CUSTOM":o.type==="slack"?"SLACK":"JSON"}const j=[{id:"device_offline",label:"Device offline",icon:V,tone:"red"},{id:"device_new",label:"New device",icon:_,tone:"green"},{id:"session_failed",label:"Session failed",icon:J,tone:"amber"},{id:"selector_health_digest",label:"Selector health digest",icon:I,tone:"green"}],be=()=>{const{toast:o}=P(),[c,u]=l.useState([]),[h,N]=l.useState(!0),[r,y]=l.useState(""),[i,S]=l.useState(["device_offline","session_failed"]),[f,T]=l.useState(!1),[C,A]=l.useState(!1),[d,E]=l.useState(!1),[m,g]=l.useState(""),[b,O]=l.useState("slack");l.useEffect(()=>{v()},[]);const v=async()=>{N(!0);try{const s=await p.getWebhookConfigs();u(s||[])}catch(s){console.error("Failed to load webhook configs",s),o("Failed to load webhooks.","error")}finally{N(!1)}},W=async()=>{if(!r.trim()){o("Webhook URL is required.","error");return}if(i.length===0){o("Select at least one trigger event.","error");return}T(!0);try{await p.addWebhookConfig(r.trim(),i,b,m||void 0),y(""),g(""),E(!1),O("slack"),S(["device_offline","session_failed"]),await v(),o("Webhook saved.","success")}catch(s){console.error("Failed to add webhook",s),o(`Failed to add webhook: ${s.message||"unknown error"}`,"error")}finally{T(!1)}},$=async(s,t)=>{if(window.confirm(`Remove webhook for "${t}"?`))try{await p.deleteWebhookConfig(s),await v(),o("Webhook removed.","success")}catch(a){console.error("Failed to delete webhook",a),o(`Failed to delete: ${a.message||"unknown error"}`,"error")}},L=async()=>{if(!r.trim()||i.length===0)return;A(!0);const s=t=>{var a;return((a=j.find(n=>n.id===t))==null?void 0:a.label)??t};try{for(const t of i)try{await p.testWebhook(r.trim(),b,m||void 0,t)}catch(a){o(`Test failed for ${s(t)}: ${a.message||"check the URL"}`,"error");return}o(i.length===1?`Test message delivered: ${s(i[0])}.`:`Test messages delivered: ${i.map(s).join(", ")}.`,"success")}finally{A(!1)}},R=s=>{S(t=>t.includes(s)?t.filter(a=>a!==s):[...t,s])},z=s=>{g(t=>t+`{{${s}}} `)};return e.jsxs("div",{className:"settings-container",children:[e.jsx(B,{icon:w,title:"Notifications",subtitle:"Configure Slack or generic webhooks to receive alerts for critical infrastructure events."}),e.jsxs("div",{className:"settings-content",children:[h?e.jsxs("div",{className:"settings-loading",style:{height:200},children:[e.jsx(x,{className:"animate-spin",size:28}),e.jsx("span",{children:"Loading webhooks…"})]}):c.length===0?e.jsxs("div",{className:"empty-state",children:[e.jsx("div",{className:"empty-state__icon",children:e.jsx(w,{size:28})}),e.jsx("h3",{className:"empty-state__title",children:"No webhooks configured"}),e.jsx("p",{className:"empty-state__copy",children:"Wire up a Slack incoming webhook or any HTTPS endpoint to receive alerts when devices go offline, new devices register, or sessions fail. Configure your first webhook below."})]}):e.jsx("div",{className:"webhook-list-grid",children:c.map(s=>{const t=(()=>{try{return JSON.parse(s.events)}catch{return[]}})();return e.jsxs("div",{className:"webhook-row-card",children:[e.jsxs("div",{className:"webhook-row-card__header",children:[e.jsx("span",{className:`pill-chip ${s.payloadTemplate?"pill-chip--admin":"pill-chip--scope"}`,children:oe(s)}),e.jsx("span",{className:"webhook-row-card__url",title:s.url,children:s.url}),e.jsxs("button",{type:"button",className:"row-action row-action--danger",onClick:()=>$(s.id,s.url),"aria-label":`Remove webhook for ${s.url}`,children:[e.jsx(D,{size:13}),e.jsx("span",{children:"Remove"})]})]}),e.jsx("div",{className:"webhook-row-card__events",children:t.length===0?e.jsx("span",{className:"webhook-row-card__no-events",children:"No triggers"}):t.map(a=>{const n=j.find(H=>H.id===a),F=(n==null?void 0:n.icon)??w;return e.jsxs("span",{className:`event-pill event-pill--${(n==null?void 0:n.tone)||"gray"}`,children:[e.jsx(F,{size:11}),(n==null?void 0:n.label)||a]},a)})})]},s.id)})}),e.jsxs("div",{className:"add-webhook-card",children:[e.jsxs("div",{className:"add-webhook-card__header",children:[e.jsx(_,{size:14}),e.jsx("span",{children:"Add a new webhook"})]}),e.jsxs("div",{className:"add-webhook-card__body",children:[e.jsx(k,{label:"Webhook URL",description:"The endpoint we POST webhook events to.",htmlFor:"webhook-url",children:e.jsx("div",{className:"setting-input-wrapper",children:e.jsx("input",{id:"webhook-url",type:"url",placeholder:"https://hooks.slack.com/services/...",value:r,onChange:s=>y(s.target.value)})})}),e.jsx(k,{label:"Message format",description:m.trim()?"Your custom payload is sent as written, whatever the format.":"Slack message for a Slack incoming webhook; JSON for anything else.",htmlFor:"webhook-format",children:e.jsx(U,{id:"webhook-format",value:b,onChange:s=>O(s.target.value),children:ae.map(s=>e.jsx("option",{value:s.id,children:s.label},s.id))})}),e.jsx(k,{label:"Trigger events",children:e.jsx("div",{className:"event-toggle-grid",children:j.map(s=>{const t=i.includes(s.id),a=s.icon;return e.jsxs("button",{type:"button",role:"checkbox","aria-checked":t,className:`event-toggle event-toggle--${s.tone} ${t?"is-selected":""}`,onClick:()=>R(s.id),children:[e.jsx(a,{size:14,className:"event-toggle__lead"}),e.jsx("span",{children:s.label}),t&&e.jsx(K,{size:14,className:"event-toggle__check"})]},s.id)})})}),e.jsxs("div",{className:"template-section",children:[e.jsxs("button",{type:"button",className:"template-section__toggle",onClick:()=>E(!d),"aria-expanded":d,children:[e.jsx(M,{size:14,className:d?"tone-amber":"tone-dim"}),e.jsx("span",{children:"Use custom payload (optional)"}),e.jsx("span",{className:"template-section__indicator",children:d?"−":"+"})]}),d&&e.jsxs("div",{className:"template-editor",children:[e.jsx("p",{className:"template-editor__hint",children:"Define a JSON or text template. Click a name below to insert it; the names are the ones your selected events carry."}),e.jsx("div",{className:"template-editor__chips",children:te(i).map(s=>e.jsx("button",{type:"button",className:"template-editor__chip","aria-label":`{{${s}}}`,title:se[s],onClick:()=>z(s),children:`{{${s}}}`},s))}),e.jsx("textarea",{className:"template-editor__textarea",placeholder:'Example: { "text": "Alert: Device {{udid}} is offline!" }',value:m,onChange:s=>g(s.target.value),rows:4})]})]})]}),e.jsxs("div",{className:"add-webhook-card__footer",children:[e.jsxs("button",{type:"button",className:"page-header-action page-header-action--ghost",onClick:L,disabled:!r.trim()||i.length===0||C,"aria-label":"Send test",title:i.length===0?"Select a trigger event to send a sample of":"Sends a sample message for each selected event",children:[C?e.jsx(x,{size:14,className:"animate-spin"}):e.jsx(X,{size:14}),e.jsx("span",{children:"Send test"})]}),e.jsxs("button",{type:"button",className:"page-header-action",onClick:W,disabled:f||!r.trim(),children:[f?e.jsx(x,{size:14,className:"animate-spin"}):e.jsx(_,{size:14}),e.jsx("span",{children:f?"Saving…":"Save webhook"})]})]})]})]})]})};export{be as WebhookSettings};
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-BbKEXUFH.js";/**
1
+ import{c as a}from"./index-B0gQkEg0.js";/**
2
2
  * @license lucide-react v0.555.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -36,7 +36,7 @@
36
36
  Learn how to configure a non-root public URL by running `npm run build`.
37
37
  -->
38
38
  <title>Xenon</title>
39
- <script type="module" crossorigin src="/xenon/assets/index-BbKEXUFH.js"></script>
39
+ <script type="module" crossorigin src="/xenon/assets/index-B0gQkEg0.js"></script>
40
40
  <link rel="stylesheet" crossorigin href="/xenon/assets/index-Bxoxt9sV.css">
41
41
  </head>
42
42
 
package/lib/schema.json CHANGED
@@ -91,7 +91,7 @@
91
91
  },
92
92
  "proxy": {
93
93
  "type": "object",
94
- "description": "Proxy configuration object. See AxiosProxy interface for details."
94
+ "description": "The proxy for every call this server makes to another Xenon server or a cloud provider: a hub's session creates, commands, device control, recordings and live-preview sockets for its nodes' phones, a node's calls to its hub, and Xenon's other internal calls. Hosts that NO_PROXY names, and loopback hosts, go direct. Without it these calls take HTTP_PROXY or HTTPS_PROXY. Webhooks, the AI providers and OpenTelemetry export don't use it. See AxiosProxy interface for details."
95
95
  },
96
96
  "deviceAvailabilityTimeoutMs": {
97
97
  "type": "number",
@@ -131,7 +131,7 @@
131
131
  "enableDashboard": {
132
132
  "type": "boolean",
133
133
  "default": false,
134
- "description": "Serve the React dashboard at /xenon/ and the Socket.io event stream."
134
+ "description": "Keep a full record of each Appium session for the dashboard: its commands, screenshots, logs and performance on the Sessions page, and its heals in Selector Health. Self-healing learns selectors either way, while self-healing is on. Without it, a session on this server's own phones isn't listed at all, while a hub still lists the sessions it sends to its nodes' phones, with their result, why they failed and their video. A node's own setting records nothing for its hub's sessions. Video is recorded either way unless the session turns it off. The dashboard itself is always served at /xenon/, whatever this is set to."
135
135
  },
136
136
  "bootedSimulators": {
137
137
  "type": "boolean",
@@ -163,7 +163,7 @@
163
163
  "sqlite",
164
164
  "postgresql"
165
165
  ],
166
- "description": "Database backend. The published plugin stores its data in SQLite only, and each server, hub or node, has its own database. postgresql is accepted so older configs still start, and has no effect: the database URL decides."
166
+ "description": "Has no effect, and is accepted only so older configs still start. Xenon stores its data in SQLite, one database per server, hub or node (see databaseUrl). At startup the database itself decides how its tables are brought up to date: from its migration history if that history matches its tables (Xenon first corrects a history that has fallen behind them), otherwise by matching them to this version's schema."
167
167
  },
168
168
  "databaseUrl": {
169
169
  "type": "string",
@@ -177,15 +177,15 @@
177
177
  "anthropic",
178
178
  "ollama"
179
179
  ],
180
- "description": "AI provider for the LLM healing tier and visual analysis. Also controlled by XENON_AI_PROVIDER."
180
+ "description": "AI provider for the LLM healing tier and visual analysis. Also controlled by XENON_AI_PROVIDER. A provider chosen on the dashboard's AI engine page replaces this one, and applies from the next AI call without a restart."
181
181
  },
182
182
  "aiModel": {
183
183
  "type": "string",
184
- "description": "Override the default model for the selected `aiProvider` (e.g. 'gemini-1.5-pro', 'gpt-4o', 'claude-sonnet-4-6'). Falls back to XENON_AI_MODEL."
184
+ "description": "Override the default model for the selected `aiProvider` (e.g. 'gemini-1.5-pro', 'gpt-4o', 'claude-sonnet-4-6'). Falls back to XENON_AI_MODEL. A model saved through the dashboard's settings (`POST /xenon/api/config`) replaces this one."
185
185
  },
186
186
  "aiBaseUrl": {
187
187
  "type": "string",
188
- "description": "Custom base URL for the AI provider (e.g. a local Ollama server or an OpenAI-compatible gateway). Falls back to XENON_AI_BASE_URL."
188
+ "description": "Custom base URL for the AI provider (e.g. a local Ollama server or an OpenAI-compatible gateway). Falls back to XENON_AI_BASE_URL. A base URL saved through the dashboard's settings (`POST /xenon/api/config`) replaces this one."
189
189
  },
190
190
  "geminiApiKey": {
191
191
  "type": "string",
@@ -202,7 +202,7 @@
202
202
  "enableSelfHealing": {
203
203
  "type": "boolean",
204
204
  "default": true,
205
- "description": "Enable the self-healing pipeline (etalon recovery → Native → Fuzzy XML → OCR → Visual AI → LLM) for failed findElement calls. A value saved with the AI self-healing switch on the dashboard's Settings page replaces this one, and applies from the next command without a restart. A session can limit the tiers it uses with `xe:options.healingTiers`; that cannot turn healing off."
205
+ "description": "Enable the self-healing pipeline (etalon recovery → Native → Fuzzy XML → OCR → Visual AI → LLM) for failed findElement calls. A value saved with the AI self-healing switch on the dashboard's Settings page replaces this one, and applies from the next command without a restart. A session can limit the tiers it uses with `xe:options.healingTiers` (`[]` turns healing off for that session); it cannot turn healing on where this is off."
206
206
  },
207
207
  "buildCleanupDays": {
208
208
  "type": "number",
@@ -294,24 +294,28 @@
294
294
  "title": "AxiosProxy",
295
295
  "properties": {
296
296
  "host": {
297
- "type": "string"
297
+ "type": "string",
298
+ "description": "The proxy's host name or address. Without it the option is ignored, with a warning."
298
299
  },
299
300
  "port": {
300
- "type": "integer"
301
+ "type": "integer",
302
+ "description": "The proxy's port."
301
303
  },
302
304
  "protocol": {
303
305
  "type": "string",
304
306
  "enum": [
305
307
  "http",
306
308
  "https"
307
- ]
309
+ ],
310
+ "description": "How Xenon talks to the proxy itself; http when not given."
308
311
  },
309
312
  "auth": {
310
- "type": "object"
313
+ "type": "object",
314
+ "description": "The proxy's credentials, as { \"username\": ..., \"password\": ... }, sent to it as Proxy-Authorization."
311
315
  }
312
316
  },
313
317
  "additionalProperties": true,
314
- "description": "Proxy configuration object. Contains 'host', 'port', 'auth', 'protocol'."
318
+ "description": "A proxy: its host, port, protocol (http or https; http when not given) and auth ({ username, password }), for example { \"host\": \"squid.lab\", \"port\": 3128 }."
315
319
  },
316
320
  "CloudConfig": {
317
321
  "type": "object",