@hot-updater/console 1.0.0-rc.8 → 1.0.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 (342) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/ConsoleFeatureUnavailable-jB4Loglh.js +1 -0
  3. package/.output/public/assets/EventDetails-B6yUsyNA.js +1 -0
  4. package/.output/public/assets/HashValueDisplay-BFX_kY3-.js +1 -0
  5. package/.output/public/assets/InsightsInfo-BNO0WznZ.js +1 -0
  6. package/.output/public/assets/InsightsPageHeader-B892oVml.js +1 -0
  7. package/.output/public/assets/NotFoundPage-DIqCpWa3.js +1 -0
  8. package/.output/public/assets/alert-dialog-Bbk_4-WJ.js +1 -0
  9. package/.output/public/assets/api-DCM0wr6O.js +1 -0
  10. package/.output/public/assets/api-keys-CIO9L_wu.js +1 -0
  11. package/.output/public/assets/api-keys-DVTL_--f.js +1 -0
  12. package/.output/public/assets/arrow-left-CnMYpqB7.js +1 -0
  13. package/.output/public/assets/arrow-up-right-BtffZA6e.js +1 -0
  14. package/.output/public/assets/chevron-right-BqD5wtFb.js +1 -0
  15. package/.output/public/assets/clipboard-DS9ucUQL.js +1 -0
  16. package/.output/public/assets/console-features-api-BH5m_PtQ.js +1 -0
  17. package/.output/public/assets/dialog-Bqbb7B0N.js +1 -0
  18. package/.output/public/assets/dist-LxKJB-Vu.js +1 -0
  19. package/.output/public/assets/empty-CuT2UhFp.js +1 -0
  20. package/.output/public/assets/field-BBDpjIqu.js +1 -0
  21. package/.output/public/assets/index-6o51GkFe.js +11 -0
  22. package/.output/public/assets/input-group-ja_30qaF.js +1 -0
  23. package/.output/public/assets/insights-CmraDzQh.js +1 -0
  24. package/.output/public/assets/insights-CxZ2GCYV.js +2 -0
  25. package/.output/public/assets/insights-api-BigAU9sZ.js +1 -0
  26. package/.output/public/assets/insights-usage-rpc-DYEhsCKT.js +1 -0
  27. package/.output/public/assets/insights-vNRCrfnS.js +42 -0
  28. package/.output/public/assets/insights_.distribution-ChOerfJt.js +1 -0
  29. package/.output/public/assets/insights_.distribution-D-e4iga8.js +1 -0
  30. package/.output/public/assets/insights_.distribution-DZtkibX1.js +2 -0
  31. package/.output/public/assets/installations-BazSJXE0.js +2 -0
  32. package/.output/public/assets/installations-ClHTqdAO.js +1 -0
  33. package/.output/public/assets/installations-DpKsBuHi.js +1 -0
  34. package/.output/public/assets/key-round-wS_SIZ6b.js +1 -0
  35. package/.output/public/assets/list-CgrYiadM.js +1 -0
  36. package/.output/public/assets/refresh-cw-BYgoNCco.js +1 -0
  37. package/.output/public/assets/remote-config-BWwHOkCK.js +1 -0
  38. package/.output/public/assets/remote-config-Br56BODb.js +6 -0
  39. package/.output/public/assets/remote-config-CXOi0LRu.js +1 -0
  40. package/.output/public/assets/routes-B88Erkpk.js +2 -0
  41. package/.output/public/assets/routes-CfzsAleC.js +1 -0
  42. package/.output/public/assets/search-DaTDPW25.js +1 -0
  43. package/.output/public/assets/select-BZZBXMkr.js +1 -0
  44. package/.output/public/assets/sidebar-DNVyJPfX.js +3 -0
  45. package/.output/public/assets/smartphone-DpTFgtKf.js +1 -0
  46. package/.output/public/assets/styles-DTGEAJFv.css +2 -0
  47. package/.output/public/assets/switch-DZ1TPwx-.js +1 -0
  48. package/.output/public/assets/table-Gm4qRWwE.js +1 -0
  49. package/.output/public/assets/tabs-C7FOEjFC.js +1 -0
  50. package/.output/public/assets/undo-2-CmrHjwFc.js +1 -0
  51. package/.output/public/assets/useCompositeListItem-9E4pj_CH.js +1 -0
  52. package/.output/public/assets/valueToPercent-CH8GCHaL.js +1 -0
  53. package/.output/server/__23tanstack-start-server-fn-resolver-BpRlos5Q.mjs +170 -0
  54. package/.output/server/_libs/@base-ui/react+[...].mjs +4389 -4207
  55. package/.output/server/_libs/@tanstack/react-router+[...].mjs +3802 -4022
  56. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +140 -97
  57. package/.output/server/_libs/lucide-react.mjs +221 -9
  58. package/.output/server/_libs/next-themes.mjs +1 -1
  59. package/.output/server/_libs/react-redux+[...].mjs +1 -1
  60. package/.output/server/_libs/recharts+[...].mjs +483 -901
  61. package/.output/server/_libs/sonner.mjs +1 -1
  62. package/.output/server/_libs/tanstack__history.mjs +68 -41
  63. package/.output/server/_libs/tanstack__query-core.mjs +2010 -309
  64. package/.output/server/_libs/tanstack__react-query.mjs +448 -21
  65. package/.output/server/_libs/verkit.mjs +102 -1
  66. package/.output/server/_ssr/BundleIdDisplay-x94o9rlU.mjs +13 -0
  67. package/.output/server/_ssr/ConsoleFeatureUnavailable-ldW8v8-q.mjs +58 -0
  68. package/.output/server/_ssr/EventDetails-Q8kfj9Nr.mjs +344 -0
  69. package/.output/server/_ssr/{HashValueDisplay-C5tS4oGo.mjs → HashValueDisplay-n1GholGS.mjs} +3 -3
  70. package/.output/server/_ssr/InsightsInfo-DuJFatQO.mjs +75 -0
  71. package/.output/server/_ssr/InsightsPageHeader-BSUX3Ye9.mjs +145 -0
  72. package/.output/server/_ssr/NotFoundPage-DZzOOzS3.mjs +37 -0
  73. package/.output/server/_ssr/{dialog-WiIAKl4L.mjs → alert-dialog-C43Doa0I.mjs} +6 -83
  74. package/.output/server/_ssr/{api-keys-aXriWrv7.mjs → api-keys-B4sYm8dM.mjs} +48 -9
  75. package/.output/server/_ssr/api-keys-CEnM9fqG.mjs +5 -0
  76. package/.output/server/_ssr/api-keys-rpc-BIFuUgMt.mjs +52 -0
  77. package/.output/server/_ssr/api-oZjRn9tP.mjs +186 -0
  78. package/.output/server/_ssr/api-rpc-BNiFRRK8.mjs +240 -0
  79. package/.output/server/_ssr/{auth-BF8mbAgF.mjs → auth-UP0wXs4i.mjs} +1 -1
  80. package/.output/server/_ssr/{auth-rpc-B7w3AdRr.mjs → auth-rpc-DnZqYxyq.mjs} +6 -6
  81. package/.output/server/_ssr/auth.server-DFpyxRDd.mjs +5374 -0
  82. package/.output/server/_ssr/bundleActivity-M5gzBFNg.mjs +18 -0
  83. package/.output/server/_ssr/config-WVX4IxSR.mjs +15 -0
  84. package/.output/server/_ssr/config.server-DskXCAyI.mjs +35 -0
  85. package/.output/server/_ssr/console-access-BJfAXknd.mjs +17 -0
  86. package/.output/server/_ssr/console-features-Oz0q20jJ.mjs +61 -0
  87. package/.output/server/_ssr/console-features-api-C9kiKvaV.mjs +43 -0
  88. package/.output/server/_ssr/console-features-rpc-P0GL7Z-R.mjs +19 -0
  89. package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-Cm6Tn0_Y.mjs} +67 -56
  90. package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-RLR4H-Fn.mjs} +2 -2
  91. package/.output/server/_ssr/databaseConfig-C5SkAyUy.mjs +5 -0
  92. package/.output/server/_ssr/{databaseJsonValue-d6I3nAS4.mjs → databaseJsonValue-Csbuzc9Q.mjs} +28 -5
  93. package/.output/server/_ssr/databaseRows-CPphvbag.mjs +73 -0
  94. package/.output/server/_ssr/dialog-BbYDMjdC.mjs +84 -0
  95. package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-CbQbBIw-.mjs} +140 -7
  96. package/.output/server/_ssr/{downloadBundle-CI7JUKvC.mjs → downloadBundle-CZysjmNW.mjs} +10 -10
  97. package/.output/server/_ssr/empty-CVLGfxuu.mjs +60 -0
  98. package/.output/server/_ssr/{empty-plugin-adapters-B5QvGpEl.mjs → empty-plugin-adapters-CPtwX8a7.mjs} +1 -1
  99. package/.output/server/_ssr/failureReports-DKDhVsXA.mjs +33 -0
  100. package/.output/server/_ssr/field-asw83X0v.mjs +189 -0
  101. package/.output/server/_ssr/getBundleChildren-Cv9iz1As.mjs +22 -0
  102. package/.output/server/_ssr/{input-group-Coj6SwVP.mjs → input-group-CuYJHosb.mjs} +2 -2
  103. package/.output/server/_ssr/insights-B55FG5fm.mjs +14 -0
  104. package/.output/server/_ssr/insights-CWnHIFUV.mjs +5 -0
  105. package/.output/server/_ssr/insights-CpLpedFZ.mjs +2315 -0
  106. package/.output/server/_ssr/insights-api-sjws23WF.mjs +122 -0
  107. package/.output/server/_ssr/insights-errors-BjnFUyZj.mjs +34 -0
  108. package/.output/server/_ssr/insights-failures-rpc-BvbWvqNf.mjs +36 -0
  109. package/.output/server/_ssr/insights-failures-xaa41Buf.mjs +46 -0
  110. package/.output/server/_ssr/insights-recovery-BWQhiZaF.mjs +28 -0
  111. package/.output/server/_ssr/insights-recovery-rpc-GiYXdkaY.mjs +32 -0
  112. package/.output/server/_ssr/{insights-rpc-l2-oBYth.mjs → insights-rpc-DP_8Rmz4.mjs} +22 -14
  113. package/.output/server/_ssr/insights-usage-BpBM0U0c.mjs +29 -0
  114. package/.output/server/_ssr/insights-usage-rpc-BiolgB8e.mjs +29 -0
  115. package/.output/server/_ssr/insights-usage-rpc-KStL0zOx.mjs +41 -0
  116. package/.output/server/_ssr/insightsUsage-CzhVjsc8.mjs +56 -0
  117. package/.output/server/_ssr/insights_.distribution-CVrnbP3R.mjs +7 -0
  118. package/.output/server/_ssr/insights_.distribution-D2HaNTAA.mjs +16 -0
  119. package/.output/server/_ssr/{insights_.distribution-INw6QxbU.mjs → insights_.distribution-DN9pR422.mjs} +50 -22
  120. package/.output/server/_ssr/installations-BHmbbnH8.mjs +5 -0
  121. package/.output/server/_ssr/{installations-DH-tQuRo.mjs → installations-BSCmFq8Q.mjs} +279 -367
  122. package/.output/server/_ssr/installations-DD1g__Nc.mjs +69 -0
  123. package/.output/server/_ssr/listReleases-BfUBlCHo.mjs +79 -0
  124. package/.output/server/_ssr/plugins-BaD1slHx.mjs +2086 -0
  125. package/.output/server/_ssr/release-adoption-005tQRpo.mjs +55 -0
  126. package/.output/server/_ssr/release-adoption-rpc-BsstqxUp.mjs +41 -0
  127. package/.output/server/_ssr/releaseAdoption-CtOFkwli.mjs +114 -0
  128. package/.output/server/_ssr/releaseManagement-DFMUnDF5.mjs +11 -0
  129. package/.output/server/_ssr/remote-config-C2Ee8lXO.mjs +5 -0
  130. package/.output/server/_ssr/remote-config-Dv1ccMo-.mjs +17 -0
  131. package/.output/server/_ssr/remote-config-of0lGQH8.mjs +2648 -0
  132. package/.output/server/_ssr/remote-config-rpc-BzX31mUS.mjs +115 -0
  133. package/.output/server/_ssr/rolldown-runtime-rSIU-vHC.mjs +13 -0
  134. package/.output/server/_ssr/{router-DfFyAp5p.mjs → router-D8NuoJ_I.mjs} +117 -136
  135. package/.output/server/_ssr/{routes-BtsfVx-p.mjs → routes-DgvnFqDy.mjs} +195 -429
  136. package/.output/server/_ssr/routes-V8sDybGf.mjs +409 -0
  137. package/.output/server/_ssr/runtime.server-De0QVWc9.mjs +104 -0
  138. package/.output/server/_ssr/schema-BiAVyEj2.mjs +361 -0
  139. package/.output/server/_ssr/{select-CyvLaMza.mjs → select-t3iXRwc9.mjs} +3 -3
  140. package/.output/server/_ssr/server-h4VxnExB.mjs +8325 -0
  141. package/.output/server/_ssr/{createSsrRpc-C-dLcQuy.mjs → sidebar-DJcErBMo.mjs} +107 -47
  142. package/.output/server/_ssr/ssr.mjs +1 -1
  143. package/.output/server/_ssr/start-BfFbHKMK.mjs +4 -0
  144. package/.output/server/_ssr/switch-E-btwMGt.mjs +43 -0
  145. package/.output/server/_ssr/{table-CkVhsNpe.mjs → table-37Dd23TN.mjs} +2 -2
  146. package/.output/server/_ssr/tabs-DEFnTm95.mjs +45 -0
  147. package/.output/server/_ssr/updateFailures-H6pnnYK4.mjs +40 -0
  148. package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-C88s9jWF.mjs} +2 -2
  149. package/.output/server/_tanstack-start-manifest_v-55AGsehh.mjs +139 -0
  150. package/.output/server/index.mjs +303 -205
  151. package/LICENSE +13 -14
  152. package/README.md +80 -71
  153. package/lib/index.d.mts +23 -11
  154. package/lib/vite-internal.mjs +13 -3
  155. package/lib/vite.d.mts +2 -0
  156. package/package.json +14 -14
  157. package/src/components/AppSidebar.tsx +74 -75
  158. package/src/components/ConsoleAccessPage.tsx +5 -1
  159. package/src/components/ConsoleFeatureUnavailable.tsx +80 -0
  160. package/src/components/NotFoundPage.tsx +7 -7
  161. package/src/components/features/FeatureSlots.tsx +103 -0
  162. package/src/components/features/api-keys/apiKeysFeatureUi.tsx +15 -0
  163. package/src/components/features/bundles/BundleMetadata.tsx +8 -5
  164. package/src/components/features/bundles/RolloutCohortsDialog.tsx +1 -1
  165. package/src/components/features/channels/ChannelManagementDialog.tsx +8 -6
  166. package/src/components/features/insights/AppUsage.tsx +41 -14
  167. package/src/components/features/insights/BuiltinBundle.tsx +36 -0
  168. package/src/components/features/insights/ComparedBundlesChart.tsx +310 -0
  169. package/src/components/features/insights/EstimatedCount.tsx +18 -0
  170. package/src/components/features/insights/EventDetails.tsx +390 -26
  171. package/src/components/features/insights/EventHistoryCard.tsx +111 -25
  172. package/src/components/features/insights/EventHistoryList.tsx +1 -1
  173. package/src/components/features/insights/FailureReports.tsx +407 -0
  174. package/src/components/features/insights/InsightsControls.tsx +240 -142
  175. package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
  176. package/src/components/features/insights/InsightsOverview.tsx +601 -403
  177. package/src/components/features/insights/InsightsPageHeader.tsx +19 -14
  178. package/src/components/features/insights/InsightsPeriodSelector.tsx +67 -12
  179. package/src/components/features/insights/InstallationHistoryCard.tsx +65 -10
  180. package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
  181. package/src/components/features/insights/ReleaseActivity.tsx +308 -0
  182. package/src/components/features/insights/ReportingDevicesSummary.tsx +13 -13
  183. package/src/components/features/insights/RollbackButton.tsx +118 -0
  184. package/src/components/features/insights/UpdateFailures.tsx +382 -0
  185. package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
  186. package/src/components/features/releases/ReleaseEditorSheet.tsx +27 -13
  187. package/src/components/features/remote-config/ConditionDialog.tsx +592 -0
  188. package/src/components/features/remote-config/ConditionsCard.tsx +247 -0
  189. package/src/components/features/remote-config/ParameterDialog.tsx +426 -0
  190. package/src/components/features/remote-config/ParametersCard.tsx +254 -0
  191. package/src/components/features/remote-config/PreviewCard.tsx +284 -0
  192. package/src/components/features/remote-config/PublishDialog.tsx +325 -0
  193. package/src/components/features/remote-config/RemoteConfigPage.tsx +353 -0
  194. package/src/components/features/remote-config/RuleSummary.tsx +21 -0
  195. package/src/components/features/remote-config/ValueInput.tsx +151 -0
  196. package/src/components/features/remote-config/VersionsCard.tsx +384 -0
  197. package/src/components/features/remote-config/remoteConfigFeatureUi.tsx +15 -0
  198. package/src/components/features/remote-config/useDateTimeText.ts +16 -0
  199. package/src/components/ui/sidebar.tsx +22 -29
  200. package/src/index.ts +26 -19
  201. package/src/lib/api-keys-api.ts +0 -28
  202. package/src/lib/api-keys-rpc.ts +27 -38
  203. package/src/lib/api-rpc.ts +125 -288
  204. package/src/lib/api.ts +15 -134
  205. package/src/lib/bundle-activity.ts +24 -4
  206. package/src/lib/console-access.ts +20 -0
  207. package/src/lib/console-features-api.ts +68 -0
  208. package/src/lib/console-features-rpc.ts +13 -0
  209. package/src/lib/console-features.ts +98 -0
  210. package/src/lib/insights-api.ts +56 -2
  211. package/src/lib/insights-errors.ts +64 -0
  212. package/src/lib/insights-failures-rpc.ts +36 -0
  213. package/src/lib/insights-failures.ts +130 -0
  214. package/src/lib/insights-recovery-rpc.ts +13 -17
  215. package/src/lib/insights-recovery.ts +12 -36
  216. package/src/lib/insights-retention.ts +21 -0
  217. package/src/lib/insights-rpc.ts +30 -12
  218. package/src/lib/insights-search.ts +50 -4
  219. package/src/lib/insights-usage-rpc.ts +15 -9
  220. package/src/lib/insights-usage.ts +41 -3
  221. package/src/lib/insights-view.ts +34 -5
  222. package/src/lib/release-adoption-api.ts +214 -0
  223. package/src/lib/release-adoption-rpc.ts +49 -0
  224. package/src/lib/release-adoption.ts +164 -0
  225. package/src/lib/remote-config-api.ts +119 -0
  226. package/src/lib/remote-config-draft.ts +666 -0
  227. package/src/lib/remote-config-rpc.ts +196 -0
  228. package/src/lib/scroll-restoration.ts +21 -0
  229. package/src/lib/server/auth.server.ts +13 -5
  230. package/src/lib/server/bundleActivity.ts +27 -28
  231. package/src/lib/server/config.server.ts +21 -76
  232. package/src/lib/server/console-feature-apis.ts +70 -0
  233. package/src/lib/server/console-runtime.server.ts +82 -38
  234. package/src/lib/server/downloadBundle.ts +14 -15
  235. package/src/lib/server/failureReports.ts +50 -0
  236. package/src/lib/server/getBundleChildren.ts +31 -122
  237. package/src/lib/server/insightsUsage.ts +52 -113
  238. package/src/lib/server/listReleases.ts +109 -44
  239. package/src/lib/server/releaseAdoption.ts +151 -0
  240. package/src/lib/server/releaseReachability.ts +8 -8
  241. package/src/lib/server/remoteConfig.ts +75 -0
  242. package/src/lib/server/runtime.server.ts +120 -31
  243. package/src/lib/server/updateFailures.ts +66 -0
  244. package/src/lib/use-dialog-target.ts +27 -0
  245. package/src/routeTree.gen.ts +44 -44
  246. package/src/router.tsx +3 -2
  247. package/src/routes/-installations-search.ts +32 -8
  248. package/src/routes/-releases-search.ts +76 -29
  249. package/src/routes/__root.tsx +7 -1
  250. package/src/routes/api/bundles/$bundleId/download.ts +2 -5
  251. package/src/routes/api-keys.tsx +5 -2
  252. package/src/routes/index.tsx +210 -152
  253. package/src/routes/insights.tsx +91 -23
  254. package/src/routes/insights_.distribution.tsx +50 -9
  255. package/src/routes/installations.tsx +97 -40
  256. package/src/routes/remote-config.tsx +42 -0
  257. package/src/styles.css +9 -0
  258. package/src/vite-internal.ts +15 -3
  259. package/src/vite.ts +2 -0
  260. package/.output/public/assets/HashValueDisplay-BpTc3Oyg.js +0 -1
  261. package/.output/public/assets/InsightsPageHeader-DC2eEst8.js +0 -1
  262. package/.output/public/assets/api-CdUyUqzR.js +0 -42
  263. package/.output/public/assets/api-keys-64PM9PwW.js +0 -1
  264. package/.output/public/assets/api-keys-api-BeaJnvY5.js +0 -1
  265. package/.output/public/assets/arrow-left-04Rh8Zgp.js +0 -1
  266. package/.output/public/assets/card-CYHs5FZv.js +0 -1
  267. package/.output/public/assets/chart-no-axes-combined-DwyMsVFW.js +0 -1
  268. package/.output/public/assets/createServerFn-DU71xucx.js +0 -3
  269. package/.output/public/assets/dialog-IVMkBRCq.js +0 -1
  270. package/.output/public/assets/dist-BR4GzeEu.js +0 -1
  271. package/.output/public/assets/field-B3CYDpjg.js +0 -1
  272. package/.output/public/assets/index-C39o7rJH.js +0 -10
  273. package/.output/public/assets/info-C3y9HNoU.js +0 -1
  274. package/.output/public/assets/input-group-nLZylhWF.js +0 -1
  275. package/.output/public/assets/insights-Bm9v8uO3.js +0 -2
  276. package/.output/public/assets/insights-DS3iO7Up.js +0 -1
  277. package/.output/public/assets/insights-search-Byj3g3Mp.js +0 -1
  278. package/.output/public/assets/insights-usage-rpc-BaGR3lgq.js +0 -1
  279. package/.output/public/assets/insights_.distribution-B1i6cjlQ.js +0 -1
  280. package/.output/public/assets/insights_.distribution-C1Wf27eB.js +0 -2
  281. package/.output/public/assets/installations-CkFW4fV9.js +0 -2
  282. package/.output/public/assets/installations-DxTaIcHD.js +0 -1
  283. package/.output/public/assets/lazyRouteComponent-BapzjYnb.js +0 -1
  284. package/.output/public/assets/list-Dqt53fWZ.js +0 -1
  285. package/.output/public/assets/loader-circle-CUKfUqow.js +0 -1
  286. package/.output/public/assets/preload-helper-ClBMtM1g.js +0 -1
  287. package/.output/public/assets/refresh-cw-CNE_2PnD.js +0 -1
  288. package/.output/public/assets/routes-Cgtvspjg.js +0 -2
  289. package/.output/public/assets/routes-O2r2UUN8.js +0 -1
  290. package/.output/public/assets/shield-check-BFcJ46kz.js +0 -1
  291. package/.output/public/assets/signing-BgspC-xP.js +0 -1
  292. package/.output/public/assets/styles-DnrWuBwd.css +0 -2
  293. package/.output/public/assets/table-BuWQgxJJ.js +0 -1
  294. package/.output/public/assets/useMutation-CSV9NCUl.js +0 -1
  295. package/.output/public/assets/useOpenInteractionType-D5kduCTD.js +0 -1
  296. package/.output/public/assets/valueToPercent-BAtcbSLk.js +0 -1
  297. package/.output/server/__23tanstack-start-server-fn-resolver-CmYkol9D.mjs +0 -150
  298. package/.output/server/_ssr/InsightsPageHeader-Dma3dVj1.mjs +0 -64
  299. package/.output/server/_ssr/api-C3bGmgY0.mjs +0 -332
  300. package/.output/server/_ssr/api-keys-api-BRF6rbRC.mjs +0 -60
  301. package/.output/server/_ssr/api-keys-rpc-BMa1ZM6N.mjs +0 -136
  302. package/.output/server/_ssr/api-rpc-BYQ2e0z6.mjs +0 -1399
  303. package/.output/server/_ssr/auth.server-CmicHh0J.mjs +0 -47
  304. package/.output/server/_ssr/bundle-signing-rpc-DbQ4pFAX.mjs +0 -16
  305. package/.output/server/_ssr/bundle-signing.server-Uqt1OF49.mjs +0 -12
  306. package/.output/server/_ssr/bundleActivity-pBrPSG01.mjs +0 -28
  307. package/.output/server/_ssr/card-B7KSO_25.mjs +0 -108
  308. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
  309. package/.output/server/_ssr/config.server-C-d1QaBU.mjs +0 -368
  310. package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
  311. package/.output/server/_ssr/deleteBundle-8X-jq7Z7.mjs +0 -66
  312. package/.output/server/_ssr/field-BDwuU8RE.mjs +0 -74
  313. package/.output/server/_ssr/getBundleChildren-DoL8l_AS.mjs +0 -50
  314. package/.output/server/_ssr/insights-BDJjaDLp.mjs +0 -938
  315. package/.output/server/_ssr/insights-D0FWpfOS.mjs +0 -10
  316. package/.output/server/_ssr/insights-recovery-Cv6RSyeR.mjs +0 -21
  317. package/.output/server/_ssr/insights-recovery-rpc-BE9YsK_T.mjs +0 -37
  318. package/.output/server/_ssr/insights-search-1czJMZgk.mjs +0 -21
  319. package/.output/server/_ssr/insights-usage-rpc-BE_aCnea.mjs +0 -184
  320. package/.output/server/_ssr/insights-usage-rpc-CtAolz-t.mjs +0 -24
  321. package/.output/server/_ssr/insightsContract-B09K_Px1.mjs +0 -303
  322. package/.output/server/_ssr/insightsHistory-Ci4rYAgl.mjs +0 -41
  323. package/.output/server/_ssr/insightsRecovery-CaghWL2t.mjs +0 -113
  324. package/.output/server/_ssr/insightsUsage-DNIj6jxF.mjs +0 -71
  325. package/.output/server/_ssr/insights_.distribution-Cp0kf1vW.mjs +0 -10
  326. package/.output/server/_ssr/installations-Bhyiwx8G.mjs +0 -29
  327. package/.output/server/_ssr/routes-xBkI2Fmt.mjs +0 -37
  328. package/.output/server/_ssr/runtime.server-CsiKFp_Z.mjs +0 -436
  329. package/.output/server/_ssr/server-BdkuOWe8.mjs +0 -7468
  330. package/.output/server/_ssr/signing-CQ70saFm.mjs +0 -133
  331. package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
  332. package/.output/server/_tanstack-start-manifest_v-CrBsoKQQ.mjs +0 -125
  333. package/src/components/features/bundles/BundleActivityChart.tsx +0 -97
  334. package/src/components/features/bundles/BundleInsightsSummary.tsx +0 -126
  335. package/src/components/features/signing/BundleSigningPage.tsx +0 -139
  336. package/src/lib/bundle-signing-api.ts +0 -14
  337. package/src/lib/bundle-signing-rpc.ts +0 -16
  338. package/src/lib/server/bundle-signing.server.ts +0 -23
  339. package/src/lib/server/deleteBundle.ts +0 -145
  340. package/src/lib/server/insightsHistory.ts +0 -52
  341. package/src/lib/server/insightsRecovery.ts +0 -187
  342. package/src/routes/signing.tsx +0 -37
