@hot-updater/console 1.0.0-rc.4 → 1.0.0-rc.41

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 (330) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/ConsoleFeatureUnavailable-DRzeMGRM.js +1 -0
  3. package/.output/public/assets/EventDetails-SB5ygJzS.js +1 -0
  4. package/.output/public/assets/HashValueDisplay-BFX_kY3-.js +1 -0
  5. package/.output/public/assets/InsightsInfo-btYppp0B.js +1 -0
  6. package/.output/public/assets/InsightsPageHeader-Ba8psD8t.js +1 -0
  7. package/.output/public/assets/NotFoundPage-CyRG2eca.js +1 -0
  8. package/.output/public/assets/alert-dialog-Bbk_4-WJ.js +1 -0
  9. package/.output/public/assets/api-DUgI6yOI.js +1 -0
  10. package/.output/public/assets/api-keys-CIO9L_wu.js +1 -0
  11. package/.output/public/assets/api-keys-CmkNKOhS.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-BMsI3Bj8.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-BEfQ2_7f.js +11 -0
  22. package/.output/public/assets/input-group-ja_30qaF.js +1 -0
  23. package/.output/public/assets/insights-B0ib9qOE.js +2 -0
  24. package/.output/public/assets/insights-BIMJx-0p.js +1 -0
  25. package/.output/public/assets/insights-CndqcWEj.js +42 -0
  26. package/.output/public/assets/insights-api-B6Ewc-OG.js +1 -0
  27. package/.output/public/assets/insights-usage-rpc-DYEhsCKT.js +1 -0
  28. package/.output/public/assets/insights_.distribution-B26PMy4k.js +2 -0
  29. package/.output/public/assets/insights_.distribution-BBDtqqYS.js +1 -0
  30. package/.output/public/assets/insights_.distribution-DhHmYS8k.js +1 -0
  31. package/.output/public/assets/installations-C3cMuwRx.js +1 -0
  32. package/.output/public/assets/installations-C3yrjwDh.js +2 -0
  33. package/.output/public/assets/installations-D3edlN75.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-0i-gDkeU.js +1 -0
  38. package/.output/public/assets/remote-config-An1ROIOQ.js +6 -0
  39. package/.output/public/assets/remote-config-CoeWgA9i.js +1 -0
  40. package/.output/public/assets/routes-DNoaJIQV.js +2 -0
  41. package/.output/public/assets/routes-cWBDtghZ.js +1 -0
  42. package/.output/public/assets/search-DaTDPW25.js +1 -0
  43. package/.output/public/assets/select-CZMQKT7F.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-D8mxMvCB.css +2 -0
  47. package/.output/public/assets/switch-CU4-fupc.js +1 -0
  48. package/.output/public/assets/table-Gm4qRWwE.js +1 -0
  49. package/.output/public/assets/tabs-R1I8CRjR.js +1 -0
  50. package/.output/public/assets/undo-2-CmrHjwFc.js +1 -0
  51. package/.output/public/assets/useCompositeListItem-BaPeC9ZO.js +1 -0
  52. package/.output/public/assets/valueToPercent-CH8GCHaL.js +1 -0
  53. package/.output/server/__23tanstack-start-server-fn-resolver-DTAqlLRd.mjs +178 -0
  54. package/.output/server/_libs/@base-ui/react+[...].mjs +6450 -2741
  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 +235 -31
  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-BNoVW3Dv.mjs +13 -0
  67. package/.output/server/_ssr/ConsoleFeatureUnavailable-BPFa-cmJ.mjs +58 -0
  68. package/.output/server/_ssr/EventDetails-9CeshfdR.mjs +344 -0
  69. package/.output/server/_ssr/HashValueDisplay-DXZlkcps.mjs +55 -0
  70. package/.output/server/_ssr/InsightsInfo-DLNdJQ0m.mjs +75 -0
  71. package/.output/server/_ssr/InsightsPageHeader-DZGVDgvv.mjs +145 -0
  72. package/.output/server/_ssr/NotFoundPage-mYHHg-2s.mjs +37 -0
  73. package/.output/server/_ssr/{dialog-DUaIgpgd.mjs → alert-dialog-Chkim7vZ.mjs} +6 -83
  74. package/.output/server/_ssr/api-C-Il5-sa.mjs +203 -0
  75. package/.output/server/_ssr/api-keys-01FGqj4d.mjs +5 -0
  76. package/.output/server/_ssr/{api-keys-DJml_XoK.mjs → api-keys-B_f9h7ln.mjs} +48 -10
  77. package/.output/server/_ssr/api-keys-rpc-Cf4YCyFc.mjs +52 -0
  78. package/.output/server/_ssr/api-rpc-DRqwxxT_.mjs +296 -0
  79. package/.output/server/_ssr/{auth-BF8mbAgF.mjs → auth-UP0wXs4i.mjs} +1 -1
  80. package/.output/server/_ssr/{auth-rpc-D_hv52-J.mjs → auth-rpc-D8nFpU9C.mjs} +6 -6
  81. package/.output/server/_ssr/auth.server-COZoXu4i.mjs +5425 -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-Dm2l7w0O.mjs +36 -0
  85. package/.output/server/_ssr/console-access--o9FXikU.mjs +17 -0
  86. package/.output/server/_ssr/console-features-BftSkf3f.mjs +60 -0
  87. package/.output/server/_ssr/console-features-api-CYclUQ4h.mjs +43 -0
  88. package/.output/server/_ssr/console-features-rpc-1WaAkPM_.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-BUuNU-qt.mjs +74 -0
  94. package/.output/server/_ssr/dialog-mfSANqjC.mjs +84 -0
  95. package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-wmnTk7w6.mjs} +140 -6
  96. package/.output/server/_ssr/{downloadBundle-B3ert-pY.mjs → downloadBundle-DViuIa9U.mjs} +10 -10
  97. package/.output/server/_ssr/empty-BPayFad3.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-DjZ5t7rH.mjs +189 -0
  101. package/.output/server/_ssr/getBundleChildren-CmjDg6Re.mjs +22 -0
  102. package/.output/server/_ssr/{input-group-Bb8ueylQ.mjs → input-group-Dx-MEkNM.mjs} +3 -52
  103. package/.output/server/_ssr/insights-BopxyJjk.mjs +14 -0
  104. package/.output/server/_ssr/insights-Bvm03d61.mjs +5 -0
  105. package/.output/server/_ssr/insights-CrLAHSDy.mjs +2315 -0
  106. package/.output/server/_ssr/insights-api-Bg8ZV_h_.mjs +122 -0
  107. package/.output/server/_ssr/insights-errors-BjnFUyZj.mjs +34 -0
  108. package/.output/server/_ssr/insights-failures-rpc-tyy5m9Cc.mjs +36 -0
  109. package/.output/server/_ssr/insights-failures-xaa41Buf.mjs +46 -0
  110. package/.output/server/_ssr/{insights-recovery-DSFM-YTL.mjs → insights-recovery-BWQhiZaF.mjs} +9 -2
  111. package/.output/server/_ssr/insights-recovery-rpc-CPJOiJAV.mjs +32 -0
  112. package/.output/server/_ssr/{insights-rpc-DrIQCjpD.mjs → insights-rpc-DoC3hla9.mjs} +22 -14
  113. package/.output/server/_ssr/insights-usage-BpBM0U0c.mjs +29 -0
  114. package/.output/server/_ssr/insights-usage-rpc-CFuTYp3T.mjs +29 -0
  115. package/.output/server/_ssr/insights-usage-rpc-O2ruP4iE.mjs +41 -0
  116. package/.output/server/_ssr/insightsUsage-CzhVjsc8.mjs +56 -0
  117. package/.output/server/_ssr/insights_.distribution-B3BVPD00.mjs +16 -0
  118. package/.output/server/_ssr/insights_.distribution-BMoisxRG.mjs +347 -0
  119. package/.output/server/_ssr/insights_.distribution-BiCcFyrG.mjs +7 -0
  120. package/.output/server/_ssr/{installations-YumqpNpa.mjs → installations-Cx3jIrwl.mjs} +280 -364
  121. package/.output/server/_ssr/installations-DTGPaKXx.mjs +69 -0
  122. package/.output/server/_ssr/installations-osiAuNKr.mjs +5 -0
  123. package/.output/server/_ssr/listReleases-BfUBlCHo.mjs +79 -0
  124. package/.output/server/_ssr/release-adoption-005tQRpo.mjs +55 -0
  125. package/.output/server/_ssr/release-adoption-rpc-BVySDR0L.mjs +41 -0
  126. package/.output/server/_ssr/releaseAdoption-2PJPA09N.mjs +114 -0
  127. package/.output/server/_ssr/releaseManagement-BTCWEY8-.mjs +11 -0
  128. package/.output/server/_ssr/remote-config-CUoAxUMy.mjs +430 -0
  129. package/.output/server/_ssr/remote-config-Cc5P4J-r.mjs +17 -0
  130. package/.output/server/_ssr/remote-config-CjdxcZ9B.mjs +5 -0
  131. package/.output/server/_ssr/remote-config-D2GmbGwI.mjs +2594 -0
  132. package/.output/server/_ssr/remote-config-rpc-kjoZ9PRS.mjs +115 -0
  133. package/.output/server/_ssr/rolldown-runtime-rSIU-vHC.mjs +13 -0
  134. package/.output/server/_ssr/{router-iP95Vqi2.mjs → router-DwGVewL-.mjs} +141 -139
  135. package/.output/server/_ssr/{routes-ivsV2TDu.mjs → routes-C4yxv3JU.mjs} +192 -685
  136. package/.output/server/_ssr/routes-D3_aegLY.mjs +409 -0
  137. package/.output/server/_ssr/runtime.server-BoWf4QhU.mjs +1716 -0
  138. package/.output/server/_ssr/schema-DrOtkRVD.mjs +361 -0
  139. package/.output/server/_ssr/select-DkzM2miy.mjs +107 -0
  140. package/.output/server/_ssr/server-BW0gzB0p.mjs +8325 -0
  141. package/.output/server/_ssr/{createSsrRpc-NHF2PxuK.mjs → sidebar-DPcOijvL.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-BqJ2mAwC.mjs +17 -0
  145. package/.output/server/_ssr/{table-D1mL8fJu.mjs → table-R7RY92q9.mjs} +2 -2
  146. package/.output/server/_ssr/tabs-trDcbRkT.mjs +45 -0
  147. package/.output/server/_ssr/updateFailures-H6pnnYK4.mjs +40 -0
  148. package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-ctA_XVZA.mjs} +2 -2
  149. package/.output/server/_tanstack-start-manifest_v-BwInaUTw.mjs +139 -0
  150. package/.output/server/index.mjs +315 -154
  151. package/LICENSE +13 -14
  152. package/README.md +82 -72
  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 +100 -71
  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 +2 -5
  166. package/src/components/features/insights/AppUsage.tsx +366 -0
  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 +400 -39
  171. package/src/components/features/insights/EventHistoryCard.tsx +112 -26
  172. package/src/components/features/insights/EventHistoryList.tsx +2 -2
  173. package/src/components/features/insights/FailureReports.tsx +407 -0
  174. package/src/components/features/insights/InsightsControls.tsx +265 -88
  175. package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
  176. package/src/components/features/insights/InsightsInfo.tsx +48 -0
  177. package/src/components/features/insights/InsightsOverview.tsx +604 -355
  178. package/src/components/features/insights/InsightsPageHeader.tsx +35 -20
  179. package/src/components/features/insights/InsightsPeriodSelector.tsx +89 -0
  180. package/src/components/features/insights/InstallationHistoryCard.tsx +66 -11
  181. package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
  182. package/src/components/features/insights/ReleaseActivity.tsx +308 -0
  183. package/src/components/features/insights/ReportingDevicesSummary.tsx +48 -0
  184. package/src/components/features/insights/RollbackButton.tsx +118 -0
  185. package/src/components/features/insights/UpdateFailures.tsx +382 -0
  186. package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
  187. package/src/components/features/releases/ReleaseEditorSheet.tsx +26 -12
  188. package/src/components/features/remote-config/ConditionDialog.tsx +581 -0
  189. package/src/components/features/remote-config/ConditionsCard.tsx +244 -0
  190. package/src/components/features/remote-config/ParameterDialog.tsx +407 -0
  191. package/src/components/features/remote-config/ParametersCard.tsx +244 -0
  192. package/src/components/features/remote-config/PreviewCard.tsx +284 -0
  193. package/src/components/features/remote-config/PublishDialog.tsx +319 -0
  194. package/src/components/features/remote-config/RemoteConfigPage.tsx +353 -0
  195. package/src/components/features/remote-config/RuleSummary.tsx +21 -0
  196. package/src/components/features/remote-config/ValueInput.tsx +148 -0
  197. package/src/components/features/remote-config/VersionsCard.tsx +361 -0
  198. package/src/components/features/remote-config/remoteConfigFeatureUi.tsx +15 -0
  199. package/src/components/features/remote-config/useDateTimeText.ts +16 -0
  200. package/src/components/ui/card.tsx +1 -1
  201. package/src/components/ui/scroll-area.tsx +52 -0
  202. package/src/components/ui/sidebar.tsx +22 -29
  203. package/src/components/ui/tabs.tsx +81 -0
  204. package/src/index.ts +26 -19
  205. package/src/lib/api-keys-api.ts +0 -28
  206. package/src/lib/api-keys-rpc.ts +27 -38
  207. package/src/lib/api-rpc.ts +189 -266
  208. package/src/lib/api.ts +19 -102
  209. package/src/lib/bundle-activity.ts +24 -4
  210. package/src/lib/console-access.ts +20 -0
  211. package/src/lib/console-features-api.ts +68 -0
  212. package/src/lib/console-features-rpc.ts +13 -0
  213. package/src/lib/console-features.ts +97 -0
  214. package/src/lib/insights-api.ts +56 -2
  215. package/src/lib/insights-errors.ts +64 -0
  216. package/src/lib/insights-failures-rpc.ts +36 -0
  217. package/src/lib/insights-failures.ts +130 -0
  218. package/src/lib/insights-recovery-rpc.ts +13 -17
  219. package/src/lib/insights-recovery.ts +10 -27
  220. package/src/lib/insights-retention.ts +21 -0
  221. package/src/lib/insights-rpc.ts +30 -12
  222. package/src/lib/insights-search.ts +93 -0
  223. package/src/lib/insights-usage-rpc.ts +29 -0
  224. package/src/lib/insights-usage.ts +76 -0
  225. package/src/lib/insights-view.ts +34 -5
  226. package/src/lib/release-adoption-api.ts +214 -0
  227. package/src/lib/release-adoption-rpc.ts +49 -0
  228. package/src/lib/release-adoption.ts +164 -0
  229. package/src/lib/remote-config-api.ts +119 -0
  230. package/src/lib/remote-config-draft.ts +652 -0
  231. package/src/lib/remote-config-rpc.ts +196 -0
  232. package/src/lib/scroll-restoration.ts +21 -0
  233. package/src/lib/server/adminInsights.ts +124 -0
  234. package/src/lib/server/auth.server.ts +13 -5
  235. package/src/lib/server/bundleActivity.ts +27 -28
  236. package/src/lib/server/config.server.ts +21 -74
  237. package/src/lib/server/console-feature-apis.ts +70 -0
  238. package/src/lib/server/console-runtime.server.ts +82 -38
  239. package/src/lib/server/downloadBundle.ts +16 -15
  240. package/src/lib/server/failureReports.ts +49 -0
  241. package/src/lib/server/getBundleChildren.ts +31 -122
  242. package/src/lib/server/insightsUsage.ts +63 -0
  243. package/src/lib/server/listReleases.ts +109 -44
  244. package/src/lib/server/releaseAdoption.ts +154 -0
  245. package/src/lib/server/releaseReachability.ts +8 -7
  246. package/src/lib/server/remoteConfig.ts +191 -0
  247. package/src/lib/server/runtime.server.ts +120 -31
  248. package/src/lib/server/updateFailures.ts +65 -0
  249. package/src/routeTree.gen.ts +65 -44
  250. package/src/router.tsx +3 -2
  251. package/src/routes/-installations-search.ts +32 -8
  252. package/src/routes/-releases-search.ts +76 -29
  253. package/src/routes/__root.tsx +10 -4
  254. package/src/routes/api/bundles/$bundleId/download.ts +2 -5
  255. package/src/routes/api-keys.tsx +5 -2
  256. package/src/routes/index.tsx +210 -152
  257. package/src/routes/insights.tsx +133 -18
  258. package/src/routes/insights_.distribution.tsx +422 -0
  259. package/src/routes/installations.tsx +98 -41
  260. package/src/routes/remote-config.tsx +42 -0
  261. package/src/styles.css +9 -0
  262. package/src/vite-internal.ts +15 -3
  263. package/src/vite.ts +2 -0
  264. package/.output/public/assets/InsightsPageHeader-C-Xc2MB9.js +0 -1
  265. package/.output/public/assets/api-keys-C1sgC6ko.js +0 -1
  266. package/.output/public/assets/api-keys-api-LchP65vQ.js +0 -1
  267. package/.output/public/assets/badge-BuMDU0Rf.js +0 -1
  268. package/.output/public/assets/card-BEvx_QKh.js +0 -1
  269. package/.output/public/assets/chart-no-axes-combined-C0G0fQ3J.js +0 -1
  270. package/.output/public/assets/composite-CHTH1Xhh.js +0 -1
  271. package/.output/public/assets/createServerFn-DHc2erub.js +0 -3
  272. package/.output/public/assets/dialog-dyZ1SphZ.js +0 -1
  273. package/.output/public/assets/dist-Bh1gaDfB.js +0 -1
  274. package/.output/public/assets/field-79SVH26s.js +0 -1
  275. package/.output/public/assets/index-BNkxg4jz.js +0 -10
  276. package/.output/public/assets/input-group-BbHfaBzQ.js +0 -1
  277. package/.output/public/assets/insights-D_7uSKfc.js +0 -1
  278. package/.output/public/assets/installations-BzoHWuXw.js +0 -1
  279. package/.output/public/assets/installations-DvjbUqd1.js +0 -2
  280. package/.output/public/assets/lazyRouteComponent-BN0MS05o.js +0 -1
  281. package/.output/public/assets/link-BG06whwB.js +0 -1
  282. package/.output/public/assets/loader-circle-Bbp8LGVC.js +0 -1
  283. package/.output/public/assets/preload-helper-uTix4PVD.js +0 -1
  284. package/.output/public/assets/refresh-cw-DZgiAOMk.js +0 -1
  285. package/.output/public/assets/routes-BFE-oRIT.js +0 -1
  286. package/.output/public/assets/routes-DaPMY31P.js +0 -2
  287. package/.output/public/assets/shield-check-DoIiaaXU.js +0 -1
  288. package/.output/public/assets/signing-BTRhaF5D.js +0 -1
  289. package/.output/public/assets/styles-C7U0oVke.css +0 -2
  290. package/.output/public/assets/table-BBEzJ7Xl.js +0 -1
  291. package/.output/public/assets/useCompositeListItem-BkPDaqyj.js +0 -42
  292. package/.output/server/__23tanstack-start-server-fn-resolver-Dr2ixDgQ.mjs +0 -146
  293. package/.output/server/_ssr/InsightsPageHeader-C5Has8Ry.mjs +0 -60
  294. package/.output/server/_ssr/api-keys-api-CrCZyvnl.mjs +0 -60
  295. package/.output/server/_ssr/api-keys-rpc-CFaNf-q0.mjs +0 -136
  296. package/.output/server/_ssr/api-rpc-B61KL3Eo.mjs +0 -1399
  297. package/.output/server/_ssr/auth.server-DF_shCeq.mjs +0 -47
  298. package/.output/server/_ssr/badge-BgsCP9Dp.mjs +0 -28
  299. package/.output/server/_ssr/bundle-signing-rpc-BBipZnFs.mjs +0 -16
  300. package/.output/server/_ssr/bundle-signing.server-Co_zHdJx.mjs +0 -12
  301. package/.output/server/_ssr/bundleActivity-D3_fIda2.mjs +0 -27
  302. package/.output/server/_ssr/card-DrbxCPtG.mjs +0 -86
  303. package/.output/server/_ssr/chart-qxa-_30a.mjs +0 -162
  304. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
  305. package/.output/server/_ssr/config.server-CBr6PDj5.mjs +0 -368
  306. package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
  307. package/.output/server/_ssr/deleteBundle-BAq3JuGr.mjs +0 -66
  308. package/.output/server/_ssr/field-CqNUlqUq.mjs +0 -74
  309. package/.output/server/_ssr/getBundleChildren-COMt16m_.mjs +0 -50
  310. package/.output/server/_ssr/insights-B-DUbksX.mjs +0 -502
  311. package/.output/server/_ssr/insights-recovery-rpc-P71FH66n.mjs +0 -37
  312. package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +0 -303
  313. package/.output/server/_ssr/insightsRecovery-CqcosG7B.mjs +0 -149
  314. package/.output/server/_ssr/installations-BQrppti7.mjs +0 -29
  315. package/.output/server/_ssr/routes-BDabsmc7.mjs +0 -37
  316. package/.output/server/_ssr/runtime.server-jmZOKIQM.mjs +0 -438
  317. package/.output/server/_ssr/server-p7yrVBfD.mjs +0 -7468
  318. package/.output/server/_ssr/signing-CxCbnFCt.mjs +0 -134
  319. package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
  320. package/.output/server/_tanstack-start-manifest_v-Cwpu0C9Q.mjs +0 -100
  321. package/src/components/features/bundles/BundleActivityChart.tsx +0 -95
  322. package/src/components/features/bundles/BundleInsightsSummary.tsx +0 -121
  323. package/src/components/features/signing/BundleSigningPage.tsx +0 -139
  324. package/src/lib/bundle-signing-api.ts +0 -14
  325. package/src/lib/bundle-signing-rpc.ts +0 -16
  326. package/src/lib/server/bundle-signing.server.ts +0 -23
  327. package/src/lib/server/deleteBundle.ts +0 -145
  328. package/src/lib/server/insightsHistory.ts +0 -52
  329. package/src/lib/server/insightsRecovery.ts +0 -186
  330. package/src/routes/signing.tsx +0 -37
