@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
@@ -13,14 +13,14 @@ import {
13
13
  Tags,
14
14
  X,
15
15
  } from "lucide-react";
16
- import { Fragment, useEffect, useState } from "react";
16
+ import { Fragment, type MouseEvent, useEffect, useState } from "react";
17
17
 
18
18
  import { BundleIdDisplay } from "@/components/BundleIdDisplay";
19
19
  import { ChannelBadge } from "@/components/ChannelBadge";
20
20
  import { EnabledStatusIcon } from "@/components/EnabledStatusIcon";
21
21
  import { BundleChildrenPanel } from "@/components/features/bundles/BundleChildrenPanel";
22
- import { BundleMovementSummary } from "@/components/features/bundles/BundleInsightsSummary";
23
22
  import { ChannelManagementDialog } from "@/components/features/channels/ChannelManagementDialog";
23
+ import { useReleaseColumns } from "@/components/features/FeatureSlots";
24
24
  import { ReleaseEditorSheet } from "@/components/features/releases/ReleaseEditorSheet";
25
25
  import { ReleaseStateBadge } from "@/components/features/releases/ReleaseStateBadge";
26
26
  import { HashValueDisplay } from "@/components/HashValueDisplay";
@@ -29,7 +29,6 @@ import { RolloutPercentageBadge } from "@/components/RolloutPercentageBadge";
29
29
  import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
30
30
  import { Badge } from "@/components/ui/badge";
31
31
  import { Button, buttonVariants } from "@/components/ui/button";
