@hot-updater/console 1.0.0-rc.8 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/ConsoleFeatureUnavailable-jB4Loglh.js +1 -0
  3. package/.output/public/assets/EventDetails-B6yUsyNA.js +1 -0
  4. package/.output/public/assets/HashValueDisplay-BFX_kY3-.js +1 -0
  5. package/.output/public/assets/InsightsInfo-BNO0WznZ.js +1 -0
  6. package/.output/public/assets/InsightsPageHeader-B892oVml.js +1 -0
  7. package/.output/public/assets/NotFoundPage-DIqCpWa3.js +1 -0
  8. package/.output/public/assets/alert-dialog-Bbk_4-WJ.js +1 -0
  9. package/.output/public/assets/api-DCM0wr6O.js +1 -0
  10. package/.output/public/assets/api-keys-CIO9L_wu.js +1 -0
  11. package/.output/public/assets/api-keys-DVTL_--f.js +1 -0
  12. package/.output/public/assets/arrow-left-CnMYpqB7.js +1 -0
  13. package/.output/public/assets/arrow-up-right-BtffZA6e.js +1 -0
  14. package/.output/public/assets/chevron-right-BqD5wtFb.js +1 -0
  15. package/.output/public/assets/clipboard-DS9ucUQL.js +1 -0
  16. package/.output/public/assets/console-features-api-BH5m_PtQ.js +1 -0
  17. package/.output/public/assets/dialog-Bqbb7B0N.js +1 -0
  18. package/.output/public/assets/dist-LxKJB-Vu.js +1 -0
  19. package/.output/public/assets/empty-CuT2UhFp.js +1 -0
  20. package/.output/public/assets/field-BBDpjIqu.js +1 -0
  21. package/.output/public/assets/index-6o51GkFe.js +11 -0
  22. package/.output/public/assets/input-group-ja_30qaF.js +1 -0
  23. package/.output/public/assets/insights-CmraDzQh.js +1 -0
  24. package/.output/public/assets/insights-CxZ2GCYV.js +2 -0
  25. package/.output/public/assets/insights-api-BigAU9sZ.js +1 -0
  26. package/.output/public/assets/insights-usage-rpc-DYEhsCKT.js +1 -0
  27. package/.output/public/assets/insights-vNRCrfnS.js +42 -0
  28. package/.output/public/assets/insights_.distribution-ChOerfJt.js +1 -0
  29. package/.output/public/assets/insights_.distribution-D-e4iga8.js +1 -0
  30. package/.output/public/assets/insights_.distribution-DZtkibX1.js +2 -0
  31. package/.output/public/assets/installations-BazSJXE0.js +2 -0
  32. package/.output/public/assets/installations-ClHTqdAO.js +1 -0
  33. package/.output/public/assets/installations-DpKsBuHi.js +1 -0
  34. package/.output/public/assets/key-round-wS_SIZ6b.js +1 -0
  35. package/.output/public/assets/list-CgrYiadM.js +1 -0
  36. package/.output/public/assets/refresh-cw-BYgoNCco.js +1 -0
  37. package/.output/public/assets/remote-config-BWwHOkCK.js +1 -0
  38. package/.output/public/assets/remote-config-Br56BODb.js +6 -0
  39. package/.output/public/assets/remote-config-CXOi0LRu.js +1 -0
  40. package/.output/public/assets/routes-B88Erkpk.js +2 -0
  41. package/.output/public/assets/routes-CfzsAleC.js +1 -0
  42. package/.output/public/assets/search-DaTDPW25.js +1 -0
  43. package/.output/public/assets/select-BZZBXMkr.js +1 -0
  44. package/.output/public/assets/sidebar-DNVyJPfX.js +3 -0
  45. package/.output/public/assets/smartphone-DpTFgtKf.js +1 -0
  46. package/.output/public/assets/styles-DTGEAJFv.css +2 -0
  47. package/.output/public/assets/switch-DZ1TPwx-.js +1 -0
  48. package/.output/public/assets/table-Gm4qRWwE.js +1 -0
  49. package/.output/public/assets/tabs-C7FOEjFC.js +1 -0
  50. package/.output/public/assets/undo-2-CmrHjwFc.js +1 -0
  51. package/.output/public/assets/useCompositeListItem-9E4pj_CH.js +1 -0
  52. package/.output/public/assets/valueToPercent-CH8GCHaL.js +1 -0
  53. package/.output/server/__23tanstack-start-server-fn-resolver-BpRlos5Q.mjs +170 -0
  54. package/.output/server/_libs/@base-ui/react+[...].mjs +4389 -4207
  55. package/.output/server/_libs/@tanstack/react-router+[...].mjs +3802 -4022
  56. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +140 -97
  57. package/.output/server/_libs/lucide-react.mjs +221 -9
  58. package/.output/server/_libs/next-themes.mjs +1 -1
  59. package/.output/server/_libs/react-redux+[...].mjs +1 -1
  60. package/.output/server/_libs/recharts+[...].mjs +483 -901
  61. package/.output/server/_libs/sonner.mjs +1 -1
  62. package/.output/server/_libs/tanstack__history.mjs +68 -41
  63. package/.output/server/_libs/tanstack__query-core.mjs +2010 -309
  64. package/.output/server/_libs/tanstack__react-query.mjs +448 -21
  65. package/.output/server/_libs/verkit.mjs +102 -1
  66. package/.output/server/_ssr/BundleIdDisplay-x94o9rlU.mjs +13 -0
  67. package/.output/server/_ssr/ConsoleFeatureUnavailable-ldW8v8-q.mjs +58 -0
  68. package/.output/server/_ssr/EventDetails-Q8kfj9Nr.mjs +344 -0
  69. package/.output/server/_ssr/{HashValueDisplay-C5tS4oGo.mjs → HashValueDisplay-n1GholGS.mjs} +3 -3
  70. package/.output/server/_ssr/InsightsInfo-DuJFatQO.mjs +75 -0
  71. package/.output/server/_ssr/InsightsPageHeader-BSUX3Ye9.mjs +145 -0
  72. package/.output/server/_ssr/NotFoundPage-DZzOOzS3.mjs +37 -0
  73. package/.output/server/_ssr/{dialog-WiIAKl4L.mjs → alert-dialog-C43Doa0I.mjs} +6 -83
  74. package/.output/server/_ssr/{api-keys-aXriWrv7.mjs → api-keys-B4sYm8dM.mjs} +48 -9
  75. package/.output/server/_ssr/api-keys-CEnM9fqG.mjs +5 -0
  76. package/.output/server/_ssr/api-keys-rpc-BIFuUgMt.mjs +52 -0
  77. package/.output/server/_ssr/api-oZjRn9tP.mjs +186 -0
  78. package/.output/server/_ssr/api-rpc-BNiFRRK8.mjs +240 -0
  79. package/.output/server/_ssr/{auth-BF8mbAgF.mjs → auth-UP0wXs4i.mjs} +1 -1
  80. package/.output/server/_ssr/{auth-rpc-B7w3AdRr.mjs → auth-rpc-DnZqYxyq.mjs} +6 -6
  81. package/.output/server/_ssr/auth.server-DFpyxRDd.mjs +5374 -0
  82. package/.output/server/_ssr/bundleActivity-M5gzBFNg.mjs +18 -0
  83. package/.output/server/_ssr/config-WVX4IxSR.mjs +15 -0
  84. package/.output/server/_ssr/config.server-DskXCAyI.mjs +35 -0
  85. package/.output/server/_ssr/console-access-BJfAXknd.mjs +17 -0
  86. package/.output/server/_ssr/console-features-Oz0q20jJ.mjs +61 -0
  87. package/.output/server/_ssr/console-features-api-C9kiKvaV.mjs +43 -0
  88. package/.output/server/_ssr/console-features-rpc-P0GL7Z-R.mjs +19 -0
  89. package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-Cm6Tn0_Y.mjs} +67 -56
  90. package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-RLR4H-Fn.mjs} +2 -2
  91. package/.output/server/_ssr/databaseConfig-C5SkAyUy.mjs +5 -0
  92. package/.output/server/_ssr/{databaseJsonValue-d6I3nAS4.mjs → databaseJsonValue-Csbuzc9Q.mjs} +28 -5
  93. package/.output/server/_ssr/databaseRows-CPphvbag.mjs +73 -0
  94. package/.output/server/_ssr/dialog-BbYDMjdC.mjs +84 -0
  95. package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-CbQbBIw-.mjs} +140 -7
  96. package/.output/server/_ssr/{downloadBundle-CI7JUKvC.mjs → downloadBundle-CZysjmNW.mjs} +10 -10
  97. package/.output/server/_ssr/empty-CVLGfxuu.mjs +60 -0
  98. package/.output/server/_ssr/{empty-plugin-adapters-B5QvGpEl.mjs → empty-plugin-adapters-CPtwX8a7.mjs} +1 -1
  99. package/.output/server/_ssr/failureReports-DKDhVsXA.mjs +33 -0
  100. package/.output/server/_ssr/field-asw83X0v.mjs +189 -0
  101. package/.output/server/_ssr/getBundleChildren-Cv9iz1As.mjs +22 -0
  102. package/.output/server/_ssr/{input-group-Coj6SwVP.mjs → input-group-CuYJHosb.mjs} +2 -2
  103. package/.output/server/_ssr/insights-B55FG5fm.mjs +14 -0
  104. package/.output/server/_ssr/insights-CWnHIFUV.mjs +5 -0
  105. package/.output/server/_ssr/insights-CpLpedFZ.mjs +2315 -0
  106. package/.output/server/_ssr/insights-api-sjws23WF.mjs +122 -0
  107. package/.output/server/_ssr/insights-errors-BjnFUyZj.mjs +34 -0
  108. package/.output/server/_ssr/insights-failures-rpc-BvbWvqNf.mjs +36 -0
  109. package/.output/server/_ssr/insights-failures-xaa41Buf.mjs +46 -0
  110. package/.output/server/_ssr/insights-recovery-BWQhiZaF.mjs +28 -0
  111. package/.output/server/_ssr/insights-recovery-rpc-GiYXdkaY.mjs +32 -0
  112. package/.output/server/_ssr/{insights-rpc-l2-oBYth.mjs → insights-rpc-DP_8Rmz4.mjs} +22 -14
  113. package/.output/server/_ssr/insights-usage-BpBM0U0c.mjs +29 -0
  114. package/.output/server/_ssr/insights-usage-rpc-BiolgB8e.mjs +29 -0
  115. package/.output/server/_ssr/insights-usage-rpc-KStL0zOx.mjs +41 -0
  116. package/.output/server/_ssr/insightsUsage-CzhVjsc8.mjs +56 -0
  117. package/.output/server/_ssr/insights_.distribution-CVrnbP3R.mjs +7 -0
  118. package/.output/server/_ssr/insights_.distribution-D2HaNTAA.mjs +16 -0
  119. package/.output/server/_ssr/{insights_.distribution-INw6QxbU.mjs → insights_.distribution-DN9pR422.mjs} +50 -22
  120. package/.output/server/_ssr/installations-BHmbbnH8.mjs +5 -0
  121. package/.output/server/_ssr/{installations-DH-tQuRo.mjs → installations-BSCmFq8Q.mjs} +279 -367
  122. package/.output/server/_ssr/installations-DD1g__Nc.mjs +69 -0
  123. package/.output/server/_ssr/listReleases-BfUBlCHo.mjs +79 -0
  124. package/.output/server/_ssr/plugins-BaD1slHx.mjs +2086 -0
  125. package/.output/server/_ssr/release-adoption-005tQRpo.mjs +55 -0
  126. package/.output/server/_ssr/release-adoption-rpc-BsstqxUp.mjs +41 -0
  127. package/.output/server/_ssr/releaseAdoption-CtOFkwli.mjs +114 -0
  128. package/.output/server/_ssr/releaseManagement-DFMUnDF5.mjs +11 -0
  129. package/.output/server/_ssr/remote-config-C2Ee8lXO.mjs +5 -0
  130. package/.output/server/_ssr/remote-config-Dv1ccMo-.mjs +17 -0
  131. package/.output/server/_ssr/remote-config-of0lGQH8.mjs +2648 -0
  132. package/.output/server/_ssr/remote-config-rpc-BzX31mUS.mjs +115 -0
  133. package/.output/server/_ssr/rolldown-runtime-rSIU-vHC.mjs +13 -0
  134. package/.output/server/_ssr/{router-DfFyAp5p.mjs → router-D8NuoJ_I.mjs} +117 -136
  135. package/.output/server/_ssr/{routes-BtsfVx-p.mjs → routes-DgvnFqDy.mjs} +195 -429
  136. package/.output/server/_ssr/routes-V8sDybGf.mjs +409 -0
  137. package/.output/server/_ssr/runtime.server-De0QVWc9.mjs +104 -0
  138. package/.output/server/_ssr/schema-BiAVyEj2.mjs +361 -0
  139. package/.output/server/_ssr/{select-CyvLaMza.mjs → select-t3iXRwc9.mjs} +3 -3
  140. package/.output/server/_ssr/server-h4VxnExB.mjs +8325 -0
  141. package/.output/server/_ssr/{createSsrRpc-C-dLcQuy.mjs → sidebar-DJcErBMo.mjs} +107 -47
  142. package/.output/server/_ssr/ssr.mjs +1 -1
  143. package/.output/server/_ssr/start-BfFbHKMK.mjs +4 -0
  144. package/.output/server/_ssr/switch-E-btwMGt.mjs +43 -0
  145. package/.output/server/_ssr/{table-CkVhsNpe.mjs → table-37Dd23TN.mjs} +2 -2
  146. package/.output/server/_ssr/tabs-DEFnTm95.mjs +45 -0
  147. package/.output/server/_ssr/updateFailures-H6pnnYK4.mjs +40 -0
  148. package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-C88s9jWF.mjs} +2 -2
  149. package/.output/server/_tanstack-start-manifest_v-55AGsehh.mjs +139 -0
  150. package/.output/server/index.mjs +303 -205
  151. package/LICENSE +13 -14
  152. package/README.md +80 -71
  153. package/lib/index.d.mts +23 -11
  154. package/lib/vite-internal.mjs +13 -3
  155. package/lib/vite.d.mts +2 -0
  156. package/package.json +14 -14
  157. package/src/components/AppSidebar.tsx +74 -75
  158. package/src/components/ConsoleAccessPage.tsx +5 -1
  159. package/src/components/ConsoleFeatureUnavailable.tsx +80 -0
  160. package/src/components/NotFoundPage.tsx +7 -7
  161. package/src/components/features/FeatureSlots.tsx +103 -0
  162. package/src/components/features/api-keys/apiKeysFeatureUi.tsx +15 -0
  163. package/src/components/features/bundles/BundleMetadata.tsx +8 -5
  164. package/src/components/features/bundles/RolloutCohortsDialog.tsx +1 -1
  165. package/src/components/features/channels/ChannelManagementDialog.tsx +8 -6
  166. package/src/components/features/insights/AppUsage.tsx +41 -14
  167. package/src/components/features/insights/BuiltinBundle.tsx +36 -0
  168. package/src/components/features/insights/ComparedBundlesChart.tsx +310 -0
  169. package/src/components/features/insights/EstimatedCount.tsx +18 -0
  170. package/src/components/features/insights/EventDetails.tsx +390 -26
  171. package/src/components/features/insights/EventHistoryCard.tsx +111 -25
  172. package/src/components/features/insights/EventHistoryList.tsx +1 -1
  173. package/src/components/features/insights/FailureReports.tsx +407 -0
  174. package/src/components/features/insights/InsightsControls.tsx +240 -142
  175. package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
  176. package/src/components/features/insights/InsightsOverview.tsx +601 -403
  177. package/src/components/features/insights/InsightsPageHeader.tsx +19 -14
  178. package/src/components/features/insights/InsightsPeriodSelector.tsx +67 -12
  179. package/src/components/features/insights/InstallationHistoryCard.tsx +65 -10
  180. package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
  181. package/src/components/features/insights/ReleaseActivity.tsx +308 -0
  182. package/src/components/features/insights/ReportingDevicesSummary.tsx +13 -13
  183. package/src/components/features/insights/RollbackButton.tsx +118 -0
  184. package/src/components/features/insights/UpdateFailures.tsx +382 -0
  185. package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
  186. package/src/components/features/releases/ReleaseEditorSheet.tsx +27 -13
  187. package/src/components/features/remote-config/ConditionDialog.tsx +592 -0
  188. package/src/components/features/remote-config/ConditionsCard.tsx +247 -0
  189. package/src/components/features/remote-config/ParameterDialog.tsx +426 -0
  190. package/src/components/features/remote-config/ParametersCard.tsx +254 -0
  191. package/src/components/features/remote-config/PreviewCard.tsx +284 -0
  192. package/src/components/features/remote-config/PublishDialog.tsx +325 -0
  193. package/src/components/features/remote-config/RemoteConfigPage.tsx +353 -0
  194. package/src/components/features/remote-config/RuleSummary.tsx +21 -0
  195. package/src/components/features/remote-config/ValueInput.tsx +151 -0
  196. package/src/components/features/remote-config/VersionsCard.tsx +384 -0
  197. package/src/components/features/remote-config/remoteConfigFeatureUi.tsx +15 -0
  198. package/src/components/features/remote-config/useDateTimeText.ts +16 -0
  199. package/src/components/ui/sidebar.tsx +22 -29
  200. package/src/index.ts +26 -19
  201. package/src/lib/api-keys-api.ts +0 -28
  202. package/src/lib/api-keys-rpc.ts +27 -38
  203. package/src/lib/api-rpc.ts +125 -288
  204. package/src/lib/api.ts +15 -134
  205. package/src/lib/bundle-activity.ts +24 -4
  206. package/src/lib/console-access.ts +20 -0
  207. package/src/lib/console-features-api.ts +68 -0
  208. package/src/lib/console-features-rpc.ts +13 -0
  209. package/src/lib/console-features.ts +98 -0
  210. package/src/lib/insights-api.ts +56 -2
  211. package/src/lib/insights-errors.ts +64 -0
  212. package/src/lib/insights-failures-rpc.ts +36 -0
  213. package/src/lib/insights-failures.ts +130 -0
  214. package/src/lib/insights-recovery-rpc.ts +13 -17
  215. package/src/lib/insights-recovery.ts +12 -36
  216. package/src/lib/insights-retention.ts +21 -0
  217. package/src/lib/insights-rpc.ts +30 -12
  218. package/src/lib/insights-search.ts +50 -4
  219. package/src/lib/insights-usage-rpc.ts +15 -9
  220. package/src/lib/insights-usage.ts +41 -3
  221. package/src/lib/insights-view.ts +34 -5
  222. package/src/lib/release-adoption-api.ts +214 -0
  223. package/src/lib/release-adoption-rpc.ts +49 -0
  224. package/src/lib/release-adoption.ts +164 -0
  225. package/src/lib/remote-config-api.ts +119 -0
  226. package/src/lib/remote-config-draft.ts +666 -0
  227. package/src/lib/remote-config-rpc.ts +196 -0
  228. package/src/lib/scroll-restoration.ts +21 -0
  229. package/src/lib/server/auth.server.ts +13 -5
  230. package/src/lib/server/bundleActivity.ts +27 -28
  231. package/src/lib/server/config.server.ts +21 -76
  232. package/src/lib/server/console-feature-apis.ts +70 -0
  233. package/src/lib/server/console-runtime.server.ts +82 -38
  234. package/src/lib/server/downloadBundle.ts +14 -15
  235. package/src/lib/server/failureReports.ts +50 -0
  236. package/src/lib/server/getBundleChildren.ts +31 -122
  237. package/src/lib/server/insightsUsage.ts +52 -113
  238. package/src/lib/server/listReleases.ts +109 -44
  239. package/src/lib/server/releaseAdoption.ts +151 -0
  240. package/src/lib/server/releaseReachability.ts +8 -8
  241. package/src/lib/server/remoteConfig.ts +75 -0
  242. package/src/lib/server/runtime.server.ts +120 -31
  243. package/src/lib/server/updateFailures.ts +66 -0
  244. package/src/lib/use-dialog-target.ts +27 -0
  245. package/src/routeTree.gen.ts +44 -44
  246. package/src/router.tsx +3 -2
  247. package/src/routes/-installations-search.ts +32 -8
  248. package/src/routes/-releases-search.ts +76 -29
  249. package/src/routes/__root.tsx +7 -1
  250. package/src/routes/api/bundles/$bundleId/download.ts +2 -5
  251. package/src/routes/api-keys.tsx +5 -2
  252. package/src/routes/index.tsx +210 -152
  253. package/src/routes/insights.tsx +91 -23
  254. package/src/routes/insights_.distribution.tsx +50 -9
  255. package/src/routes/installations.tsx +97 -40
  256. package/src/routes/remote-config.tsx +42 -0
  257. package/src/styles.css +9 -0
  258. package/src/vite-internal.ts +15 -3
  259. package/src/vite.ts +2 -0
  260. package/.output/public/assets/HashValueDisplay-BpTc3Oyg.js +0 -1
  261. package/.output/public/assets/InsightsPageHeader-DC2eEst8.js +0 -1
  262. package/.output/public/assets/api-CdUyUqzR.js +0 -42
  263. package/.output/public/assets/api-keys-64PM9PwW.js +0 -1
  264. package/.output/public/assets/api-keys-api-BeaJnvY5.js +0 -1
  265. package/.output/public/assets/arrow-left-04Rh8Zgp.js +0 -1
  266. package/.output/public/assets/card-CYHs5FZv.js +0 -1
  267. package/.output/public/assets/chart-no-axes-combined-DwyMsVFW.js +0 -1
  268. package/.output/public/assets/createServerFn-DU71xucx.js +0 -3
  269. package/.output/public/assets/dialog-IVMkBRCq.js +0 -1
  270. package/.output/public/assets/dist-BR4GzeEu.js +0 -1
  271. package/.output/public/assets/field-B3CYDpjg.js +0 -1
  272. package/.output/public/assets/index-C39o7rJH.js +0 -10
  273. package/.output/public/assets/info-C3y9HNoU.js +0 -1
  274. package/.output/public/assets/input-group-nLZylhWF.js +0 -1
  275. package/.output/public/assets/insights-Bm9v8uO3.js +0 -2
  276. package/.output/public/assets/insights-DS3iO7Up.js +0 -1
  277. package/.output/public/assets/insights-search-Byj3g3Mp.js +0 -1
  278. package/.output/public/assets/insights-usage-rpc-BaGR3lgq.js +0 -1
  279. package/.output/public/assets/insights_.distribution-B1i6cjlQ.js +0 -1
  280. package/.output/public/assets/insights_.distribution-C1Wf27eB.js +0 -2
  281. package/.output/public/assets/installations-CkFW4fV9.js +0 -2
  282. package/.output/public/assets/installations-DxTaIcHD.js +0 -1
  283. package/.output/public/assets/lazyRouteComponent-BapzjYnb.js +0 -1
  284. package/.output/public/assets/list-Dqt53fWZ.js +0 -1
  285. package/.output/public/assets/loader-circle-CUKfUqow.js +0 -1
  286. package/.output/public/assets/preload-helper-ClBMtM1g.js +0 -1
  287. package/.output/public/assets/refresh-cw-CNE_2PnD.js +0 -1
  288. package/.output/public/assets/routes-Cgtvspjg.js +0 -2
  289. package/.output/public/assets/routes-O2r2UUN8.js +0 -1
  290. package/.output/public/assets/shield-check-BFcJ46kz.js +0 -1
  291. package/.output/public/assets/signing-BgspC-xP.js +0 -1
  292. package/.output/public/assets/styles-DnrWuBwd.css +0 -2
  293. package/.output/public/assets/table-BuWQgxJJ.js +0 -1
  294. package/.output/public/assets/useMutation-CSV9NCUl.js +0 -1
  295. package/.output/public/assets/useOpenInteractionType-D5kduCTD.js +0 -1
  296. package/.output/public/assets/valueToPercent-BAtcbSLk.js +0 -1
  297. package/.output/server/__23tanstack-start-server-fn-resolver-CmYkol9D.mjs +0 -150
  298. package/.output/server/_ssr/InsightsPageHeader-Dma3dVj1.mjs +0 -64
  299. package/.output/server/_ssr/api-C3bGmgY0.mjs +0 -332
  300. package/.output/server/_ssr/api-keys-api-BRF6rbRC.mjs +0 -60
  301. package/.output/server/_ssr/api-keys-rpc-BMa1ZM6N.mjs +0 -136
  302. package/.output/server/_ssr/api-rpc-BYQ2e0z6.mjs +0 -1399
  303. package/.output/server/_ssr/auth.server-CmicHh0J.mjs +0 -47
  304. package/.output/server/_ssr/bundle-signing-rpc-DbQ4pFAX.mjs +0 -16
  305. package/.output/server/_ssr/bundle-signing.server-Uqt1OF49.mjs +0 -12
  306. package/.output/server/_ssr/bundleActivity-pBrPSG01.mjs +0 -28
  307. package/.output/server/_ssr/card-B7KSO_25.mjs +0 -108
  308. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
  309. package/.output/server/_ssr/config.server-C-d1QaBU.mjs +0 -368
  310. package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
  311. package/.output/server/_ssr/deleteBundle-8X-jq7Z7.mjs +0 -66
  312. package/.output/server/_ssr/field-BDwuU8RE.mjs +0 -74
  313. package/.output/server/_ssr/getBundleChildren-DoL8l_AS.mjs +0 -50
  314. package/.output/server/_ssr/insights-BDJjaDLp.mjs +0 -938
  315. package/.output/server/_ssr/insights-D0FWpfOS.mjs +0 -10
  316. package/.output/server/_ssr/insights-recovery-Cv6RSyeR.mjs +0 -21
  317. package/.output/server/_ssr/insights-recovery-rpc-BE9YsK_T.mjs +0 -37
  318. package/.output/server/_ssr/insights-search-1czJMZgk.mjs +0 -21
  319. package/.output/server/_ssr/insights-usage-rpc-BE_aCnea.mjs +0 -184
  320. package/.output/server/_ssr/insights-usage-rpc-CtAolz-t.mjs +0 -24
  321. package/.output/server/_ssr/insightsContract-B09K_Px1.mjs +0 -303
  322. package/.output/server/_ssr/insightsHistory-Ci4rYAgl.mjs +0 -41
  323. package/.output/server/_ssr/insightsRecovery-CaghWL2t.mjs +0 -113
  324. package/.output/server/_ssr/insightsUsage-DNIj6jxF.mjs +0 -71
  325. package/.output/server/_ssr/insights_.distribution-Cp0kf1vW.mjs +0 -10
  326. package/.output/server/_ssr/installations-Bhyiwx8G.mjs +0 -29
  327. package/.output/server/_ssr/routes-xBkI2Fmt.mjs +0 -37
  328. package/.output/server/_ssr/runtime.server-CsiKFp_Z.mjs +0 -436
  329. package/.output/server/_ssr/server-BdkuOWe8.mjs +0 -7468
  330. package/.output/server/_ssr/signing-CQ70saFm.mjs +0 -133
  331. package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
  332. package/.output/server/_tanstack-start-manifest_v-CrBsoKQQ.mjs +0 -125
  333. package/src/components/features/bundles/BundleActivityChart.tsx +0 -97
  334. package/src/components/features/bundles/BundleInsightsSummary.tsx +0 -126
  335. package/src/components/features/signing/BundleSigningPage.tsx +0 -139
  336. package/src/lib/bundle-signing-api.ts +0 -14
  337. package/src/lib/bundle-signing-rpc.ts +0 -16
  338. package/src/lib/server/bundle-signing.server.ts +0 -23
  339. package/src/lib/server/deleteBundle.ts +0 -145
  340. package/src/lib/server/insightsHistory.ts +0 -52
  341. package/src/lib/server/insightsRecovery.ts +0 -187
  342. package/src/routes/signing.tsx +0 -37