@@ -1,41 +1,156 @@
1
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
1
2
  import { createFileRoute } from "@tanstack/react-router";
2
- import { useState } from "react";
3
3
 
4
+ import { ConsoleFeatureUnavailable } from "@/components/ConsoleFeatureUnavailable";
5
+ import { AppUsage } from "@/components/features/insights/AppUsage";
6
+ import { FailureReports } from "@/components/features/insights/FailureReports";
4
7
  import { InsightsControls } from "@/components/features/insights/InsightsControls";
5
8
  import { InsightsOverview } from "@/components/features/insights/InsightsOverview";
6
9
  import { InsightsPageHeader } from "@/components/features/insights/InsightsPageHeader";
10
+ import { UpdateFailures } from "@/components/features/insights/UpdateFailures";
11
+ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
12
+ import { requireConsoleFeature } from "@/lib/console-features-api";
7
13
  import {
8
- type InsightsWindow,
9
- type InsightsOverviewInput,
14
+ useInsightsRetention,
15
+ useUpdateFailuresQuery,
10
16
  } from "@/lib/insights-api";
17
+ import {
18
+ comparedBundleIds,
19
+ validateInsightsSearch,
20
+ } from "@/lib/insights-search";
21
+ import type { AppUsageScope, UsageWindow } from "@/lib/insights-usage";
22
+ import { getAppUsageReportRpc } from "@/lib/insights-usage-rpc";
23
+ import { useReleaseHealth } from "@/lib/release-adoption-api";
11
24
 