32
- import { Input } from "@/components/ui/input";
33
32
  import {
34
33
  Select,
35
34
  SelectContent,
@@ -56,11 +55,12 @@ import {
56
55
  useChannelsQuery,
57
56
  useReleasesQuery,
58
57
  } from "@/lib/api";
59
- import { useBundleActivityQuery } from "@/lib/bundle-activity";
60
58
  import type { ReleaseListRow } from "@/lib/server/releaseReachability";
61
59
  import { cn } from "@/lib/utils";
62
60
 
63
61
  import {
62
+ hasReleaseFilters,
63
+ releaseFilterOf,
64
64
  type ReleaseSearch,
65
65
  updateReleaseFilters,
66
66
  validateReleaseSearch,
@@ -68,10 +68,14 @@ import {
68
68
 
69
69
  const PAGE_SIZE = 20;
70
70
  const platformFilterItems = [
71
- { label: "All Platforms", value: "all" },
72
71
  { label: "iOS", value: "ios" },
73
72
  { label: "Android", value: "android" },
74
73
  ];
74
+ const statusFilterItems = [
75
+ { label: "Any Status", value: "all" },
76
+ { label: "Enabled", value: "enabled" },
77
+ { label: "Disabled", value: "disabled" },
78
+ ];
75
79
  const dateFormatter = new Intl.DateTimeFormat(undefined, {
76
80
  dateStyle: "medium",
77
81
  timeStyle: "short",
@@ -80,6 +84,21 @@ const tableDateFormatter = new Intl.DateTimeFormat(undefined, {
80
84
  dateStyle: "medium",
81
85
  });
82
86
 
87
+ /**
88
+ * Opens a bundle from a click anywhere on its row or card, except on a
89
+ * control inside it, which keeps its own action.
90
+ */
91
+ const openOutsideControls =
92
+ (open: () => void) => (event: MouseEvent<HTMLElement>) => {
93
+ if (
94
+ event.target instanceof Element &&
95
+ event.target.closest("button, a, input, select, textarea")
96
+ ) {
97
+ return;
98
+ }
99
+ open();
100
+ };
101
+
83
102
  export const Route = createFileRoute("/")({
84
103
  component: BundlesPage,
85
104
  validateSearch: validateReleaseSearch,
@@ -98,16 +117,9 @@ function BundleFilterToolbar({
98
117
  onManageChannels: () => void;
99
118
  search: ReleaseSearch;
100
119
  }) {
101
- const [targetAppVersion, setTargetAppVersion] = useState(
102
- search.targetAppVersion ?? "",
103
- );
104
- const hasFilters = Boolean(
105
- search.bundleId ||
106
- search.channelId ||
107
- search.enabled !== undefined ||
108
- search.platform ||
109
- search.targetAppVersion,
110
- );
120
+ const hasFilters = hasReleaseFilters(search);
121
+ // Releases list by channel and platform together: the index serves that pair.
122
+ const channelSelected = search.channelId !== undefined;
111
123
  const channelFilterItems = [
112
124
  { label: "All Channels", value: "all" },
113
125
  ...channels.map((channel) => ({
@@ -115,14 +127,6 @@ function BundleFilterToolbar({
115
127
  value: channel.id,
116
128
  })),
117
129
  ];
118
- const applyTargetAppVersion = () => {
119
- const value = targetAppVersion.trim();
120
- onChange({ targetAppVersion: value || undefined });
121
- };
122
-
123
- useEffect(() => {
124
- setTargetAppVersion(search.targetAppVersion ?? "");
125
- }, [search.targetAppVersion]);
126
130
 
127
131
  return (
128
132
  <header className="sticky top-0 z-10 flex shrink-0 flex-wrap items-center gap-2 border-b bg-background px-3 py-3 sm:h-12 sm:flex-nowrap sm:bg-card/70 sm:px-4 sm:py-0 sm:backdrop-blur-sm">
@@ -133,65 +137,85 @@ function BundleFilterToolbar({
133
137
  <span className="text-xs font-medium">Filters</span>
134
138
  </div>
135
139
  <Select
136
- items={platformFilterItems}
140
+ items={channelFilterItems}
137
141
  onValueChange={(value) =>
138
142
  onChange({
139
- platform:
140
- value === "all" ? undefined : (value as "ios" | "android"),
143
+ channelId: value === null || value === "all" ? undefined : value,
141
144
  })
142
145
  }
143
- value={search.platform ?? "all"}
146
+ value={search.channelId ?? "all"}
147
+ >
148
+ <SelectTrigger
149
+ aria-label="Channel"
150
+ className="h-8 w-[calc(50%-0.25rem)] min-w-[132px] text-xs sm:w-[140px]"
151
+ >
152
+ <SelectValue />
153
+ </SelectTrigger>
154
+ <SelectContent>
155
+ <SelectGroup>
156
+ <SelectItem value="all">All Channels</SelectItem>
157
+ {channels.map((channel) => (
158
+ <SelectItem key={channel.id} value={channel.id}>
159
+ {channel.name}
160
+ </SelectItem>
161
+ ))}
162
+ </SelectGroup>
163
+ </SelectContent>
164
+ </Select>
165
+ <Select
166
+ disabled={!channelSelected}
167
+ items={platformFilterItems}
168
+ onValueChange={(value) =>
169
+ onChange({ platform: value === "android" ? "android" : "ios" })
170
+ }
171
+ value={search.platform ?? "ios"}
144
172
  >
145
173
  <SelectTrigger
146
174
  aria-label="Platform"
147
175
  className="h-8 w-[calc(50%-0.25rem)] min-w-[132px] text-xs sm:w-[140px]"
176
+ title={channelSelected ? undefined : "Choose a channel first"}
148
177
  >
149
178
  <SelectValue />
150
179
  </SelectTrigger>
151
180
  <SelectContent>
152
181
  <SelectGroup>
153
- <SelectItem value="all">All Platforms</SelectItem>
154
182
  <SelectItem value="ios">iOS</SelectItem>
155
183
  <SelectItem value="android">Android</SelectItem>
156
184
  </SelectGroup>
157
185
  </SelectContent>
158
186
  </Select>
159
- <Input
160
- aria-label="Target app version"
161
- className="h-8 w-full min-w-[132px] text-xs sm:w-[160px]"
162
- onBlur={applyTargetAppVersion}
163
- onChange={(event) => setTargetAppVersion(event.target.value)}
164
- onKeyDown={(event) => {
165
- if (event.key === "Enter") {
166
- applyTargetAppVersion();
167
- }
168
- }}
169
- placeholder="Target version"
170
- value={targetAppVersion}
171
- />
172
187
  <Select
173
- items={channelFilterItems}
188
+ disabled={!channelSelected && search.scopeKey === undefined}
189
+ items={statusFilterItems}
174
190
  onValueChange={(value) =>
175
191
  onChange({
176
- channelId: value === null || value === "all" ? undefined : value,
192
+ enabled:
193
+ value === "enabled"
194
+ ? true
195
+ : value === "disabled"
196
+ ? false
197
+ : undefined,
177
198
  })
178
199
  }
179
- value={search.channelId ?? "all"}
200
+ value={
201
+ search.enabled === undefined
202
+ ? "all"
203
+ : search.enabled
204
+ ? "enabled"
205
+ : "disabled"
206
+ }
180
207
  >
181
208
  <SelectTrigger
182
- aria-label="Channel"
209
+ aria-label="Status"
183
210
  className="h-8 w-[calc(50%-0.25rem)] min-w-[132px] text-xs sm:w-[140px]"
184
211
  >
185
212
  <SelectValue />
186
213
  </SelectTrigger>
187
214
  <SelectContent>
188
215
  <SelectGroup>
189
- <SelectItem value="all">All Channels</SelectItem>
190
- {channels.map((channel) => (
191
- <SelectItem key={channel.id} value={channel.id}>
192
- {channel.name}
193
- </SelectItem>
194
- ))}
216
+ <SelectItem value="all">Any Status</SelectItem>
217
+ <SelectItem value="enabled">Enabled</SelectItem>
218
+ <SelectItem value="disabled">Disabled</SelectItem>
195
219
  </SelectGroup>
196
220
  </SelectContent>
197
221
  </Select>
@@ -217,6 +241,19 @@ function BundleFilterToolbar({
217
241
  </button>
218
242
  </Badge>
219
243
  ) : null}
244
+ {search.scopeKey ? (
245
+ <Badge className="max-w-48 gap-1" variant="secondary">
246
+ Target filter
247
+ <button
248
+ aria-label="Clear target filter"
249
+ className="rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
250
+ onClick={() => onChange({ scopeKey: undefined })}
251
+ type="button"
252
+ >
253
+ <X className="size-3" />
254
+ </button>
255
+ </Badge>
256
+ ) : null}
220
257
  {hasFilters ? (
221
258
  <Button
222
259
  className="h-8 px-2 text-xs text-muted-foreground hover:text-foreground sm:ml-auto"
@@ -283,7 +320,7 @@ function BundleEntry({
283
320
  <BundleIdDisplay bundleId={release.id} fullOnMobile />
284
321
  {!release.bundle_id ? (
285
322
  <span className="inline-flex items-center gap-1.5 text-sm">
286
- <RotateCcw className="size-3.5" /> Built-in app
323
+ <RotateCcw className="size-3.5" /> Built-in bundle
287
324
  </span>
288
325
  ) : null}
289
326
  </button>
@@ -294,9 +331,9 @@ function BundleEntry({
294
331
  ) : null}
295
332
  {release.currentlyUnreachable ? (
296
333
  <span
297
- aria-label="Currently unreachable. No catalog segment or cohort selects this bundle first."
334
+ aria-label="Unreachable: no device gets this bundle with the current delivery settings."
298
335
  className="inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground"
299
- title="No catalog segment or cohort selects this bundle first with the current delivery settings."
336
+ title="No device gets this bundle with the current delivery settings."
300
337
  >
301
338
  <CircleOff className="size-3.5" />
302
339
  Unreachable
@@ -376,13 +413,8 @@ function BundlesPage() {
376
413
  const releasesQuery = useReleasesQuery({
377
414
  afterReleaseId: search.afterReleaseId,
378
415
  beforeReleaseId: search.beforeReleaseId,
379
- bundleId: search.bundleId,
380
- channelId: search.channelId,
381
- enabled: search.enabled,
416
+ filter: releaseFilterOf(search),
382
417
  limit: PAGE_SIZE,
383
- page: search.page,
384
- platform: search.platform,
385
- targetAppVersion: search.targetAppVersion,
386
418
  });
387
419
  const channelsQuery = useChannelsQuery();
388
420
  const channels = channelsQuery.data ?? [];
@@ -396,17 +428,26 @@ function BundlesPage() {
396
428
  );
397
429
  const patchCountsQuery = useBundleChildCountsQuery(bundleIds);
398
430
  const patchCountsByBundleId = patchCountsQuery.data ?? {};
399
- const pagination = releasesQuery.data?.pagination;
400
- const activityQuery = useBundleActivityQuery(
401
- releases.flatMap((release) => {
402
- const channel = channels.find(
403
- (item) => item.id === release.channel_id,
404
- )?.name;
405
- return channel
406
- ? [{ releaseId: release.id, platform: release.platform, channel }]
407
- : [];
408
- }),
409
- );
431
+ const nextCursor = releasesQuery.data?.next;
432
+ const previousCursor = releasesQuery.data?.previous;
433
+ const slotReleases = releases.flatMap((release) => {
434
+ const channel = channels.find(
435
+ (item) => item.id === release.channel_id,
436
+ )?.name;
437
+ return channel
438
+ ? [
439
+ {
440
+ releaseId: release.id,
441
+ platform: release.platform,
442
+ channel,
443
+ builtIn: !release.bundle_id,
444
+ },
445
+ ]
446
+ : [];
447
+ });
448
+ // Features add columns, such as release activity where the console reads it.
449
+ const featureColumns = useReleaseColumns();
450
+ const columnCount = 10 + featureColumns.length;
410
451
  const channelNames = new Map(
411
452
  channels.map((channel) => [channel.id, channel.name]),
412
453
  );
@@ -435,33 +476,31 @@ function BundlesPage() {
435
476
  }
436
477
  changeFilters({ bundleId: bundle.id });
437
478
  };
438
- const currentPage = pagination?.currentPage ?? 1;
439
- const startEntry =
440
- releases.length === 0 ? 0 : (currentPage - 1) * PAGE_SIZE + 1;
441
- const endEntry = startEntry === 0 ? 0 : startEntry + releases.length - 1;
442
- const previousPage = currentPage - 1;
443
- const firstReleaseId = releases[0]?.id;
444
- const lastReleaseId = releases.at(-1)?.id;
445
- const previousSearch: ReleaseSearch | null =
446
- pagination?.hasPreviousPage && firstReleaseId
447
- ? {
448
- ...search,
449
- afterReleaseId: firstReleaseId,
450
- beforeReleaseId: undefined,
451
- page: previousPage > 1 ? previousPage : undefined,
452
- releaseId: undefined,
453
- }
454
- : null;
455
- const nextSearch: ReleaseSearch | null =
456
- pagination?.hasNextPage && lastReleaseId
457
- ? {
458
- ...search,
459
- afterReleaseId: undefined,
460
- beforeReleaseId: lastReleaseId,
461
- page: currentPage + 1,
462
- releaseId: undefined,
463
- }
464
- : null;
479
+ // Pages go by key: newer or older than an edge release, never by number.
480
+ const onLaterPage =
481
+ search.afterReleaseId !== undefined || search.beforeReleaseId !== undefined;
482
+ const previousSearch: ReleaseSearch | null = previousCursor
483
+ ? {
484
+ ...search,
485
+ afterReleaseId: previousCursor,
486
+ beforeReleaseId: undefined,
487
+ releaseId: undefined,
488
+ }
489
+ : null;
490
+ const nextSearch: ReleaseSearch | null = nextCursor
491
+ ? {
492
+ ...search,
493
+ afterReleaseId: undefined,
494
+ beforeReleaseId: nextCursor,
495
+ releaseId: undefined,
496
+ }
497
+ : null;
498
+ const newestSearch: ReleaseSearch = {
499
+ ...search,
500
+ afterReleaseId: undefined,
501
+ beforeReleaseId: undefined,
502
+ releaseId: undefined,
503
+ };
465
504
 
466
505
  return (
467
506
  <div className="flex h-svh min-h-0 min-w-0 flex-col">
@@ -503,13 +542,24 @@ function BundlesPage() {
503
542
 
504
543
  return (
505
544
  <Fragment key={release.id}>
545
+ {/* The ID button opens it for keyboards and screen
546
+ readers; a tap anywhere else on the card does too. */}
506
547
  <article
548
+ aria-label={`Bundle ${release.id}`}
507
549
  className={cn(
508
- "flex flex-col gap-4 border-b p-4 last:border-b-0",
550
+ "flex cursor-pointer touch-manipulation flex-col gap-4 border-b p-4 transition-[background-color,filter] last:border-b-0 hover:bg-muted/10 active:bg-muted/20 motion-reduce:transition-none data-[state=selected]:bg-muted/15",
509
551
  release.currentlyUnreachable &&
510
- "bg-muted/35 saturate-50 transition-[background-color,filter] focus-within:saturate-100 motion-reduce:transition-none",
552
+ "bg-muted/35 saturate-50 hover:bg-muted/50 hover:saturate-100 focus-within:saturate-100",
511
553
  isExpanded && "border-b-0 bg-primary/5",
512
554
  )}
555
+ data-state={
556
+ search.releaseId === release.id
557
+ ? "selected"
558
+ : undefined
559
+ }
560
+ onClick={openOutsideControls(() =>
561
+ go({ ...search, releaseId: release.id }, false),
562
+ )}
513
563
  >
514
564
  <div className="flex items-start justify-between gap-3">
515
565
  <BundleEntry
@@ -568,24 +618,29 @@ function BundlesPage() {
568
618
  : "—"}
569
619
  </dd>
570
620
  </div>
571
- <div className="col-span-2 rounded-md bg-muted/40 p-3">
572
- <dt className="mb-2 text-muted-foreground">
573
- Activity · 30d
574
- </dt>
575
- <dd>
576
- <BundleMovementSummary
577
- report={activityQuery.data?.[release.id]}
578
- loading={activityQuery.isFetching}
579
- />
580
- </dd>
581
- </div>
621
+ {featureColumns.map(({ Cell, feature, label }) => (
622
+ <div
623
+ className="col-span-2 rounded-md bg-muted/40 p-3"
624
+ key={feature}
625
+ >
626
+ <dt className="mb-2 text-muted-foreground">
627
+ {label}
628
+ </dt>
629
+ <dd>
630
+ <Cell
631
+ releaseId={release.id}
632
+ releases={slotReleases}
633
+ />
634
+ </dd>
635
+ </div>
636
+ ))}
582
637
  <div className="col-span-2 flex flex-wrap items-center gap-2 rounded-md bg-muted/40 p-3">
583
638
  <ReleaseStateBadge release={release} />
584
639
  {release.should_force_update ? (
585
640
  <Badge variant="secondary">Force update</Badge>
586
641
  ) : null}
587
642
  </div>
588
- <div className="col-span-2 grid gap-2 rounded-md border bg-background/80 p-3">
643
+ <div className="col-span-2 grid grid-cols-1 gap-2 rounded-md border bg-background/80 p-3">
589
644
  <div className="flex items-start justify-between gap-3">
590
645
  <dt className="text-muted-foreground">
591
646
  Target
@@ -619,13 +674,11 @@ function BundlesPage() {
619
674
  })
620
675
  ) : (
621
676
  <p className="p-8 text-center text-sm text-muted-foreground">
622
- {search.bundleId ||
623
- search.channelId ||
624
- search.enabled !== undefined ||
625
- search.platform ||
626
- search.targetAppVersion
627
- ? "No bundles match these filters."
628
- : "No bundles yet. Deploy an update to see it here."}
677
+ {onLaterPage
678
+ ? "No more bundles on this side."
679
+ : hasReleaseFilters(search)
680
+ ? "No bundles match these filters."
681
+ : "No bundles yet. Deploy an update to see it here."}
629
682
  </p>
630
683
  )}
631
684
  </div>
@@ -641,7 +694,9 @@ function BundlesPage() {
641
694
  <TableHead>Enabled</TableHead>
642
695
  <TableHead>Force update</TableHead>
643
696
  <TableHead>Rollout</TableHead>
644
- <TableHead>Activity · 30d</TableHead>
697
+ {featureColumns.map(({ feature, label }) => (
698
+ <TableHead key={feature}>{label}</TableHead>
699
+ ))}
645
700
  <TableHead>Message</TableHead>
646
701
  <TableHead>Created</TableHead>
647
702
  </TableRow>
@@ -650,7 +705,7 @@ function BundlesPage() {
650
705
  {releasesQuery.isPending
651
706
  ? Array.from({ length: 7 }, (_, index) => (
652
707
  <TableRow key={index}>
653
- <TableCell colSpan={11}>
708
+ <TableCell colSpan={columnCount}>
654
709
  <Skeleton className="h-8 w-full" />
655
710
  </TableCell>
656
711
  </TableRow>
@@ -674,17 +729,9 @@ function BundlesPage() {
674
729
  ? "selected"
675
730
  : undefined
676
731
  }
677
- onClick={(event) => {
678
- if (
679
- event.target instanceof Element &&
680
- event.target.closest(
681
- "button, a, input, select, textarea",
682
- )
683
- ) {
684
- return;
685
- }
686
- go({ ...search, releaseId: release.id }, false);
687
- }}
732
+ onClick={openOutsideControls(() =>
733
+ go({ ...search, releaseId: release.id }, false),
734
+ )}
688
735
  onKeyDown={(event) => {
689
736
  if (
690
737
  event.target !== event.currentTarget ||
@@ -807,12 +854,14 @@ function BundlesPage() {
807
854
  percentage={release.rollout_cohort_count / 10}
808
855
  />
809
856
  </TableCell>
810
- <TableCell>
811
- <BundleMovementSummary
812
- report={activityQuery.data?.[release.id]}
813
- loading={activityQuery.isFetching}
814
- />
815
- </TableCell>
857
+ {featureColumns.map(({ Cell, feature }) => (
858
+ <TableCell key={feature}>
859
+ <Cell
860
+ releaseId={release.id}
861
+ releases={slotReleases}
862
+ />
863
+ </TableCell>
864
+ ))}
816
865
  <TableCell
817
866
  className="text-sm text-muted-foreground"
818
867
  title={release.message ?? undefined}
@@ -834,7 +883,7 @@ function BundlesPage() {
834
883
  <TableRow className="hover:bg-transparent">
835
884
  <TableCell
836
885
  className="border-t-0 p-0"
837
- colSpan={11}
886
+ colSpan={columnCount}
838
887
  >
839
888
  <ReleaseBundleChildrenPanel
840
889
  onDetailClick={openChildBundle}
@@ -851,15 +900,13 @@ function BundlesPage() {
851
900
  <TableRow>
852
901
  <TableCell
853
902
  className="h-40 text-center text-muted-foreground"
854
- colSpan={11}
903
+ colSpan={columnCount}
855
904
  >
856
- {search.bundleId ||
857
- search.channelId ||
858
- search.enabled !== undefined ||
859
- search.platform ||
860
- search.targetAppVersion
861
- ? "No bundles match these filters."
862
- : "No bundles yet. Deploy an update to see it here."}
905
+ {onLaterPage
906
+ ? "No more bundles on this side."
907
+ : hasReleaseFilters(search)
908
+ ? "No bundles match these filters."
909
+ : "No bundles yet. Deploy an update to see it here."}
863
910
  </TableCell>
864
911
  </TableRow>
865
912
  ) : null}
@@ -875,13 +922,23 @@ function BundlesPage() {
875
922
  className="flex flex-col gap-3 px-2 sm:flex-row sm:items-center sm:justify-between"
876
923
  >
877
924
  <p className="text-xs font-medium text-muted-foreground">
878
- Showing <span className="text-foreground">{startEntry}</span> to{" "}
879
- <span className="text-foreground">{endEntry}</span> entries
925
+ <span className="text-foreground">{releases.length}</span>{" "}
926
+ {releases.length === 1 ? "entry" : "entries"}, newest first
880
927
  </p>
881
928
  <div className="flex flex-wrap items-center gap-3 sm:justify-end">
882
- <p className="text-xs font-medium text-muted-foreground">
883
- Page <span className="text-foreground">{currentPage}</span>
884
- </p>
929
+ {onLaterPage ? (
930
+ <Link
931
+ className={buttonVariants({
932
+ className: "h-8 flex-1 px-3 text-xs sm:flex-none",
933
+ size: "sm",
934
+ variant: "ghost",
935
+ })}
936
+ search={newestSearch}
937
+ to="/"
938
+ >
939
+ Newest
940
+ </Link>
941
+ ) : null}
885
942
  {previousSearch ? (
886
943
  <Link
887
944
  className={buttonVariants({
@@ -940,6 +997,7 @@ function BundlesPage() {
940
997
  onOpenChange={(open) =>
941
998
  !open && go({ ...search, releaseId: undefined }, false)
942
999
  }
1000
+ onShowScope={(scopeKey) => changeFilters({ scopeKey })}
943
1001
  open={Boolean(search.releaseId)}
944
1002
  releaseId={search.releaseId ?? ""}
945
1003
  />