@@ -0,0 +1,151 @@
1
+ import type { HotUpdaterCoreApi, ReleaseRow } from "@hot-updater/plugin-core";
2
+ import type {
3
+ InsightsBundleEventFilter,
4
+ InsightsModel,
5
+ } from "@hot-updater/server/plugins";
6
+
7
+ import { insightsPeriodEnd, recoveryWindows } from "../insights-recovery";
8
+ import {
9
+ ADOPTION_CANDIDATES,
10
+ type AdoptionRelease,
11
+ type AdoptionReleaseInput,
12
+ type AdoptionReleaseResult,
13
+ type AdoptionReleasesInput,
14
+ type BundleEventsInput,
15
+ type BundleEventsSeries,
16
+ readAdoptionReleaseInput,
17
+ readAdoptionReleasesInput,
18
+ readBundleEventsInput,
19
+ } from "../release-adoption";
20
+
21
+ type ReleaseReads = Pick<
22
+ HotUpdaterCoreApi,
23
+ "findChannelByName" | "getRelease" | "listReleases"
24
+ >;
25
+
26
+ /** Rows read past a focused release to find the bundle before it. */
27
+ const PREVIOUS_ROWS = 4;
28
+
29
+ const labelOf = (release: ReleaseRow): AdoptionRelease => ({
30
+ releaseId: release.id,
31
+ bundleId: release.bundle_id!,
32
+ deployedAtMs: release.created_at_ms,
33
+ targetAppVersion: release.target_app_version,
34
+ enabled: release.enabled,
35
+ revision: release.revision,
36
+ });
37
+
38
+ // A release without a bundle has nothing to apply.
39
+ const bundles = (releases: readonly ReleaseRow[]) =>
40
+ releases.filter((release) => release.bundle_id).map(labelOf);
41
+
42
+ /**
43
+ * The newest bundle deployments of a channel and platform, newest first: one
44
+ * read of the channel and one of its newest releases.
45
+ */
46
+ export async function listAdoptionReleases(
47
+ core: ReleaseReads,
48
+ input: AdoptionReleasesInput,
49
+ ): Promise<readonly AdoptionRelease[]> {
50
+ const { platform, channel } = readAdoptionReleasesInput(input);
51
+ const row = await core.findChannelByName(channel);
52
+ if (row === null) return [];
53
+ return bundles(
54
+ await core.listReleases({
55
+ filter: { kind: "channelPlatform", channelId: row.id, platform },
56
+ order: "desc",
57
+ limit: ADOPTION_CANDIDATES,
58
+ }),
59
+ );
60
+ }
61
+
62
+ /**
63
+ * One bundle deployment of the channel and platform, for a focus the newest
64
+ * ones leave out, and with `withPrevious` the bundle deployed before it.
65
+ */
66
+ export async function getAdoptionRelease(
67
+ core: ReleaseReads,
68
+ input: AdoptionReleaseInput,
69
+ ): Promise<AdoptionReleaseResult> {
70
+ const { platform, channel, releaseId, withPrevious } =
71
+ readAdoptionReleaseInput(input);
72
+ const [row, release] = await Promise.all([
73
+ core.findChannelByName(channel),
74
+ core.getRelease(releaseId),
75
+ ]);
76
+ if (
77
+ row === null ||
78
+ release === null ||
79
+ !release.bundle_id ||
80
+ release.platform !== platform ||
81
+ release.channel_id !== row.id
82
+ )
83
+ return withPrevious ? { release: null, previous: null } : { release: null };
84
+ if (!withPrevious) return { release: labelOf(release) };
85
+ // Newest first, `after` a release lists older ones.
86
+ const older = await core.listReleases({
87
+ filter: { kind: "channelPlatform", channelId: row.id, platform },
88
+ order: "desc",
89
+ after: release.id,
90
+ limit: PREVIOUS_ROWS,
91
+ });
92
+ return { release: labelOf(release), previous: bundles(older)[0] ?? null };
93
+ }
94
+
95
+ /**
96
+ * One bundle's counts of one type in each interval of the period, from
97
+ * their hourly counts, never the events or the release table. Downloads are
98
+ * the bundle's download hours, which count the downloads a launch or crash
99
+ * implied too. Launches add two series: apply reports, and the kept
100
+ * UNCHANGED reports that moved an installation to the bundle with no apply
101
+ * report.
102
+ */
103
+ export async function getBundleEvents(
104
+ model: Pick<InsightsModel, "countEventSeries">,
105
+ input: BundleEventsInput,
106
+ now = Date.now(),
107
+ ): Promise<BundleEventsSeries> {
108
+ const { platform, channel, window, endMs, bundleId, type } =
109
+ readBundleEventsInput(input);
110
+ const { durationMs, intervalMs } = recoveryWindows[window];
111
+ const end = Math.min(endMs, insightsPeriodEnd(now));
112
+ const timeRange = { start: Math.max(0, end - durationMs), end };
113
+ const series = (filter: InsightsBundleEventFilter) =>
114
+ model.countEventSeries({ filter, timeRange, intervalMs });
115
+ const points =
116
+ type === "RECOVERED"
117
+ ? await series({ platform, channel, type, fromBundleId: bundleId })
118
+ : type === "DOWNLOADED"
119
+ ? await series({
120
+ platform,
121
+ channel,
122
+ type: "UPDATE_DOWNLOADED",
123
+ toBundleId: bundleId,
124
+ })
125
+ : await Promise.all([
126
+ series({
127
+ platform,
128
+ channel,
129
+ type: "UPDATE_APPLIED",
130
+ toBundleId: bundleId,
131
+ }),
132
+ series({
133
+ platform,
134
+ channel,
135
+ type: "UNCHANGED",
136
+ toBundleId: bundleId,
137
+ }),
138
+ ]).then(([applied, moved]) =>
139
+ applied.map((point, index) => ({
140
+ startMs: point.startMs,
141
+ events: point.events + (moved[index]?.events ?? 0),
142
+ })),
143
+ );
144
+ return {
145
+ bundleId,
146
+ type,
147
+ measuredAtMs: now,
148
+ points,
149
+ total: points.reduce((sum, point) => sum + point.events, 0),
150
+ };
151
+ }
@@ -1,12 +1,12 @@
1
- import {
2
- isReleaseEligibleForCohort,
3
- NUMERIC_COHORT_SIZE,
4
- } from "@hot-updater/core";
5
1
  import type {
6
2
  CompiledReleaseCatalog,
7
- ReleaseCatalogModel,
3
+ HotUpdaterCoreApi,
8
4
  ReleaseRow,
9
5
  } from "@hot-updater/plugin-core";