@@ -0,0 +1,2315 @@
1
+ import { i as __toESM } from "../_runtime.mjs";
2
+ import { u as createServerFn } from "./createMiddleware-Cm6Tn0_Y.mjs";
3
+ import { t as consoleAccess } from "./console-access-BJfAXknd.mjs";
4
+ import { C as ScrollAreaScrollbar, S as ScrollAreaThumb, T as ScrollAreaRoot, _ as ComboboxList$1, _t as require_react, b as ComboboxTrigger$1, d as ComboboxRoot, f as ComboboxEmpty$1, g as ComboboxPortal, gt as require_jsx_runtime, h as ComboboxPositioner, m as ComboboxPopup, p as ComboboxItem$1, u as ComboboxItemIndicator, v as ComboboxClear$1, w as ScrollAreaViewport, x as ScrollAreaCorner, y as ComboboxInput$1 } from "../_libs/@base-ui/react+[...].mjs";
5
+ import { E as List, G as Check, H as ChevronRight, I as Copy, W as ChevronDown, X as ArrowUpRight, Z as ArrowRight, g as RefreshCw, h as RotateCcw, i as TriangleAlert, m as RotateCw, r as Undo2, t as X } from "../_libs/lucide-react.mjs";
6
+ import { A as createSsrRpc, C as Skeleton, O as buttonVariants, a as SheetContent, c as SheetTitle, i as Sheet, k as cn, l as SheetTrigger, n as Input, o as SheetDescription, r as Separator$1, s as SheetHeader, t as Button$1 } from "./sidebar-DJcErBMo.mjs";
7
+ import { a as Badge, c as CardDescription, d as CardTitle, f as Field, g as FieldLabel, h as FieldGroup, i as AlertTitle, l as CardFooter, o as Card, r as AlertDescription, s as CardContent, t as Alert, u as CardHeader } from "./field-asw83X0v.mjs";
8
+ import { a as AlertDialogDescription, i as AlertDialogContent, l as AlertDialogTitle, n as AlertDialogAction, o as AlertDialogFooter, r as AlertDialogCancel, s as AlertDialogHeader, t as AlertDialog$1 } from "./alert-dialog-C43Doa0I.mjs";
9
+ import { a as TableHeader, i as TableHead, n as TableBody, o as TableRow, r as TableCell, t as Table } from "./table-37Dd23TN.mjs";
10
+ import { a as useQueries, i as useQuery, s as useQueryClient } from "../_libs/tanstack__react-query.mjs";
11
+ import { n as toast } from "../_libs/sonner.mjs";
12
+ import { h as Link } from "../_libs/@tanstack/react-router+[...].mjs";
13
+ import { i as EmptyHeader, n as EmptyContent, o as EmptyTitle, r as EmptyDescription, t as Empty } from "./empty-CVLGfxuu.mjs";
14
+ import { r as recoveryWindows, t as insightsPeriodEnd } from "./insights-recovery-BWQhiZaF.mjs";
15
+ import { a as failureReasonLabel, i as failureRate, o as failureStageLabel, r as failureDetailParts, s as formatRate, t as checkFailureRate } from "./insights-failures-xaa41Buf.mjs";
16
+ import { t as groupFailureReports } from "./insights-errors-BjnFUyZj.mjs";
17
+ import { n as usageMetrics } from "./insights-usage-BpBM0U0c.mjs";
18
+ import { a as readBundleEventsInput, i as readAdoptionReleasesInput, n as crashRateOf, o as recommendsRollback, r as readAdoptionReleaseInput, t as CRASH_RATE_THRESHOLD } from "./release-adoption-005tQRpo.mjs";
19
+ import { i as useFailureReportsQuery, l as useInsightsRetention, r as keepsTwelveMonths, t as DEFAULT_INSIGHTS_RETENTION, u as useUpdateFailuresQuery } from "./insights-api-sjws23WF.mjs";
20
+ import { r as comparedBundleIds, t as InsightsInfo } from "./InsightsInfo-DuJFatQO.mjs";
21
+ import { t as Route } from "./insights-B55FG5fm.mjs";
22
+ import { i as TabsTrigger, n as TabsContent, r as TabsList, t as Tabs$1 } from "./tabs-DEFnTm95.mjs";
23
+ import { a as SelectItem, i as SelectGroup, n as Select$1, o as SelectTrigger, r as SelectContent, s as SelectValue, t as PlatformIcon } from "./select-t3iXRwc9.mjs";
24
+ import { i as InputGroupInput, n as InputGroupAddon, r as InputGroupButton, t as InputGroup } from "./input-group-CuYJHosb.mjs";
25
+ import { i as useChannelsQuery, l as usePreflightReleaseMutation, m as useUpdateReleaseMutation } from "./api-oZjRn9tP.mjs";
26
+ import { t as BundleIdDisplay } from "./BundleIdDisplay-x94o9rlU.mjs";
27
+ import { n as getAppUsageReportRpc, t as Progress$1 } from "./insights-usage-rpc-KStL0zOx.mjs";
28
+ import { i as InsightsPeriodSelector, n as InsightsErrorAlert, o as UsagePeriodSelector, r as InsightsPageHeader } from "./InsightsPageHeader-BSUX3Ye9.mjs";
29
+ import { i as HttpResponseBody, n as EventTimestamp, o as useInsightsTimeFormat, t as EventBundleTransition } from "./EventDetails-Q8kfj9Nr.mjs";
30
+ import { a as CartesianGrid, i as Line, l as ResponsiveContainer, n as YAxis, o as ReferenceLine, r as XAxis, s as Tooltip, t as LineChart } from "../_libs/recharts+[...].mjs";
31
+ //#region node_modules/.nitro/vite/services/ssr/assets/insights-CpLpedFZ.js
32
+ var import_react = /* @__PURE__ */ __toESM(require_react());
33
+ var import_jsx_runtime = require_jsx_runtime();
34
+ var THEMES = {
35
+ light: "",
36
+ dark: ".dark"
37
+ };
38
+ var INITIAL_DIMENSION = {
39
+ width: 320,
40
+ height: 200
41
+ };
42
+ var ChartContext = import_react.createContext(null);
43
+ function useChart() {
44
+ const context = import_react.useContext(ChartContext);
45
+ if (!context) throw new Error("useChart must be used within a <ChartContainer />");
46
+ return context;
47
+ }
48
+ function ChartContainer({ id, className, children, config, initialDimension = INITIAL_DIMENSION, ...props }) {
49
+ const uniqueId = import_react.useId();
50
+ const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}`;
51
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChartContext.Provider, {
52
+ value: { config },
53
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
54
+ "data-slot": "chart",
55
+ "data-chart": chartId,
56
+ className: cn("flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden", className),
57
+ ...props,
58
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChartStyle, {
59
+ id: chartId,
60
+ config
61
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ResponsiveContainer, {
62
+ initialDimension,
63
+ children
64
+ })]
65
+ })
66
+ });
67
+ }
68
+ var ChartStyle = ({ id, config }) => {
69
+ const colorConfig = Object.entries(config).filter(([, config]) => config.theme ?? config.color);
70
+ if (!colorConfig.length) return null;
71
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { dangerouslySetInnerHTML: { __html: Object.entries(THEMES).map(([theme, prefix]) => `
72
+ ${prefix} [data-chart=${id}] {
73
+ ${colorConfig.map(([key, itemConfig]) => {
74
+ const color = itemConfig.theme?.[theme] ?? itemConfig.color;
75
+ return color ? ` --color-${key}: ${color};` : null;
76
+ }).join("\n")}
77
+ }
78
+ `).join("\n") } });
79
+ };
80
+ var ChartTooltip = Tooltip;
81
+ function ChartTooltipContent({ active, payload, className, indicator = "dot", hideLabel = false, hideIndicator = false, label, labelFormatter, labelClassName, formatter, color, nameKey, labelKey }) {
82
+ const { config } = useChart();
83
+ const tooltipLabel = import_react.useMemo(() => {
84
+ if (hideLabel || !payload?.length) return null;
85
+ const [item] = payload;
86
+ const itemConfig = getPayloadConfigFromPayload(config, item, `${labelKey ?? item?.dataKey ?? item?.name ?? "value"}`);
87
+ const value = !labelKey && typeof label === "string" ? config[label]?.label ?? label : itemConfig?.label;
88
+ if (labelFormatter) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
89
+ className: cn("font-medium", labelClassName),
90
+ children: labelFormatter(value, payload)
91
+ });
92
+ if (!value) return null;
93
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
94
+ className: cn("font-medium", labelClassName),
95
+ children: value
96
+ });
97
+ }, [
98
+ label,
99
+ labelFormatter,
100
+ payload,
101
+ hideLabel,
102
+ labelClassName,
103
+ config,
104
+ labelKey
105
+ ]);
106
+ if (!active || !payload?.length) return null;
107
+ const nestLabel = payload.length === 1 && indicator !== "dot";
108
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
109
+ className: cn("grid min-w-32 items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs/relaxed shadow-xl", className),
110
+ children: [!nestLabel ? tooltipLabel : null, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
111
+ className: "grid gap-1.5",
112
+ children: payload.filter((item) => item.type !== "none").map((item, index) => {
113
+ const itemConfig = getPayloadConfigFromPayload(config, item, `${nameKey ?? item.name ?? item.dataKey ?? "value"}`);
114
+ const indicatorColor = color ?? item.payload?.fill ?? item.color;
115
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
116
+ className: cn("flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground", indicator === "dot" && "items-center"),
117
+ children: formatter && item?.value !== void 0 && item.name ? formatter(item.value, item.name, item, index, item.payload) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [itemConfig?.icon ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(itemConfig.icon, {}) : !hideIndicator && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
118
+ className: cn("shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)", {
119
+ "h-2.5 w-2.5": indicator === "dot",
120
+ "w-1": indicator === "line",
121
+ "w-0 border-[1.5px] border-dashed bg-transparent": indicator === "dashed",
122
+ "my-0.5": nestLabel && indicator === "dashed"
123
+ }),
124
+ style: {
125
+ "--color-bg": indicatorColor,
126
+ "--color-border": indicatorColor
127
+ }
128
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
129
+ className: cn("flex flex-1 justify-between gap-2 leading-none", nestLabel ? "items-end" : "items-center"),
130
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
131
+ className: "grid gap-1.5",
132
+ children: [nestLabel ? tooltipLabel : null, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
133
+ className: "text-muted-foreground",
134
+ children: itemConfig?.label ?? item.name
135
+ })]
136
+ }), item.value != null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
137
+ className: "font-mono font-medium text-foreground tabular-nums",
138
+ children: typeof item.value === "number" ? item.value.toLocaleString() : String(item.value)
139
+ })]
140
+ })] })
141
+ }, index);
142
+ })
143
+ })]
144
+ });
145
+ }
146
+ function getPayloadConfigFromPayload(config, payload, key) {
147
+ if (typeof payload !== "object" || payload === null) return;
148
+ const payloadPayload = "payload" in payload && typeof payload.payload === "object" && payload.payload !== null ? payload.payload : void 0;
149
+ let configLabelKey = key;
150
+ if (key in payload && typeof payload[key] === "string") configLabelKey = payload[key];
151
+ else if (payloadPayload && key in payloadPayload && typeof payloadPayload[key] === "string") configLabelKey = payloadPayload[key];
152
+ return configLabelKey in config ? config[configLabelKey] : config[key];
153
+ }
154
+ function ScrollArea$1({ className, children, ...props }) {
155
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ScrollAreaRoot, {
156
+ "data-slot": "scroll-area",
157
+ className: cn("relative", className),
158
+ ...props,
159
+ children: [
160
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollAreaViewport, {
161
+ "data-slot": "scroll-area-viewport",
162
+ className: "size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1",
163
+ children
164
+ }),
165
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollBar, {}),
166
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollAreaCorner, {})
167
+ ]
168
+ });
169
+ }
170
+ function ScrollBar({ className, orientation = "vertical", ...props }) {
171
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollAreaScrollbar, {
172
+ "data-slot": "scroll-area-scrollbar",
173
+ "data-orientation": orientation,
174
+ orientation,
175
+ className: cn("flex touch-none p-px transition-colors select-none data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent", className),
176
+ ...props,
177
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollAreaThumb, {
178
+ "data-slot": "scroll-area-thumb",
179
+ className: "relative flex-1 rounded-full bg-border"
180
+ })
181
+ });
182
+ }
183
+ /**
184
+ * A unique count the server estimates from a HyperLogLog sketch, typically
185
+ * within about 3% of the true count. It shows as approximate, as analytics
186
+ * consoles mark estimates, and says so to screen readers. Zero is exact: no
187
+ * report reached the sketch.
188
+ */
189
+ function EstimatedCount({ value }) {
190
+ if (value === 0) return "0";
191
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
192
+ title: "Estimated",
193
+ children: [
194
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
195
+ "aria-hidden": "true",
196
+ className: "mr-1 text-muted-foreground",
197
+ children: "≈"
198
+ }),
199
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
200
+ className: "sr-only",
201
+ children: "Estimated "
202
+ }),
203
+ value.toLocaleString()
204
+ ]
205
+ });
206
+ }
207
+ function ReportingDevicesSummary({ window, count, isPending, partial }) {
208
+ const metric = usageMetrics[window];
209
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
210
+ "aria-label": `${metric.label} over ${metric.period}`,
211
+ role: "region",
212
+ className: "flex items-center gap-3",
213
+ children: [isPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, {
214
+ "aria-label": "Loading active users",
215
+ className: "h-9 w-12"
216
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
217
+ className: "text-4xl font-semibold tracking-tight tabular-nums",
218
+ children: count === void 0 ? "—" : partial ? `≥${count.toLocaleString()}` : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EstimatedCount, { value: count })
219
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
220
+ className: "flex items-center gap-1 text-sm text-muted-foreground",
221
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: metric.label }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(InsightsInfo, {
222
+ label: `How ${metric.label} is counted`,
223
+ children: [
224
+ "Installations that reported in the last ",
225
+ metric.period,
226
+ ", estimated within about 3%. The chart shows them per ",
227
+ metric.interval,
228
+ ".",
229
+ partial ? " Some history is missing, so this is a minimum." : ""
230
+ ]
231
+ })]
232
+ })]
233
+ });
234
+ }
235
+ var dates$1 = new Intl.DateTimeFormat("en", {
236
+ month: "short",
237
+ day: "numeric",
238
+ timeZone: "UTC"
239
+ });
240
+ var times$1 = new Intl.DateTimeFormat("en", {
241
+ month: "short",
242
+ day: "numeric",
243
+ hour: "numeric",
244
+ timeZone: "UTC"
245
+ });
246
+ function AppUsage({ query, search, window, onWindowChange, retention = DEFAULT_INSIGHTS_RETENTION }) {
247
+ const report = query.error ? void 0 : query.data;
248
+ const formatTime = (ms) => (window === "30d" || window === "12m" ? dates$1 : times$1).format(ms);
249
+ const distributionPeriod = report ? `since ${dates$1.format(report.distributionSinceMs)}, 00:00 UTC` : "in the selected period";
250
+ const distributionTotals = report ? {
251
+ versions: report.versions.reduce((sum, row) => sum + row.installations, 0),
252
+ platforms: report.platforms.reduce((sum, row) => sum + row.installations, 0)
253
+ } : void 0;
254
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
255
+ className: "grid min-w-0 gap-6 xl:grid-cols-[minmax(0,2fr)_minmax(16rem,1fr)]",
256
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, {
257
+ "aria-label": "App usage",
258
+ role: "region",
259
+ className: "min-w-0 shadow-sm",
260
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
261
+ className: "flex-row flex-wrap items-center justify-between gap-4",
262
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, { children: "App usage" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UsagePeriodSelector, {
263
+ window,
264
+ onWindowChange,
265
+ twelveMonths: keepsTwelveMonths(retention)
266
+ })]
267
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardContent, {
268
+ className: "flex flex-col gap-4",
269
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReportingDevicesSummary, {
270
+ window,
271
+ count: report?.activeInstallations,
272
+ isPending: query.isPending,
273
+ partial: report?.truncated ?? false
274
+ }), query.isPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, {
275
+ "aria-label": "Loading app usage",
276
+ className: "h-48"
277
+ }) : query.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
278
+ className: "flex flex-col items-start gap-4",
279
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(InsightsErrorAlert, {
280
+ error: query.error,
281
+ fallbackTitle: "App usage unavailable"
282
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
283
+ variant: "outline",
284
+ onClick: () => void query.refetch(),
285
+ children: "Retry app usage"
286
+ })]
287
+ }) : report?.activeInstallations === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Empty, {
288
+ className: "h-48",
289
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(EmptyHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyTitle, { children: "No activity reported" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyDescription, { children: "Try another period, platform, channel, or app version." })] })
290
+ }) : report ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
291
+ className: "mb-2 text-right text-xs text-muted-foreground",
292
+ children: "Reporting installations · UTC"
293
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChartContainer, {
294
+ "aria-label": "Active users per interval",
295
+ className: "h-48 w-full aspect-auto",
296
+ config: { installations: {
297
+ label: "Active users",
298
+ color: "var(--chart-2)"
299
+ } },
300
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(LineChart, {
301
+ accessibilityLayer: true,
302
+ data: report.points,
303
+ margin: {
304
+ right: 8,
305
+ top: 8
306
+ },
307
+ children: [
308
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CartesianGrid, { vertical: false }),
309
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(XAxis, {
310
+ dataKey: "startMs",
311
+ tick: { fill: "var(--muted-foreground)" },
312
+ tickFormatter: formatTime,
313
+ axisLine: false,
314
+ tickLine: false,
315
+ minTickGap: 45
316
+ }),
317
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(YAxis, {
318
+ tick: { fill: "var(--muted-foreground)" },
319
+ allowDecimals: false,
320
+ axisLine: false,
321
+ tickLine: false,
322
+ width: "auto"
323
+ }),
324
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChartTooltip, { content: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChartTooltipContent, { labelFormatter: (_, payload) => `${times$1.format(payload[0]?.payload.startMs)} · UTC` }) }),
325
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Line, {
326
+ dataKey: "installations",
327
+ type: "linear",
328
+ stroke: "var(--color-installations)",
329
+ strokeWidth: 2,
330
+ dot: { r: 2 },
331
+ activeDot: { r: 4 },
332
+ isAnimationActive: false
333
+ })
334
+ ]
335
+ })
336
+ })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", {
337
+ className: "text-xs text-muted-foreground",
338
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", {
339
+ className: "cursor-pointer py-2",
340
+ children: "View usage data"
341
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Table, {
342
+ "aria-label": "App usage by interval",
343
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Interval · UTC" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
344
+ className: "text-right",
345
+ children: "Active users"
346
+ })] }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableBody, { children: report.points.map((point) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: times$1.format(point.startMs) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
347
+ className: "text-right tabular-nums",
348
+ children: point.installations === null ? "Unknown" : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EstimatedCount, { value: point.installations })
349
+ })] }, point.startMs)) })]
350
+ })]
351
+ })] }) : null]
352
+ })]
353
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, {
354
+ "aria-label": "Usage distribution",
355
+ role: "region",
356
+ className: "flex min-w-0 flex-col shadow-sm",
357
+ children: [
358
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
359
+ className: "flex-row items-center justify-between gap-3",
360
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
361
+ className: "flex min-h-9 items-center",
362
+ children: "Distribution"
363
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(InsightsInfo, {
364
+ label: "How distribution is counted",
365
+ children: [
366
+ "Each installation counts once, by its latest report",
367
+ " ",
368
+ distributionPeriod,
369
+ ".",
370
+ report?.truncated ? " Some history is missing." : ""
371
+ ]
372
+ })]
373
+ }),
374
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, {
375
+ className: "flex-1",
376
+ children: query.isPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, {
377
+ "aria-label": "Loading distribution",
378
+ className: "h-48"
379
+ }) : query.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InsightsErrorAlert, {
380
+ error: query.error,
381
+ fallbackTitle: "Distribution unavailable"
382
+ }) : !report?.activeInstallations ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Empty, {
383
+ className: "h-40",
384
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(EmptyHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyTitle, { children: "No reporting installations" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyDescription, { children: "Try a different filter or period." })] })
385
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
386
+ className: "flex flex-col gap-6",
387
+ children: ["versions", "platforms"].map((dimension) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
388
+ "aria-label": dimension === "versions" ? "App version distribution" : "Platform distribution",
389
+ className: "flex flex-col gap-4",
390
+ children: [
391
+ dimension === "platforms" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Separator$1, {}) : null,
392
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", {
393
+ className: "flex items-center gap-2 text-sm font-medium text-muted-foreground",
394
+ children: [dimension === "versions" ? "App versions" : "Platforms", dimension === "versions" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
395
+ variant: "secondary",
396
+ children: report.versions.length
397
+ }) : null]
398
+ }),
399
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollArea$1, {
400
+ className: "min-w-0 [&_[data-slot=scroll-area-viewport]]:max-h-40",
401
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dl", {
402
+ className: "flex flex-col gap-4 pr-4",
403
+ children: report[dimension].map((row) => {
404
+ const name = dimension === "platforms" ? row.name === "ios" ? "iOS" : "Android" : row.name;
405
+ const share = row.installations / (distributionTotals?.[dimension] ?? 1) * 100;
406
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
407
+ className: "group relative flex min-h-11 flex-col gap-2 rounded-sm focus-within:ring-2 focus-within:ring-ring sm:min-h-0",
408
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
409
+ className: "flex min-w-0 items-baseline justify-between gap-3",
410
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dt", {
411
+ className: "flex min-w-0 items-center gap-2 text-sm",
412
+ title: name,
413
+ children: [dimension === "platforms" && (row.name === "ios" || row.name === "android") ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlatformIcon, {
414
+ platform: row.name,
415
+ className: "size-4 shrink-0"
416
+ }) : null, dimension === "versions" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
417
+ to: "/insights/distribution",
418
+ search: {
419
+ ...search,
420
+ version: row.name
421
+ },
422
+ "aria-label": `View bundles for app version ${row.name}`,
423
+ className: "flex min-w-0 items-center gap-1 after:absolute after:inset-0 group-hover:underline",
424
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
425
+ className: "truncate",
426
+ children: name
427
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, {
428
+ "aria-hidden": "true",
429
+ className: "size-3 shrink-0 text-muted-foreground"
430
+ })]
431
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
432
+ className: "truncate",
433
+ children: name
434
+ })]
435
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dd", {
436
+ className: "flex shrink-0 gap-3 text-sm tabular-nums",
437
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: row.installations.toLocaleString() }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
438
+ className: "min-w-10 text-right text-muted-foreground",
439
+ children: [share.toFixed(1), "%"]
440
+ })]
441
+ })]
442
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Progress$1, {
443
+ "aria-label": `${name} share`,
444
+ "aria-valuetext": `${row.installations} installations, ${share.toFixed(1)}%`,
445
+ value: share
446
+ })]
447
+ }, row.name);
448
+ })
449
+ })
450
+ })
451
+ ]
452
+ }, dimension))
453
+ })
454
+ }),
455
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardFooter, {
456
+ className: "border-t pt-4",
457
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
458
+ to: "/insights/distribution",
459
+ search,
460
+ className: buttonVariants({
461
+ variant: "outline",
462
+ className: "h-11 w-full sm:h-9"
463
+ }),
464
+ children: ["View distribution", /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowRight, {
465
+ "aria-hidden": "true",
466
+ "data-icon": "inline-end"
467
+ })]
468
+ })
469
+ })
470
+ ]
471
+ })]
472
+ });
473
+ }
474
+ var messageOf = (event) => event.failure?.errorMessage || "No error message reported";
475
+ function FailureDetail({ group }) {
476
+ const [selectedId, setSelectedId] = (0, import_react.useState)(group.reports[0].id);
477
+ const event = group.reports.find(({ id }) => id === selectedId) ?? group.reports[0];
478
+ const failure = event.failure;
479
+ const formatter = useInsightsTimeFormat();
480
+ const context = [
481
+ ["App", `${event.platform === "ios" ? "iOS" : "Android"} ${event.appVersion}`],
482
+ ["Channel", event.channel],
483
+ ["SDK", event.sdkVersion ?? "Not reported"],
484
+ ["Stage", failureStageLabel(failure.stage)],
485
+ ["Resource", failure.resource],
486
+ ["HTTP status", failure.httpStatus?.toString()],
487
+ ["Origin code", failure.originCode],
488
+ ["Transport", failure.transport]
489
+ ].filter(([, value]) => value !== void 0);
490
+ const copy = async () => {
491
+ try {
492
+ await navigator.clipboard.writeText(JSON.stringify(event, null, 2));
493
+ toast.success("Error report copied");
494
+ } catch {
495
+ toast.error("Could not copy the report");
496
+ }
497
+ };
498
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SheetContent, {
499
+ className: "data-[side=right]:w-full data-[side=right]:sm:max-w-2xl",
500
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SheetHeader, {
501
+ className: "border-b pr-14",
502
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SheetTitle, { children: "Error details" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SheetDescription, { children: [
503
+ group.reports.length.toLocaleString(),
504
+ " loaded reports ·",
505
+ " ",
506
+ group.installations.toLocaleString(),
507
+ " installations"
508
+ ] })]
509
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
510
+ className: "flex min-h-0 flex-1 flex-col gap-6 overflow-y-auto p-6",
511
+ children: [
512
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
513
+ "aria-label": "Original error",
514
+ className: "flex min-w-0 flex-col gap-3",
515
+ children: [
516
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
517
+ className: "text-xs text-muted-foreground",
518
+ children: [
519
+ failureStageLabel(failure.stage),
520
+ " ·",
521
+ " ",
522
+ failureReasonLabel(failure.reason)
523
+ ]
524
+ }),
525
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
526
+ className: cn("text-sm leading-relaxed whitespace-pre-wrap wrap-anywhere", failure.errorMessage ? "font-mono" : "text-muted-foreground"),
527
+ children: messageOf(event)
528
+ }),
529
+ !failure.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
530
+ className: "text-sm text-muted-foreground",
531
+ children: "This client did not send the original error. Upgrade the app’s Insights plugin and server to collect details for future failures."
532
+ }) : null,
533
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
534
+ className: "w-fit",
535
+ variant: "outline",
536
+ onClick: () => void copy(),
537
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Copy, {
538
+ "aria-hidden": "true",
539
+ "data-icon": "inline-start"
540
+ }), " Copy report"]
541
+ })
542
+ ]
543
+ }),
544
+ event.httpResponse ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
545
+ "aria-label": "Server response",
546
+ className: "flex min-w-0 flex-col gap-3",
547
+ children: [
548
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", {
549
+ className: "text-sm font-medium",
550
+ children: ["Server response · HTTP ", event.httpResponse.status]
551
+ }),
552
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
553
+ className: "text-xs text-muted-foreground",
554
+ children: "Most recent response available when this report was created."
555
+ }),
556
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
557
+ className: "font-mono text-xs wrap-anywhere",
558
+ children: ["GET ", event.httpResponse.path]
559
+ }),
560
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
561
+ className: "text-xs text-muted-foreground",
562
+ children: [
563
+ "Response received",
564
+ " ",
565
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EventTimestamp, {
566
+ value: event.httpResponse.receivedAtMs,
567
+ formatter
568
+ })
569
+ ]
570
+ }),
571
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HttpResponseBody, { response: event.httpResponse })
572
+ ]
573
+ }) : null,
574
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
575
+ "aria-label": "Stack trace",
576
+ className: "flex min-w-0 flex-col gap-2",
577
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
578
+ className: "text-sm font-medium",
579
+ children: "Stack trace"
580
+ }), failure.errorStack ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
581
+ className: "rounded-md border bg-muted/30 p-4 font-mono text-xs leading-relaxed whitespace-pre-wrap wrap-anywhere",
582
+ children: failure.errorStack
583
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
584
+ className: "text-sm text-muted-foreground",
585
+ children: "No stack trace was sent with this report."
586
+ })]
587
+ }),
588
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
589
+ "aria-label": "Report context",
590
+ className: "flex flex-col gap-3",
591
+ children: [
592
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
593
+ className: "text-sm font-medium",
594
+ children: "Report context"
595
+ }),
596
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
597
+ className: "text-xs text-muted-foreground",
598
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EventTimestamp, {
599
+ value: event.receivedAtMs,
600
+ formatter
601
+ })
602
+ }),
603
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dl", {
604
+ className: "grid grid-cols-2 gap-x-6 gap-y-4",
605
+ children: context.map(([label, value]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
606
+ className: "min-w-0",
607
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
608
+ className: "text-xs text-muted-foreground",
609
+ children: label
610
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
611
+ className: "mt-1 text-sm wrap-anywhere",
612
+ children: value
613
+ })]
614
+ }, label))
615
+ }),
616
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EventBundleTransition, { event }),
617
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
618
+ variant: "outline",
619
+ className: "w-fit",
620
+ render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, {
621
+ to: "/installations",
622
+ search: {
623
+ query: event.installId,
624
+ installId: event.installId
625
+ }
626
+ }),
627
+ children: [
628
+ "Installation history",
629
+ " ",
630
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowUpRight, {
631
+ "aria-hidden": "true",
632
+ "data-icon": "inline-end"
633
+ })
634
+ ]
635
+ }),
636
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
637
+ className: "font-mono text-xs text-muted-foreground wrap-anywhere",
638
+ children: event.installId
639
+ })
640
+ ]
641
+ }),
642
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
643
+ "aria-label": "Occurrences",
644
+ className: "flex flex-col gap-2",
645
+ children: [
646
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
647
+ className: "text-sm font-medium",
648
+ children: "Occurrences in loaded reports"
649
+ }),
650
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
651
+ className: "text-xs text-muted-foreground",
652
+ children: "Select a report to inspect its stack and context. Times use your browser’s zone."
653
+ }),
654
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
655
+ className: "flex flex-col gap-1",
656
+ children: group.reports.map((report) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
657
+ variant: report.id === event.id ? "secondary" : "ghost",
658
+ "aria-pressed": report.id === event.id,
659
+ className: "h-auto min-h-11 justify-between gap-4 whitespace-normal",
660
+ onClick: () => setSelectedId(report.id),
661
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
662
+ className: "text-left tabular-nums",
663
+ children: formatter.format(report.receivedAtMs)
664
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
665
+ className: "shrink-0",
666
+ children: report.appVersion
667
+ })]
668
+ }, report.id))
669
+ })
670
+ ]
671
+ })
672
+ ]
673
+ })]
674
+ });
675
+ }
676
+ function FailureReportsList({ pages, error, isPending, isFetching, hasNextPage, onLoadMore, onRefresh }) {
677
+ const groups = groupFailureReports(pages.flatMap((page) => page.data));
678
+ const reportCount = groups.reduce((sum, group) => sum + group.reports.length, 0);
679
+ const scanned = pages.reduce((sum, page) => sum + page.scanned, 0);
680
+ const formatter = useInsightsTimeFormat();
681
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
682
+ "aria-label": "Errors to investigate",
683
+ className: "flex min-w-0 flex-col gap-3",
684
+ "aria-busy": isFetching,
685
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
686
+ className: "flex items-center justify-between gap-4",
687
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
688
+ className: "flex flex-col gap-1",
689
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
690
+ className: "text-sm font-medium",
691
+ children: "Errors to investigate"
692
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
693
+ className: "text-xs text-muted-foreground",
694
+ children: "Original messages, most reported first. Select an error for its stack and affected app."
695
+ })]
696
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
697
+ variant: "ghost",
698
+ size: "icon-lg",
699
+ "aria-label": "Refresh error reports",
700
+ disabled: isFetching,
701
+ onClick: onRefresh,
702
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RefreshCw, { "aria-hidden": "true" })
703
+ })]
704
+ }), isPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, {
705
+ className: "h-40",
706
+ "aria-label": "Loading error reports"
707
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
708
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InsightsErrorAlert, {
709
+ error,
710
+ fallbackTitle: "Error reports unavailable"
711
+ }) : null,
712
+ groups.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Table, {
713
+ "aria-label": "Error messages",
714
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, { children: [
715
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Error" }),
716
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
717
+ className: "text-right",
718
+ children: "Reports"
719
+ }),
720
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
721
+ className: "hidden text-right sm:table-cell",
722
+ children: "Installations"
723
+ }),
724
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
725
+ className: "hidden lg:table-cell",
726
+ children: "Last seen"
727
+ })
728
+ ] }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableBody, { children: groups.map((group) => {
729
+ const latest = group.reports[0];
730
+ const failure = latest.failure;
731
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, { children: [
732
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
733
+ className: "max-w-md whitespace-normal",
734
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Sheet, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SheetTrigger, {
735
+ render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
736
+ variant: "link",
737
+ className: "h-auto min-h-11 w-full justify-start whitespace-normal px-0 text-left"
738
+ }),
739
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
740
+ className: "flex min-w-0 flex-col gap-1",
741
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
742
+ className: cn("line-clamp-2 text-xs leading-relaxed wrap-anywhere", latest.failure?.errorMessage ? "font-mono" : "text-muted-foreground"),
743
+ children: messageOf(latest)
744
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
745
+ className: "text-xs font-normal text-muted-foreground",
746
+ children: [failureStageLabel(failure.stage), ...failureDetailParts(failure)].join(" · ")
747
+ })]
748
+ })
749
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FailureDetail, { group })] })
750
+ }),
751
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
752
+ className: "text-right font-medium tabular-nums",
753
+ children: group.reports.length.toLocaleString()
754
+ }),
755
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
756
+ className: "hidden text-right tabular-nums sm:table-cell",
757
+ children: group.installations.toLocaleString()
758
+ }),
759
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
760
+ className: "hidden text-xs text-muted-foreground tabular-nums lg:table-cell",
761
+ children: formatter.format(latest.receivedAtMs)
762
+ })
763
+ ] }, group.key);
764
+ }) })]
765
+ }) : !error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Empty, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(EmptyHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyTitle, { children: hasNextPage ? "No failures in the events loaded so far" : "No error reports in this range" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyDescription, { children: hasNextPage ? "Load older events to continue investigating this period." : "Reports appear here when a client sends an update failure. Raw history may be shorter than aggregate history." })] }) }) : null,
766
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
767
+ className: "flex flex-wrap items-center justify-between gap-3",
768
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
769
+ className: "text-xs text-muted-foreground",
770
+ role: "status",
771
+ children: [
772
+ reportCount.toLocaleString(),
773
+ " failure reports from",
774
+ " ",
775
+ scanned.toLocaleString(),
776
+ " events reviewed.",
777
+ hasNextPage ? " More events remain; these counts are not period totals." : error ? " Scan interrupted." : " End of available range.",
778
+ pages[0] ? ` Raw history since ${formatter.format(pages[0].sinceMs)}.` : ""
779
+ ]
780
+ }), hasNextPage ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
781
+ variant: "outline",
782
+ disabled: isFetching,
783
+ onClick: onLoadMore,
784
+ children: isFetching ? "Loading…" : "Load older events"
785
+ }) : null]
786
+ })
787
+ ] })]
788
+ });
789
+ }
790
+ function FailureReports({ input }) {
791
+ const query = useFailureReportsQuery(input, true);
792
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FailureReportsList, {
793
+ pages: query.data?.pages ?? [],
794
+ error: query.error,
795
+ isPending: query.isPending,
796
+ isFetching: query.isFetching,
797
+ hasNextPage: query.hasNextPage,
798
+ onLoadMore: () => void query.fetchNextPage(),
799
+ onRefresh: () => void query.refetch()
800
+ });
801
+ }
802
+ var Combobox$1 = ComboboxRoot;
803
+ function ComboboxTrigger({ className, children, ...props }) {
804
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ComboboxTrigger$1, {
805
+ "data-slot": "combobox-trigger",
806
+ className: cn("[&_svg:not([class*='size-'])]:size-3.5", className),
807
+ ...props,
808
+ children: [children, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, { className: "text-muted-foreground size-3.5 pointer-events-none" })]
809
+ });
810
+ }
811
+ function ComboboxClear({ className, ...props }) {
812
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComboboxClear$1, {
813
+ "aria-label": "Clear selection",
814
+ "data-slot": "combobox-clear",
815
+ render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InputGroupButton, {
816
+ variant: "ghost",
817
+ size: "icon-xs"
818
+ }),
819
+ className: cn(className),
820
+ ...props,
821
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "pointer-events-none" })
822
+ });
823
+ }
824
+ function ComboboxInput({ className, children, disabled = false, showTrigger = true, showClear = false, ...props }) {
825
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(InputGroup, {
826
+ className: cn("w-auto", className),
827
+ children: [
828
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComboboxInput$1, {
829
+ render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InputGroupInput, { disabled }),
830
+ ...props
831
+ }),
832
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(InputGroupAddon, {
833
+ align: "inline-end",
834
+ children: [showTrigger && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComboboxTrigger, { render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InputGroupButton, {
835
+ size: "icon-xs",
836
+ variant: "ghost",
837
+ "data-slot": "input-group-button",
838
+ className: "group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent",
839
+ disabled
840
+ }) }), showClear && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComboboxClear, { disabled })]
841
+ }),
842
+ children
843
+ ]
844
+ });
845
+ }
846
+ function ComboboxContent({ className, side = "bottom", sideOffset = 6, align = "start", alignOffset = 0, anchor, ...props }) {
847
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComboboxPortal, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComboboxPositioner, {
848
+ side,
849
+ sideOffset,
850
+ align,
851
+ alignOffset,
852
+ anchor,
853
+ className: "isolate z-50",
854
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComboboxPopup, {
855
+ "data-slot": "combobox-content",
856
+ "data-chips": !!anchor,
857
+ className: cn("bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 *:data-[slot=input-group]:bg-input/20 dark:bg-popover max-h-72 min-w-32 overflow-hidden rounded-lg shadow-md ring-1 duration-100 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-7 *:data-[slot=input-group]:border-none *:data-[slot=input-group]:shadow-none dark group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) data-[chips=true]:min-w-(--anchor-width)", className),
858
+ ...props
859
+ })
860
+ }) });
861
+ }
862
+ function ComboboxList({ className, ...props }) {
863
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComboboxList$1, {
864
+ "data-slot": "combobox-list",
865
+ className: cn("no-scrollbar max-h-[min(calc(--spacing(72)---spacing(9)),calc(var(--available-height)---spacing(9)))] scroll-py-1 overflow-y-auto p-1 data-empty:p-0 overflow-y-auto overscroll-contain", className),
866
+ ...props
867
+ });
868
+ }
869
+ function ComboboxItem({ className, children, ...props }) {
870
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ComboboxItem$1, {
871
+ "data-slot": "combobox-item",
872
+ className: cn("data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground min-h-7 gap-2 rounded-md px-2 py-1 text-xs/relaxed [&_svg:not([class*='size-'])]:size-3.5 relative flex w-full cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", className),
873
+ ...props,
874
+ children: [children, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComboboxItemIndicator, {
875
+ render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pointer-events-none absolute right-2 flex items-center justify-center" }),
876
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: "pointer-events-none" })
877
+ })]
878
+ });
879
+ }
880
+ function ComboboxEmpty({ className, ...props }) {
881
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComboboxEmpty$1, {
882
+ "data-slot": "combobox-empty",
883
+ className: cn("text-muted-foreground hidden w-full justify-center py-2 text-center text-xs/relaxed group-data-empty/combobox-content:flex", className),
884
+ ...props
885
+ });
886
+ }
887
+ function InsightsControls({ onFiltersChange, scope, releaseScope, appVersions }) {
888
+ const [platform, setPlatform] = (0, import_react.useState)(scope.platform);
889
+ const [channel, setChannel] = (0, import_react.useState)(scope.channel);
890
+ const [appVersion, setAppVersion] = (0, import_react.useState)(scope.appVersion ?? "");
891
+ const [releasePlatform, setReleasePlatform] = (0, import_react.useState)(releaseScope.platform);
892
+ const [releaseId, setReleaseId] = (0, import_react.useState)(releaseScope.releaseId ?? "");
893
+ const versions = [...new Set([...appVersions, ...appVersion ? [appVersion] : []])];
894
+ const channelsQuery = useChannelsQuery();
895
+ const channels = channelsQuery.data?.map((item) => item.name) ?? [];
896
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, {
897
+ className: "shadow-sm",
898
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
899
+ className: "gap-1 border-b px-5 py-4",
900
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, { children: "Filters" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Channel applies to all reports. Other filters affect the report named in their label." })]
901
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, {
902
+ className: "px-5 py-4",
903
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("form", {
904
+ "aria-label": "Insights filters",
905
+ onSubmit: (event) => {
906
+ event.preventDefault();
907
+ onFiltersChange({
908
+ scope: {
909
+ platform,
910
+ channel,
911
+ ...appVersion ? { appVersion } : {}
912
+ },
913
+ releaseScope: {
914
+ platform: releasePlatform,
915
+ channel,
916
+ ...releaseId.trim() ? { releaseId: releaseId.trim() } : {}
917
+ }
918
+ });
919
+ },
920
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(FieldGroup, {
921
+ className: "grid grid-cols-1 items-end gap-4 sm:grid-cols-2 xl:grid-cols-[minmax(12rem,1.25fr)_9rem_minmax(10rem,1fr)_9rem_minmax(12rem,1fr)_auto]",
922
+ children: [
923
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Field, {
924
+ className: "min-w-0",
925
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldLabel, {
926
+ htmlFor: "insights-channel",
927
+ children: "Channel"
928
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Combobox$1, {
929
+ items: channels,
930
+ value: channel,
931
+ onValueChange: (value) => {
932
+ if (value !== null) setChannel(value);
933
+ },
934
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComboboxInput, {
935
+ className: "h-11 w-full sm:h-9 [&_input]:h-full",
936
+ id: "insights-channel",
937
+ placeholder: "Search channels…",
938
+ maxLength: 1024
939
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ComboboxContent, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComboboxEmpty, { children: channelsQuery.isPending ? "Loading channels…" : channelsQuery.isError ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
940
+ className: "flex flex-col items-center gap-2 p-2",
941
+ children: ["Couldn't load channels.", /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
942
+ onClick: () => void channelsQuery.refetch(),
943
+ size: "sm",
944
+ variant: "outline",
945
+ children: "Retry channels"
946
+ })]
947
+ }) : "No channels found." }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComboboxList, { children: (item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComboboxItem, {
948
+ className: "min-h-11 pr-8 sm:min-h-8",
949
+ value: item,
950
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
951
+ className: "truncate",
952
+ children: item
953
+ })
954
+ }, item) })] })]
955
+ })]
956
+ }),
957
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Field, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldLabel, {
958
+ htmlFor: "insights-platform",
959
+ children: "Usage platform"
960
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Select$1, {
961
+ items: {
962
+ all: "All platforms",
963
+ ios: "iOS",
964
+ android: "Android"
965
+ },
966
+ value: platform,
967
+ onValueChange: (value) => {
968
+ if (value === "all" || value === "ios" || value === "android") setPlatform(value);
969
+ },
970
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, {
971
+ id: "insights-platform",
972
+ className: "min-h-11 w-full sm:min-h-9",
973
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectValue, { children: [platform !== "all" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlatformIcon, {
974
+ platform,
975
+ className: "size-3.5"
976
+ }) : null, platform === "all" ? "All platforms" : platform === "ios" ? "iOS" : "Android"] })
977
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectGroup, { children: [
978
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
979
+ value: "all",
980
+ children: "All platforms"
981
+ }),
982
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectItem, {
983
+ value: "ios",
984
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlatformIcon, {
985
+ platform: "ios",
986
+ className: "size-3.5"
987
+ }), "iOS"]
988
+ }),
989
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectItem, {
990
+ value: "android",
991
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlatformIcon, {
992
+ platform: "android",
993
+ className: "size-3.5"
994
+ }), "Android"]
995
+ })
996
+ ] }) })]
997
+ })] }),
998
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Field, {
999
+ className: "min-w-0",
1000
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldLabel, {
1001
+ htmlFor: "insights-app-version",
1002
+ children: "App version"
1003
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Select$1, {
1004
+ items: Object.fromEntries([["all", "All versions"], ...versions.map((version) => [`version:${version}`, version])]),
1005
+ value: appVersion ? `version:${appVersion}` : "all",
1006
+ onValueChange: (value) => {
1007
+ if (value !== null) setAppVersion(value === "all" ? "" : value.slice(8));
1008
+ },
1009
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, {
1010
+ id: "insights-app-version",
1011
+ className: "min-h-11 w-full sm:min-h-9",
1012
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue, {})
1013
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectGroup, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
1014
+ value: "all",
1015
+ children: "All versions"
1016
+ }), versions.map((version) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
1017
+ value: `version:${version}`,
1018
+ children: version
1019
+ }, version))] }) })]
1020
+ })]
1021
+ }),
1022
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Field, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldLabel, {
1023
+ htmlFor: "insights-release-platform",
1024
+ children: "Health platform"
1025
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Select$1, {
1026
+ items: {
1027
+ ios: "iOS",
1028
+ android: "Android"
1029
+ },
1030
+ value: releasePlatform,
1031
+ onValueChange: (value) => {
1032
+ if (value === "ios" || value === "android") setReleasePlatform(value);
1033
+ },
1034
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, {
1035
+ id: "insights-release-platform",
1036
+ className: "min-h-11 w-full sm:min-h-9",
1037
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectValue, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlatformIcon, {
1038
+ platform: releasePlatform,
1039
+ className: "size-3.5"
1040
+ }), releasePlatform === "ios" ? "iOS" : "Android"] })
1041
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectGroup, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectItem, {
1042
+ value: "ios",
1043
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlatformIcon, {
1044
+ platform: "ios",
1045
+ className: "size-3.5"
1046
+ }), "iOS"]
1047
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectItem, {
1048
+ value: "android",
1049
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlatformIcon, {
1050
+ platform: "android",
1051
+ className: "size-3.5"
1052
+ }), "Android"]
1053
+ })] }) })]
1054
+ })] }),
1055
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Field, {
1056
+ className: "min-w-0",
1057
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(FieldLabel, {
1058
+ htmlFor: "insights-release-id",
1059
+ children: [
1060
+ "Release ID",
1061
+ " ",
1062
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1063
+ className: "text-muted-foreground",
1064
+ children: "(optional)"
1065
+ })
1066
+ ]
1067
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
1068
+ autoComplete: "off",
1069
+ className: "h-11 sm:h-9",
1070
+ id: "insights-release-id",
1071
+ maxLength: 1024,
1072
+ name: "releaseId",
1073
+ placeholder: "All releases…",
1074
+ value: releaseId,
1075
+ onChange: (event) => setReleaseId(event.target.value)
1076
+ })]
1077
+ }),
1078
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
1079
+ className: "h-11 w-full sm:h-9",
1080
+ size: "lg",
1081
+ type: "submit",
1082
+ variant: "outline",
1083
+ children: "Apply filters"
1084
+ })
1085
+ ]
1086
+ })
1087
+ })
1088
+ })]
1089
+ });
1090
+ }
1091
+ var HOUR$1 = 36e5;
1092
+ var DAY$1 = 24 * HOUR$1;
1093
+ var times = new Intl.DateTimeFormat("en", {
1094
+ month: "short",
1095
+ day: "numeric",
1096
+ hour: "2-digit",
1097
+ minute: "2-digit",
1098
+ hourCycle: "h23",
1099
+ timeZone: "UTC"
1100
+ });
1101
+ var dates = new Intl.DateTimeFormat("en", {
1102
+ month: "short",
1103
+ day: "numeric",
1104
+ timeZone: "UTC"
1105
+ });
1106
+ var clock = new Intl.DateTimeFormat("en", {
1107
+ hour: "2-digit",
1108
+ minute: "2-digit",
1109
+ hourCycle: "h23",
1110
+ timeZone: "UTC"
1111
+ });
1112
+ /** One hue per compared bundle, in the validated order of `--series-*`. */
1113
+ var SERIES = [
1114
+ "--series-1",
1115
+ "--series-2",
1116
+ "--series-3",
1117
+ "--series-4"
1118
+ ];
1119
+ var formatDeployedAt = (ms) => `${times.format(ms)} UTC`;
1120
+ /**
1121
+ * A color for each bundle that stays with it: a bundle keeps its hue while
1122
+ * others are added or removed, and a new one takes the first free hue.
1123
+ */
1124
+ function useSeriesColors(releaseIds) {
1125
+ const [slots] = (0, import_react.useState)(() => /* @__PURE__ */ new Map());
1126
+ for (const releaseId of slots.keys()) if (!releaseIds.includes(releaseId)) slots.delete(releaseId);
1127
+ for (const releaseId of releaseIds) {
1128
+ if (slots.has(releaseId)) continue;
1129
+ const used = new Set(slots.values());
1130
+ let slot = 0;
1131
+ while (used.has(slot)) slot += 1;
1132
+ slots.set(releaseId, slot);
1133
+ }
1134
+ return (releaseId) => `var(${SERIES[(slots.get(releaseId) ?? 0) % SERIES.length]})`;
1135
+ }
1136
+ /** An interval as the tooltip names it, in UTC. */
1137
+ var spanOf = (startMs, endMs) => dates.format(startMs) === dates.format(endMs) ? `${times.format(startMs)}–${clock.format(endMs)} UTC` : `${times.format(startMs)} – ${times.format(endMs)} UTC`;
1138
+ /**
1139
+ * One row per interval of the period, at the interval's end, with each
1140
+ * bundle's count as `b<index>` and its dashed count, when it has one, as
1141
+ * `d<index>`: none before the interval it was deployed in, and none while
1142
+ * its counts load.
1143
+ */
1144
+ var curveOf = (period, lines) => Array.from({ length: Math.round(period.durationMs / period.intervalMs) }, (_, interval) => {
1145
+ const startMs = period.startMs + interval * period.intervalMs;
1146
+ const endMs = startMs + period.intervalMs;
1147
+ const countOf = (release, points) => points === void 0 || endMs <= release.deployedAtMs ? null : points.find((point) => point.startMs === startMs)?.events ?? 0;
1148
+ return Object.fromEntries([
1149
+ ["startMs", startMs],
1150
+ ["endMs", endMs],
1151
+ ...lines.flatMap((line, index) => [[`b${index}`, countOf(line.release, line.points)], ..."dashed" in line ? [[`d${index}`, countOf(line.release, line.dashed)]] : []])
1152
+ ]);
1153
+ });
1154
+ /**
1155
+ * Ticks on whole UTC times between the first count and the period's end, so
1156
+ * a date names its midnight: every 4 hours of a day, each midnight of a week,
1157
+ * every fifth midnight of a month.
1158
+ */
1159
+ var ticksOf = (period) => {
1160
+ const step = period.intervalMs === HOUR$1 ? 4 * HOUR$1 : period.intervalMs === 6 * HOUR$1 ? DAY$1 : 5 * DAY$1;
1161
+ const ticks = [];
1162
+ for (let at = Math.ceil((period.startMs + period.intervalMs) / step) * step; at < period.endMs; at += step) ticks.push(at);
1163
+ return ticks;
1164
+ };
1165
+ /**
1166
+ * Each bundle's counts per interval of the period on one timeline, with its
1167
+ * deployment marked: a solid line, and a dashed one in its hue when the
1168
+ * bundle has a second count. A count sits at the end of its interval, so a
1169
+ * line starts after its deployment, with the interval it was deployed in.
1170
+ */
1171
+ function ComparedBundlesChart({ period, lines, colorOf, active, label, names }) {
1172
+ const measuredAtMs = Math.max(0, ...lines.map((line) => line.measuredAtMs ?? 0));
1173
+ const curve = curveOf(period, lines);
1174
+ const ticks = ticksOf(period);
1175
+ const tick = (value) => period.intervalMs === HOUR$1 ? times.format(value) : dates.format(value);
1176
+ const labelOf = (release, name) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1177
+ className: "flex items-center gap-1.5 whitespace-nowrap",
1178
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
1179
+ bundleId: release.releaseId,
1180
+ className: "whitespace-nowrap"
1181
+ }), name ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1182
+ className: "text-muted-foreground",
1183
+ children: name
1184
+ }) : null]
1185
+ });
1186
+ const config = Object.fromEntries(lines.flatMap((line, index) => [[`b${index}`, {
1187
+ label: labelOf(line.release, names?.solid),
1188
+ color: colorOf(line.release.releaseId)
1189
+ }], ..."dashed" in line ? [[`d${index}`, {
1190
+ label: labelOf(line.release, names?.dashed),
1191
+ color: colorOf(line.release.releaseId)
1192
+ }]] : []]));
1193
+ const isolated = (key, color, hollow) => ({ cx, cy, index: at }) => {
1194
+ return curve[at]?.[key] == null || cx == null || cy == null || curve[at - 1]?.[key] != null || curve[at + 1]?.[key] != null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("g", {}, `${key}-${at}`) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", {
1195
+ cx,
1196
+ cy,
1197
+ r: 4,
1198
+ fill: hollow ? "var(--background)" : color,
1199
+ stroke: color,
1200
+ strokeWidth: hollow ? 2 : 0
1201
+ }, `${key}-${at}`);
1202
+ };
1203
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChartContainer, {
1204
+ "aria-label": label,
1205
+ className: "h-64 w-full aspect-auto",
1206
+ config,
1207
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(LineChart, {
1208
+ accessibilityLayer: true,
1209
+ data: curve,
1210
+ margin: {
1211
+ left: -12,
1212
+ right: 12,
1213
+ top: 8
1214
+ },
1215
+ children: [
1216
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CartesianGrid, { vertical: false }),
1217
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(XAxis, {
1218
+ dataKey: "endMs",
1219
+ type: "number",
1220
+ domain: ["dataMin", "dataMax"],
1221
+ ticks,
1222
+ tickFormatter: tick,
1223
+ axisLine: false,
1224
+ tickLine: false,
1225
+ minTickGap: 32
1226
+ }),
1227
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(YAxis, {
1228
+ allowDecimals: false,
1229
+ axisLine: false,
1230
+ tickLine: false
1231
+ }),
1232
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChartTooltip, { content: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChartTooltipContent, { labelFormatter: (_, payload) => {
1233
+ const point = payload[0]?.payload;
1234
+ return point ? `${spanOf(point.startMs, point.endMs)}${point.endMs > measuredAtMs ? " · In progress" : ""}` : "";
1235
+ } }) }),
1236
+ lines.map(({ release }) => release.deployedAtMs >= period.startMs ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReferenceLine, {
1237
+ x: release.deployedAtMs,
1238
+ stroke: colorOf(release.releaseId),
1239
+ strokeOpacity: .5,
1240
+ ifOverflow: "extendDomain"
1241
+ }, `deployed-${release.releaseId}`) : null),
1242
+ lines.flatMap((line, index) => [..."dashed" in line ? ["d"] : [], "b"].map((kind) => {
1243
+ const { releaseId } = line.release;
1244
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Line, {
1245
+ dataKey: `${kind}${index}`,
1246
+ type: "linear",
1247
+ stroke: `var(--color-${kind}${index})`,
1248
+ strokeDasharray: kind === "d" ? "6 4" : void 0,
1249
+ strokeWidth: active === releaseId ? 3 : 2,
1250
+ strokeOpacity: active === null || active === releaseId ? 1 : .25,
1251
+ dot: isolated(`${kind}${index}`, colorOf(releaseId), kind === "d"),
1252
+ activeDot: { r: 4 },
1253
+ connectNulls: false,
1254
+ isAnimationActive: false
1255
+ }, `${kind}-${releaseId}`);
1256
+ }))
1257
+ ]
1258
+ })
1259
+ });
1260
+ }
1261
+ var percent$1 = (rate) => `${(rate * 100).toFixed(1)}%`;
1262
+ /**
1263
+ * Rolls a crashing bundle back the way its editor does: disabling its
1264
+ * release sends installations running it back to the previous enabled
1265
+ * release on their next update check.
1266
+ */
1267
+ function RollbackButton({ release, rate, crashes, attempts }) {
1268
+ const [open, setOpen] = (0, import_react.useState)(false);
1269
+ const [error, setError] = (0, import_react.useState)("");
1270
+ const queryClient = useQueryClient();
1271
+ const preflight = usePreflightReleaseMutation();
1272
+ const update = useUpdateReleaseMutation();
1273
+ const busy = preflight.isPending || update.isPending;
1274
+ const id = release.releaseId;
1275
+ const rollBack = async () => {
1276
+ const input = {
1277
+ expectedRevision: release.revision,
1278
+ patch: { enabled: false },
1279
+ releaseId: release.releaseId
1280
+ };
1281
+ setError("");
1282
+ try {
1283
+ await preflight.mutateAsync(input);
1284
+ await update.mutateAsync(input);
1285
+ await queryClient.invalidateQueries({ queryKey: ["insights", "adoption-releases"] });
1286
+ toast.success(`${id} rolled back`, { description: "Installations running it return to the previous enabled bundle on their next update check." });
1287
+ setOpen(false);
1288
+ } catch (caught) {
1289
+ setError(caught instanceof Error ? caught.message : "The bundle could not be rolled back.");
1290
+ }
1291
+ };
1292
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialog$1, {
1293
+ open,
1294
+ onOpenChange: (next) => {
1295
+ if (busy) return;
1296
+ setOpen(next);
1297
+ if (!next) setError("");
1298
+ },
1299
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
1300
+ variant: "destructive",
1301
+ size: "sm",
1302
+ onClick: () => setOpen(true),
1303
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Undo2, {
1304
+ "aria-hidden": "true",
1305
+ "data-icon": "inline-start"
1306
+ }), "Roll back"]
1307
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogContent, { children: [
1308
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogHeader, { children: [
1309
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogTitle, { children: "Roll back this bundle?" }),
1310
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, { bundleId: id }),
1311
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogDescription, { children: [
1312
+ "It crashed for ",
1313
+ percent$1(rate),
1314
+ " of the installations that tried it (",
1315
+ crashes.toLocaleString(),
1316
+ " of ",
1317
+ attempts.toLocaleString(),
1318
+ "). Rolling back disables it, so installations running it return to the previous enabled bundle on their next update check. You can enable it again in its bundle details."
1319
+ ] })
1320
+ ] }),
1321
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1322
+ role: "alert",
1323
+ className: "text-sm text-destructive",
1324
+ children: error
1325
+ }) : null,
1326
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogFooter, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogCancel, {
1327
+ disabled: busy,
1328
+ children: "Cancel"
1329
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogAction, {
1330
+ variant: "destructive",
1331
+ disabled: busy,
1332
+ onClick: () => void rollBack(),
1333
+ children: busy ? "Rolling back…" : "Roll back"
1334
+ })] })
1335
+ ] })]
1336
+ });
1337
+ }
1338
+ var HOUR = 36e5;
1339
+ var DAY = 24 * HOUR;
1340
+ var percent = (rate) => `${(rate * 100).toFixed(1)}%`;
1341
+ var lengthOf = (durationMs) => durationMs > DAY ? `${durationMs / DAY} days` : `${durationMs / HOUR} hours`;
1342
+ var cadenceOf = (intervalMs) => intervalMs === HOUR ? "Hourly" : intervalMs === DAY ? "Daily" : `Every ${intervalMs / HOUR} hours`;
1343
+ /** A bundle's crash numbers over the period, once both counts are read. */
1344
+ var crashesOf = ({ release, launched, recovered }) => launched === void 0 || recovered === void 0 ? void 0 : {
1345
+ crashes: recovered.total,
1346
+ ...crashRateOf(launched.total, recovered.total),
1347
+ recommended: recommendsRollback(release, launched.total, recovered.total)
1348
+ };
1349
+ /** A line sample for the legend: solid or dashed, in the text color. */
1350
+ function LineSample({ dashed }) {
1351
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
1352
+ "aria-hidden": "true",
1353
+ className: "h-3 w-6 shrink-0",
1354
+ viewBox: "0 0 24 12",
1355
+ preserveAspectRatio: "none",
1356
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", {
1357
+ x1: "0",
1358
+ y1: "6",
1359
+ x2: "24",
1360
+ y2: "6",
1361
+ stroke: "currentColor",
1362
+ strokeWidth: "2",
1363
+ strokeDasharray: dashed ? "6 4" : void 0
1364
+ })
1365
+ });
1366
+ }
1367
+ /**
1368
+ * Release health: how the newest bundles fare after their deployment, on
1369
+ * one timeline, per interval. Adoption shows installations downloading each
1370
+ * bundle (dashed) and launching it (solid), so the gap is the restarts still
1371
+ * to come; Crashes shows them crashing back from it, and offers a rollback
1372
+ * when a bundle crashes for too many of them.
1373
+ */
1374
+ function InsightsOverview({ input, chart, onChartChange, health, onReleasesChange, onWindowChange }) {
1375
+ const [active, setActive] = (0, import_react.useState)(null);
1376
+ const { period, candidates, releases } = health;
1377
+ const ids = (releases ?? []).map(({ release }) => release.releaseId);
1378
+ const colorOf = useSeriesColors(ids);
1379
+ const addable = (candidates ?? []).filter((candidate) => !ids.includes(candidate.releaseId));
1380
+ const full = ids.length >= 4;
1381
+ const wider = period.durationMs < 7 * DAY ? "7d" : period.durationMs < 30 * DAY ? "30d" : null;
1382
+ const reset = health.isDefault ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
1383
+ variant: "ghost",
1384
+ size: "sm",
1385
+ onClick: () => onReleasesChange(void 0),
1386
+ children: [
1387
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RotateCcw, {
1388
+ "aria-hidden": "true",
1389
+ "data-icon": "inline-start"
1390
+ }),
1391
+ "Show the newest ",
1392
+ 2
1393
+ ]
1394
+ });
1395
+ const seriesOf = (bundle) => chart === "adoption" ? bundle.launched : bundle.recovered;
1396
+ const loaded = (releases ?? []).filter((bundle) => seriesOf(bundle) !== void 0 && (chart !== "adoption" || bundle.downloaded !== void 0));
1397
+ const failed = (releases ?? []).find(({ error }) => error !== null)?.error ?? null;
1398
+ const flagged = (releases ?? []).flatMap((bundle) => {
1399
+ const crashes = crashesOf(bundle);
1400
+ return crashes?.recommended ? [{
1401
+ ...bundle,
1402
+ ...crashes
1403
+ }] : [];
1404
+ });
1405
+ const remove = (releaseId) => {
1406
+ const rest = ids.filter((id) => id !== releaseId);
1407
+ onReleasesChange(rest.length === 0 ? void 0 : rest);
1408
+ };
1409
+ const heading = chart === "adoption" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1410
+ className: "flex flex-col gap-1",
1411
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1412
+ className: "flex items-center gap-1 text-sm font-medium",
1413
+ children: ["Installations downloading and launching each bundle", /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InsightsInfo, {
1414
+ label: "About adoption",
1415
+ children: "Dashed: installations that downloaded the bundle. Solid: installations that launched it. After a forced update the two lines meet."
1416
+ })]
1417
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1418
+ className: "flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground",
1419
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: [
1420
+ cadenceOf(period.intervalMs),
1421
+ " · last ",
1422
+ lengthOf(period.durationMs),
1423
+ " ",
1424
+ "· UTC"
1425
+ ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", {
1426
+ "aria-label": "Line styles",
1427
+ className: "flex items-center gap-3",
1428
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", {
1429
+ className: "flex items-center gap-1.5",
1430
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(LineSample, { dashed: true }), "Downloaded"]
1431
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", {
1432
+ className: "flex items-center gap-1.5",
1433
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(LineSample, {}), "Launched"]
1434
+ })]
1435
+ })]
1436
+ })]
1437
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1438
+ className: "flex flex-col gap-1",
1439
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1440
+ className: "flex items-center gap-1 text-sm font-medium",
1441
+ children: ["Crashes after each deployment", /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(InsightsInfo, {
1442
+ label: "About crashes",
1443
+ children: [
1444
+ "Installations that crashed on a bundle and rolled back. Crash rate = crashed ÷ (launched + crashed). A rollback is suggested at",
1445
+ " ",
1446
+ percent(CRASH_RATE_THRESHOLD),
1447
+ " once ",
1448
+ 20,
1449
+ " ",
1450
+ "installations tried it."
1451
+ ]
1452
+ })]
1453
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
1454
+ className: "text-xs text-muted-foreground",
1455
+ children: [
1456
+ cadenceOf(period.intervalMs),
1457
+ " · last ",
1458
+ lengthOf(period.durationMs),
1459
+ " · UTC"
1460
+ ]
1461
+ })]
1462
+ });
1463
+ const body = (() => {
1464
+ if (releases === void 0) return health.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InsightsErrorAlert, {
1465
+ error: health.error,
1466
+ fallbackTitle: "Release health unavailable"
1467
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, {
1468
+ "aria-label": "Loading release health",
1469
+ className: "h-64"
1470
+ });
1471
+ if (releases.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Empty, {
1472
+ className: "min-h-64",
1473
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(EmptyHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyTitle, { children: "No bundles deployed here yet" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyDescription, { children: "Release health follows the bundle deployments of the selected channel and health platform. Deploy one, or choose another in the filters." })] }), reset ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyContent, { children: reset }) : null]
1474
+ });
1475
+ if (loaded.length === 0) return failed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InsightsErrorAlert, {
1476
+ error: failed,
1477
+ fallbackTitle: "Release health unavailable"
1478
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, {
1479
+ "aria-label": "Loading release health",
1480
+ className: "h-64"
1481
+ });
1482
+ if (loaded.every((bundle) => seriesOf(bundle).total === 0 && (chart !== "adoption" || bundle.downloaded.total === 0))) return chart === "adoption" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Empty, {
1483
+ className: "min-h-48",
1484
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(EmptyHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(EmptyTitle, { children: [
1485
+ "No installation downloaded or launched these bundles in the last",
1486
+ " ",
1487
+ lengthOf(period.durationMs)
1488
+ ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyDescription, { children: wider ? "Choose a longer period, or refresh after apps download them." : "Refresh after apps download them." })] }), wider ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
1489
+ variant: "outline",
1490
+ size: "sm",
1491
+ onClick: () => onWindowChange(wider),
1492
+ children: ["Show ", lengthOf(wider === "7d" ? 7 * DAY : 30 * DAY)]
1493
+ }) }) : null]
1494
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Empty, {
1495
+ className: "min-h-48",
1496
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(EmptyHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(EmptyTitle, { children: ["No crashes in the last ", lengthOf(period.durationMs)] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyDescription, { children: "No launch of these bundles crashed and recovered." })] })
1497
+ });
1498
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComparedBundlesChart, {
1499
+ period,
1500
+ lines: releases.map((bundle) => ({
1501
+ release: bundle.release,
1502
+ points: seriesOf(bundle)?.points,
1503
+ ...chart === "adoption" ? { dashed: bundle.downloaded?.points } : {},
1504
+ measuredAtMs: seriesOf(bundle)?.measuredAtMs
1505
+ })),
1506
+ colorOf,
1507
+ active,
1508
+ names: chart === "adoption" ? {
1509
+ solid: "Launched",
1510
+ dashed: "Downloaded"
1511
+ } : { solid: "Crashed" },
1512
+ label: chart === "adoption" ? "Installations downloading (dashed) and launching (solid) each bundle per interval" : "Crashes of each bundle per interval"
1513
+ });
1514
+ })();
1515
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("section", {
1516
+ "aria-label": "Release health",
1517
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, {
1518
+ className: "min-w-0 overflow-hidden shadow-sm",
1519
+ children: [
1520
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardHeader, {
1521
+ className: "flex flex-col gap-4 p-6",
1522
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1523
+ className: "flex flex-wrap items-center justify-between gap-4",
1524
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, { children: "Release health" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1525
+ className: "flex items-center gap-2",
1526
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(InsightsPeriodSelector, {
1527
+ window: input.window,
1528
+ onWindowChange
1529
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
1530
+ "aria-label": "Refresh release health",
1531
+ variant: "ghost",
1532
+ size: "icon-lg",
1533
+ disabled: health.isFetching,
1534
+ onClick: health.refresh,
1535
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RotateCw, {
1536
+ "aria-hidden": "true",
1537
+ className: cn(health.isFetching && "motion-safe:animate-spin")
1538
+ })
1539
+ })]
1540
+ })]
1541
+ })
1542
+ }),
1543
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardContent, {
1544
+ "aria-busy": health.isFetching,
1545
+ className: "flex flex-col gap-5 px-6 pb-6",
1546
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Tabs$1, {
1547
+ value: chart,
1548
+ onValueChange: (value) => onChartChange(value),
1549
+ className: "gap-5",
1550
+ children: [
1551
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1552
+ className: "flex flex-wrap items-center justify-between gap-3",
1553
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TabsList, {
1554
+ "aria-label": "Release health chart",
1555
+ className: "min-h-11 sm:min-h-9",
1556
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabsTrigger, {
1557
+ value: "adoption",
1558
+ className: "px-3",
1559
+ children: "Adoption"
1560
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TabsTrigger, {
1561
+ value: "crashes",
1562
+ className: "px-3",
1563
+ children: ["Crashes", flagged.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriangleAlert, {
1564
+ "aria-hidden": "true",
1565
+ className: "text-destructive"
1566
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1567
+ className: "sr-only",
1568
+ children: ", rollback recommended"
1569
+ })] }) : null]
1570
+ })]
1571
+ }), reset]
1572
+ }),
1573
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TabsContent, {
1574
+ value: "adoption",
1575
+ className: "flex flex-col gap-4",
1576
+ children: [heading, chart === "adoption" ? body : null]
1577
+ }),
1578
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TabsContent, {
1579
+ value: "crashes",
1580
+ className: "flex flex-col gap-4",
1581
+ children: [
1582
+ flagged.map((bundle) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert, {
1583
+ variant: "destructive",
1584
+ children: [
1585
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriangleAlert, { "aria-hidden": "true" }),
1586
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertTitle, { children: [
1587
+ "Roll back",
1588
+ " ",
1589
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
1590
+ bundleId: bundle.release.releaseId,
1591
+ className: "text-sm"
1592
+ })
1593
+ ] }),
1594
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDescription, {
1595
+ className: "flex flex-col items-start gap-3",
1596
+ children: [
1597
+ "It crashed for ",
1598
+ percent(bundle.rate),
1599
+ " of the installations that tried it (",
1600
+ bundle.crashes.toLocaleString(),
1601
+ " of",
1602
+ " ",
1603
+ bundle.attempts.toLocaleString(),
1604
+ "), above",
1605
+ " ",
1606
+ percent(CRASH_RATE_THRESHOLD),
1607
+ ".",
1608
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RollbackButton, {
1609
+ release: bundle.release,
1610
+ rate: bundle.rate,
1611
+ crashes: bundle.crashes,
1612
+ attempts: bundle.attempts
1613
+ })
1614
+ ]
1615
+ })
1616
+ ]
1617
+ }, bundle.release.releaseId)),
1618
+ heading,
1619
+ chart === "crashes" ? body : null
1620
+ ]
1621
+ })
1622
+ ]
1623
+ }), releases !== void 0 && releases.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1624
+ failed && loaded.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InsightsErrorAlert, {
1625
+ error: failed,
1626
+ fallbackTitle: "Some bundles could not be read"
1627
+ }) : null,
1628
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Table, {
1629
+ "aria-label": "Compared bundles",
1630
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, { children: [
1631
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Bundle" }),
1632
+ chart === "adoption" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
1633
+ className: "text-right",
1634
+ children: "Downloaded"
1635
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
1636
+ className: "text-right",
1637
+ children: "Launched"
1638
+ })] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
1639
+ className: "text-right",
1640
+ children: "Crashed"
1641
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
1642
+ className: "text-right",
1643
+ children: "Crash rate"
1644
+ })] }),
1645
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
1646
+ className: "w-10",
1647
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1648
+ className: "sr-only",
1649
+ children: "Remove"
1650
+ })
1651
+ })
1652
+ ] }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableBody, { children: releases.map((bundle) => {
1653
+ const { release } = bundle;
1654
+ const crashes = crashesOf(bundle);
1655
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, {
1656
+ "data-state": active === release.releaseId ? "selected" : void 0,
1657
+ onMouseEnter: () => setActive(release.releaseId),
1658
+ onMouseLeave: () => setActive(null),
1659
+ onFocus: () => setActive(release.releaseId),
1660
+ onBlur: () => setActive(null),
1661
+ children: [
1662
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
1663
+ className: "w-full max-w-0 whitespace-normal",
1664
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1665
+ className: "flex min-w-0 items-center gap-2 sm:gap-3",
1666
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
1667
+ "aria-hidden": "true",
1668
+ className: "h-3 w-4 shrink-0 sm:w-6",
1669
+ viewBox: "0 0 24 12",
1670
+ preserveAspectRatio: "none",
1671
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", {
1672
+ x1: "0",
1673
+ y1: "6",
1674
+ x2: "24",
1675
+ y2: "6",
1676
+ stroke: colorOf(release.releaseId),
1677
+ strokeWidth: "3"
1678
+ })
1679
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1680
+ className: "flex min-w-0 flex-col",
1681
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1682
+ className: "flex min-w-0 items-center gap-2",
1683
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
1684
+ bundleId: release.releaseId,
1685
+ className: "min-w-0"
1686
+ }), release.enabled ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
1687
+ variant: "outline",
1688
+ children: "Disabled"
1689
+ })]
1690
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1691
+ className: "text-xs break-words text-muted-foreground",
1692
+ children: [
1693
+ "Deployed",
1694
+ " ",
1695
+ formatDeployedAt(release.deployedAtMs),
1696
+ release.targetAppVersion ? ` · ${release.targetAppVersion}` : ""
1697
+ ]
1698
+ })]
1699
+ })]
1700
+ })
1701
+ }),
1702
+ chart === "adoption" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
1703
+ className: "text-right whitespace-nowrap tabular-nums",
1704
+ children: bundle.downloaded?.total.toLocaleString() ?? "—"
1705
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
1706
+ className: "text-right font-medium whitespace-nowrap tabular-nums",
1707
+ children: bundle.launched?.total.toLocaleString() ?? "—"
1708
+ })] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
1709
+ className: "text-right whitespace-nowrap tabular-nums",
1710
+ children: crashes === void 0 ? "—" : crashes.crashes === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1711
+ className: "text-muted-foreground",
1712
+ children: "0"
1713
+ }) : crashes.crashes.toLocaleString()
1714
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
1715
+ className: "text-right whitespace-nowrap tabular-nums",
1716
+ children: crashes === void 0 || crashes.attempts === 0 ? "—" : crashes.recommended ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1717
+ className: "inline-flex items-center gap-1 font-medium text-destructive",
1718
+ children: [
1719
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriangleAlert, {
1720
+ "aria-hidden": "true",
1721
+ className: "size-3.5"
1722
+ }),
1723
+ percent(crashes.rate),
1724
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1725
+ className: "sr-only",
1726
+ children: ", rollback recommended"
1727
+ })
1728
+ ]
1729
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1730
+ className: cn(crashes.attempts < 20 && "text-muted-foreground"),
1731
+ title: crashes.attempts < 20 ? `Only ${crashes.attempts} installations tried it` : void 0,
1732
+ children: percent(crashes.rate)
1733
+ })
1734
+ })] }),
1735
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
1736
+ "aria-label": `Remove ${release.releaseId}`,
1737
+ variant: "ghost",
1738
+ size: "icon-xs",
1739
+ onClick: () => remove(release.releaseId),
1740
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { "aria-hidden": "true" })
1741
+ }) })
1742
+ ]
1743
+ }, release.releaseId);
1744
+ }) })]
1745
+ }),
1746
+ addable.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Select$1, {
1747
+ items: Object.fromEntries(addable.map((candidate) => [candidate.releaseId, `${candidate.releaseId} · ${formatDeployedAt(candidate.deployedAtMs)}`])),
1748
+ value: null,
1749
+ disabled: full,
1750
+ onValueChange: (value) => {
1751
+ if (value !== null) onReleasesChange([...ids, value]);
1752
+ },
1753
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, {
1754
+ "aria-label": "Add a bundle",
1755
+ className: "min-h-11 w-full sm:min-h-9 sm:w-80",
1756
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue, { placeholder: full ? `Up to 4 bundles` : "Add a bundle" })
1757
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectGroup, { children: addable.map((candidate) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
1758
+ value: candidate.releaseId,
1759
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1760
+ className: "flex min-w-0 flex-col gap-0.5",
1761
+ children: [
1762
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, { bundleId: candidate.releaseId }),
1763
+ " ",
1764
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1765
+ className: "text-xs text-muted-foreground",
1766
+ children: [
1767
+ "Deployed",
1768
+ " ",
1769
+ formatDeployedAt(candidate.deployedAtMs)
1770
+ ]
1771
+ })
1772
+ ]
1773
+ })
1774
+ }, candidate.releaseId)) }) })]
1775
+ }) : null
1776
+ ] }) : null]
1777
+ }),
1778
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardFooter, {
1779
+ className: "flex items-center justify-end border-t px-6 py-5",
1780
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
1781
+ className: buttonVariants({
1782
+ variant: "outline",
1783
+ size: "sm"
1784
+ }),
1785
+ to: "/installations",
1786
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(List, { "aria-hidden": "true" }), "Event history"]
1787
+ })
1788
+ })
1789
+ ]
1790
+ })
1791
+ });
1792
+ }
1793
+ var share = (events, total) => total === 0 ? "—" : `${(events / total * 100).toFixed(1)}%`;
1794
+ /**
1795
+ * One kind of failure: its rate as the headline, then how many failures and
1796
+ * installations it holds.
1797
+ */
1798
+ function FailureSummary({ id, title, info, rate, of, failures, installations, previous, children }) {
1799
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
1800
+ "aria-labelledby": id,
1801
+ className: "flex min-w-0 flex-col gap-1",
1802
+ children: [
1803
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", {
1804
+ id,
1805
+ className: "flex min-h-11 items-center gap-1 text-sm font-medium sm:min-h-7",
1806
+ children: [title, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InsightsInfo, {
1807
+ label: `About ${title.toLowerCase()}`,
1808
+ children: info
1809
+ })]
1810
+ }),
1811
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1812
+ className: "text-3xl font-semibold tracking-tight tabular-nums",
1813
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Rate, { value: rate })
1814
+ }),
1815
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1816
+ className: "text-xs text-muted-foreground",
1817
+ children: of
1818
+ }),
1819
+ previous ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RateChange, {
1820
+ value: rate,
1821
+ previous: previous.rate,
1822
+ complete: previous.complete
1823
+ }) : null,
1824
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
1825
+ className: "mt-2 text-sm text-muted-foreground tabular-nums",
1826
+ children: [
1827
+ failures.toLocaleString(),
1828
+ " ",
1829
+ failures === 1 ? "failure" : "failures",
1830
+ " · ",
1831
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EstimatedCount, { value: installations }),
1832
+ " ",
1833
+ installations === 1 ? "installation" : "installations"
1834
+ ]
1835
+ }),
1836
+ children
1837
+ ]
1838
+ });
1839
+ }
1840
+ function Rate({ value }) {
1841
+ const formatted = formatRate(value);
1842
+ return value === null ? formatted : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [formatted.slice(0, -1), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1843
+ className: "ml-0.5 text-sm font-normal text-muted-foreground",
1844
+ children: "%"
1845
+ })] });
1846
+ }
1847
+ function RateChange({ value, previous, complete }) {
1848
+ if (!complete || value === null || previous === null) return null;
1849
+ const difference = (value - previous) * 100;
1850
+ const rounded = Number(difference.toFixed(2));
1851
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1852
+ className: cn("mt-1 block text-xs font-normal", rounded > 0 ? "text-warning" : "text-muted-foreground"),
1853
+ children: [
1854
+ rounded === 0 ? "Unchanged" : `${rounded > 0 ? "↑ +" : "↓ "}${rounded.toFixed(2)} pp`,
1855
+ " ",
1856
+ "vs previous period"
1857
+ ]
1858
+ });
1859
+ }
1860
+ /** One stage and reason, and what else its clients knew, most first. */
1861
+ function FailureRow({ failure, total }) {
1862
+ const label = `${failureStageLabel(failure.stage)} · ${failureReasonLabel(failure.reason)}`;
1863
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, {
1864
+ className: "[&>td]:px-2 [&>td]:py-3 [&>td]:align-top sm:[&>td]:px-4",
1865
+ children: [
1866
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
1867
+ className: "whitespace-normal",
1868
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", {
1869
+ className: "group/failure",
1870
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("summary", {
1871
+ "aria-label": label,
1872
+ className: "flex min-h-11 w-fit max-w-full cursor-pointer list-none items-center gap-3 rounded-md outline-none focus-visible:ring-2 focus-visible:ring-ring [&::-webkit-details-marker]:hidden",
1873
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, {
1874
+ "aria-hidden": "true",
1875
+ className: "size-4 shrink-0 text-muted-foreground group-open/failure:rotate-90"
1876
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1877
+ className: "flex min-w-0 flex-col gap-1",
1878
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1879
+ className: "text-xs text-muted-foreground",
1880
+ children: failureStageLabel(failure.stage)
1881
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1882
+ className: "text-sm font-medium wrap-anywhere",
1883
+ children: failureReasonLabel(failure.reason)
1884
+ })]
1885
+ })]
1886
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
1887
+ "aria-label": `${label} details`,
1888
+ className: "mt-3 flex flex-col gap-3 rounded-md border bg-muted/20 p-3 text-sm",
1889
+ children: failure.details.map((detail) => {
1890
+ const parts = failureDetailParts(detail);
1891
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", {
1892
+ className: "flex justify-between gap-4",
1893
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1894
+ className: "wrap-anywhere",
1895
+ children: parts.length === 0 ? "No details reported" : parts.join(" · ")
1896
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1897
+ className: "shrink-0 font-medium tabular-nums",
1898
+ children: detail.events.toLocaleString()
1899
+ })]
1900
+ }, JSON.stringify(detail));
1901
+ })
1902
+ })]
1903
+ })
1904
+ }),
1905
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
1906
+ className: "text-right tabular-nums",
1907
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1908
+ className: "inline-flex min-h-11 items-center text-sm font-medium",
1909
+ children: failure.events.toLocaleString()
1910
+ })
1911
+ }),
1912
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
1913
+ className: "text-right tabular-nums text-muted-foreground",
1914
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1915
+ className: "inline-flex min-h-11 items-center text-sm",
1916
+ children: share(failure.events, total)
1917
+ })
1918
+ })
1919
+ ]
1920
+ });
1921
+ }
1922
+ function FailuresReport({ report, errors }) {
1923
+ const breakdown = report.breakdown ?? [];
1924
+ const total = breakdown.reduce((sum, { events }) => sum + events, 0);
1925
+ const unreasoned = breakdown.filter(({ reason }) => reason === "unknown").reduce((sum, { events }) => sum + events, 0);
1926
+ const patchAttempts = report.patchDownloads + report.patchFallbacks;
1927
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1928
+ className: "flex flex-col gap-8",
1929
+ children: [
1930
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1931
+ className: cn("grid gap-6", report.checks && "sm:grid-cols-2"),
1932
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FailureSummary, {
1933
+ id: "download-install-failures",
1934
+ title: "Downloads & installs",
1935
+ info: "Failed downloads and installs ÷ those plus successful downloads.",
1936
+ rate: failureRate(report),
1937
+ of: "of update attempts failed",
1938
+ failures: report.failedUpdates,
1939
+ installations: report.failedInstallations,
1940
+ previous: report.previous && {
1941
+ rate: report.previous.attemptRate,
1942
+ complete: report.previous.complete
1943
+ },
1944
+ children: patchAttempts > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
1945
+ className: "text-sm text-muted-foreground tabular-nums",
1946
+ children: [
1947
+ report.patchFallbacks.toLocaleString(),
1948
+ " of",
1949
+ " ",
1950
+ patchAttempts.toLocaleString(),
1951
+ " patch downloads fell back to the full archive"
1952
+ ]
1953
+ }) : null
1954
+ }), report.checks ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FailureSummary, {
1955
+ id: "update-check-failures",
1956
+ title: "Update checks",
1957
+ info: "Installations whose update check failed ÷ active installations. A check that fails offline isn't reported.",
1958
+ rate: checkFailureRate(report.checks),
1959
+ of: "of active installations had a check fail",
1960
+ failures: report.checks.failures,
1961
+ installations: report.checks.failedInstallations,
1962
+ previous: report.previous && {
1963
+ rate: report.previous.checkRate,
1964
+ complete: report.previous.complete
1965
+ }
1966
+ }) : null]
1967
+ }),
1968
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Separator$1, {}),
1969
+ errors ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [errors, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Separator$1, {})] }) : null,
1970
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
1971
+ "aria-labelledby": "failures-by-stage",
1972
+ className: "flex flex-col gap-3",
1973
+ children: [
1974
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
1975
+ className: "text-sm font-medium",
1976
+ id: "failures-by-stage",
1977
+ children: "Failures by stage and reason"
1978
+ }),
1979
+ unreasoned * 2 > total ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1980
+ className: "text-sm text-muted-foreground",
1981
+ children: "Most report no reason: an SDK that doesn't classify failures reports every failed check, offline ones included."
1982
+ }) : null,
1983
+ breakdown.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1984
+ className: "text-sm text-muted-foreground",
1985
+ children: "No update failures in this period."
1986
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Table, {
1987
+ "aria-label": "Failures by stage and reason",
1988
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, {
1989
+ className: "[&>th]:px-2 sm:[&>th]:px-4",
1990
+ children: [
1991
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Stage / reason" }),
1992
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
1993
+ className: "w-16 text-right sm:w-24",
1994
+ children: "Reports"
1995
+ }),
1996
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
1997
+ className: "w-16 text-right sm:w-24",
1998
+ children: "Share"
1999
+ })
2000
+ ]
2001
+ }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableBody, { children: breakdown.map((failure) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FailureRow, {
2002
+ failure,
2003
+ total
2004
+ }, `${failure.stage} ${failure.reason}`)) })]
2005
+ })
2006
+ ]
2007
+ })
2008
+ ]
2009
+ });
2010
+ }
2011
+ /**
2012
+ * Update failures for Release health's scope and period: counts, rates, and
2013
+ * the stage and reason breakdown with what else the clients knew.
2014
+ */
2015
+ function UpdateFailures({ errors, query, onRefresh }) {
2016
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("section", {
2017
+ "aria-label": "Update failures",
2018
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, {
2019
+ className: "min-w-0 overflow-hidden shadow-sm",
2020
+ children: [
2021
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
2022
+ className: "flex flex-col gap-1.5 p-4 sm:p-6",
2023
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2024
+ className: "flex flex-wrap items-center justify-between gap-4",
2025
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, { children: "Update failures" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
2026
+ "aria-label": "Refresh update failures",
2027
+ variant: "ghost",
2028
+ size: "icon-lg",
2029
+ disabled: query.isFetching,
2030
+ onClick: onRefresh,
2031
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RotateCw, { "aria-hidden": "true" })
2032
+ })]
2033
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Track failure rates, inspect original errors, and open individual reports in the selected Release health scope and period." })]
2034
+ }),
2035
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, {
2036
+ className: "px-4 pb-4 sm:px-6 sm:pb-6",
2037
+ children: query.isPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, {
2038
+ "aria-label": "Loading update failures",
2039
+ className: "h-64"
2040
+ }) : query.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InsightsErrorAlert, {
2041
+ error: query.error,
2042
+ fallbackTitle: "Update failures unavailable"
2043
+ }) : query.data ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FailuresReport, {
2044
+ report: query.data,
2045
+ errors
2046
+ }) : null
2047
+ }),
2048
+ query.data?.coverage.kind === "partial" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardFooter, {
2049
+ className: "border-t px-6 py-5",
2050
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2051
+ className: "text-xs text-muted-foreground",
2052
+ children: "Partial"
2053
+ })
2054
+ }) : null
2055
+ ]
2056
+ })
2057
+ });
2058
+ }
2059
+ var listAdoptionReleasesRpc = createServerFn({ method: "GET" }).middleware([consoleAccess]).validator(readAdoptionReleasesInput).handler(createSsrRpc("d11a48f7251928b7819e2376aed4902fa66277bf6d30312d87ca53468332f256"));
2060
+ var getAdoptionReleaseRpc = createServerFn({ method: "GET" }).middleware([consoleAccess]).validator(readAdoptionReleaseInput).handler(createSsrRpc("16e3be74610cddd54e5e894419dca16817a7b63c09a4eed15042cdac9c39e1e0"));
2061
+ var getBundleEventsRpc = createServerFn({ method: "GET" }).middleware([consoleAccess]).validator(readBundleEventsInput).handler(createSsrRpc("bfcca2365dfef4d071bdcddf97538a2637482c77bb747969dbdcd6769ca98416"));
2062
+ /** What each Release health chart reads per bundle. */
2063
+ var READS = {
2064
+ adoption: ["DOWNLOADED", "LAUNCHED"],
2065
+ crashes: ["LAUNCHED", "RECOVERED"]
2066
+ };
2067
+ /**
2068
+ * Release health's reads, each keyed by what it depends on: the newest
2069
+ * deployments by channel and platform, kept five minutes; a bundle the
2070
+ * newest leave out, kept; and one count series per bundle and report type,
2071
+ * so adding a bundle or opening the other chart reads only what is new.
2072
+ */
2073
+ function useReleaseHealth({ platform, channel, window, releaseIds, focusReleaseId, chart }) {
2074
+ const { durationMs, intervalMs } = recoveryWindows[window];
2075
+ const endMs = insightsPeriodEnd(Date.now());
2076
+ const scope = {
2077
+ platform,
2078
+ channel
2079
+ };
2080
+ const candidatesQuery = useQuery({
2081
+ queryKey: [
2082
+ "insights",
2083
+ "adoption-releases",
2084
+ scope
2085
+ ],
2086
+ queryFn: () => listAdoptionReleasesRpc({ data: scope }),
2087
+ staleTime: 5 * 6e4
2088
+ });
2089
+ const candidates = candidatesQuery.data;
2090
+ const focusIndex = focusReleaseId === void 0 || candidates === void 0 ? -1 : candidates.findIndex((release) => release.releaseId === focusReleaseId);
2091
+ const focusQuery = useQuery({
2092
+ queryKey: [
2093
+ "insights",
2094
+ "adoption-release",
2095
+ scope,
2096
+ focusReleaseId,
2097
+ true
2098
+ ],
2099
+ queryFn: () => getAdoptionReleaseRpc({ data: {
2100
+ ...scope,
2101
+ releaseId: focusReleaseId,
2102
+ withPrevious: true
2103
+ } }),
2104
+ enabled: releaseIds === void 0 && focusReleaseId !== void 0 && candidates !== void 0 && (focusIndex === -1 || focusIndex === candidates.length - 1),
2105
+ staleTime: Number.POSITIVE_INFINITY
2106
+ });
2107
+ const known = /* @__PURE__ */ new Map();
2108
+ for (const release of [
2109
+ ...focusQuery.data?.release ? [focusQuery.data.release] : [],
2110
+ ...focusQuery.data?.previous ? [focusQuery.data.previous] : [],
2111
+ ...candidates ?? []
2112
+ ]) known.set(release.releaseId, release);
2113
+ const newest = (list) => list.slice(0, 2).map((release) => release.releaseId);
2114
+ const chosen = releaseIds !== void 0 ? releaseIds.slice(0, 4) : candidates === void 0 ? void 0 : focusReleaseId === void 0 ? newest(candidates) : focusIndex !== -1 && focusIndex < candidates.length - 1 ? [focusReleaseId, candidates[focusIndex + 1].releaseId] : focusQuery.data === void 0 ? void 0 : focusQuery.data.release === null ? newest(candidates) : [focusQuery.data.release.releaseId, focusQuery.data.previous?.releaseId].filter((id) => id !== void 0);
2115
+ const labelQueries = useQueries({ queries: (chosen ?? []).filter((releaseId) => !known.has(releaseId) && releaseId !== focusReleaseId).map((releaseId) => ({
2116
+ queryKey: [
2117
+ "insights",
2118
+ "adoption-release",
2119
+ scope,
2120
+ releaseId,
2121
+ false
2122
+ ],
2123
+ queryFn: () => getAdoptionReleaseRpc({ data: {
2124
+ ...scope,
2125
+ releaseId
2126
+ } }),
2127
+ staleTime: Number.POSITIVE_INFINITY
2128
+ })) });
2129
+ for (const query of labelQueries) if (query.data?.release) known.set(query.data.release.releaseId, query.data.release);
2130
+ const releases = chosen === void 0 || labelQueries.some((query) => query.isPending) ? void 0 : chosen.flatMap((releaseId) => {
2131
+ const release = known.get(releaseId);
2132
+ return release === void 0 ? [] : [release];
2133
+ });
2134
+ const reads = (releases ?? []).flatMap((release) => READS[chart].map((type) => ({
2135
+ release,
2136
+ type
2137
+ })));
2138
+ const seriesQueries = useQueries({ queries: reads.map(({ release, type }) => {
2139
+ const data = {
2140
+ ...scope,
2141
+ window,
2142
+ endMs,
2143
+ bundleId: release.bundleId,
2144
+ type
2145
+ };
2146
+ return {
2147
+ queryKey: [
2148
+ "insights",
2149
+ "bundle-events",
2150
+ data
2151
+ ],
2152
+ queryFn: () => getBundleEventsRpc({ data }),
2153
+ staleTime: 3e4,
2154
+ placeholderData: (previous) => previous?.bundleId === release.bundleId && previous.type === type ? previous : void 0
2155
+ };
2156
+ }) });
2157
+ const seriesOf = (bundleId, type) => {
2158
+ const index = reads.findIndex((read) => read.release.bundleId === bundleId && read.type === type);
2159
+ return index === -1 ? void 0 : seriesQueries[index];
2160
+ };
2161
+ return {
2162
+ period: {
2163
+ startMs: endMs - durationMs,
2164
+ endMs,
2165
+ durationMs,
2166
+ intervalMs
2167
+ },
2168
+ candidates,
2169
+ releases: releases?.map((release) => {
2170
+ const downloaded = seriesOf(release.bundleId, "DOWNLOADED");
2171
+ const launched = seriesOf(release.bundleId, "LAUNCHED");
2172
+ const recovered = seriesOf(release.bundleId, "RECOVERED");
2173
+ return {
2174
+ release,
2175
+ downloaded: downloaded?.data,
2176
+ launched: launched?.data,
2177
+ recovered: recovered?.data,
2178
+ error: downloaded?.error ?? launched?.error ?? recovered?.error ?? null
2179
+ };
2180
+ }),
2181
+ isDefault: releaseIds === void 0,
2182
+ error: candidatesQuery.error ?? focusQuery.error ?? null,
2183
+ isFetching: candidatesQuery.isFetching || focusQuery.isFetching || seriesQueries.some((query) => query.isFetching),
2184
+ refresh: () => {
2185
+ candidatesQuery.refetch();
2186
+ for (const query of seriesQueries) query.refetch();
2187
+ }
2188
+ };
2189
+ }
2190
+ function InsightsPage() {
2191
+ const queryClient = useQueryClient();
2192
+ const search = Route.useSearch();
2193
+ const navigate = Route.useNavigate();
2194
+ const retention = useInsightsRetention();
2195
+ const usageWindow = search.window ?? "24h";
2196
+ const bundleWindow = search.bundleWindow ?? "24h";
2197
+ const channel = search.channel ?? search.healthChannel ?? "production";
2198
+ const scope = {
2199
+ platform: search.platform ?? "all",
2200
+ channel,
2201
+ appVersion: search.appVersion
2202
+ };
2203
+ const setUsageWindow = (window) => void navigate({ search: {
2204
+ ...search,
2205
+ window
2206
+ } });
2207
+ const input = {
2208
+ ...scope,
2209
+ window: usageWindow
2210
+ };
2211
+ const bundleInput = {
2212
+ platform: search.healthPlatform ?? "ios",
2213
+ channel,
2214
+ window: bundleWindow,
2215
+ ...search.releaseId === void 0 ? {} : { releaseId: search.releaseId }
2216
+ };
2217
+ const query = useQuery({
2218
+ queryKey: [
2219
+ "insights",
2220
+ "app-usage",
2221
+ input
2222
+ ],
2223
+ queryFn: () => getAppUsageReportRpc({ data: input }),
2224
+ staleTime: 3e4
2225
+ });
2226
+ const chart = search.healthChart ?? "adoption";
2227
+ const health = useReleaseHealth({
2228
+ platform: bundleInput.platform,
2229
+ channel,
2230
+ window: bundleWindow,
2231
+ releaseIds: comparedBundleIds(search.bundles),
2232
+ focusReleaseId: search.releaseId,
2233
+ chart
2234
+ });
2235
+ const failuresQuery = useUpdateFailuresQuery(bundleInput);
2236
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2237
+ className: "flex h-svh min-h-0 flex-col",
2238
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(InsightsPageHeader, {
2239
+ view: "overview",
2240
+ overviewSearch: search
2241
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2242
+ className: "min-h-0 min-w-0 flex-1 overflow-y-auto bg-muted/5 px-4 py-6 sm:p-8",
2243
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2244
+ className: "mx-auto flex w-full max-w-6xl flex-col gap-6",
2245
+ children: [
2246
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InsightsControls, {
2247
+ scope,
2248
+ releaseScope: bundleInput,
2249
+ appVersions: query.data?.appVersions ?? [],
2250
+ onFiltersChange: ({ scope, releaseScope }) => void navigate({ search: {
2251
+ ...search,
2252
+ ...scope,
2253
+ appVersion: scope.appVersion,
2254
+ healthPlatform: releaseScope.platform,
2255
+ healthChannel: void 0,
2256
+ releaseId: releaseScope.releaseId,
2257
+ bundles: void 0
2258
+ } })
2259
+ }, JSON.stringify({
2260
+ scope,
2261
+ bundleInput
2262
+ })),
2263
+ query.data?.truncated && !query.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertTitle, { children: "Partial history" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDescription, { children: [
2264
+ "Only reports since",
2265
+ " ",
2266
+ new Date(query.data.sinceMs).toLocaleString("en", { timeZone: "UTC" }),
2267
+ " ",
2268
+ "UTC are available. The active-user count is a lower bound. Choose a shorter period for a more complete view."
2269
+ ] })] }) : null,
2270
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppUsage, {
2271
+ query,
2272
+ search: {
2273
+ ...scope,
2274
+ window: usageWindow,
2275
+ bundleWindow
2276
+ },
2277
+ window: usageWindow,
2278
+ onWindowChange: setUsageWindow,
2279
+ retention
2280
+ }),
2281
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InsightsOverview, {
2282
+ input: bundleInput,
2283
+ chart,
2284
+ onChartChange: (healthChart) => void navigate({ search: {
2285
+ ...search,
2286
+ healthChart
2287
+ } }),
2288
+ health,
2289
+ onReleasesChange: (ids) => void navigate({ search: {
2290
+ ...search,
2291
+ bundles: ids?.join(",")
2292
+ } }),
2293
+ onWindowChange: (window) => void navigate({ search: {
2294
+ ...search,
2295
+ bundleWindow: window
2296
+ } })
2297
+ }),
2298
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpdateFailures, {
2299
+ query: failuresQuery,
2300
+ errors: failuresQuery.data ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FailureReports, { input: {
2301
+ ...bundleInput,
2302
+ beforeReceivedAtMs: failuresQuery.data.endMs
2303
+ } }, JSON.stringify(bundleInput)) : null,
2304
+ onRefresh: () => {
2305
+ failuresQuery.refetch();
2306
+ queryClient.invalidateQueries({ queryKey: ["insights", "failure-reports"] });
2307
+ }
2308
+ })
2309
+ ]
2310
+ })
2311
+ })]
2312
+ });
2313
+ }
2314
+ //#endregion
2315
+ export { InsightsPage as component };