@hot-updater/console 1.1.4 → 1.2.0-rc.1

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 (273) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/-native-builds-search-C-TGpFAm.js +1 -0
  3. package/.output/public/assets/{ConsoleFeatureUnavailable-C0KfsKWL.js → ConsoleFeatureUnavailable-CUtcXuY2.js} +1 -1
  4. package/.output/public/assets/EventDetails-Dl2RvPAk.js +1 -0
  5. package/.output/public/assets/HashValueDisplay-6lNRxDJX.js +1 -0
  6. package/.output/public/assets/InsightsPageHeader-DHhqY3DT.js +1 -0
  7. package/.output/public/assets/NotFoundPage-DZ3DxyRA.js +1 -0
  8. package/.output/public/assets/PlatformIcon-BBWQlWm9.js +1 -0
  9. package/.output/public/assets/RolloutPercentageBadge-Cxogpi8F.js +1 -0
  10. package/.output/public/assets/StoreSubmission-JpngSvHS.js +1 -0
  11. package/.output/public/assets/alert-dialog-BqYScr9y.js +1 -0
  12. package/.output/public/assets/api-P2g8dCNg.js +1 -0
  13. package/.output/public/assets/api-keys-DWQC2q6y.js +1 -0
  14. package/.output/public/assets/api-keys-FueRkF9q.js +1 -0
  15. package/.output/public/assets/arrow-left-g9cKqXuC.js +1 -0
  16. package/.output/public/assets/arrow-right-CNII8TYn.js +1 -0
  17. package/.output/public/assets/arrow-up-right-DK8Zdg40.js +1 -0
  18. package/.output/public/assets/badge-DWFcDWho.js +1 -0
  19. package/.output/public/assets/check-CXVR3s3X.js +1 -0
  20. package/.output/public/assets/chevron-right-BoUXo82O.js +1 -0
  21. package/.output/public/assets/circle-off-C7Muo0ss.js +1 -0
  22. package/.output/public/assets/{clipboard-DTkrlIMn.js → clipboard-DQbOcrL7.js} +1 -1
  23. package/.output/public/assets/console-features-api-DEXSwFtc.js +1 -0
  24. package/.output/public/assets/copy-DsuLj3oj.js +1 -0
  25. package/.output/public/assets/dialog-CNUPxNxH.js +1 -0
  26. package/.output/public/assets/{dist-dDj-R6uh.js → dist-CPlO6ko9.js} +1 -1
  27. package/.output/public/assets/download-Cf24LBde.js +1 -0
  28. package/.output/public/assets/{empty-Bf4DanOL.js → empty-B3Uwdd6k.js} +1 -1
  29. package/.output/public/assets/{field-GxF9SyUo.js → field-D-2-mnNh.js} +1 -1
  30. package/.output/public/assets/history-DqHlgSuV.js +1 -0
  31. package/.output/public/assets/index-BWU6owTG.js +10 -0
  32. package/.output/public/assets/{input-group-B4nrqIh0.js → input-group-D3OUBk4n.js} +1 -1
  33. package/.output/public/assets/insights-B-0odqpt.js +42 -0
  34. package/.output/public/assets/insights-BSVGfxSL.js +1 -0
  35. package/.output/public/assets/insights-NK5B6ReS.js +2 -0
  36. package/.output/public/assets/insights-api-BVCXJsoV.js +1 -0
  37. package/.output/public/assets/insights-search-DghCbEnk.js +1 -0
  38. package/.output/public/assets/insights-usage-rpc-CKQH0pUS.js +1 -0
  39. package/.output/public/assets/insights_.distribution-CJvgwcVZ.js +2 -0
  40. package/.output/public/assets/insights_.distribution-DblZM0mO.js +1 -0
  41. package/.output/public/assets/insights_.distribution-dvk6vYDM.js +1 -0
  42. package/.output/public/assets/installations-B674P5Z5.js +2 -0
  43. package/.output/public/assets/installations-BWxczZ2t.js +1 -0
  44. package/.output/public/assets/installations-Dv4kWQYz.js +1 -0
  45. package/.output/public/assets/{key-round-B4fI1psK.js → key-round-DwNKkHhy.js} +1 -1
  46. package/.output/public/assets/link-DTEyW70e.js +2 -0
  47. package/.output/public/assets/{list-Dtj7TtzV.js → list--NIee1FZ.js} +1 -1
  48. package/.output/public/assets/native-builds-BOC71grI.js +1 -0
  49. package/.output/public/assets/native-builds-BswZazmc.js +1 -0
  50. package/.output/public/assets/native-builds-c4BpOLAe.js +2 -0
  51. package/.output/public/assets/native-builds_._buildId-D_Spjl10.js +1 -0
  52. package/.output/public/assets/native-builds_._buildId-DtoXD8rz.js +2 -0
  53. package/.output/public/assets/plus-BReVKct-.js +1 -0
  54. package/.output/public/assets/preload-helper-B44EvKhG.js +1 -0
  55. package/.output/public/assets/{refresh-cw-DjIKjmrS.js → refresh-cw-B9HHjLej.js} +1 -1
  56. package/.output/public/assets/relativeTime-YQt84dYr.js +1 -0
  57. package/.output/public/assets/remote-config-B7ujqhLa.js +6 -0
  58. package/.output/public/assets/remote-config-WjpozBG5.js +1 -0
  59. package/.output/public/assets/remote-config-gXeH6PBW.js +1 -0
  60. package/.output/public/assets/rotate-cw-Cv2aVbYr.js +1 -0
  61. package/.output/public/assets/routes-BDdBIxYS.js +1 -0
  62. package/.output/public/assets/routes-CCD33Azv.js +2 -0
  63. package/.output/public/assets/search-BCel4Oy6.js +1 -0
  64. package/.output/public/assets/select-CBIU0_M1.js +1 -0
  65. package/.output/public/assets/{sidebar-BqX460IW.js → sidebar-Di9SN47G.js} +1 -1
  66. package/.output/public/assets/smartphone-CMaSy_ir.js +1 -0
  67. package/.output/public/assets/styles-Ccxthuz1.css +2 -0
  68. package/.output/public/assets/switch-BpdiHL_t.js +1 -0
  69. package/.output/public/assets/{table-ek1DmRHq.js → table-Bpy3SvSo.js} +1 -1
  70. package/.output/public/assets/{tabs-CgEH_fMT.js → tabs-Xmt7tZeG.js} +1 -1
  71. package/.output/public/assets/{undo-2-DxXx9Zvr.js → undo-2-CUX96_BG.js} +1 -1
  72. package/.output/public/assets/useCompositeListItem-JmyDfo3-.js +1 -0
  73. package/.output/public/assets/useInfiniteQuery-DX4WpBRG.js +1 -0
  74. package/.output/public/assets/useMutation-DbYw7boA.js +1 -0
  75. package/.output/public/assets/uuidv7-zXMtwR9Q.js +1 -0
  76. package/.output/public/assets/{valueToPercent-B44vDssN.js → valueToPercent-BLwGy_xK.js} +1 -1
  77. package/.output/server/{__23tanstack-start-server-fn-resolver-KpQ5Z_24.mjs → __23tanstack-start-server-fn-resolver-CygMPYHZ.mjs} +64 -40
  78. package/.output/server/_chunks/ssr-renderer.mjs +2 -2
  79. package/.output/server/_libs/@tanstack/react-router+[...].mjs +1 -1
  80. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +1 -1
  81. package/.output/server/_libs/lucide-react.mjs +88 -1
  82. package/.output/server/_libs/uqr.mjs +858 -0
  83. package/.output/server/_ssr/-native-builds-search-CwNcoXf4.mjs +37 -0
  84. package/.output/server/_ssr/{BundleIdDisplay-CjSnY65J.mjs → BundleIdDisplay-DOPz4pUS.mjs} +2 -2
  85. package/.output/server/_ssr/{ConsoleFeatureUnavailable-CHxn9f-w.mjs → ConsoleFeatureUnavailable-Xb1JKqcc.mjs} +7 -7
  86. package/.output/server/_ssr/{EventDetails-l6dF54zp.mjs → EventDetails-D7MejFCN.mjs} +8 -8
  87. package/.output/server/_ssr/{HashValueDisplay-Zs7ABoOi.mjs → HashValueDisplay-Dd-wu5rY.mjs} +2 -2
  88. package/.output/server/_ssr/{InsightsPageHeader-ZlAlDxQy.mjs → InsightsPageHeader-CWf1n4Vt.mjs} +13 -11
  89. package/.output/server/_ssr/{NotFoundPage-u28ndvp1.mjs → NotFoundPage-C5rFdUpg.mjs} +2 -2
  90. package/.output/server/_ssr/PlatformIcon-CFLiTjh1.mjs +29 -0
  91. package/.output/server/_ssr/RolloutPercentageBadge-g0Bhfl3M.mjs +21 -0
  92. package/.output/server/_ssr/StoreSubmission-JZ2Zltak.mjs +139 -0
  93. package/.output/server/_ssr/{alert-dialog-B8AqlJwo.mjs → alert-dialog-CvNvTvIJ.mjs} +1 -1
  94. package/.output/server/_ssr/{api-C_0B7NXM.mjs → api-0vtwA5vY.mjs} +126 -7
  95. package/.output/server/_ssr/{api-keys-pRNvn2-9.mjs → api-keys-D5dyA4D1.mjs} +10 -10
  96. package/.output/server/_ssr/{api-keys-rpc-wecWs0nA.mjs → api-keys-rpc-BOIUHhDi.mjs} +5 -5
  97. package/.output/server/_ssr/{api-keys-CLH38u9B.mjs → api-keys-yFlQDwQF.mjs} +2 -2
  98. package/.output/server/_ssr/{api-rpc-DWN8eOHS.mjs → api-rpc-COeFOpYi.mjs} +79 -12
  99. package/.output/server/_ssr/{auth-UP0wXs4i.mjs → auth-Z-wLddzh.mjs} +1 -1
  100. package/.output/server/_ssr/{auth-rpc-CLRArbIl.mjs → auth-rpc-D1ss2GUI.mjs} +6 -6
  101. package/.output/server/_ssr/{auth.server-DR9zc0EU.mjs → auth.server-DYS1OJ6P.mjs} +771 -641
  102. package/.output/server/_ssr/{badge-BpT7r-ng.mjs → badge-CEdVRgyl.mjs} +1 -1
  103. package/.output/server/_ssr/{bundleActivity-0qFIcvqt.mjs → bundleActivity-MLrjNJCT.mjs} +1 -1
  104. package/.output/server/_ssr/{config-WVX4IxSR.mjs → config-CjIBFhJX.mjs} +1 -1
  105. package/.output/server/_ssr/config.server-NnMFjMFX.mjs +43 -0
  106. package/.output/server/_ssr/{console-access-aUpQs49I.mjs → console-access-33BNlH97.mjs} +3 -3
  107. package/.output/server/_ssr/{console-features-4xAGfZHw.mjs → console-features-BTV-XuhQ.mjs} +1 -1
  108. package/.output/server/_ssr/{console-features-api-DSSgCV3v.mjs → console-features-api-v1fSycue.mjs} +6 -6
  109. package/.output/server/_ssr/{console-features-rpc-D6kMlR2A.mjs → console-features-rpc-C7XNVqm0.mjs} +5 -5
  110. package/.output/server/_ssr/createCsrfMiddleware-BwHbC8n-.mjs +159 -0
  111. package/.output/server/_ssr/createMiddleware-C3ACsfuG.mjs +24 -0
  112. package/.output/server/_ssr/{createMiddleware-B1iPez7u.mjs → createServerFn-AQvBMBjJ.mjs} +2 -23
  113. package/.output/server/_ssr/{createServerRpc-BaDJLGBx.mjs → createServerRpc-BVvRVOz5.mjs} +2 -2
  114. package/.output/server/_ssr/{databaseConfig-C5SkAyUy.mjs → databaseConfig-B_Bg7qS5.mjs} +1 -1
  115. package/.output/server/_ssr/{databaseJsonValue-Csbuzc9Q.mjs → databaseJsonValue-kS7cvEtz.mjs} +1 -1
  116. package/.output/server/_ssr/{databaseRows-CPphvbag.mjs → databaseRows-B6H8Cmgq.mjs} +3 -3
  117. package/.output/server/_ssr/{dialog-B4ALf7NJ.mjs → dialog-C8CS8RMt.mjs} +1 -1
  118. package/.output/server/_ssr/{dist-CbQbBIw-.mjs → dist-TEpM_UAD.mjs} +125 -125
  119. package/.output/server/_ssr/{downloadBundle-94jPBGSO.mjs → downloadBundle-DCmr7vsj.mjs} +1 -1
  120. package/.output/server/_ssr/{empty-Bh1FEeYF.mjs → empty-CkdEAvCY.mjs} +2 -2
  121. package/.output/server/_ssr/{empty-plugin-adapters-CpCHi1TV.mjs → empty-plugin-adapters-zrZ0xs6n.mjs} +1 -1
  122. package/.output/server/_ssr/{failureReports-Bh22wois.mjs → failureReports-Cn_zU9S8.mjs} +3 -3
  123. package/.output/server/_ssr/{field-HKCaPH3e.mjs → field-CW08sZXd.mjs} +2 -2
  124. package/.output/server/_ssr/{getBundleChildren-DfxB2K8D.mjs → getBundleChildren-rtkEEJw5.mjs} +2 -2
  125. package/.output/server/_ssr/git-CTA8Aqr0.mjs +56 -0
  126. package/.output/server/_ssr/{input-group-xm3LzDuq.mjs → input-group-D-TpFPuG.mjs} +1 -1
  127. package/.output/server/_ssr/{insights-BKZpJ_K4.mjs → insights-CMyzPs8l.mjs} +28 -27
  128. package/.output/server/_ssr/{insights-CCyTACIw.mjs → insights-CYeBl8N9.mjs} +5 -5
  129. package/.output/server/_ssr/{insights-CNqWQLJR.mjs → insights-DAi9KknT.mjs} +2 -2
  130. package/.output/server/_ssr/{insights-api-DtRFz5X3.mjs → insights-api-BFARQ5ct.mjs} +6 -6
  131. package/.output/server/_ssr/{insights-errors-Iy-lBaM7.mjs → insights-errors-7BCwFybo.mjs} +2 -2
  132. package/.output/server/_ssr/{insights-failures-BJN3RfLu.mjs → insights-failures-B9VPkzZE.mjs} +2 -2
  133. package/.output/server/_ssr/{insights-failures-rpc-DLZ91uOG.mjs → insights-failures-rpc-Q4WNNJkU.mjs} +12 -12
  134. package/.output/server/_ssr/{insights-recovery-BUuQdlA3.mjs → insights-recovery-BtXVU3ro.mjs} +1 -1
  135. package/.output/server/_ssr/{insights-recovery-rpc-BQHUhPi7.mjs → insights-recovery-rpc-CY5RYJKa.mjs} +8 -8
  136. package/.output/server/_ssr/{insights-rpc-C6Vukyn7.mjs → insights-rpc-BO7_evvs.mjs} +5 -5
  137. package/.output/server/_ssr/{insights-search-CBYoxT7v.mjs → insights-search-DKfxgHD3.mjs} +5 -5
  138. package/.output/server/_ssr/{insights-usage-CHqvtehH.mjs → insights-usage-DiGnuxW8.mjs} +1 -1
  139. package/.output/server/_ssr/{insights-usage-rpc-BJ7a-xco.mjs → insights-usage-rpc-B4czku50.mjs} +8 -8
  140. package/.output/server/_ssr/{insights-usage-rpc-C-lNUIsD.mjs → insights-usage-rpc-CUCTFska.mjs} +5 -5
  141. package/.output/server/_ssr/{insightsUsage-COUG-_-U.mjs → insightsUsage-ClAmoUz8.mjs} +3 -3
  142. package/.output/server/_ssr/{insights_.distribution-DmLW8I8a.mjs → insights_.distribution-BDeUhZGj.mjs} +2 -2
  143. package/.output/server/_ssr/{insights_.distribution-CTwilMPm.mjs → insights_.distribution-BRrbZN1X.mjs} +5 -5
  144. package/.output/server/_ssr/{insights_.distribution-DztDyrBj.mjs → insights_.distribution-C2MXpzKd.mjs} +15 -14
  145. package/.output/server/_ssr/{installations-Bct7ag5y.mjs → installations-BVk7okiY.mjs} +4 -4
  146. package/.output/server/_ssr/{installations-B6MK5iA8.mjs → installations-BjLMgBoH.mjs} +2 -2
  147. package/.output/server/_ssr/{installations-_N4PtYFl.mjs → installations-DxGZEC5G.mjs} +12 -12
  148. package/.output/server/_ssr/{listReleases-BfUBlCHo.mjs → listReleases-D_GY00SQ.mjs} +1 -1
  149. package/.output/server/_ssr/native-builds-CSCgFB21.mjs +10 -0
  150. package/.output/server/_ssr/native-builds-CdlyAldW.mjs +569 -0
  151. package/.output/server/_ssr/native-builds-DhXL5zf_.mjs +137 -0
  152. package/.output/server/_ssr/native-builds_._buildId-CMp1h8dT.mjs +10 -0
  153. package/.output/server/_ssr/native-builds_._buildId-GHpGaYyE.mjs +813 -0
  154. package/.output/server/_ssr/nativeBuildQr-84cnFQPX.mjs +59 -0
  155. package/.output/server/_ssr/nativeBuilds-B_7BPjt_.mjs +133 -0
  156. package/.output/server/_ssr/{plugins-BaD1slHx.mjs → plugins-RIryUNWj.mjs} +6 -5
  157. package/.output/server/_ssr/qr-code-C7kNRIDX.mjs +22 -0
  158. package/.output/server/_ssr/{release-adoption-DuYJTGuX.mjs → release-adoption-DoAtNPED.mjs} +2 -2
  159. package/.output/server/_ssr/{release-adoption-rpc-CHl7G_Zy.mjs → release-adoption-rpc-BJXZvtoQ.mjs} +10 -10
  160. package/.output/server/_ssr/{releaseAdoption-B39cpvfo.mjs → releaseAdoption-a2byXMfz.mjs} +4 -4
  161. package/.output/server/_ssr/releaseCatalogCompiler-DLcean2k.mjs +364 -0
  162. package/.output/server/_ssr/{releaseManagement-DFMUnDF5.mjs → releaseManagement-DHUux2az.mjs} +1 -1
  163. package/.output/server/_ssr/{releaseReachability-bFsuElSS.mjs → releaseReachability-DZOK_dH-.mjs} +2 -2
  164. package/.output/server/_ssr/{remote-config-Mj05qVYZ.mjs → remote-config-DjsEl270.mjs} +20 -19
  165. package/.output/server/_ssr/{remote-config-10-obSZ0.mjs → remote-config-DlyyohL2.mjs} +2 -2
  166. package/.output/server/_ssr/{remote-config-Dj1eTYi4.mjs → remote-config-XdYuByHz.mjs} +2 -2
  167. package/.output/server/_ssr/{remote-config-rpc-lteqDSLn.mjs → remote-config-rpc-GMLPb-90.mjs} +6 -6
  168. package/.output/server/_ssr/request-guard-CPdRBuGW.mjs +108 -0
  169. package/.output/server/_ssr/{router-Dr4jjjhx.mjs → router-vpxd56RW.mjs} +317 -71
  170. package/.output/server/_ssr/{routes-BZbrY_qw.mjs → routes-DyEM4b3x.mjs} +14 -14
  171. package/.output/server/_ssr/{routes-B5KrCDwk.mjs → routes-dQe3XhiS.mjs} +99 -81
  172. package/.output/server/_ssr/{runtime.server-DsHqVqTr.mjs → runtime.server-BdMY1blX.mjs} +4 -4
  173. package/.output/server/_ssr/schema-ChlRtZIo.mjs +23 -0
  174. package/.output/server/_ssr/schema-migration-rpc-BfGzEK_o.mjs +65 -0
  175. package/.output/server/_ssr/{select-BtdGNSmp.mjs → select-DM1uwtk9.mjs} +3 -27
  176. package/.output/server/_ssr/{server-BxaHSwHh.mjs → server-BUUKNSPI.mjs} +7 -161
  177. package/.output/server/_ssr/{sidebar-CVsgosaD.mjs → sidebar-DB2YQlok.mjs} +4 -4
  178. package/.output/server/_ssr/ssr.mjs +1 -1
  179. package/.output/server/_ssr/start-BvDsQehg.mjs +49 -0
  180. package/.output/server/_ssr/{switch-BmARgkUP.mjs → switch-DLD58zLy.mjs} +2 -2
  181. package/.output/server/_ssr/{table-M5P3YUfY.mjs → table-P4TnAZhl.mjs} +1 -1
  182. package/.output/server/_ssr/{tabs-CrmfOlJj.mjs → tabs-B7ZeLicF.mjs} +2 -2
  183. package/.output/server/_ssr/{updateFailures-z1IQy_4q.mjs → updateFailures-n-tGv90S.mjs} +3 -3
  184. package/.output/server/_ssr/{uuidv7-C88s9jWF.mjs → uuidv7-BIF4nb_q.mjs} +2 -2
  185. package/.output/server/_ssr/{schema-BiAVyEj2.mjs → version-CQ98ZBpL-CWSkTT9J.mjs} +2 -22
  186. package/.output/server/_tanstack-start-manifest_v-C-KC63Zq.mjs +184 -0
  187. package/.output/server/index.mjs +432 -271
  188. package/lib/vite-internal.mjs +32 -1
  189. package/package.json +8 -7
  190. package/src/components/AppSidebar.tsx +12 -1
  191. package/src/components/ConsoleAccessPage.tsx +49 -3
  192. package/src/components/QrCode.tsx +35 -0
  193. package/src/components/SchemaMigrationRequired.tsx +139 -0
  194. package/src/components/features/insights/BuiltinBundle.tsx +5 -3
  195. package/src/components/features/native-builds/BuiltInBundleCard.tsx +163 -0
  196. package/src/components/features/native-builds/CommitLink.tsx +30 -0
  197. package/src/components/features/native-builds/CopyableText.tsx +48 -0
  198. package/src/components/features/native-builds/ImportBuild.tsx +91 -0
  199. package/src/components/features/native-builds/InstallCard.tsx +205 -0
  200. package/src/components/features/native-builds/NativeBuildDetailPage.tsx +175 -0
  201. package/src/components/features/native-builds/NativeBuildsPage.tsx +151 -0
  202. package/src/components/features/native-builds/NativeBuildsTable.tsx +426 -0
  203. package/src/components/features/native-builds/PatchesCard.tsx +222 -0
  204. package/src/components/features/native-builds/ReleaseNativeBuildLink.tsx +101 -0
  205. package/src/components/features/native-builds/ServedNowCard.tsx +214 -0
  206. package/src/components/features/native-builds/ShortHash.tsx +41 -0
  207. package/src/components/features/native-builds/StoreSubmission.tsx +73 -0
  208. package/src/components/features/releases/ReleaseEditorSheet.tsx +8 -0
  209. package/src/lib/api-rpc.ts +98 -0
  210. package/src/lib/api.ts +120 -0
  211. package/src/lib/native-builds.ts +162 -0
  212. package/src/lib/qr-code.ts +16 -0
  213. package/src/lib/schema-migration-api.ts +54 -0
  214. package/src/lib/schema-migration-rpc.ts +26 -0
  215. package/src/lib/schema-migration.ts +101 -0
  216. package/src/lib/server/config.server.ts +23 -14
  217. package/src/lib/server/console-runtime.server.ts +4 -6
  218. package/src/lib/server/local-console.ts +164 -0
  219. package/src/lib/server/nativeBuildQr.ts +73 -0
  220. package/src/lib/server/nativeBuilds.ts +288 -0
  221. package/src/lib/shell.ts +9 -0
  222. package/src/routeTree.gen.ts +88 -1
  223. package/src/router.tsx +11 -1
  224. package/src/routes/-native-builds-search.ts +51 -0
  225. package/src/routes/[_]hot-updater/native-builds/qr.ts +14 -0
  226. package/src/routes/__root.tsx +11 -1
  227. package/src/routes/api/native-builds/$buildId/download.ts +50 -0
  228. package/src/routes/native-builds.tsx +21 -0
  229. package/src/routes/native-builds_.$buildId.tsx +23 -0
  230. package/src/start.ts +33 -0
  231. package/src/virtual-modules.d.ts +18 -0
  232. package/src/vite-internal.ts +57 -1
  233. package/.output/public/assets/EventDetails-gbD_fCqc.js +0 -1
  234. package/.output/public/assets/HashValueDisplay-BGkDPaYi.js +0 -1
  235. package/.output/public/assets/InsightsPageHeader-BRw8XkJW.js +0 -1
  236. package/.output/public/assets/NotFoundPage-Cei6W2lm.js +0 -1
  237. package/.output/public/assets/alert-dialog-BtVg50WQ.js +0 -1
  238. package/.output/public/assets/api-DoicFJfd.js +0 -1
  239. package/.output/public/assets/api-keys-D81VHHNM.js +0 -1
  240. package/.output/public/assets/api-keys-Dia6i8K1.js +0 -1
  241. package/.output/public/assets/arrow-left-DsivSewE.js +0 -1
  242. package/.output/public/assets/arrow-up-right-B5aM0F7f.js +0 -1
  243. package/.output/public/assets/badge-Bqf3UDee.js +0 -1
  244. package/.output/public/assets/circle-off-BsbZQtsF.js +0 -1
  245. package/.output/public/assets/console-features-api-DPE4C06Y.js +0 -2
  246. package/.output/public/assets/dialog-CGKPxz3n.js +0 -1
  247. package/.output/public/assets/index-DrkdiRIn.js +0 -10
  248. package/.output/public/assets/insights-C-5t8wh2.js +0 -2
  249. package/.output/public/assets/insights-D_ngQdiz.js +0 -1
  250. package/.output/public/assets/insights-KigRBDgC.js +0 -42
  251. package/.output/public/assets/insights-api-1ZjVu3Sb.js +0 -1
  252. package/.output/public/assets/insights-search-aT8BVCci.js +0 -1
  253. package/.output/public/assets/insights-usage-rpc-DqTiXI1_.js +0 -1
  254. package/.output/public/assets/insights_.distribution-Bzl6RFBR.js +0 -2
  255. package/.output/public/assets/insights_.distribution-Cthgttqb.js +0 -1
  256. package/.output/public/assets/insights_.distribution-D2c2v1bu.js +0 -1
  257. package/.output/public/assets/installations-2Nx8Htf-.js +0 -1
  258. package/.output/public/assets/installations-BskJ1xQr.js +0 -2
  259. package/.output/public/assets/installations-K2iJ_r0h.js +0 -1
  260. package/.output/public/assets/remote-config-D9X3NiwV.js +0 -6
  261. package/.output/public/assets/remote-config-DzOOiBIO.js +0 -1
  262. package/.output/public/assets/remote-config-ikg7kak0.js +0 -1
  263. package/.output/public/assets/routes-9rb9P5Y7.js +0 -2
  264. package/.output/public/assets/routes-P7683vKQ.js +0 -1
  265. package/.output/public/assets/search-DuI1FBpg.js +0 -1
  266. package/.output/public/assets/select-M3H2gxCc.js +0 -1
  267. package/.output/public/assets/smartphone-CtsHuiIC.js +0 -1
  268. package/.output/public/assets/styles-CpiW_YdW.css +0 -2
  269. package/.output/public/assets/switch-By_vfm8I.js +0 -1
  270. package/.output/public/assets/useCompositeListItem-B-UkrDlu.js +0 -1
  271. package/.output/server/_ssr/config.server-DTdsG47T.mjs +0 -35
  272. package/.output/server/_ssr/start-B6ECIE_7.mjs +0 -4
  273. package/.output/server/_tanstack-start-manifest_v-_BY9hYTm.mjs +0 -136
