@hot-updater/console 1.0.0-rc.9 → 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 (341) 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-CyMmr0O9.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-CKvQ4VIE.mjs → alert-dialog-C43Doa0I.mjs} +6 -83
  74. package/.output/server/_ssr/{api-keys-CzZ7Ont4.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-DH6Bhwcz.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-BrGSjz4S.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-DDKmIrpV.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-cIsU-kHG.mjs → insights_.distribution-DN9pR422.mjs} +50 -22
  120. package/.output/server/_ssr/installations-BHmbbnH8.mjs +5 -0
  121. package/.output/server/_ssr/{installations-C5anhyP0.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-BuanSsRI.mjs → router-D8NuoJ_I.mjs} +107 -134
  135. package/.output/server/_ssr/{routes-DY-vqU6R.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-DD2szs3Y.mjs → select-t3iXRwc9.mjs} +3 -3
  140. package/.output/server/_ssr/server-h4VxnExB.mjs +8325 -0
  141. package/.output/server/_ssr/{createSsrRpc-CdlfejmI.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-CTj6n-4F.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 +309 -211
  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 +69 -74
  158. package/src/components/ConsoleFeatureUnavailable.tsx +80 -0
  159. package/src/components/NotFoundPage.tsx +7 -7
  160. package/src/components/features/FeatureSlots.tsx +103 -0
  161. package/src/components/features/api-keys/apiKeysFeatureUi.tsx +15 -0
  162. package/src/components/features/bundles/BundleMetadata.tsx +8 -5
  163. package/src/components/features/bundles/RolloutCohortsDialog.tsx +1 -1
  164. package/src/components/features/channels/ChannelManagementDialog.tsx +8 -6
  165. package/src/components/features/insights/AppUsage.tsx +41 -14
  166. package/src/components/features/insights/BuiltinBundle.tsx +36 -0
  167. package/src/components/features/insights/ComparedBundlesChart.tsx +310 -0
  168. package/src/components/features/insights/EstimatedCount.tsx +18 -0
  169. package/src/components/features/insights/EventDetails.tsx +390 -26
  170. package/src/components/features/insights/EventHistoryCard.tsx +111 -25
  171. package/src/components/features/insights/EventHistoryList.tsx +1 -1
  172. package/src/components/features/insights/FailureReports.tsx +407 -0
  173. package/src/components/features/insights/InsightsControls.tsx +240 -142
  174. package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
  175. package/src/components/features/insights/InsightsOverview.tsx +601 -403
  176. package/src/components/features/insights/InsightsPageHeader.tsx +19 -14
  177. package/src/components/features/insights/InsightsPeriodSelector.tsx +67 -12
  178. package/src/components/features/insights/InstallationHistoryCard.tsx +65 -10
  179. package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
  180. package/src/components/features/insights/ReleaseActivity.tsx +308 -0
  181. package/src/components/features/insights/ReportingDevicesSummary.tsx +13 -13
  182. package/src/components/features/insights/RollbackButton.tsx +118 -0
  183. package/src/components/features/insights/UpdateFailures.tsx +382 -0
  184. package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
  185. package/src/components/features/releases/ReleaseEditorSheet.tsx +27 -13
  186. package/src/components/features/remote-config/ConditionDialog.tsx +592 -0
  187. package/src/components/features/remote-config/ConditionsCard.tsx +247 -0
  188. package/src/components/features/remote-config/ParameterDialog.tsx +426 -0
  189. package/src/components/features/remote-config/ParametersCard.tsx +254 -0
  190. package/src/components/features/remote-config/PreviewCard.tsx +284 -0
  191. package/src/components/features/remote-config/PublishDialog.tsx +325 -0
  192. package/src/components/features/remote-config/RemoteConfigPage.tsx +353 -0
  193. package/src/components/features/remote-config/RuleSummary.tsx +21 -0
  194. package/src/components/features/remote-config/ValueInput.tsx +151 -0
  195. package/src/components/features/remote-config/VersionsCard.tsx +384 -0
  196. package/src/components/features/remote-config/remoteConfigFeatureUi.tsx +15 -0
  197. package/src/components/features/remote-config/useDateTimeText.ts +16 -0
  198. package/src/components/ui/sidebar.tsx +22 -29
  199. package/src/index.ts +26 -19
  200. package/src/lib/api-keys-api.ts +0 -28
  201. package/src/lib/api-keys-rpc.ts +27 -38
  202. package/src/lib/api-rpc.ts +125 -288
  203. package/src/lib/api.ts +15 -134
  204. package/src/lib/bundle-activity.ts +24 -4
  205. package/src/lib/console-access.ts +20 -0
  206. package/src/lib/console-features-api.ts +68 -0
  207. package/src/lib/console-features-rpc.ts +13 -0
  208. package/src/lib/console-features.ts +98 -0
  209. package/src/lib/insights-api.ts +56 -2
  210. package/src/lib/insights-errors.ts +64 -0
  211. package/src/lib/insights-failures-rpc.ts +36 -0
  212. package/src/lib/insights-failures.ts +130 -0
  213. package/src/lib/insights-recovery-rpc.ts +13 -17
  214. package/src/lib/insights-recovery.ts +12 -36
  215. package/src/lib/insights-retention.ts +21 -0
  216. package/src/lib/insights-rpc.ts +30 -12
  217. package/src/lib/insights-search.ts +50 -4
  218. package/src/lib/insights-usage-rpc.ts +15 -9
  219. package/src/lib/insights-usage.ts +41 -3
  220. package/src/lib/insights-view.ts +34 -5
  221. package/src/lib/release-adoption-api.ts +214 -0
  222. package/src/lib/release-adoption-rpc.ts +49 -0
  223. package/src/lib/release-adoption.ts +164 -0
  224. package/src/lib/remote-config-api.ts +119 -0
  225. package/src/lib/remote-config-draft.ts +666 -0
  226. package/src/lib/remote-config-rpc.ts +196 -0
  227. package/src/lib/scroll-restoration.ts +21 -0
  228. package/src/lib/server/auth.server.ts +13 -5
  229. package/src/lib/server/bundleActivity.ts +27 -28
  230. package/src/lib/server/config.server.ts +21 -76
  231. package/src/lib/server/console-feature-apis.ts +70 -0
  232. package/src/lib/server/console-runtime.server.ts +82 -38
  233. package/src/lib/server/downloadBundle.ts +14 -15
  234. package/src/lib/server/failureReports.ts +50 -0
  235. package/src/lib/server/getBundleChildren.ts +31 -122
  236. package/src/lib/server/insightsUsage.ts +52 -113
  237. package/src/lib/server/listReleases.ts +109 -44
  238. package/src/lib/server/releaseAdoption.ts +151 -0
  239. package/src/lib/server/releaseReachability.ts +8 -8
  240. package/src/lib/server/remoteConfig.ts +75 -0
  241. package/src/lib/server/runtime.server.ts +120 -31
  242. package/src/lib/server/updateFailures.ts +66 -0
  243. package/src/lib/use-dialog-target.ts +27 -0
  244. package/src/routeTree.gen.ts +44 -44
  245. package/src/router.tsx +3 -2
  246. package/src/routes/-installations-search.ts +32 -8
  247. package/src/routes/-releases-search.ts +76 -29
  248. package/src/routes/__root.tsx +7 -1
  249. package/src/routes/api/bundles/$bundleId/download.ts +2 -5
  250. package/src/routes/api-keys.tsx +5 -2
  251. package/src/routes/index.tsx +210 -152
  252. package/src/routes/insights.tsx +91 -23
  253. package/src/routes/insights_.distribution.tsx +50 -9
  254. package/src/routes/installations.tsx +97 -40
  255. package/src/routes/remote-config.tsx +42 -0
  256. package/src/styles.css +9 -0
  257. package/src/vite-internal.ts +15 -3
  258. package/src/vite.ts +2 -0
  259. package/.output/public/assets/HashValueDisplay-BpTc3Oyg.js +0 -1
  260. package/.output/public/assets/InsightsPageHeader-DC2eEst8.js +0 -1
  261. package/.output/public/assets/api-CdUyUqzR.js +0 -42
  262. package/.output/public/assets/api-keys-64PM9PwW.js +0 -1
  263. package/.output/public/assets/api-keys-api-BeaJnvY5.js +0 -1
  264. package/.output/public/assets/arrow-left-04Rh8Zgp.js +0 -1
  265. package/.output/public/assets/card-CYHs5FZv.js +0 -1
  266. package/.output/public/assets/chart-no-axes-combined-DwyMsVFW.js +0 -1
  267. package/.output/public/assets/createServerFn-DU71xucx.js +0 -3
  268. package/.output/public/assets/dialog-IVMkBRCq.js +0 -1
  269. package/.output/public/assets/dist-BR4GzeEu.js +0 -1
  270. package/.output/public/assets/field-B3CYDpjg.js +0 -1
  271. package/.output/public/assets/index-Cb_D1D6N.js +0 -10
  272. package/.output/public/assets/info-C3y9HNoU.js +0 -1
  273. package/.output/public/assets/input-group-nLZylhWF.js +0 -1
  274. package/.output/public/assets/insights-Bm9v8uO3.js +0 -2
  275. package/.output/public/assets/insights-DS3iO7Up.js +0 -1
  276. package/.output/public/assets/insights-search-Byj3g3Mp.js +0 -1
  277. package/.output/public/assets/insights-usage-rpc-BaGR3lgq.js +0 -1
  278. package/.output/public/assets/insights_.distribution-B1i6cjlQ.js +0 -1
  279. package/.output/public/assets/insights_.distribution-C1Wf27eB.js +0 -2
  280. package/.output/public/assets/installations-CkFW4fV9.js +0 -2
  281. package/.output/public/assets/installations-DxTaIcHD.js +0 -1
  282. package/.output/public/assets/lazyRouteComponent-BapzjYnb.js +0 -1
  283. package/.output/public/assets/list-Dqt53fWZ.js +0 -1
  284. package/.output/public/assets/loader-circle-CUKfUqow.js +0 -1
  285. package/.output/public/assets/preload-helper-ClBMtM1g.js +0 -1
  286. package/.output/public/assets/refresh-cw-CNE_2PnD.js +0 -1
  287. package/.output/public/assets/routes-Cgtvspjg.js +0 -2
  288. package/.output/public/assets/routes-O2r2UUN8.js +0 -1
  289. package/.output/public/assets/shield-check-BFcJ46kz.js +0 -1
  290. package/.output/public/assets/signing-BgspC-xP.js +0 -1
  291. package/.output/public/assets/styles-DnrWuBwd.css +0 -2
  292. package/.output/public/assets/table-BuWQgxJJ.js +0 -1
  293. package/.output/public/assets/useMutation-CSV9NCUl.js +0 -1
  294. package/.output/public/assets/useOpenInteractionType-D5kduCTD.js +0 -1
  295. package/.output/public/assets/valueToPercent-BAtcbSLk.js +0 -1
  296. package/.output/server/__23tanstack-start-server-fn-resolver-DBRojHaG.mjs +0 -150
  297. package/.output/server/_ssr/InsightsPageHeader-mtxVuZKK.mjs +0 -64
  298. package/.output/server/_ssr/api-BfCgYlj5.mjs +0 -332
  299. package/.output/server/_ssr/api-keys-api-C3oeq01R.mjs +0 -60
  300. package/.output/server/_ssr/api-keys-rpc-CdPFdGOx.mjs +0 -136
  301. package/.output/server/_ssr/api-rpc-B2E1ZIG5.mjs +0 -1399
  302. package/.output/server/_ssr/auth.server-CmicHh0J.mjs +0 -47
  303. package/.output/server/_ssr/bundle-signing-rpc-Di7-I6fE.mjs +0 -16
  304. package/.output/server/_ssr/bundle-signing.server-Uqt1OF49.mjs +0 -12
  305. package/.output/server/_ssr/bundleActivity-pBrPSG01.mjs +0 -28
  306. package/.output/server/_ssr/card-Bemj0dpY.mjs +0 -108
  307. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
  308. package/.output/server/_ssr/config.server-B8WsT3s0.mjs +0 -368
  309. package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
  310. package/.output/server/_ssr/deleteBundle-8X-jq7Z7.mjs +0 -66
  311. package/.output/server/_ssr/field-BrQ2u0fT.mjs +0 -74
  312. package/.output/server/_ssr/getBundleChildren-DoL8l_AS.mjs +0 -50
  313. package/.output/server/_ssr/insights-BwQ-wF2a.mjs +0 -10
  314. package/.output/server/_ssr/insights-gr1I_dJU.mjs +0 -938
  315. package/.output/server/_ssr/insights-recovery-Cv6RSyeR.mjs +0 -21
  316. package/.output/server/_ssr/insights-recovery-rpc-DOzxfYfv.mjs +0 -37
  317. package/.output/server/_ssr/insights-search-1czJMZgk.mjs +0 -21
  318. package/.output/server/_ssr/insights-usage-rpc-4ImOq_0p.mjs +0 -184
  319. package/.output/server/_ssr/insights-usage-rpc-BUruODjE.mjs +0 -24
  320. package/.output/server/_ssr/insightsContract-B09K_Px1.mjs +0 -303
  321. package/.output/server/_ssr/insightsHistory-Ci4rYAgl.mjs +0 -41
  322. package/.output/server/_ssr/insightsRecovery-CaghWL2t.mjs +0 -113
  323. package/.output/server/_ssr/insightsUsage-DNIj6jxF.mjs +0 -71
  324. package/.output/server/_ssr/insights_.distribution-VmHuikRE.mjs +0 -10
  325. package/.output/server/_ssr/installations-HXMF_kKf.mjs +0 -29
  326. package/.output/server/_ssr/routes-Dru3IRj0.mjs +0 -37
  327. package/.output/server/_ssr/runtime.server-CsiKFp_Z.mjs +0 -436
  328. package/.output/server/_ssr/server-DJm72Kj-.mjs +0 -7468
  329. package/.output/server/_ssr/signing-H8Lg4lPe.mjs +0 -133
  330. package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
  331. package/.output/server/_tanstack-start-manifest_v-DsFcTLw_.mjs +0 -125
  332. package/src/components/features/bundles/BundleActivityChart.tsx +0 -97
  333. package/src/components/features/bundles/BundleInsightsSummary.tsx +0 -126
  334. package/src/components/features/signing/BundleSigningPage.tsx +0 -139
  335. package/src/lib/bundle-signing-api.ts +0 -14
  336. package/src/lib/bundle-signing-rpc.ts +0 -16
  337. package/src/lib/server/bundle-signing.server.ts +0 -23
  338. package/src/lib/server/deleteBundle.ts +0 -145
  339. package/src/lib/server/insightsHistory.ts +0 -52
  340. package/src/lib/server/insightsRecovery.ts +0 -187
  341. package/src/routes/signing.tsx +0 -37
@@ -1,62 +1,96 @@
1
- import { useQuery } from "@tanstack/react-query";
1
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
2
2
  import { createFileRoute } from "@tanstack/react-router";
3
3
 
4
+ import { ConsoleFeatureUnavailable } from "@/components/ConsoleFeatureUnavailable";
4
5
  import { AppUsage } from "@/components/features/insights/AppUsage";
6
+ import { FailureReports } from "@/components/features/insights/FailureReports";
5
7
  import { InsightsControls } from "@/components/features/insights/InsightsControls";
6
8
  import { InsightsOverview } from "@/components/features/insights/InsightsOverview";
7
9
  import { InsightsPageHeader } from "@/components/features/insights/InsightsPageHeader";
10
+ import { UpdateFailures } from "@/components/features/insights/UpdateFailures";
8
11
  import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
9
- import type { InsightsWindow } from "@/lib/insights-api";
10
- import { getRecoveryReportRpc } from "@/lib/insights-recovery-rpc";
11
- import { validateInsightsSearch } from "@/lib/insights-search";
12
- import type { AppUsageScope } from "@/lib/insights-usage";
12
+ import { requireConsoleFeature } from "@/lib/console-features-api";
13
+ import {
14
+ useInsightsRetention,
15
+ useUpdateFailuresQuery,
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";
13
22
  import { getAppUsageReportRpc } from "@/lib/insights-usage-rpc";
23
+ import { useReleaseHealth } from "@/lib/release-adoption-api";
14
24
 
15
25
  export const Route = createFileRoute("/insights")({
26
+ beforeLoad: ({ context }) =>
27
+ requireConsoleFeature(context.queryClient, "insightsAnalytics"),
28
+ notFoundComponent: ConsoleFeatureUnavailable,
16
29
  component: InsightsPage,
17
30
  validateSearch: validateInsightsSearch,
18
31
  });
19
32
 
20
33
  function InsightsPage() {
34
+ const queryClient = useQueryClient();
21
35
  const search = Route.useSearch();
22
36
  const navigate = Route.useNavigate();
37
+ const retention = useInsightsRetention();
23
38
  const usageWindow = search.window ?? "24h";
24
39
  const bundleWindow = search.bundleWindow ?? "24h";
40
+ const channel = search.channel ?? search.healthChannel ?? "production";
25
41
  const scope: AppUsageScope = {
26
42
  platform: search.platform ?? "all",
27
- channel: search.channel ?? "production",
43
+ channel,
28
44
  appVersion: search.appVersion,
29
45
  };
30
- const setUsageWindow = (window: InsightsWindow) =>
46
+ const setUsageWindow = (window: UsageWindow) =>
31
47
  void navigate({ search: { ...search, window } });
32
- const setBundleWindow = (bundleWindow: InsightsWindow) =>
33
- void navigate({ search: { ...search, bundleWindow } });
34
- const setScope = (scope: AppUsageScope) =>
35
- void navigate({
36
- search: { ...search, ...scope, appVersion: scope.appVersion },
37
- });
38
48
  const input = { ...scope, window: usageWindow };
39
- const bundleInput = { ...scope, window: bundleWindow };
49
+ const bundleInput = {
50
+ platform: search.healthPlatform ?? "ios",
51
+ channel,
52
+ window: bundleWindow,
53
+ ...(search.releaseId === undefined ? {} : { releaseId: search.releaseId }),
54
+ } as const;
40
55
  const query = useQuery({
41
56
  queryKey: ["insights", "app-usage", input],
42
57
  queryFn: () => getAppUsageReportRpc({ data: input }),
43
58
  staleTime: 30_000,
44
59
  });
45
- const bundleQuery = useQuery({
46
- queryKey: ["insights", "recovery", bundleInput],
47
- queryFn: () => getRecoveryReportRpc({ data: bundleInput }),
48
- staleTime: 30_000,
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,
49
68
  });
69
+ const failuresQuery = useUpdateFailuresQuery(bundleInput);
50
70
  return (
51
71
  <div className="flex h-svh min-h-0 flex-col">
52
72
  <InsightsPageHeader view="overview" overviewSearch={search} />
53
73
  <div className="min-h-0 min-w-0 flex-1 overflow-y-auto bg-muted/5 px-4 py-6 sm:p-8">
54
74
  <div className="mx-auto flex w-full max-w-6xl flex-col gap-6">
55
75
  <InsightsControls
56
- key={JSON.stringify(scope)}
76
+ key={JSON.stringify({ scope, bundleInput })}
57
77
  scope={scope}
78
+ releaseScope={bundleInput}
58
79
  appVersions={query.data?.appVersions ?? []}
59
- onScopeChange={setScope}
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
+ }
60
94
  />
61
95
  {query.data?.truncated && !query.error ? (
62
96
  <Alert>
@@ -76,12 +110,46 @@ function InsightsPage() {
76
110
  search={{ ...scope, window: usageWindow, bundleWindow }}
77
111
  window={usageWindow}
78
112
  onWindowChange={setUsageWindow}
113
+ retention={retention}
79
114
  />
80
115
  <InsightsOverview
81
116
  input={bundleInput}
82
- query={bundleQuery}
83
- onWindowChange={setBundleWindow}
84
- onRefresh={() => void bundleQuery.refetch()}
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
+ });
152
+ }}
85
153
  />
86
154
  </div>
87
155
  </div>
@@ -2,10 +2,13 @@ import { useQuery } from "@tanstack/react-query";
2
2
  import { createFileRoute, Link } from "@tanstack/react-router";
3
3
  import { ArrowLeft, ArrowUpRight, RotateCw } from "lucide-react";
4
4
 
5
+ import { BundleIdDisplay } from "@/components/BundleIdDisplay";
6
+ import { ConsoleFeatureUnavailable } from "@/components/ConsoleFeatureUnavailable";
7
+ import { BuiltinBundleBadge } from "@/components/features/insights/BuiltinBundle";
5
8
  import { InsightsErrorAlert } from "@/components/features/insights/InsightsErrorAlert";
6
9
  import { InsightsInfo } from "@/components/features/insights/InsightsInfo";
7
10
  import { InsightsPageHeader } from "@/components/features/insights/InsightsPageHeader";
8
- import { InsightsPeriodSelector } from "@/components/features/insights/InsightsPeriodSelector";
11
+ import { UsagePeriodSelector } from "@/components/features/insights/InsightsPeriodSelector";
9
12
  import { PlatformIcon } from "@/components/PlatformIcon";
10
13
  import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
11
14
  import { Badge } from "@/components/ui/badge";
@@ -34,20 +37,51 @@ import {
34
37
  SelectValue,
35
38
  } from "@/components/ui/select";
36
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";
37
43
  import {
38
44
  validateDistributionSearch,
39
45
  validateInsightsSearch,
40
46
  } from "@/lib/insights-search";
47
+ import type { BundleDistribution } from "@/lib/insights-usage";
41
48
  import { getAppUsageReportRpc } from "@/lib/insights-usage-rpc";
42
49
 
43
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
+
44
74
  export const Route = createFileRoute("/insights_/distribution")({
75
+ beforeLoad: ({ context }) =>
76
+ requireConsoleFeature(context.queryClient, "insightsAnalytics"),
77
+ notFoundComponent: ConsoleFeatureUnavailable,
45
78
  component: DistributionPage,
46
79
  validateSearch: validateDistributionSearch,
47
80
  });
48
81
 
49
82
  function DistributionPage() {
50
83
  const search = Route.useSearch();
84
+ const retention = useInsightsRetention();
51
85
  const navigate = Route.useNavigate();
52
86
  const input = {
53
87
  platform: search.platform ?? "all",
@@ -61,6 +95,9 @@ function DistributionPage() {
61
95
  staleTime: 30_000,
62
96
  });
63
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";
64
101
  const rows = (report?.bundleDistribution ?? []).filter(
65
102
  (row) => !search.version || row.appVersion === search.version,
66
103
  );
@@ -98,7 +135,8 @@ function DistributionPage() {
98
135
  Distribution
99
136
  </h2>
100
137
  <div className="flex items-center gap-3">
101
- <InsightsPeriodSelector
138
+ <UsagePeriodSelector
139
+ twelveMonths={keepsTwelveMonths(retention)}
102
140
  window={input.window}
103
141
  onWindowChange={(window) => changeSearch({ window })}
104
142
  />
@@ -182,11 +220,10 @@ function DistributionPage() {
182
220
  <div className="flex items-center justify-between gap-2">
183
221
  <CardTitle>Bundles by app version</CardTitle>
184
222
  <InsightsInfo label="How bundle distribution is counted">
185
- Each installation contributes its latest matching report in
186
- this period. Share is within its app version. Unknown bundle
187
- means no deployment ID was observed for the reported file.
188
- These counts describe reporting installations, not all
189
- installed devices.
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.
190
227
  </InsightsInfo>
191
228
  </div>
192
229
  <div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
@@ -280,6 +317,7 @@ function DistributionPage() {
280
317
  row.appVersion,
281
318
  row.platform,
282
319
  row.releaseId,
320
+ row.builtinBundleId,
283
321
  ])}
284
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"
285
323
  >
@@ -314,11 +352,14 @@ function DistributionPage() {
314
352
  className="size-3.5 shrink-0"
315
353
  />
316
354
  </Link>
317
- ) : (
355
+ ) : row.builtinBundleId ? null : (
318
356
  <span className="text-sm text-muted-foreground">
319
357
  Unknown bundle
320
358
  </span>
321
359
  )}
360
+ {row.builtinBundleId ? (
361
+ <BuiltinBundle bundleId={row.builtinBundleId} />
362
+ ) : null}
322
363
  </div>
323
364
  <div className="col-start-2 row-start-1 text-right md:col-auto md:row-auto">
324
365
  <span className="text-sm font-medium tabular-nums">
@@ -336,7 +377,7 @@ function DistributionPage() {
336
377
  {share.toFixed(1)}%
337
378
  </span>
338
379
  <Progress
339
- aria-label={`${row.releaseId ?? "Unknown bundle"} share in ${row.appVersion} on ${row.platform}`}
380
+ aria-label={`${bundleName(row)} share in ${row.appVersion} on ${row.platform}`}
340
381
  value={share}
341
382
  />
342
383
  </div>
@@ -1,10 +1,12 @@
1
1
  import {
2
2
  createFileRoute,
3
3
  useElementScrollRestoration,
4
+ useLocation,
4
5
  } from "@tanstack/react-router";
5
6
  import { ArrowLeft } from "lucide-react";
6
7
  import { useEffect, useLayoutEffect, useRef, useState } from "react";
7
8
 
9
+ import { ConsoleFeatureUnavailable } from "@/components/ConsoleFeatureUnavailable";
8
10
  import { EventHistoryCard } from "@/components/features/insights/EventHistoryCard";
9
11
  import { InsightsPageHeader } from "@/components/features/insights/InsightsPageHeader";
10
12
  import { InstallationHistoryCard } from "@/components/features/insights/InstallationHistoryCard";
@@ -15,15 +17,20 @@ import {
15
17
  } from "@/components/features/insights/InstallationPageHeader";
16
18
  import { Button } from "@/components/ui/button";
17
19
  import { Card, CardContent } from "@/components/ui/card";
20
+ import { requireConsoleFeature } from "@/lib/console-features-api";
18
21
  import {
19
22
  useInsightsEventsQuery,
20
23
  useInsightsInstallationEventsQuery,
21
24
  useInsightsInstallationQuery,
22
25
  useInsightsInstallationsQuery,
26
+ useInsightsRetention,
23
27
  } from "@/lib/insights-api";
28
+ import { formatDays } from "@/lib/insights-retention";
29
+ import { DEFAULT_EVENT_RANGE, eventRangeBounds } from "@/lib/insights-view";
24
30
 
25
31
  import {
26
- getInsightsScrollRestorationKey,
32
+ type InsightsPaginationState,
33
+ installationsScrollRestorationKey,
27
34
  validateInstallationsSearch,
28
35
  } from "./-installations-search";
29
36
 
@@ -40,30 +47,50 @@ const popCursor = (stack: readonly string[]) => ({
40
47
  });
41
48
 
42
49
  export const Route = createFileRoute("/installations")({
50
+ beforeLoad: ({ context }) =>
51
+ requireConsoleFeature(context.queryClient, "insights"),
52
+ notFoundComponent: ConsoleFeatureUnavailable,
43
53
  component: InstallationsPage,
54
+ staticData: { scrollRestorationKey: installationsScrollRestorationKey },
44
55
  validateSearch: validateInstallationsSearch,
45
56
  });
46
57
 
47
58
  function InstallationsPage() {
48
59
  const search = Route.useSearch();
49
60
  const navigate = Route.useNavigate();
61
+ const retention = useInsightsRetention();
50
62
  const [draftQuery, setDraftQuery] = useState(search.query ?? "");
51
63
  const [initialEventsBefore] = useState(freshBefore);
52
64
  const [initialHistoryBefore] = useState(freshBefore);
53
- const [eventsBack, setEventsBack] = useState<readonly string[]>([]);
54
- const [searchBack, setSearchBack] = useState<readonly string[]>([]);
55
- const [historyBack, setHistoryBack] = useState<readonly string[]>([]);
65
+ const pagination = useLocation({
66
+ select: (location) => location.state.insightsPagination,
67
+ });
68
+ const eventsBack = pagination?.eventsBack ?? [];
69
+ const searchBack = pagination?.searchBack ?? [];
70
+ const historyBack = pagination?.historyBack ?? [];
56
71
  const query = search.query?.trim() ?? "";
57
72
  const hasSearchQuery = query.length > 0;
58
73
  const hasSelection = search.installId !== undefined;
59
74
  const hasLookup = hasSearchQuery || hasSelection;
60
75
  const eventsBefore = search.eventsBefore ?? initialEventsBefore;
76
+ const eventsRange = search.eventsRange ?? DEFAULT_EVENT_RANGE;
61
77
  const historyBefore = search.historyBefore ?? initialHistoryBefore;
62
78
 
63
- const updateSearch = (next: Partial<typeof search>, replace = false) => {
79
+ const updateSearch = (
80
+ next: Partial<typeof search>,
81
+ replace = false,
82
+ nextPagination: InsightsPaginationState = {},
83
+ ) => {
64
84
  void navigate({
65
85
  to: "/installations",
66
86
  search: { ...search, ...next },
87
+ state: (previous) => ({
88
+ ...previous,
89
+ insightsPagination: {
90
+ ...previous.insightsPagination,
91
+ ...nextPagination,
92
+ },
93
+ }),
67
94
  replace,
68
95
  });
69
96
  };
@@ -86,7 +113,7 @@ function InstallationsPage() {
86
113
 
87
114
  const events = useInsightsEventsQuery(
88
115
  {
89
- beforeReceivedAtMs: eventsBefore,
116
+ ...eventRangeBounds(eventsRange, eventsBefore),
90
117
  cursor: search.eventsCursor,
91
118
  limit: 20,
92
119
  },
@@ -104,7 +131,6 @@ function InstallationsPage() {
104
131
  useEffect(() => {
105
132
  const firstInstallId = matches.data?.data[0]?.installId;
106
133
  if (!hasSearchQuery || hasSelection || firstInstallId === undefined) return;
107
- setHistoryBack([]);
108
134
  updateSearch(
109
135
  {
110
136
  historyBefore: freshBefore(),
@@ -112,6 +138,7 @@ function InstallationsPage() {
112
138
  installId: firstInstallId,
113
139
  },
114
140
  true,
141
+ { historyBack: [] },
115
142
  );
116
143
  }, [hasSearchQuery, hasSelection, matches.data?.data]);
117
144
 
@@ -141,7 +168,7 @@ function InstallationsPage() {
141
168
  const scrollRef = useRef<HTMLDivElement>(null);
142
169
  const scrollEntry = useElementScrollRestoration({
143
170
  id: scrollRestorationId,
144
- getKey: getInsightsScrollRestorationKey,
171
+ getKey: installationsScrollRestorationKey,
145
172
  });
146
173
  useLayoutEffect(() => {
147
174
  if (!hasLookup && !events.isLoading && scrollRef.current) {
@@ -151,15 +178,17 @@ function InstallationsPage() {
151
178
 
152
179
  const clearLookup = () => {
153
180
  setDraftQuery("");
154
- setHistoryBack([]);
155
- setSearchBack([]);
156
- updateSearch({
157
- historyBefore: undefined,
158
- historyCursor: undefined,
159
- installId: undefined,
160
- query: undefined,
161
- searchCursor: undefined,
162
- });
181
+ updateSearch(
182
+ {
183
+ historyBefore: undefined,
184
+ historyCursor: undefined,
185
+ installId: undefined,
186
+ query: undefined,
187
+ searchCursor: undefined,
188
+ },
189
+ false,
190
+ { historyBack: [], searchBack: [] },
191
+ );
163
192
  };
164
193
  const installationLookup = (
165
194
  <InstallationSearchPanel
@@ -172,15 +201,17 @@ function InstallationsPage() {
172
201
  clearLookup();
173
202
  return;
174
203
  }
175
- setHistoryBack([]);
176
- setSearchBack([]);
177
- updateSearch({
178
- historyBefore: undefined,
179
- historyCursor: undefined,
180
- installId: undefined,
181
- query: nextQuery,
182
- searchCursor: undefined,
183
- });
204
+ updateSearch(
205
+ {
206
+ historyBefore: undefined,
207
+ historyCursor: undefined,
208
+ installId: undefined,
209
+ query: nextQuery,
210
+ searchCursor: undefined,
211
+ },
212
+ false,
213
+ { historyBack: [], searchBack: [] },
214
+ );
184
215
  }}
185
216
  />
186
217
  );
@@ -213,45 +244,60 @@ function InstallationsPage() {
213
244
  ) : null}
214
245
  {!hasLookup ? (
215
246
  <EventHistoryCard
247
+ description={`Downloads, launches, crashes, update failures, and other changes to what an installation runs, newest first, kept for ${formatDays(retention.rawDays)}. Launches that change nothing aren't listed.`}
216
248
  error={events.error}
217
249
  eventsLocation={{
218
250
  eventsBefore,
219
251
  eventsCursor: search.eventsCursor,
252
+ eventsRange: search.eventsRange,
220
253
  }}
221
254
  history={events.data}
222
255
  isFetching={events.isFetching}
223
256
  isLoading={events.isLoading}
224
257
  onNext={() => {
225
258
  if (!events.data?.nextCursor) return;
226
- setEventsBack(pushCursor(eventsBack, search.eventsCursor));
227
259
  updateSearch(
228
260
  {
229
261
  eventsCursor: events.data.nextCursor,
230
262
  },
231
263
  true,
264
+ { eventsBack: pushCursor(eventsBack, search.eventsCursor) },
232
265
  );
233
266
  }}
234
267
  onPrevious={() => {
235
268
  const previous = popCursor(eventsBack);
236
- setEventsBack(previous.stack);
237
269
  updateSearch(
238
270
  {
239
271
  eventsCursor: previous.cursor,
240
272
  },
241
273
  true,
274
+ { eventsBack: previous.stack },
275
+ );
276
+ }}
277
+ onRangeChange={(range) => {
278
+ updateSearch(
279
+ {
280
+ eventsBefore: freshBefore(),
281
+ eventsCursor: undefined,
282
+ eventsRange: range,
283
+ },
284
+ false,
285
+ { eventsBack: [] },
242
286
  );
243
287
  }}
244
288
  onRefresh={() => {
245
- setEventsBack([]);
246
289
  updateSearch(
247
290
  {
248
291
  eventsBefore: freshBefore(),
249
292
  eventsCursor: undefined,
250
293
  },
251
294
  true,
295
+ { eventsBack: [] },
252
296
  );
253
297
  }}
254
298
  pageNumber={eventsBack.length + 1}
299
+ range={eventsRange}
300
+ rawDays={retention.rawDays}
255
301
  >
256
302
  {installationLookup}
257
303
  </EventHistoryCard>
@@ -266,33 +312,38 @@ function InstallationsPage() {
266
312
  error={matches.error}
267
313
  onNext={() => {
268
314
  if (!matches.data?.nextCursor) return;
269
- setSearchBack(pushCursor(searchBack, search.searchCursor));
270
315
  updateSearch(
271
316
  {
272
317
  installId: undefined,
273
318
  searchCursor: matches.data.nextCursor,
274
319
  },
275
320
  true,
321
+ {
322
+ searchBack: pushCursor(searchBack, search.searchCursor),
323
+ },
276
324
  );
277
325
  }}
278
326
  onPrevious={() => {
279
327
  const previous = popCursor(searchBack);
280
- setSearchBack(previous.stack);
281
328
  updateSearch(
282
329
  {
283
330
  installId: undefined,
284
331
  searchCursor: previous.cursor,
285
332
  },
286
333
  true,
334
+ { searchBack: previous.stack },
287
335
  );
288
336
  }}
289
337
  onSelect={(installId) => {
290
- setHistoryBack([]);
291
- updateSearch({
292
- historyBefore: freshBefore(),
293
- historyCursor: undefined,
294
- installId,
295
- });
338
+ updateSearch(
339
+ {
340
+ historyBefore: freshBefore(),
341
+ historyCursor: undefined,
342
+ installId,
343
+ },
344
+ false,
345
+ { historyBack: [] },
346
+ );
296
347
  }}
297
348
  pageNumber={searchBack.length + 1}
298
349
  results={matches.data}
@@ -305,35 +356,41 @@ function InstallationsPage() {
305
356
  isLoading={history.isLoading || selectedInstallation.isLoading}
306
357
  onNext={() => {
307
358
  if (!history.data?.nextCursor) return;
308
- setHistoryBack(pushCursor(historyBack, search.historyCursor));
309
359
  updateSearch(
310
360
  {
311
361
  historyCursor: history.data.nextCursor,
312
362
  },
313
363
  true,
364
+ {
365
+ historyBack: pushCursor(
366
+ historyBack,
367
+ search.historyCursor,
368
+ ),
369
+ },
314
370
  );
315
371
  }}
316
372
  onPrevious={() => {
317
373
  const previous = popCursor(historyBack);
318
- setHistoryBack(previous.stack);
319
374
  updateSearch(
320
375
  {
321
376
  historyCursor: previous.cursor,
322
377
  },
323
378
  true,
379
+ { historyBack: previous.stack },
324
380
  );
325
381
  }}
326
382
  onRefresh={() => {
327
- setHistoryBack([]);
328
383
  updateSearch(
329
384
  {
330
385
  historyBefore: freshBefore(),
331
386
  historyCursor: undefined,
332
387
  },
333
388
  true,
389
+ { historyBack: [] },
334
390
  );
335
391
  }}
336
392
  pageNumber={historyBack.length + 1}
393
+ retention={retention}
337
394
  selectedEvent={selectedEvent}
338
395
  selectedInstallId={search.installId}
339
396
  />
@@ -0,0 +1,42 @@
1
+ import { createFileRoute } from "@tanstack/react-router";
2
+
3
+ import { ConsoleFeatureUnavailable } from "@/components/ConsoleFeatureUnavailable";
4
+ import {
5
+ REMOTE_CONFIG_TABS,
6
+ RemoteConfigPage,
7
+ type RemoteConfigTab,
8
+ } from "@/components/features/remote-config/RemoteConfigPage";
9
+ import { requireConsoleFeature } from "@/lib/console-features-api";
10
+
11
+ interface RemoteConfigSearch {
12
+ /** The open view; Parameters when absent. */
13
+ readonly tab?: RemoteConfigTab;
14
+ }
15
+
16
+ export const Route = createFileRoute("/remote-config")({
17
+ beforeLoad: ({ context }) =>
18
+ requireConsoleFeature(context.queryClient, "remoteConfig"),
19
+ notFoundComponent: ConsoleFeatureUnavailable,
20
+ component: RemoteConfigRoute,
21
+ validateSearch: (search: Record<string, unknown>): RemoteConfigSearch =>
22
+ REMOTE_CONFIG_TABS.includes(search.tab as RemoteConfigTab) &&
23
+ search.tab !== "parameters"
24
+ ? { tab: search.tab as RemoteConfigTab }
25
+ : {},
26
+ });
27
+
28
+ function RemoteConfigRoute() {
29
+ const { tab = "parameters" } = Route.useSearch();
30
+ const navigate = Route.useNavigate();
31
+ return (
32
+ <RemoteConfigPage
33
+ onTabChange={(next) =>
34
+ void navigate({
35
+ search: next === "parameters" ? {} : { tab: next },
36
+ replace: true,
37
+ })
38
+ }
39
+ tab={tab}
40
+ />
41
+ );
42
+ }