6
+ import {
7
+ isReleaseEligibleForCohort,
8
+ NUMERIC_COHORT_SIZE,
9
+ } from "@hot-updater/protocol";
10
10
 
11
11
  export type ReleaseReachabilityRow = ReleaseRow & {
12
12
  readonly currentlyUnreachable: boolean;
@@ -37,7 +37,6 @@ function collectReachableFromIndexes(
37
37
  for (const cohort of cohorts) {
38
38
  const selected = descriptors.find(
39
39
  (descriptor) =>
40
- descriptor.kind === "BUNDLE" &&
41
40
  descriptor.bundleId !== null &&
42
41
  isReleaseEligibleForCohort(descriptor, cohort),
43
42
  );
@@ -63,15 +62,16 @@ export function collectCurrentlyReachableReleaseIds(
63
62
  return reachableReleaseIds;
64
63
  }
65
64
 
65
+ /** Marks each release its scope's compiled catalog no longer serves: one catalog read per scope. */
66
66
  export async function addReleaseReachability(
67
- releaseCatalogs: Pick<ReleaseCatalogModel, "findByScopeKey">,
67
+ core: Pick<HotUpdaterCoreApi, "getReleaseCatalogRow">,
68
68
  releases: readonly ReleaseRow[],
69
69
  ): Promise<readonly ReleaseReachabilityRow[]> {
70
70
  const scopeKeys = [...new Set(releases.map(({ scope_key }) => scope_key))];
71
71
  const reachableByScope = new Map(
72
72
  await Promise.all(
73
73
  scopeKeys.map(async (scopeKey) => {
74
- const row = await releaseCatalogs.findByScopeKey(scopeKey);
74
+ const row = await core.getReleaseCatalogRow(scopeKey);
75
75
  const reachableReleaseIds =
76
76
  row === null
77
77
  ? new Set<string>()
@@ -0,0 +1,75 @@
1
+ import type { RemoteDatabase } from "@hot-updater/plugin-core";
2
+ import {
3
+ createRemoteConfigAdminApi,
4
+ type RemoteConfigActive,
5
+ type RemoteConfigAdminApi,
6
+ type RemoteConfigPublishResult,
7
+ type RemoteConfigTemplateIssue,
8
+ RemoteConfigValidationError,
9
+ type RemoteConfigVersionDetail,
10
+ type RemoteConfigVersionsPage,
11
+ } from "@hot-updater/server/plugins";
12
+
13
+ import { ConsoleFeatureUnavailableError } from "../console-features";
14
+
15
+ /** How a publish from the console ended; `invalid` lists what to fix. */
16
+ export type ConsolePublishResult =
17
+ | RemoteConfigPublishResult
18
+ | {
19
+ readonly status: "invalid";
20
+ readonly issues: readonly RemoteConfigTemplateIssue[];
21
+ };
22
+
23
+ /** The Remote Config the console pages use. */
24
+ export interface ConsoleRemoteConfig {
25
+ getActive(): Promise<RemoteConfigActive>;
26
+ listVersions(input: {
27
+ readonly limit?: number;
28
+ readonly cursor?: string;
29
+ }): Promise<RemoteConfigVersionsPage>;
30
+ getVersion(version: number): Promise<RemoteConfigVersionDetail | null>;
31
+ publish(input: {
32
+ readonly template: unknown;
33
+ readonly baseVersion: number;
34
+ readonly description?: string;
35
+ }): Promise<ConsolePublishResult>;
36
+ rollback(input: {
37
+ readonly version: number;
38
+ readonly baseVersion: number;
39
+ }): Promise<ConsolePublishResult | { readonly status: "not_found" }>;
40
+ }
41
+
42
+ const invalidOf = (error: unknown): ConsolePublishResult => {
43
+ if (error instanceof RemoteConfigValidationError) {
44
+ return { status: "invalid", issues: error.issues };
45
+ }
46
+ throw error;
47
+ };
48
+
49
+ /**
50
+ * Remote Config over the `remoteConfig()` API the console assembled on the
51
+ * database, or over the admin routes, which answer as the API does.
52
+ */
53
+ export const createLocalRemoteConfig = (
54
+ api: RemoteConfigAdminApi,
55
+ ): ConsoleRemoteConfig => ({
56
+ getActive: () => api.getActive(),
57
+ listVersions: (input) => api.listVersions(input),
58
+ getVersion: (version) => api.getVersion(version),
59
+ publish: (input) => api.publish(input).catch(invalidOf),
60
+ rollback: (input) => api.rollback(input),
61
+ });
62
+
63
+ /**
64
+ * Remote Config over a self-hosted server's admin routes. A route the server
65
+ * does not mount answers core's 404: it runs without `remoteConfig()`.
66
+ */
67
+ export const createAdminRemoteConfig = (
68
+ fetchAdmin: RemoteDatabase["fetchAdmin"],
69
+ ): ConsoleRemoteConfig =>
70
+ createLocalRemoteConfig(
71
+ createRemoteConfigAdminApi(fetchAdmin, {
72
+ unavailable: () =>
73
+ new ConsoleFeatureUnavailableError("remoteConfig", { remote: true }),
74
+ }),
75
+ );
@@ -1,36 +1,125 @@
1
- import type {
2
- ApiKeyModel,
3
- BundleRepository,
4
- DatabasePlugin,
1
+ import {
2
+ type ConfiguredDatabase,
3
+ isRemoteDatabase,
5
4
  } from "@hot-updater/plugin-core";
5
+
6
+ import {
7
+ type ConsoleFeature,
8
+ ConsoleFeatureUnavailableError,
9
+ type ConsoleFeatures,
10
+ consoleFeatures,
11
+ resolveConsoleFeatures,
12
+ } from "../console-features";
6
13
  import {
7
- createInsightsProvider,
8
- type InsightsProvider,
9
- } from "@hot-updater/server";
10
-
11
- export function createRuntimeHotUpdater(config: {
12
- readonly database: DatabasePlugin;
13
- }): InsightsProvider {
14
- return createInsightsProvider(config.database.models.insights);
14
+ type ConsoleFeatureApis,
15
+ consoleFeatureApis,
16
+ } from "./console-feature-apis";
17
+
18
+ export type { ConsoleFeatureApis } from "./console-feature-apis";
19
+
20
+ export interface ConsoleRuntime {
21
+ /** Whether the console reaches a self-hosted server through its admin API. */
22
+ readonly remote: boolean;
23
+ /** The features on here, from the plugins the console's config lists. */
24
+ features(): Promise<ConsoleFeatures>;
25
+ /** What serves the features the console can serve here; {@link requireFeature} hands it out. */
26
+ readonly apis: Partial<ConsoleFeatureApis>;
15
27
  }
16
28
 
17
- export function createApiKeyStore(config: {
18
- readonly database: BundleRepository;
19
- }): ApiKeyModel | null {
20
- const models: unknown = Reflect.get(config.database, "models");
21
- const apiKeys: unknown =
22
- typeof models === "object" && models !== null
23
- ? Reflect.get(models, "apiKeys")
24
- : undefined;
25
- if (
26
- typeof apiKeys !== "object" ||
27
- apiKeys === null ||
28
- typeof Reflect.get(apiKeys, "create") !== "function" ||
29
- typeof Reflect.get(apiKeys, "findByHash") !== "function" ||
30
- typeof Reflect.get(apiKeys, "list") !== "function" ||
31
- typeof Reflect.get(apiKeys, "revoke") !== "function"
32
- ) {
33
- return null;
29
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
30
+ typeof value === "object" && value !== null;
31
+
32
+ /**
33
+ * Whether one of Hot Updater's own factories made `plugin`: the brand they
34
+ * set under a registered symbol, read here with a copy of the key so the
35
+ * console imports no package's internals. Assembly refuses any other plugin
36
+ * that takes one of their ids, so features key on it.
37
+ */
38
+ const isOfficialPlugin = (plugin: unknown): boolean =>
39
+ isRecord(plugin) &&
40
+ Reflect.get(plugin, Symbol.for("@hot-updater/server/official-plugin")) ===
41
+ true;
42
+
43
+ const featureIds = Object.keys(consoleFeatureApis) as ConsoleFeature[];
44
+
45
+ /**
46
+ * What serves each feature, built by its factory: over the plugins' APIs the
47
+ * console assembled, or over a self-hosted server's admin API. A feature
48
+ * without its plugin, or without a factory for the admin API, has none.
49
+ */
50
+ const featureApis = (
51
+ build: (feature: ConsoleFeature) => unknown,
52
+ ): Partial<ConsoleFeatureApis> =>
53
+ Object.fromEntries(
54
+ featureIds.flatMap((feature) => {
55
+ const api = build(feature);
56
+ return api === undefined ? [] : [[feature, api]];
57
+ }),
58
+ );
59
+
60
+ /**
61
+ * The console's features and what serves them, for the plugins its config
62
+ * lists, the plugins the server runs:
63
+ * - a self-hosted server (`standaloneRepository`) runs them itself; the
64
+ * console serves what that server's admin API serves of their features,
65
+ * and nothing that needs the database;
66
+ * - otherwise the console runs them over the database, as the server does,
67
+ * and serves their features through `api`, their APIs.
68
+ */
69
+ export const createConsoleRuntime = (config: {
70
+ readonly database: ConfiguredDatabase;
71
+ readonly plugins: readonly unknown[];
72
+ readonly api?: Readonly<Record<string, unknown>>;
73
+ }): ConsoleRuntime => {
74
+ const { database } = config;
75
+ // A feature's plugin is Hot Updater's own, which alone may take its id:
76
+ // the server's assembly refuses any other, and features key on the
77
+ // factory's mark, so another plugin under that id turns on nothing.
78
+ const official = new Set(
79
+ config.plugins
80
+ .filter(isOfficialPlugin)
81
+ .map((plugin) => (plugin as { id: string }).id),
82
+ );
83
+ if (isRemoteDatabase(database)) {
84
+ const { fetchAdmin } = database;
85
+ const features = resolveConsoleFeatures([...official], { remote: true });
86
+ return {
87
+ remote: true,
88
+ features: async () => features,
89
+ apis: featureApis((feature) =>
90
+ consoleFeatureApis[feature].remote?.(fetchAdmin),
91
+ ),
92
+ };
34
93
  }
35
- return apiKeys as ApiKeyModel;
36
- }
94
+ const api = config.api ?? {};
95
+ const features = resolveConsoleFeatures([...official], { remote: false });
96
+ return {
97
+ remote: false,
98
+ features: async () => features,
99
+ apis: featureApis((feature) => {
100
+ const { plugin } = consoleFeatures[feature];
101
+ const pluginApi = official.has(plugin) ? api[plugin] : undefined;
102
+ return pluginApi === undefined
103
+ ? undefined
104
+ : consoleFeatureApis[feature].local(pluginApi as never);
105
+ }),
106
+ };
107
+ };
108
+
109
+ /**
110
+ * What serves `feature`. Every feature's server function asks for it first,
111
+ * so a feature the console does not serve is refused with one error the
112
+ * client recognizes, before anything is read.
113
+ */
114
+ export const requireFeature = async <F extends ConsoleFeature>(
115
+ runtime: ConsoleRuntime,
116
+ feature: F,
117
+ ): Promise<ConsoleFeatureApis[F]> => {
118
+ const api = runtime.apis[feature];
119
+ if (api === undefined || !(await runtime.features())[feature]) {
120
+ throw new ConsoleFeatureUnavailableError(feature, {
121
+ remote: runtime.remote,
122
+ });
123
+ }
124
+ return api as ConsoleFeatureApis[F];
125
+ };
@@ -0,0 +1,66 @@
1
+ import type { InsightsReads } from "@hot-updater/server/plugins";
2
+
3
+ import {
4
+ checkFailureRate,
5
+ failureRate,
6
+ readUpdateFailuresInput,
7
+ type UpdateFailuresInput,
8
+ type UpdateFailuresReport,
9
+ } from "../insights-failures";
10
+ import { insightsPeriodEnd, recoveryWindows } from "../insights-recovery";
11
+
12
+ /** A release's or a channel's update failures over a period that ends with the current hour. */
13
+ export async function getUpdateFailuresReport(
14
+ reads: Pick<InsightsReads, "getUpdateFailures">,
15
+ input: UpdateFailuresInput,
16
+ now = Date.now(),
17
+ ): Promise<UpdateFailuresReport> {
18
+ readUpdateFailuresInput(input);
19
+ const end = insightsPeriodEnd(now);
20
+ const timeRange =
21
+ input.window === undefined
22
+ ? undefined
23
+ : {
24
+ start: Math.max(0, end - recoveryWindows[input.window].durationMs),
25
+ end,
26
+ };
27
+ const scope = {
28
+ platform: input.platform,
29
+ channel: input.channel,
30
+ ...(input.releaseId === undefined ? {} : { releaseId: input.releaseId }),
31
+ };
32
+ const previousRange =
33
+ timeRange && timeRange.start >= timeRange.end - timeRange.start
34
+ ? {
35
+ start: timeRange.start - (timeRange.end - timeRange.start),
36
+ end: timeRange.start,
37
+ }
38
+ : undefined;
39
+ const [failures, previous] = await Promise.all([
40
+ reads.getUpdateFailures({
41
+ ...scope,
42
+ ...(timeRange === undefined ? {} : { timeRange }),
43
+ }),
44
+ previousRange === undefined
45
+ ? undefined
46
+ : reads.getUpdateFailures({ ...scope, timeRange: previousRange }),
47
+ ]);
48
+ return {
49
+ ...failures,
50
+ startMs: timeRange?.start ?? null,
51
+ endMs: end,
52
+ ...(previous === undefined
53
+ ? {}
54
+ : {
55
+ previous: {
56
+ attemptRate: failureRate(previous),
57
+ checkRate: previous.checks
58
+ ? checkFailureRate(previous.checks)
59
+ : null,
60
+ complete:
61
+ previous.coverage.kind === "complete" &&
62
+ failures.coverage.kind === "complete",
63
+ },
64
+ }),
65
+ };
66
+ }
@@ -0,0 +1,27 @@
1
+ import { useState } from "react";
2
+
3
+ /**
4
+ * What a dialog opened for, kept while it animates closed. Closing clears
5
+ * the target at once, and without the kept one the dialog would empty or
6
+ * switch to another state, such as "Delete ?", during its exit animation.
7
+ */
8
+ export function useDialogTarget<T>(target: T | null): T | null {
9
+ const [shown, setShown] = useState(target);
10
+ if (target !== null && target !== shown) setShown(target);
11
+ return target ?? shown;
12
+ }
13
+
14
+ /**
15
+ * A number that changes each time `open` turns true, to key a dialog's form
16
+ * so every opening starts from its props, even one that comes before the
17
+ * last close finished animating.
18
+ */
19
+ export function useOpenSession(open: boolean): number {
20
+ const [session, setSession] = useState(0);
21
+ const [wasOpen, setWasOpen] = useState(open);
22
+ if (open !== wasOpen) {
23
+ setWasOpen(open);
24
+ if (open) setSession(session + 1);
25
+ }
26
+ return session;
27
+ }