@@ -0,0 +1,91 @@
1
+ import { ArrowUpRight, FileUp } from "lucide-react";
2
+
3
+ import { Button, buttonVariants } from "@/components/ui/button";
4
+ import {
5
+ Dialog,
6
+ DialogContent,
7
+ DialogDescription,
8
+ DialogHeader,
9
+ DialogTitle,
10
+ DialogTrigger,
11
+ } from "@/components/ui/dialog";
12
+
13
+ import { CopyableText } from "./CopyableText";
14
+
15
+ /** The command that tracks a build made elsewhere. */
16
+ export const IMPORT_COMMAND =
17
+ "npx hot-updater native-build import <path-to-.ipa|.aab|.apk|.app|.app.zip>";
18
+
19
+ /** `native-build import`'s section of the native build guide. */
20
+ export const IMPORT_GUIDE_URL =
21
+ "https://hot-updater.dev/docs/guides/native-build#import-a-build-made-elsewhere";
22
+
23
+ /**
24
+ * How to track a build made elsewhere: the CLI command, which reads the
25
+ * artifact on the machine that has it. The Console uploads nothing, since a
26
+ * host may cap a request's body far below an `.ipa`.
27
+ */
28
+ export function ImportBuildSteps() {
29
+ return (
30
+ <div className="flex min-w-0 flex-col gap-2">
31
+ <p className="text-xs text-muted-foreground">
32
+ Track an <code className="font-mono">.ipa</code>,{" "}
33
+ <code className="font-mono">.aab</code>,{" "}
34
+ <code className="font-mono">.apk</code> or simulator{" "}
35
+ <code className="font-mono">.app</code> (or its{" "}
36
+ <code className="font-mono">.app.zip</code>) built elsewhere, such as by
37
+ EAS, Xcode Cloud or Fastlane, so the OTAs you deploy after it patch from
38
+ its built-in bundle. Run in the app's project:
39
+ </p>
40
+ <CopyableText label="the import command" value={IMPORT_COMMAND} wrap />
41
+ <p className="text-xs text-muted-foreground">
42
+ Add <code className="font-mono">-c &lt;channel&gt;</code> (
43
+ <code className="font-mono">--channel</code>) when the app reports
44
+ another channel than the one it was built with, such as one its code
45
+ sets or switches to at runtime.
46
+ </p>
47
+ </div>
48
+ );
49
+ }
50
+
51
+ /** The toolbar's "Import a build" button and the command it shows. */
52
+ export function ImportBuildDialog() {
53
+ return (
54
+ <Dialog>
55
+ <DialogTrigger
56
+ render={
57
+ <Button
58
+ aria-label="Import a build"
59
+ className="min-h-11 sm:min-h-7"
60
+ size="sm"
61
+ variant="outline"
62
+ />
63
+ }
64
+ >
65
+ <FileUp data-icon="inline-start" />
66
+ <span className="hidden sm:inline">Import a build</span>
67
+ </DialogTrigger>
68
+ <DialogContent className="sm:max-w-lg">
69
+ <DialogHeader>
70
+ <DialogTitle>Import a build</DialogTitle>
71
+ <DialogDescription>
72
+ The build appears here once the command tracks it.
73
+ </DialogDescription>
74
+ </DialogHeader>
75
+ <ImportBuildSteps />
76
+ <a
77
+ className={buttonVariants({
78
+ className: "min-h-11 w-full sm:min-h-8 sm:w-fit",
79
+ variant: "outline",
80
+ })}
81
+ href={IMPORT_GUIDE_URL}
82
+ rel="noreferrer"
83
+ target="_blank"
84
+ >
85
+ Import guide
86
+ <ArrowUpRight data-icon="inline-end" />
87
+ </a>
88
+ </DialogContent>
89
+ </Dialog>
90
+ );
91
+ }
@@ -0,0 +1,205 @@
1
+ import type { NativeBuildRow } from "@hot-updater/plugin-core";
2
+ import { AlertTriangle, Download } from "lucide-react";
3
+
4
+ import { QrCode } from "@/components/QrCode";
5
+ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
6
+ import { buttonVariants } from "@/components/ui/button";
7
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
8
+ import { Skeleton } from "@/components/ui/skeleton";
9
+ import { useNativeBuildDownloadUrlQuery } from "@/lib/api";
10
+ import {
11
+ apkInstallCommand,
12
+ nativeBuildDownloadPath,
13
+ nativeBuildFileName,
14
+ simulatorInstallCommand,
15
+ } from "@/lib/native-builds";
16
+
17
+ import { CopyableText } from "./CopyableText";
18
+ import { isSubmitted } from "./StoreSubmission";
19
+
20
+ /**
21
+ * What an artifact installs on, where the file alone isn't enough: the card
22
+ * says it once, under the QR code, or without one, in its place.
23
+ */
24
+ const INSTALL_LIMITS: Partial<Record<NativeBuildRow["artifact_kind"], string>> =
25
+ {
26
+ ipa: "The .ipa installs only on devices its signing profile allows (development or ad hoc); an App Store export reaches testers through TestFlight.",
27
+ aab: "The .aab installs through Google Play or bundletool, not from the file.",
28
+ };
29
+
30
+ /**
31
+ * What scanning a build's QR code gets, by its artifact. The install steps
32
+ * beside it add only what this doesn't say, so the card says each thing once.
33
+ */
34
+ const QR_CAPTIONS: Record<NativeBuildRow["artifact_kind"], string> = {
35
+ apk: "Scan with an Android phone's camera, then open the APK to install it.",
36
+ ipa: `Scan to download. ${INSTALL_LIMITS.ipa}`,
37
+ aab: `Scan to download. ${INSTALL_LIMITS.aab}`,
38
+ app: "Scan to download the .app.zip on a Mac.",
39
+ };
40
+
41
+ /**
42
+ * The QR code of the build's download: the storage's URL, which a phone
43
+ * opens without the console's sign-in, read again before it expires.
44
+ */
45
+ function DownloadQrCode({ build }: { readonly build: NativeBuildRow }) {
46
+ const downloadUrl = useNativeBuildDownloadUrlQuery(build.id, true);
47
+ const url = downloadUrl.data?.url;
48
+
49
+ // A storage without download URLs serves the build only through the
50
+ // console, which a phone that scans isn't signed in to.
51
+ if (url === null) {
52
+ const limits = INSTALL_LIMITS[build.artifact_kind];
53
+ return (
54
+ <div className="flex flex-col gap-2 text-xs text-muted-foreground">
55
+ <p>
56
+ This storage gives no download link a phone can open without signing
57
+ in, so there is no QR code. Download the build here, or open this page
58
+ on the phone signed in to the console.
59
+ </p>
60
+ {limits ? <p>{limits}</p> : null}
61
+ </div>
62
+ );
63
+ }
64
+
65
+ return (
66
+ <div className="flex flex-col items-center gap-2">
67
+ {url === undefined ? (
68
+ downloadUrl.isError ? (
69
+ <Alert variant="destructive">
70
+ <AlertTriangle />
71
+ <AlertTitle>The download link couldn't be created</AlertTitle>
72
+ <AlertDescription>{downloadUrl.error.message}</AlertDescription>
73
+ </Alert>
74
+ ) : (
75
+ <Skeleton className="size-48" />
76
+ )
77
+ ) : (
78
+ <QrCode
79
+ className="size-48"
80
+ label={`Download ${nativeBuildFileName(build)}`}
81
+ value={url}
82
+ />
83
+ )}
84
+ <p className="max-w-56 text-center text-xs text-muted-foreground">
85
+ {QR_CAPTIONS[build.artifact_kind]}
86
+ </p>
87
+ </div>
88
+ );
89
+ }
90
+
91
+ /** The store's label of a submitted build. */
92
+ function SubmittedVersion({ build }: { readonly build: NativeBuildRow }) {
93
+ return (
94
+ <span className="font-mono text-foreground" translate="no">
95
+ {build.submitted_version ?? "this build"}
96
+ </span>
97
+ );
98
+ }
99
+
100
+ /**
101
+ * How else the artifact installs, beside its QR code's caption and its
102
+ * download: none of it repeats the caption.
103
+ */
104
+ function InstallSteps({ build }: { readonly build: NativeBuildRow }) {
105
+ switch (build.artifact_kind) {
106
+ case "apk":
107
+ return (
108
+ <div className="flex min-w-0 flex-col gap-2">
109
+ <p className="text-xs text-muted-foreground">
110
+ On an emulator or a phone over USB, download the APK and install it:
111
+ </p>
112
+ <CopyableText
113
+ label="the install command"
114
+ value={apkInstallCommand(build)}
115
+ />
116
+ </div>
117
+ );
118
+ case "app":
119
+ return (
120
+ <div className="flex min-w-0 flex-col gap-2">
121
+ <p className="text-xs text-muted-foreground">
122
+ It runs only in the iOS Simulator. Install it on the booted one:
123
+ </p>
124
+ <CopyableText
125
+ label="the install command"
126
+ value={simulatorInstallCommand(build)}
127
+ />
128
+ </div>
129
+ );
130
+ case "aab":
131
+ return isSubmitted(build) ? (
132
+ <p className="text-xs text-muted-foreground">
133
+ Submitted to Google Play internal testing as{" "}
134
+ <SubmittedVersion build={build} />. Testers get it once its release is
135
+ rolled out; on an app Play Console hasn't published, the release is a
136
+ draft you roll out in Play Console.
137
+ </p>
138
+ ) : (
139
+ <div className="flex min-w-0 flex-col gap-2">
140
+ <p className="text-xs text-muted-foreground">
141
+ For testers, build and release to internal testing:
142
+ </p>
143
+ <CopyableText
144
+ label="the Google Play command"
145
+ value="npx hot-updater google-play upload"
146
+ />
147
+ </div>
148
+ );
149
+ case "ipa":
150
+ return isSubmitted(build) ? (
151
+ <p className="text-xs text-muted-foreground">
152
+ On TestFlight as <SubmittedVersion build={build} />.
153
+ </p>
154
+ ) : (
155
+ <div className="flex min-w-0 flex-col gap-2">
156
+ <p className="text-xs text-muted-foreground">
157
+ Over USB, Xcode or Apple Configurator installs it. For testers:
158
+ </p>
159
+ <CopyableText
160
+ label="the TestFlight command"
161
+ value="npx hot-updater testflight"
162
+ />
163
+ </div>
164
+ );
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Installs one native build: every artifact by a QR code of its download,
170
+ * which a phone scans, and by download here; an APK also with `adb`, a
171
+ * simulator app with `xcrun simctl`, and an IPA's or an AAB's testers through
172
+ * its store.
173
+ */
174
+ export function InstallCard({ build }: { readonly build: NativeBuildRow }) {
175
+ const fileName = nativeBuildFileName(build);
176
+ return (
177
+ <Card className="overflow-hidden shadow-sm">
178
+ <CardHeader className="border-b px-4 py-3">
179
+ <CardTitle className="text-sm">
180
+ <h2>Install</h2>
181
+ </CardTitle>
182
+ </CardHeader>
183
+ <CardContent className="grid gap-6 p-4 sm:grid-cols-[12rem_minmax(0,1fr)] sm:p-6">
184
+ <DownloadQrCode build={build} />
185
+ <div className="flex min-w-0 flex-col gap-4">
186
+ <InstallSteps build={build} />
187
+ <a
188
+ className={buttonVariants({
189
+ className:
190
+ "h-auto min-h-11 w-full min-w-0 py-2 whitespace-normal sm:min-h-8 sm:w-fit",
191
+ variant: "outline",
192
+ })}
193
+ download
194
+ href={nativeBuildDownloadPath(build.id)}
195
+ >
196
+ <Download data-icon="inline-start" />
197
+ <span className="text-left [overflow-wrap:anywhere]">
198
+ Download {fileName}
199
+ </span>
200
+ </a>
201
+ </div>
202
+ </CardContent>
203
+ </Card>
204
+ );
205
+ }
@@ -0,0 +1,175 @@
1
+ import { Link } from "@tanstack/react-router";
2
+ import { AlertTriangle, ChevronLeft, HardDrive, RefreshCw } from "lucide-react";
3
+
4
+ import { PlatformIcon } from "@/components/PlatformIcon";
5
+ import {
6
+ Alert,
7
+ AlertAction,
8
+ AlertDescription,
9
+ AlertTitle,
10
+ } from "@/components/ui/alert";
11
+ import { Button, buttonVariants } from "@/components/ui/button";
12
+ import {
13
+ Empty,
14
+ EmptyContent,
15
+ EmptyDescription,
16
+ EmptyHeader,
17
+ EmptyMedia,
18
+ EmptyTitle,
19
+ } from "@/components/ui/empty";
20
+ import { SidebarTrigger } from "@/components/ui/sidebar";
21
+ import { Skeleton } from "@/components/ui/skeleton";
22
+ import { type KeysetPage, useNativeBuildDetailQuery } from "@/lib/api";
23
+ import {
24
+ formatByteSize,
25
+ nativeBuildArtifacts,
26
+ platformLabel,
27
+ } from "@/lib/native-builds";
28
+
29
+ import { BuiltInBundleCard } from "./BuiltInBundleCard";
30
+ import { InstallCard } from "./InstallCard";
31
+ import { PatchesCard } from "./PatchesCard";
32
+ import { ServedNowCard } from "./ServedNowCard";
33
+
34
+ export interface NativeBuildDetailSearch {
35
+ /** Patches older than this target bundle: the next page. */
36
+ readonly patchesBefore?: string;
37
+ /** Patches newer than this target bundle: the previous page. */
38
+ readonly patchesAfter?: string;
39
+ }
40
+
41
+ /**
42
+ * One native build: how to install it, its built-in bundle as the base its
43
+ * OTAs patch from, what an install is served now, and the patches from it.
44
+ * The patch page is in the URL, so a link opens it.
45
+ */
46
+ export function NativeBuildDetailPage({
47
+ buildId,
48
+ search,
49
+ onSearchChange,
50
+ }: {
51
+ readonly buildId: string;
52
+ readonly search: NativeBuildDetailSearch;
53
+ readonly onSearchChange: (search: NativeBuildDetailSearch) => void;
54
+ }) {
55
+ const query = useNativeBuildDetailQuery(buildId);
56
+ const detail = query.data;
57
+ const page: KeysetPage = {
58
+ ...(search.patchesBefore === undefined
59
+ ? {}
60
+ : { before: search.patchesBefore }),
61
+ ...(search.patchesAfter === undefined
62
+ ? {}
63
+ : { after: search.patchesAfter }),
64
+ };
65
+
66
+ return (
67
+ <div className="flex h-svh min-h-0 flex-col">
68
+ <header className="sticky top-0 z-10 flex shrink-0 items-center gap-x-2 border-b bg-background px-3 py-3 sm:min-h-12 sm:bg-card/70 sm:px-4 sm:backdrop-blur-sm">
69
+ <SidebarTrigger className="-ml-1 size-11 lg:size-7" />
70
+ <Link
71
+ className={buttonVariants({
72
+ className: "min-h-11 sm:min-h-7",
73
+ size: "sm",
74
+ variant: "ghost",
75
+ })}
76
+ to="/native-builds"
77
+ >
78
+ <ChevronLeft data-icon="inline-start" />
79
+ Native builds
80
+ </Link>
81
+ {detail ? (
82
+ <h1 className="flex min-w-0 items-center gap-1.5 text-sm font-medium">
83
+ <PlatformIcon
84
+ className="size-3.5 shrink-0 text-muted-foreground"
85
+ platform={detail.build.platform}
86
+ />
87
+ <span className="min-w-0 [overflow-wrap:anywhere]">
88
+ {platformLabel(detail.build.platform)}{" "}
89
+ {nativeBuildArtifacts[detail.build.artifact_kind].label} ·{" "}
90
+ <span translate="no">{detail.channel}</span>
91
+ </span>
92
+ <span className="hidden text-xs font-normal text-muted-foreground tabular-nums sm:inline">
93
+ {formatByteSize(detail.build.byte_size)}
94
+ </span>
95
+ </h1>
96
+ ) : (
97
+ <h1 className="sr-only">Native build</h1>
98
+ )}
99
+ </header>
100
+
101
+ <div className="min-h-0 min-w-0 flex-1 overflow-y-auto bg-muted/5 p-3 sm:p-6">
102
+ <div className="mx-auto flex w-full max-w-5xl flex-col gap-4 sm:gap-6">
103
+ {query.isError ? (
104
+ <Alert variant="destructive">
105
+ <AlertTriangle />
106
+ <AlertTitle>The native build couldn't be loaded</AlertTitle>
107
+ <AlertDescription>{query.error.message}</AlertDescription>
108
+ <AlertAction>
109
+ <Button
110
+ onClick={() => void query.refetch()}
111
+ size="xs"
112
+ variant="outline"
113
+ >
114
+ <RefreshCw data-icon="inline-start" />
115
+ Retry
116
+ </Button>
117
+ </AlertAction>
118
+ </Alert>
119
+ ) : detail === undefined ? (
120
+ <>
121
+ <span className="sr-only">Loading the native build</span>
122
+ <Skeleton className="h-56 w-full rounded-xl" />
123
+ <Skeleton className="h-64 w-full rounded-xl" />
124
+ </>
125
+ ) : detail === null ? (
126
+ <Empty className="border py-16">
127
+ <EmptyHeader>
128
+ <EmptyMedia variant="icon">
129
+ <HardDrive />
130
+ </EmptyMedia>
131
+ <EmptyTitle>Native build not found</EmptyTitle>
132
+ <EmptyDescription>
133
+ No native build has this ID. It may have been deleted with{" "}
134
+ <code className="font-mono">native-build delete</code>, or be
135
+ from another console.
136
+ </EmptyDescription>
137
+ </EmptyHeader>
138
+ <EmptyContent>
139
+ <Link
140
+ className={buttonVariants({
141
+ className: "min-h-11 sm:min-h-8",
142
+ variant: "outline",
143
+ })}
144
+ to="/native-builds"
145
+ >
146
+ Show all native builds
147
+ </Link>
148
+ </EmptyContent>
149
+ </Empty>
150
+ ) : (
151
+ <>
152
+ <InstallCard build={detail.build} />
153
+ <BuiltInBundleCard detail={detail} />
154
+ <ServedNowCard detail={detail} />
155
+ <PatchesCard
156
+ detail={detail}
157
+ onPage={(next) =>
158
+ onSearchChange({
159
+ ...(next.before === undefined
160
+ ? {}
161
+ : { patchesBefore: next.before }),
162
+ ...(next.after === undefined
163
+ ? {}
164
+ : { patchesAfter: next.after }),
165
+ })
166
+ }
167
+ page={page}
168
+ />
169
+ </>
170
+ )}
171
+ </div>
172
+ </div>
173
+ </div>
174
+ );
175
+ }
@@ -0,0 +1,151 @@
1
+ import { HardDrive, RefreshCw } from "lucide-react";
2
+
3
+ import { Button } from "@/components/ui/button";
4
+ import { Card, CardContent } from "@/components/ui/card";
5
+ import {
6
+ Select,
7
+ SelectContent,
8
+ SelectGroup,
9
+ SelectItem,
10
+ SelectTrigger,
11
+ SelectValue,
12
+ } from "@/components/ui/select";
13
+ import { SidebarTrigger } from "@/components/ui/sidebar";
14
+ import {
15
+ type NativeBuildPage,
16
+ useChannelsQuery,
17
+ useNativeBuildsQuery,
18
+ } from "@/lib/api";
19
+
20
+ import { ImportBuildDialog } from "./ImportBuild";
21
+ import { NativeBuildsTable } from "./NativeBuildsTable";
22
+
23
+ /**
24
+ * The filter's values: a channel's is its name behind a prefix, so no channel
25
+ * name, `all` included, can be taken for "All channels".
26
+ */
27
+ const ALL_CHANNELS = "all";
28
+ const CHANNEL_VALUE_PREFIX = "channel:";
29
+ const channelValue = (name: string) => `${CHANNEL_VALUE_PREFIX}${name}`;
30
+
31
+ /**
32
+ * The native builds Hot Updater tracks: one page at a time, newest first,
33
+ * optionally only those that embed one channel. The page and the channel
34
+ * are in the URL, so a pull request links to its channel's builds.
35
+ */
36
+ export function NativeBuildsPage({
37
+ search,
38
+ onSearchChange,
39
+ }: {
40
+ readonly search: NativeBuildPage;
41
+ readonly onSearchChange: (search: NativeBuildPage) => void;
42
+ }) {
43
+ const { channel, before, after } = search;
44
+ const builds = useNativeBuildsQuery(search);
45
+ // The filter's choices; the rows decode their channel from its ID.
46
+ const channels = useChannelsQuery();
47
+ const channelNames = channels.data?.map(({ name }) => name) ?? [];
48
+ // A filtered channel may be gone, or not loaded yet: it stays a choice.
49
+ const choices =
50
+ channel === undefined || channelNames.includes(channel)
51
+ ? channelNames
52
+ : [channel, ...channelNames];
53
+ const filterBy = (next: string | undefined) =>
54
+ onSearchChange(next === undefined ? {} : { channel: next });
55
+
56
+ return (
57
+ <div className="flex h-svh min-h-0 flex-col">
58
+ <header className="sticky top-0 z-10 flex shrink-0 flex-wrap items-center gap-x-3 gap-y-1 border-b bg-background px-3 py-3 sm:min-h-12 sm:flex-nowrap sm:bg-card/70 sm:px-4 sm:backdrop-blur-sm">
59
+ <SidebarTrigger className="-ml-1 size-11 lg:size-7" />
60
+ <div className="flex items-center gap-1.5">
61
+ <HardDrive
62
+ aria-hidden="true"
63
+ className="size-3.5 text-muted-foreground"
64
+ />
65
+ <h1 className="text-sm font-medium">Native builds</h1>
66
+ </div>
67
+ <p className="basis-full pl-12 text-xs text-muted-foreground sm:basis-auto sm:pl-0">
68
+ Store and test builds, each the built-in bundle its OTAs patch from.
69
+ </p>
70
+ </header>
71
+
72
+ <div className="min-h-0 min-w-0 flex-1 overflow-y-auto bg-muted/5 p-3 sm:p-6">
73
+ <Card className="mx-auto w-full max-w-6xl overflow-hidden shadow-sm">
74
+ <div className="flex items-center gap-2 border-b px-4 py-3">
75
+ <Select
76
+ items={[
77
+ { label: "All channels", value: ALL_CHANNELS },
78
+ ...choices.map((name) => ({
79
+ label: name,
80
+ value: channelValue(name),
81
+ })),
82
+ ]}
83
+ onOpenChange={(open) => {
84
+ // A pull request's channel appears while the page is open.
85
+ if (open) void channels.refetch();
86
+ }}
87
+ onValueChange={(value) =>
88
+ filterBy(
89
+ value?.startsWith(CHANNEL_VALUE_PREFIX)
90
+ ? value.slice(CHANNEL_VALUE_PREFIX.length)
91
+ : undefined,
92
+ )
93
+ }
94
+ value={
95
+ channel === undefined ? ALL_CHANNELS : channelValue(channel)
96
+ }
97
+ >
98
+ <SelectTrigger
99
+ aria-label="Channel"
100
+ className="h-11 min-w-0 flex-1 text-xs sm:h-8 sm:w-48 sm:flex-none"
101
+ >
102
+ <SelectValue />
103
+ </SelectTrigger>
104
+ <SelectContent>
105
+ <SelectGroup>
106
+ <SelectItem value={ALL_CHANNELS}>All channels</SelectItem>
107
+ {choices.map((name) => (
108
+ <SelectItem
109
+ key={name}
110
+ translate="no"
111
+ value={channelValue(name)}
112
+ >
113
+ {name}
114
+ </SelectItem>
115
+ ))}
116
+ </SelectGroup>
117
+ </SelectContent>
118
+ </Select>
119
+ <div className="ml-auto">
120
+ <ImportBuildDialog />
121
+ </div>
122
+ <Button
123
+ aria-label="Refresh native builds"
124
+ className="size-11 sm:size-7"
125
+ disabled={builds.isLoading}
126
+ onClick={() => void builds.refetch()}
127
+ size="icon-sm"
128
+ variant="ghost"
129
+ >
130
+ <RefreshCw aria-hidden="true" />
131
+ </Button>
132
+ </div>
133
+ <CardContent className="p-0">
134
+ <NativeBuildsTable
135
+ builds={builds}
136
+ channel={channel}
137
+ onChannel={filterBy}
138
+ onPage={(page) =>
139
+ onSearchChange({
140
+ ...(channel === undefined ? {} : { channel }),
141
+ ...page,
142
+ })
143
+ }
144
+ paged={before !== undefined || after !== undefined}
145
+ />
146
+ </CardContent>
147
+ </Card>
148
+ </div>
149
+ </div>
150
+ );
151
+ }