12
25
  export const Route = createFileRoute("/insights")({
26
+ beforeLoad: ({ context }) =>
27
+ requireConsoleFeature(context.queryClient, "insightsAnalytics"),
28
+ notFoundComponent: ConsoleFeatureUnavailable,
13
29
  component: InsightsPage,
30
+ validateSearch: validateInsightsSearch,
14
31
  });
15
32
 
16
33
  function InsightsPage() {
17
- const [window, setWindow] = useState<InsightsWindow>("30d");
18
- const [scope, setScope] = useState<Omit<InsightsOverviewInput, "window">>({
19
- platform: "ios",
20
- channel: "production",
34
+ const queryClient = useQueryClient();
35
+ const search = Route.useSearch();
36
+ const navigate = Route.useNavigate();
37
+ const retention = useInsightsRetention();
38
+ const usageWindow = search.window ?? "24h";
39
+ const bundleWindow = search.bundleWindow ?? "24h";
40
+ const channel = search.channel ?? search.healthChannel ?? "production";
41
+ const scope: AppUsageScope = {
42
+ platform: search.platform ?? "all",
43
+ channel,
44
+ appVersion: search.appVersion,
45
+ };
46
+ const setUsageWindow = (window: UsageWindow) =>
47
+ void navigate({ search: { ...search, window } });
48
+ const input = { ...scope, window: usageWindow };
49
+ const bundleInput = {
50
+ platform: search.healthPlatform ?? "ios",
51
+ channel,
52
+ window: bundleWindow,
53
+ ...(search.releaseId === undefined ? {} : { releaseId: search.releaseId }),
54
+ } as const;
55
+ const query = useQuery({
56
+ queryKey: ["insights", "app-usage", input],
57
+ queryFn: () => getAppUsageReportRpc({ data: input }),
58
+ staleTime: 30_000,
21
59
  });
22
-
60
+ const chart = search.healthChart ?? "adoption";
61
+ const health = useReleaseHealth({
62
+ platform: bundleInput.platform,
63
+ channel,
64
+ window: bundleWindow,
65
+ releaseIds: comparedBundleIds(search.bundles),
66
+ focusReleaseId: search.releaseId,
67
+ chart,
68
+ });
69
+ const failuresQuery = useUpdateFailuresQuery(bundleInput);
23
70
  return (
24
71
  <div className="flex h-svh min-h-0 flex-col">
25
- <InsightsPageHeader view="overview" />
26
- <div className="min-h-0 min-w-0 flex-1 overflow-y-auto bg-muted/5 p-3 sm:p-6">
27
- <div className="mx-auto flex w-full max-w-4xl flex-col gap-4 sm:gap-6">
72
+ <InsightsPageHeader view="overview" overviewSearch={search} />
73
+ <div className="min-h-0 min-w-0 flex-1 overflow-y-auto bg-muted/5 px-4 py-6 sm:p-8">
74
+ <div className="mx-auto flex w-full max-w-6xl flex-col gap-6">
28
75
  <InsightsControls
76
+ key={JSON.stringify({ scope, bundleInput })}
29
77
  scope={scope}
30
- onScopeChange={(next) => {
31
- setScope(next);
32
- }}
33
- onWindowChange={(next) => {
34
- setWindow(next);
78
+ releaseScope={bundleInput}
79
+ appVersions={query.data?.appVersions ?? []}
80
+ onFiltersChange={({ scope, releaseScope }) =>
81
+ void navigate({
82
+ search: {
83
+ ...search,
84
+ ...scope,
85
+ appVersion: scope.appVersion,
86
+ healthPlatform: releaseScope.platform,
87
+ healthChannel: undefined,
88
+ releaseId: releaseScope.releaseId,
89
+ // Bundles chosen in another scope do not carry over.
90
+ bundles: undefined,
91
+ },
92
+ })
93
+ }
94
+ />
95
+ {query.data?.truncated && !query.error ? (
96
+ <Alert>
97
+ <AlertTitle>Partial history</AlertTitle>
98
+ <AlertDescription>
99
+ Only reports since{" "}
100
+ {new Date(query.data.sinceMs).toLocaleString("en", {
101
+ timeZone: "UTC",
102
+ })}{" "}
103
+ UTC are available. The active-user count is a lower bound.
104
+ Choose a shorter period for a more complete view.
105
+ </AlertDescription>
106
+ </Alert>
107
+ ) : null}
108
+ <AppUsage
109
+ query={query}
110
+ search={{ ...scope, window: usageWindow, bundleWindow }}
111
+ window={usageWindow}
112
+ onWindowChange={setUsageWindow}
113
+ retention={retention}
114
+ />
115
+ <InsightsOverview
116
+ input={bundleInput}
117
+ chart={chart}
118
+ onChartChange={(healthChart) =>
119
+ void navigate({ search: { ...search, healthChart } })
120
+ }
121
+ health={health}
122
+ onReleasesChange={(ids) =>
123
+ void navigate({ search: { ...search, bundles: ids?.join(",") } })
124
+ }
125
+ onWindowChange={(window) =>
126
+ void navigate({
127
+ search: {
128
+ ...search,
129
+ bundleWindow: window,
130
+ },
131
+ })
132
+ }
133
+ />
134
+ <UpdateFailures
135
+ query={failuresQuery}
136
+ errors={
137
+ failuresQuery.data ? (
138
+ <FailureReports
139
+ key={JSON.stringify(bundleInput)}
140
+ input={{
141
+ ...bundleInput,
142
+ beforeReceivedAtMs: failuresQuery.data.endMs,
143
+ }}
144
+ />
145
+ ) : null
146
+ }
147
+ onRefresh={() => {
148
+ void failuresQuery.refetch();
149
+ void queryClient.invalidateQueries({
150
+ queryKey: ["insights", "failure-reports"],
151
+ });
35
152
  }}
36
- window={window}
37
153
  />
38
- <InsightsOverview input={{ ...scope, window }} />
39
154
  </div>
40
155
  </div>
41
156
  </div>
@@ -0,0 +1,422 @@
1
+ import { useQuery } from "@tanstack/react-query";
2
+ import { createFileRoute, Link } from "@tanstack/react-router";
3
+ import { ArrowLeft, ArrowUpRight, RotateCw } from "lucide-react";
4
+
5
+ import { BundleIdDisplay } from "@/components/BundleIdDisplay";
6
+ import { ConsoleFeatureUnavailable } from "@/components/ConsoleFeatureUnavailable";
7
+ import { BuiltinBundleBadge } from "@/components/features/insights/BuiltinBundle";
8
+ import { InsightsErrorAlert } from "@/components/features/insights/InsightsErrorAlert";
9
+ import { InsightsInfo } from "@/components/features/insights/InsightsInfo";
10
+ import { InsightsPageHeader } from "@/components/features/insights/InsightsPageHeader";
11
+ import { UsagePeriodSelector } from "@/components/features/insights/InsightsPeriodSelector";
12
+ import { PlatformIcon } from "@/components/PlatformIcon";
13
+ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
14
+ import { Badge } from "@/components/ui/badge";
15
+ import { Button, buttonVariants } from "@/components/ui/button";
16
+ import {
17
+ Card,
18
+ CardContent,
19
+ CardFooter,
20
+ CardHeader,
21
+ CardTitle,
22
+ } from "@/components/ui/card";
23
+ import {
24
+ Empty,
25
+ EmptyDescription,
26
+ EmptyHeader,
27
+ EmptyTitle,
28
+ } from "@/components/ui/empty";
29
+ import { Field, FieldLabel } from "@/components/ui/field";
30
+ import { Progress } from "@/components/ui/progress";
31
+ import {
32
+ Select,
33
+ SelectContent,
34
+ SelectGroup,
35
+ SelectItem,
36
+ SelectTrigger,
37
+ SelectValue,
38
+ } from "@/components/ui/select";
39
+ import { Skeleton } from "@/components/ui/skeleton";
40
+ import { requireConsoleFeature } from "@/lib/console-features-api";
41
+ import { useInsightsRetention } from "@/lib/insights-api";
42
+ import { keepsTwelveMonths } from "@/lib/insights-retention";
43
+ import {
44
+ validateDistributionSearch,
45
+ validateInsightsSearch,
46
+ } from "@/lib/insights-search";
47
+ import type { BundleDistribution } from "@/lib/insights-usage";
48
+ import { getAppUsageReportRpc } from "@/lib/insights-usage-rpc";
49
+
50
+ const PAGE_SIZE = 20;
51
+ const dates = new Intl.DateTimeFormat("en", {
52
+ month: "short",
53
+ day: "numeric",
54
+ timeZone: "UTC",
55
+ });
56
+
57
+ /** How a distribution row's bundle reads aloud. */
58
+ const bundleName = (row: BundleDistribution): string =>
59
+ row.releaseId ??
60
+ (row.builtinBundleId
61
+ ? `Built-in bundle ${row.builtinBundleId}`
62
+ : "Unknown bundle");
63
+
64
+ /** The bundle an app version's native build shipped, by its ID. */
65
+ function BuiltinBundle({ bundleId }: { readonly bundleId: string }) {
66
+ return (
67
+ <div className="flex min-h-11 min-w-0 flex-wrap items-center gap-2 md:min-h-9">
68
+ <BuiltinBundleBadge />
69
+ <BundleIdDisplay bundleId={bundleId} className="text-muted-foreground" />
70
+ </div>
71
+ );
72
+ }
73
+
74
+ export const Route = createFileRoute("/insights_/distribution")({
75
+ beforeLoad: ({ context }) =>
76
+ requireConsoleFeature(context.queryClient, "insightsAnalytics"),
77
+ notFoundComponent: ConsoleFeatureUnavailable,
78
+ component: DistributionPage,
79
+ validateSearch: validateDistributionSearch,
80
+ });
81
+
82
+ function DistributionPage() {
83
+ const search = Route.useSearch();
84
+ const retention = useInsightsRetention();
85
+ const navigate = Route.useNavigate();
86
+ const input = {
87
+ platform: search.platform ?? "all",
88
+ channel: search.channel ?? "production",
89
+ appVersion: search.appVersion,
90
+ window: search.window ?? "24h",
91
+ };
92
+ const query = useQuery({
93
+ queryKey: ["insights", "app-usage", input],
94
+ queryFn: () => getAppUsageReportRpc({ data: input }),
95
+ staleTime: 30_000,
96
+ });
97
+ const report = query.error ? undefined : query.data;
98
+ const distributionPeriod = report
99
+ ? `since ${dates.format(report.distributionSinceMs)}, 00:00 UTC`
100
+ : "in this period";
101
+ const rows = (report?.bundleDistribution ?? []).filter(
102
+ (row) => !search.version || row.appVersion === search.version,
103
+ );
104
+ const total = rows.reduce((sum, row) => sum + row.installations, 0);
105
+ const pageCount = Math.max(1, Math.ceil(rows.length / PAGE_SIZE));
106
+ const page = Math.min(search.page ?? 1, pageCount);
107
+ const versions = [
108
+ ...new Set([
109
+ ...(report?.versions.map((row) => row.name) ?? []),
110
+ ...(search.version ? [search.version] : []),
111
+ ]),
112
+ ];
113
+ const overviewSearch = validateInsightsSearch(search);
114
+ const changeSearch = (next: Partial<typeof search>) =>
115
+ void navigate({ search: { ...search, page: undefined, ...next } });
116
+
117
+ return (
118
+ <div className="flex h-svh min-h-0 flex-col">
119
+ <InsightsPageHeader view="distribution" overviewSearch={overviewSearch} />
120
+ <div className="min-h-0 min-w-0 flex-1 overflow-y-auto bg-muted/5 px-4 py-6 sm:p-8">
121
+ <div className="mx-auto flex w-full max-w-6xl flex-col gap-6">
122
+ <Link
123
+ to="/insights"
124
+ search={overviewSearch}
125
+ className={buttonVariants({
126
+ variant: "ghost",
127
+ className: "h-11 w-fit sm:h-9",
128
+ })}
129
+ >
130
+ <ArrowLeft aria-hidden="true" data-icon="inline-start" />
131
+ Back to overview
132
+ </Link>
133
+ <div className="flex flex-wrap items-center justify-between gap-4">
134
+ <h2 className="text-2xl font-semibold tracking-tight">
135
+ Distribution
136
+ </h2>
137
+ <div className="flex items-center gap-3">
138
+ <UsagePeriodSelector
139
+ twelveMonths={keepsTwelveMonths(retention)}
140
+ window={input.window}
141
+ onWindowChange={(window) => changeSearch({ window })}
142
+ />
143
+ <Button
144
+ aria-label="Refresh distribution"
145
+ variant="ghost"
146
+ size="icon"
147
+ className="size-11 sm:size-9"
148
+ disabled={query.isFetching}
149
+ onClick={() => void query.refetch()}
150
+ >
151
+ <RotateCw aria-hidden="true" />
152
+ </Button>
153
+ </div>
154
+ </div>
155
+ <div className="flex flex-wrap items-end justify-between gap-4">
156
+ <Field className="w-full sm:w-64">
157
+ <FieldLabel htmlFor="distribution-version">
158
+ App version
159
+ </FieldLabel>
160
+ <Select
161
+ items={Object.fromEntries([
162
+ ["all", "All versions"],
163
+ ...versions.map((version) => [`version:${version}`, version]),
164
+ ])}
165
+ value={search.version ? `version:${search.version}` : "all"}
166
+ onValueChange={(value) => {
167
+ if (value !== null)
168
+ changeSearch({
169
+ version: value === "all" ? undefined : value.slice(8),
170
+ });
171
+ }}
172
+ >
173
+ <SelectTrigger
174
+ id="distribution-version"
175
+ className="min-h-11 w-full sm:min-h-9"
176
+ >
177
+ <SelectValue />
178
+ </SelectTrigger>
179
+ <SelectContent>
180
+ <SelectGroup>
181
+ <SelectItem value="all">All versions</SelectItem>
182
+ {versions.map((version) => (
183
+ <SelectItem value={`version:${version}`} key={version}>
184
+ {version}
185
+ </SelectItem>
186
+ ))}
187
+ </SelectGroup>
188
+ </SelectContent>
189
+ </Select>
190
+ </Field>
191
+ <div
192
+ className="flex min-h-9 flex-wrap items-center gap-2"
193
+ aria-label="Distribution scope"
194
+ >
195
+ <Badge variant="outline" className="gap-1.5">
196
+ {input.platform !== "all" ? (
197
+ <PlatformIcon
198
+ platform={input.platform}
199
+ className="size-3.5"
200
+ />
201
+ ) : null}
202
+ {input.platform === "all"
203
+ ? "All platforms"
204
+ : input.platform === "ios"
205
+ ? "iOS"
206
+ : "Android"}
207
+ </Badge>
208
+ <Badge variant="outline">{input.channel}</Badge>
209
+ {input.appVersion ? (
210
+ <Badge variant="outline">Reports from {input.appVersion}</Badge>
211
+ ) : null}
212
+ </div>
213
+ </div>
214
+ <Card
215
+ aria-label="Bundles by app version"
216
+ role="region"
217
+ className="min-w-0"
218
+ >
219
+ <CardHeader className="flex flex-col gap-3">
220
+ <div className="flex items-center justify-between gap-2">
221
+ <CardTitle>Bundles by app version</CardTitle>
222
+ <InsightsInfo label="How bundle distribution is counted">
223
+ Each installation counts once, by its latest report{" "}
224
+ {distributionPeriod}. Shares are within each app version.
225
+ Built-in bundle: the bundle the native build shipped, by its
226
+ ID. Unknown bundle: no deployment and no built-in bundle ID.
227
+ </InsightsInfo>
228
+ </div>
229
+ <div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
230
+ {query.isPending ? (
231
+ <Skeleton className="h-9 w-16" />
232
+ ) : (
233
+ <p className="text-3xl font-semibold tabular-nums">
234
+ {report
235
+ ? `${report.truncated ? "≥" : ""}${total.toLocaleString()}`
236
+ : "—"}
237
+ </p>
238
+ )}
239
+ <span className="text-sm text-muted-foreground">
240
+ Reporting installations
241
+ </span>
242
+ </div>
243
+ </CardHeader>
244
+ <CardContent className="flex min-w-0 flex-col gap-4">
245
+ {report?.truncated ? (
246
+ <Alert>
247
+ <AlertTitle>Partial history</AlertTitle>
248
+ <AlertDescription>
249
+ Counts and shares reflect reports since{" "}
250
+ {new Date(report.sinceMs).toLocaleString("en", {
251
+ timeZone: "UTC",
252
+ })}{" "}
253
+ UTC. Choose a shorter period for a more complete view.
254
+ </AlertDescription>
255
+ </Alert>
256
+ ) : null}
257
+ {query.isPending ? (
258
+ <Skeleton
259
+ aria-label="Loading bundle distribution"
260
+ className="h-64"
261
+ />
262
+ ) : query.error ? (
263
+ <div className="flex flex-col items-start gap-4">
264
+ <InsightsErrorAlert
265
+ error={query.error}
266
+ fallbackTitle="Distribution unavailable"
267
+ />
268
+ <Button
269
+ variant="outline"
270
+ onClick={() => void query.refetch()}
271
+ >
272
+ Retry distribution
273
+ </Button>
274
+ </div>
275
+ ) : rows.length === 0 ? (
276
+ <Empty>
277
+ <EmptyHeader>
278
+ <EmptyTitle>No bundles reported</EmptyTitle>
279
+ <EmptyDescription>
280
+ Choose another version or period, or change the filters in
281
+ the overview.
282
+ </EmptyDescription>
283
+ </EmptyHeader>
284
+ <Button
285
+ variant="outline"
286
+ onClick={() => changeSearch({ version: undefined })}
287
+ >
288
+ Show all versions
289
+ </Button>
290
+ </Empty>
291
+ ) : (
292
+ <div className="min-w-0">
293
+ <div
294
+ aria-hidden="true"
295
+ className="hidden grid-cols-[8rem_minmax(0,1fr)_7rem_7rem] gap-4 border-b px-3 pb-3 text-xs text-muted-foreground md:grid"
296
+ >
297
+ <span>App version</span>
298
+ <span>Bundle ID</span>
299
+ <span className="text-right">Active installations</span>
300
+ <span className="text-right">Version share</span>
301
+ </div>
302
+ <ul aria-label="Bundle distribution" className="divide-y">
303
+ {rows
304
+ .slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE)
305
+ .map((row) => {
306
+ const versionTotal =
307
+ report?.versions.find(
308
+ (version) => version.name === row.appVersion,
309
+ )?.installations ?? 0;
310
+ const share =
311
+ versionTotal > 0
312
+ ? (row.installations / versionTotal) * 100
313
+ : 0;
314
+ return (
315
+ <li
316
+ key={JSON.stringify([
317
+ row.appVersion,
318
+ row.platform,
319
+ row.releaseId,
320
+ row.builtinBundleId,
321
+ ])}
322
+ className="grid min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-x-4 gap-y-3 px-1 py-4 md:grid-cols-[8rem_minmax(0,1fr)_7rem_7rem] md:px-3"
323
+ >
324
+ <div className="flex min-w-0 flex-col gap-1">
325
+ <span
326
+ className="truncate text-sm font-medium"
327
+ title={row.appVersion}
328
+ >
329
+ {row.appVersion}
330
+ </span>
331
+ <span className="flex items-center gap-1.5 text-xs text-muted-foreground">
332
+ <PlatformIcon
333
+ platform={row.platform}
334
+ className="size-3.5"
335
+ />
336
+ {row.platform === "ios" ? "iOS" : "Android"}
337
+ </span>
338
+ </div>
339
+ <div className="col-span-2 row-start-2 min-w-0 md:col-span-1 md:row-auto">
340
+ {row.releaseId ? (
341
+ <Link
342
+ to="/"
343
+ search={{ releaseId: row.releaseId }}
344
+ aria-label={`Review bundle ${row.releaseId} for app version ${row.appVersion}`}
345
+ className="flex min-h-11 min-w-0 items-center gap-2 text-xs underline-offset-4 hover:underline md:min-h-9"
346
+ >
347
+ <span className="break-all font-mono">
348
+ {row.releaseId}
349
+ </span>
350
+ <ArrowUpRight
351
+ aria-hidden="true"
352
+ className="size-3.5 shrink-0"
353
+ />
354
+ </Link>
355
+ ) : row.builtinBundleId ? null : (
356
+ <span className="text-sm text-muted-foreground">
357
+ Unknown bundle
358
+ </span>
359
+ )}
360
+ {row.builtinBundleId ? (
361
+ <BuiltinBundle bundleId={row.builtinBundleId} />
362
+ ) : null}
363
+ </div>
364
+ <div className="col-start-2 row-start-1 text-right md:col-auto md:row-auto">
365
+ <span className="text-sm font-medium tabular-nums">
366
+ {row.installations.toLocaleString()}
367
+ </span>
368
+ <span className="ml-1 text-xs text-muted-foreground md:sr-only">
369
+ active
370
+ </span>
371
+ <p className="mt-1 text-xs tabular-nums text-muted-foreground md:hidden">
372
+ {share.toFixed(1)}% of version
373
+ </p>
374
+ </div>
375
+ <div className="hidden flex-col gap-2 md:flex">
376
+ <span className="text-right text-sm tabular-nums">
377
+ {share.toFixed(1)}%
378
+ </span>
379
+ <Progress
380
+ aria-label={`${bundleName(row)} share in ${row.appVersion} on ${row.platform}`}
381
+ value={share}
382
+ />
383
+ </div>
384
+ </li>
385
+ );
386
+ })}
387
+ </ul>
388
+ </div>
389
+ )}
390
+ </CardContent>
391
+ {report && rows.length > 0 ? (
392
+ <CardFooter className="flex flex-wrap items-center justify-between gap-3 border-t pt-4">
393
+ <p className="text-xs tabular-nums text-muted-foreground">
394
+ {(page - 1) * PAGE_SIZE + 1}–
395
+ {Math.min(page * PAGE_SIZE, rows.length)} of {rows.length}
396
+ </p>
397
+ <nav aria-label="Distribution pages" className="flex gap-2">
398
+ <Button
399
+ variant="outline"
400
+ className="h-11 sm:h-9"
401
+ disabled={page <= 1}
402
+ onClick={() => changeSearch({ page: page - 1 })}
403
+ >
404
+ Previous
405
+ </Button>
406
+ <Button
407
+ variant="outline"
408
+ className="h-11 sm:h-9"
409
+ disabled={page >= pageCount}
410
+ onClick={() => changeSearch({ page: page + 1 })}
411
+ >
412
+ Next
413
+ </Button>
414
+ </nav>
415
+ </CardFooter>
416
+ ) : null}
417
+ </Card>
418
+ </div>
419
+ </div>
420
+ </div>
421
+ );
422
+ }