@hot-updater/console 0.36.7 → 1.0.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 (262) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/BundleIdDisplay-D4ErdU5_.js +1 -0
  3. package/.output/public/assets/InsightsCapabilityContext-B42VqvP6.js +1 -0
  4. package/.output/public/assets/api-keys-api-DGVTTvTJ.js +1 -0
  5. package/.output/public/assets/api-keys-z16OZIP4.js +1 -0
  6. package/.output/public/assets/badge-Br_KDO0J.js +1 -0
  7. package/.output/public/assets/card-DzuEXvyJ.js +1 -0
  8. package/.output/public/assets/chart-no-axes-combined-FaYMGmQS.js +1 -0
  9. package/.output/public/assets/createServerFn-gM2-LFBy.js +3 -0
  10. package/.output/public/assets/dialog-BeAENgez.js +1 -0
  11. package/.output/public/assets/dist-Bssh_cv0.js +1 -0
  12. package/.output/public/assets/field-Buq148-A.js +1 -0
  13. package/.output/public/assets/index-uOpQAIC4.js +10 -0
  14. package/.output/public/assets/input-group-B3p5UpyW.js +1 -0
  15. package/.output/public/assets/insights-DJTr7Ozn.js +1 -0
  16. package/.output/public/assets/insights-api-DVMOoRMS.js +1 -0
  17. package/.output/public/assets/installations-BY64iIAK.js +2 -0
  18. package/.output/public/assets/installations-D5v9i3AL.js +1 -0
  19. package/.output/public/assets/lazyRouteComponent-DKYu5cZJ.js +1 -0
  20. package/.output/public/assets/preload-helper-CQ36nxok.js +1 -0
  21. package/.output/public/assets/refresh-cw-BkbfZ62O.js +1 -0
  22. package/.output/public/assets/routes-BuoI5-Cr.js +2 -0
  23. package/.output/public/assets/routes-C8fvgETO.js +1 -0
  24. package/.output/public/assets/search-qsIDvS2I.js +1 -0
  25. package/.output/public/assets/shield-check-DE_hVuHu.js +1 -0
  26. package/.output/public/assets/signing-mJD88Y4C.js +1 -0
  27. package/.output/public/assets/styles-BZIG7t42.css +2 -0
  28. package/.output/public/assets/table-RamFEclP.js +1 -0
  29. package/.output/public/assets/useCompositeListItem-ChzDqdpc.js +42 -0
  30. package/.output/public/assets/useMutation-JsGVjQMY.js +1 -0
  31. package/.output/public/assets/useOpenInteractionType-Dwfy-SpA.js +1 -0
  32. package/.output/server/__23tanstack-start-server-fn-resolver-Dftxlm2E.mjs +146 -0
  33. package/.output/server/_libs/@base-ui/react+[...].mjs +16124 -0
  34. package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +1554 -0
  35. package/.output/server/_libs/@tanstack/react-router+[...].mjs +41 -10
  36. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +132 -0
  37. package/.output/server/_libs/d3-array.mjs +231 -0
  38. package/.output/server/_libs/d3-color.mjs +330 -0
  39. package/.output/server/_libs/d3-format.mjs +252 -0
  40. package/.output/server/_libs/d3-interpolate.mjs +207 -0
  41. package/.output/server/_libs/d3-path.mjs +77 -0
  42. package/.output/server/_libs/d3-scale+[...].mjs +1907 -0
  43. package/.output/server/_libs/d3-shape.mjs +818 -0
  44. package/.output/server/_libs/decimal.js-light.mjs +884 -0
  45. package/.output/server/_libs/es-toolkit.mjs +1134 -0
  46. package/.output/server/_libs/eventemitter3.mjs +236 -0
  47. package/.output/server/_libs/immer.mjs +532 -0
  48. package/.output/server/_libs/lucide-react.mjs +141 -13
  49. package/.output/server/_libs/next-themes.mjs +2 -2
  50. package/.output/server/_libs/react-redux+[...].mjs +205 -0
  51. package/.output/server/_libs/recharts+[...].mjs +14117 -0
  52. package/.output/server/_libs/sonner.mjs +2 -2
  53. package/.output/server/_libs/tanstack__query-core.mjs +114 -1
  54. package/.output/server/_libs/tanstack__react-query.mjs +3 -4
  55. package/.output/server/_runtime.mjs +10 -1
  56. package/.output/server/_ssr/BundleActivityChart-CWcdPuUn.mjs +288 -0
  57. package/.output/server/_ssr/BundleIdDisplay-BEvB3MvA.mjs +13 -0
  58. package/.output/server/_ssr/InsightsCapabilityContext-Cx8fjm2s.mjs +15 -0
  59. package/.output/server/_ssr/api-keys-CpBqHpuC.mjs +300 -0
  60. package/.output/server/_ssr/api-keys-api-Czkp57UV.mjs +60 -0
  61. package/.output/server/_ssr/api-keys-rpc-BTtMDSoR.mjs +136 -0
  62. package/.output/server/_ssr/api-rpc-BEX8ISai.mjs +1468 -0
  63. package/.output/server/_ssr/auth-BF8mbAgF.mjs +21 -0
  64. package/.output/server/_ssr/auth-rpc-BFMEC-TT.mjs +24 -0
  65. package/.output/server/_ssr/auth.server-DW71WzsD.mjs +47 -0
  66. package/.output/server/_ssr/badge-CpWXQwyF.mjs +26 -0
  67. package/.output/server/_ssr/bundle-signing-rpc-BbCFUQSu.mjs +16 -0
  68. package/.output/server/_ssr/bundle-signing.server-C4NW5mWx.mjs +12 -0
  69. package/.output/server/_ssr/card-Bsc8mRis.mjs +86 -0
  70. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +13 -0
  71. package/.output/server/_ssr/config.server-DQYEr_5X.mjs +361 -0
  72. package/.output/server/_ssr/{createServerFn-DzU0k62V.mjs → createServerFn-B2hcXoPM.mjs} +1 -1
  73. package/.output/server/_ssr/createServerRpc-DB29C_5p.mjs +12 -0
  74. package/.output/server/_ssr/{sidebar-Cv_H0_Jv.mjs → createSsrRpc-DhfrxEjg.mjs} +68 -50
  75. package/.output/server/_ssr/databaseRows-QlCYLi0H.mjs +166 -0
  76. package/.output/server/_ssr/deleteBundle-Cu_XOMt0.mjs +66 -0
  77. package/.output/server/_ssr/dialog-C0vKUp8R.mjs +170 -0
  78. package/.output/server/_ssr/dist-BVSLTpoP.mjs +250 -0
  79. package/.output/server/_ssr/downloadBundle-CNnYxYO2.mjs +24 -0
  80. package/.output/server/_ssr/{empty-plugin-adapters-B4HCzgmM.mjs → empty-plugin-adapters-B5QvGpEl.mjs} +1 -1
  81. package/.output/server/_ssr/field-DASCh-5d.mjs +74 -0
  82. package/.output/server/_ssr/{getBundleChildren-CVtSiH5t.mjs → getBundleChildren-BcRMFoDF.mjs} +10 -18
  83. package/.output/server/_ssr/input-group-etIsBUqv.mjs +288 -0
  84. package/.output/server/_ssr/insights-api-C1F7UreR.mjs +71 -0
  85. package/.output/server/_ssr/insights-input-9fEJF-ZG.mjs +96 -0
  86. package/.output/server/_ssr/insights-rpc-C-8R6kED.mjs +155 -0
  87. package/.output/server/_ssr/insights-ygT_aIfo.mjs +858 -0
  88. package/.output/server/_ssr/installations-DqRT1QTd.mjs +535 -0
  89. package/.output/server/_ssr/installations-K8TCYTYh.mjs +19 -0
  90. package/.output/server/_ssr/router-BxBEvtF1.mjs +513 -0
  91. package/.output/server/_ssr/routes-BC7rn-tf.mjs +37 -0
  92. package/.output/server/_ssr/routes-CM5KAwpB.mjs +2285 -0
  93. package/.output/server/_ssr/runtime.server-la7TRAH_.mjs +527 -0
  94. package/.output/server/_ssr/server-CpzW--lT.mjs +7468 -0
  95. package/.output/server/_ssr/signing-jk4HANnl.mjs +134 -0
  96. package/.output/server/_ssr/ssr.mjs +4 -5740
  97. package/.output/server/_ssr/start-DpViQGnt.mjs +4 -0
  98. package/.output/server/_ssr/table-CU58HeI7.mjs +53 -0
  99. package/.output/server/_ssr/uuidv7-CaKuKXrV.mjs +31 -0
  100. package/.output/server/_tanstack-start-manifest_v-Mz9MmSlD.mjs +103 -0
  101. package/.output/server/index.mjs +229 -47
  102. package/README.md +29 -8
  103. package/lib/index.d.mts +35 -0
  104. package/lib/index.mjs +4 -0
  105. package/lib/vite-internal.mjs +127 -0
  106. package/lib/vite.d.mts +10 -0
  107. package/lib/vite.mjs +5 -0
  108. package/package.json +51 -29
  109. package/public/apple-touch-icon.png +0 -0
  110. package/public/favicon-16x16.png +0 -0
  111. package/public/favicon-32x32.png +0 -0
  112. package/public/favicon.ico +0 -0
  113. package/public/logo.svg +1 -0
  114. package/public/manifest.json +30 -0
  115. package/public/robots.txt +3 -0
  116. package/src/components/AppSidebar.tsx +159 -0
  117. package/src/components/BundleIdDisplay.tsx +18 -0
  118. package/src/components/ChannelBadge.tsx +31 -0
  119. package/src/components/ConsoleAccessPage.tsx +122 -0
  120. package/src/components/EnabledStatusIcon.tsx +54 -0
  121. package/src/components/HashValueDisplay.tsx +85 -0
  122. package/src/components/HotUpdaterLogo.tsx +30 -0
  123. package/src/components/NotFoundPage.tsx +33 -0
  124. package/src/components/PlatformIcon.tsx +39 -0
  125. package/src/components/RolloutPercentageBadge.tsx +28 -0
  126. package/src/components/ThemeProvider.tsx +116 -0
  127. package/src/components/TimestampDisplay.tsx +41 -0
  128. package/src/components/features/api-keys/ApiKeysPage.tsx +377 -0
  129. package/src/components/features/bundles/BundleActivityChart.tsx +193 -0
  130. package/src/components/features/bundles/BundleChildrenPanel.tsx +201 -0
  131. package/src/components/features/bundles/BundleInsightsSummary.tsx +123 -0
  132. package/src/components/features/bundles/BundleMetadata.tsx +136 -0
  133. package/src/components/features/bundles/RolloutCohortsDialog.tsx +201 -0
  134. package/src/components/features/channels/ChannelManagementDialog.tsx +228 -0
  135. package/src/components/features/insights/ActivityChart.tsx +150 -0
  136. package/src/components/features/insights/BundleDistribution.tsx +133 -0
  137. package/src/components/features/insights/BundleSelector.tsx +114 -0
  138. package/src/components/features/insights/InsightsCapabilityContext.tsx +24 -0
  139. package/src/components/features/insights/InsightsControls.tsx +115 -0
  140. package/src/components/features/insights/InsightsErrorAlert.tsx +21 -0
  141. package/src/components/features/insights/InsightsOverview.tsx +208 -0
  142. package/src/components/features/insights/InstallationHistoryCard.tsx +248 -0
  143. package/src/components/features/insights/InstallationMatchesCard.tsx +132 -0
  144. package/src/components/features/insights/InstallationPageHeader.tsx +138 -0
  145. package/src/components/features/insights/InstallationPagination.tsx +48 -0
  146. package/src/components/features/insights/InstallationSearch.tsx +230 -0
  147. package/src/components/features/insights/UpdateOutcomes.tsx +147 -0
  148. package/src/components/features/releases/ReleaseEditorSheet.tsx +879 -0
  149. package/src/components/features/releases/ReleaseStateBadge.tsx +20 -0
  150. package/src/components/features/signing/BundleSigningPage.tsx +139 -0
  151. package/src/components/ui/alert-dialog.tsx +185 -0
  152. package/src/components/ui/alert.tsx +76 -0
  153. package/src/components/ui/badge.tsx +49 -0
  154. package/src/components/ui/button.tsx +58 -0
  155. package/src/components/ui/card.tsx +89 -0
  156. package/src/components/ui/chart.tsx +371 -0
  157. package/src/components/ui/combobox.tsx +303 -0
  158. package/src/components/ui/dialog.tsx +156 -0
  159. package/src/components/ui/dropdown-menu.tsx +270 -0
  160. package/src/components/ui/field.tsx +235 -0
  161. package/src/components/ui/input-group.tsx +147 -0
  162. package/src/components/ui/input.tsx +19 -0
  163. package/src/components/ui/label.tsx +25 -0
  164. package/src/components/ui/progress.tsx +84 -0
  165. package/src/components/ui/select.tsx +205 -0
  166. package/src/components/ui/separator.tsx +23 -0
  167. package/src/components/ui/sheet.tsx +132 -0
  168. package/src/components/ui/sidebar.tsx +779 -0
  169. package/src/components/ui/skeleton.tsx +13 -0
  170. package/src/components/ui/slider.tsx +56 -0
  171. package/src/components/ui/sonner.tsx +45 -0
  172. package/src/components/ui/switch.tsx +25 -0
  173. package/src/components/ui/table.tsx +114 -0
  174. package/src/components/ui/textarea.tsx +18 -0
  175. package/src/components/ui/toggle-group.tsx +89 -0
  176. package/src/components/ui/toggle.tsx +43 -0
  177. package/src/components/ui/tooltip.tsx +66 -0
  178. package/src/hooks/use-mobile.ts +30 -0
  179. package/src/index.ts +47 -0
  180. package/src/lib/api-keys-api.ts +69 -0
  181. package/src/lib/api-keys-rpc.ts +87 -0
  182. package/src/lib/api-rpc.ts +478 -0
  183. package/src/lib/api.ts +444 -0
  184. package/src/lib/auth-rpc.ts +16 -0
  185. package/src/lib/bundle-signing-api.ts +14 -0
  186. package/src/lib/bundle-signing-rpc.ts +16 -0
  187. package/src/lib/constants.ts +1 -0
  188. package/src/lib/git.ts +51 -0
  189. package/src/lib/insights-api.ts +144 -0
  190. package/src/lib/insights-error.ts +19 -0
  191. package/src/lib/insights-input.ts +187 -0
  192. package/src/lib/insights-overview.ts +170 -0
  193. package/src/lib/insights-rpc.ts +218 -0
  194. package/src/lib/server/auth.server.ts +43 -0
  195. package/src/lib/server/bundle-signing.server.ts +23 -0
  196. package/src/lib/server/config.server.ts +109 -0
  197. package/src/lib/server/console-runtime.server.ts +61 -0
  198. package/src/lib/server/deleteBundle.ts +145 -0
  199. package/src/lib/server/downloadBundle.ts +46 -0
  200. package/src/lib/server/getBundleChildren.ts +130 -0
  201. package/src/lib/server/listReleases.ts +61 -0
  202. package/src/lib/server/releaseActivity.ts +27 -0
  203. package/src/lib/server/releaseReachability.ts +95 -0
  204. package/src/lib/server/runtime.server.ts +172 -0
  205. package/src/lib/utils.ts +9 -0
  206. package/src/logo.svg +1 -0
  207. package/src/routeTree.gen.ts +199 -0
  208. package/src/router.tsx +21 -0
  209. package/src/routes/-installations-search.ts +19 -0
  210. package/src/routes/-releases-search.ts +74 -0
  211. package/src/routes/__root.tsx +174 -0
  212. package/src/routes/api/auth/$.ts +19 -0
  213. package/src/routes/api/bundles/$bundleId/download.ts +17 -0
  214. package/src/routes/api-keys.tsx +37 -0
  215. package/src/routes/index.tsx +982 -0
  216. package/src/routes/insights.tsx +139 -0
  217. package/src/routes/installations.tsx +194 -0
  218. package/src/routes/signing.tsx +37 -0
  219. package/src/styles.css +152 -0
  220. package/src/virtual-modules.d.ts +21 -0
  221. package/src/vite-env.d.ts +1 -0
  222. package/src/vite-internal.ts +195 -0
  223. package/src/vite.ts +12 -0
  224. package/.output/public/assets/dist-DJX53faP.js +0 -3
  225. package/.output/public/assets/index-Iylis5kY.js +0 -10
  226. package/.output/public/assets/routes-HT21hs0C.js +0 -54
  227. package/.output/public/assets/styles-C0Re4quB.css +0 -2
  228. package/.output/server/__23tanstack-start-server-fn-resolver-CgwgZ0-i.mjs +0 -66
  229. package/.output/server/_libs/@floating-ui/core+[...].mjs +0 -698
  230. package/.output/server/_libs/@floating-ui/dom+[...].mjs +0 -644
  231. package/.output/server/_libs/@floating-ui/react-dom+[...].mjs +0 -839
  232. package/.output/server/_libs/@radix-ui/react-alert-dialog+[...].mjs +0 -2138
  233. package/.output/server/_libs/@radix-ui/react-popper+[...].mjs +0 -308
  234. package/.output/server/_libs/@radix-ui/react-select+[...].mjs +0 -1038
  235. package/.output/server/_libs/@tanstack/devtools-event-client+[...].mjs +0 -196
  236. package/.output/server/_libs/@tanstack/form-core+[...].mjs +0 -3228
  237. package/.output/server/_libs/@tanstack/react-form+[...].mjs +0 -430
  238. package/.output/server/_libs/@tanstack/react-table+[...].mjs +0 -2382
  239. package/.output/server/_libs/radix-ui__number.mjs +0 -6
  240. package/.output/server/_libs/radix-ui__primitive.mjs +0 -9
  241. package/.output/server/_libs/radix-ui__react-arrow.mjs +0 -23
  242. package/.output/server/_libs/radix-ui__react-collection.mjs +0 -78
  243. package/.output/server/_libs/radix-ui__react-direction.mjs +0 -13
  244. package/.output/server/_libs/radix-ui__react-label.mjs +0 -22
  245. package/.output/server/_libs/radix-ui__react-separator.mjs +0 -30
  246. package/.output/server/_libs/radix-ui__react-slider.mjs +0 -510
  247. package/.output/server/_libs/radix-ui__react-switch.mjs +0 -157
  248. package/.output/server/_libs/radix-ui__react-tooltip.mjs +0 -493
  249. package/.output/server/_ssr/api-rpc-BIbDUKSz.mjs +0 -292
  250. package/.output/server/_ssr/config.server-BSS366KT.mjs +0 -41
  251. package/.output/server/_ssr/deleteBundle-BgbZTtpW.mjs +0 -141
  252. package/.output/server/_ssr/dist-KboydRVk.mjs +0 -120
  253. package/.output/server/_ssr/router-C3hqEy3k.mjs +0 -307
  254. package/.output/server/_ssr/routes-B0lQvvlC.mjs +0 -3048
  255. package/.output/server/_ssr/start-02sLteYG.mjs +0 -4
  256. package/.output/server/_ssr/storageProfile-FVkjPoGb.mjs +0 -9
  257. package/.output/server/_tanstack-start-manifest_v-Bpx52WOU.mjs +0 -20
  258. package/.output/server/node_modules/tslib/modules/index.js +0 -70
  259. package/.output/server/node_modules/tslib/modules/package.json +0 -3
  260. package/.output/server/node_modules/tslib/package.json +0 -47
  261. package/.output/server/node_modules/tslib/tslib.js +0 -484
  262. package/.output/server/package.json +0 -9
@@ -0,0 +1,2285 @@
1
+ import { i as __toESM } from "../_runtime.mjs";
2
+ import { B as DialogPopup, F as DialogRoot, H as DialogClose, R as DialogTitle, U as DialogBackdrop, V as DialogDescription, _ as SelectPortal, a as SliderTrack, at as require_react, b as SelectTrigger$1, c as SelectGroup$1, d as SelectItemText, f as SelectItemIndicator, g as SelectPositioner, h as SelectPopup, i as SliderThumb, it as require_jsx_runtime, l as SelectScrollUpArrow, m as SelectList, n as SwitchRoot, o as SliderControl, p as SelectItem$1, r as SliderIndicator, s as SliderRoot, t as SwitchThumb, u as SelectScrollDownArrow, v as SelectIcon, x as SelectRoot, y as SelectValue$1, z as DialogPortal } from "../_libs/@base-ui/react+[...].mjs";
3
+ import { A as ChevronRight, C as FingerprintPattern, D as CircleOff, F as ArrowRight, M as ChevronDown, N as Check, S as Funnel, T as Download, _ as Minus, a as Tag, f as Plus, i as Tags, j as ChevronLeft, k as ChevronUp, m as Package, n as TriangleAlert, r as Trash2, t as X, u as RotateCcw, v as LoaderCircle, w as ExternalLink, y as List } from "../_libs/lucide-react.mjs";
4
+ import { C as cn, S as buttonVariants, T as useIsMobile, _ as Skeleton, g as SidebarTrigger, n as Input, r as Separator$1, t as Button$1, v as Tooltip$1, x as TooltipTrigger, y as TooltipContent } from "./createSsrRpc-DhfrxEjg.mjs";
5
+ import { p as Link } from "../_libs/@tanstack/react-router+[...].mjs";
6
+ import { a as Card, i as AlertTitle, l as CardHeader, o as CardContent, r as AlertDescription, s as CardDescription, t as Alert, u as CardTitle } from "./card-Bsc8mRis.mjs";
7
+ import { _ as DialogTrigger, a as AlertDialogDescription, d as Dialog$1, f as DialogContent, g as DialogTitle$1, h as DialogHeader, i as AlertDialogContent, l as AlertDialogTitle, m as DialogFooter, n as AlertDialogAction, o as AlertDialogFooter, p as DialogDescription$1, r as AlertDialogCancel, s as AlertDialogHeader, t as AlertDialog$1 } from "./dialog-C0vKUp8R.mjs";
8
+ import { t as Badge } from "./badge-CpWXQwyF.mjs";
9
+ import { a as FieldLabel, i as FieldGroup, n as FieldDescription, o as Label, r as FieldError, t as Field } from "./field-DASCh-5d.mjs";
10
+ import { a as TableHeader, i as TableHead, n as TableBody, o as TableRow, r as TableCell, t as Table } from "./table-CU58HeI7.mjs";
11
+ import { n as toast } from "../_libs/sonner.mjs";
12
+ import { C as normalizeCohortValue, S as isValidCohort, f as getNumericCohortRolloutPosition, g as getPatchFileHash, h as getPatchBaseFileHash, m as getPatchBaseBundleId, r as NUMERIC_COHORT_SIZE, t as INVALID_COHORT_ERROR_MESSAGE, w as normalizeRolloutCohortCount } from "./dist-BVSLTpoP.mjs";
13
+ import { r as extractTimestampFromUUIDv7, t as createUUIDv7 } from "./uuidv7-CaKuKXrV.mjs";
14
+ import { n as useInsightsCapability } from "./InsightsCapabilityContext-Cx8fjm2s.mjs";
15
+ import { C as useUpdateReleaseMutation, S as useReleasesQuery, a as InputGroupText, b as useReleaseCatalogDiagnosticsQuery, c as useBundleChildrenQuery, d as useChannelsQuery, f as useConfigQuery, h as useDeleteReleaseMutation, i as InputGroupInput, l as useBundleEventInsightsQuery, m as useDeleteChannelMutation, n as InputGroupAddon, o as Textarea, p as useCreateChannelMutation, s as useBundleChildCountsQuery, t as InputGroup, u as useBundleQuery, v as usePreflightReleaseMutation, x as useReleaseQuery, y as usePromoteReleaseMutation } from "./input-group-etIsBUqv.mjs";
16
+ import { a as InsightsErrorAlert, t as BundleActivityChart } from "./BundleActivityChart-CWcdPuUn.mjs";
17
+ import { t as BundleIdDisplay } from "./BundleIdDisplay-BEvB3MvA.mjs";
18
+ import { n as updateReleaseFilters, t as Route } from "./routes-BC7rn-tf.mjs";
19
+ import { n as require_dayjs_min, t as require_relativeTime } from "../_libs/dayjs.mjs";
20
+ import { t as normalizeRange } from "../_libs/verkit.mjs";
21
+ //#region node_modules/.nitro/vite/services/ssr/assets/routes-CM5KAwpB.js
22
+ var import_react = /* @__PURE__ */ __toESM(require_react());
23
+ var import_jsx_runtime = require_jsx_runtime();
24
+ var import_dayjs_min = /* @__PURE__ */ __toESM(require_dayjs_min());
25
+ var import_relativeTime = /* @__PURE__ */ __toESM(require_relativeTime());
26
+ var channelColors = {
27
+ production: "bg-green-500/10 text-green-700 dark:text-green-400 border-green-500/20",
28
+ dev: "bg-blue-500/10 text-blue-700 dark:text-blue-400 border-blue-500/20",
29
+ staging: "bg-yellow-500/10 text-yellow-700 dark:text-yellow-400 border-yellow-500/20"
30
+ };
31
+ function ChannelBadge({ channel, className }) {
32
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
33
+ variant: "outline",
34
+ className: cn(channelColors[channel.toLowerCase()] || "bg-gray-500/10 text-gray-700 dark:text-gray-400 border-gray-500/20", className),
35
+ children: channel
36
+ });
37
+ }
38
+ function EnabledStatusIcon({ enabled, className, falseIcon = "x", colorMode = "semantic" }) {
39
+ const iconColorClassName = colorMode === "inherit" ? "text-current" : void 0;
40
+ if (enabled) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: cn("h-4 w-4", iconColorClassName ?? "text-green-600 dark:text-green-400", className) });
41
+ if (falseIcon === "minus") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Minus, { className: cn("h-4 w-4", iconColorClassName ?? "text-muted-foreground", className) });
42
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: cn("h-4 w-4", iconColorClassName ?? "text-red-600 dark:text-red-400", className) });
43
+ }
44
+ import_dayjs_min.default.extend(import_relativeTime.default);
45
+ function TimestampDisplay({ uuid, format = "relative" }) {
46
+ const date = (0, import_dayjs_min.default)(extractTimestampFromUUIDv7(uuid));
47
+ if (format === "relative") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
48
+ className: "text-sm text-muted-foreground",
49
+ children: date.fromNow()
50
+ });
51
+ if (format === "absolute") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
52
+ className: "text-sm text-muted-foreground",
53
+ children: date.format("YYYY-MM-DD HH:mm:ss")
54
+ });
55
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
56
+ className: "flex flex-col",
57
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
58
+ className: "text-sm",
59
+ children: date.fromNow()
60
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
61
+ className: "text-xs text-muted-foreground",
62
+ children: date.format("YYYY-MM-DD HH:mm:ss")
63
+ })]
64
+ });
65
+ }
66
+ function BundleChildrenPanel({ panelId, bundle, bundles, loading, onDetailClick }) {
67
+ const isMobile = useIsMobile();
68
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
69
+ "aria-live": "polite",
70
+ className: "border-t bg-muted/10 p-3 sm:p-4",
71
+ id: panelId,
72
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
73
+ className: "flex flex-col gap-4",
74
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
75
+ className: "flex flex-wrap items-center justify-between gap-3",
76
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
77
+ className: "flex min-w-0 items-start gap-2 text-sm sm:items-center",
78
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
79
+ className: "text-muted-foreground",
80
+ children: "Base bundle"
81
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
82
+ bundleId: bundle.id,
83
+ fullOnMobile: true,
84
+ maxLength: 18
85
+ })]
86
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
87
+ variant: "outline",
88
+ children: [
89
+ bundles.length,
90
+ " ",
91
+ bundles.length === 1 ? "patch" : "patches"
92
+ ]
93
+ })]
94
+ }), loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
95
+ className: "flex flex-col gap-2",
96
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" })]
97
+ }) : bundles.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
98
+ className: "flex flex-col gap-2",
99
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
100
+ className: "text-xs font-semibold uppercase text-muted-foreground/70",
101
+ children: "Patch bundles from this base"
102
+ }), isMobile ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
103
+ className: "flex flex-col gap-2",
104
+ children: bundles.map((childBundle) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
105
+ className: "rounded-md border bg-background p-3",
106
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
107
+ className: "flex flex-col gap-3",
108
+ children: [
109
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
110
+ className: "space-y-1",
111
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
112
+ className: "text-[11px] font-medium uppercase text-muted-foreground/70",
113
+ children: "Patch Bundle"
114
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
115
+ bundleId: childBundle.id,
116
+ fullOnMobile: true,
117
+ maxLength: 18
118
+ })]
119
+ }),
120
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
121
+ className: "space-y-1",
122
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
123
+ className: "text-[11px] font-medium uppercase text-muted-foreground/70",
124
+ children: "Relation"
125
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
126
+ className: "flex flex-col items-start gap-1 text-sm",
127
+ children: [
128
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
129
+ bundleId: bundle.id,
130
+ fullOnMobile: true,
131
+ maxLength: 12
132
+ }),
133
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowRight, { className: "size-4 rotate-90 text-muted-foreground" }),
134
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
135
+ bundleId: childBundle.id,
136
+ fullOnMobile: true,
137
+ maxLength: 12
138
+ })
139
+ ]
140
+ })]
141
+ }),
142
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
143
+ className: "flex items-center justify-between gap-3",
144
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
145
+ className: "space-y-1",
146
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
147
+ className: "text-[11px] font-medium uppercase text-muted-foreground/70",
148
+ children: "Artifact"
149
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
150
+ variant: "secondary",
151
+ children: "bsdiff"
152
+ })]
153
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
154
+ className: "space-y-1 text-right",
155
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
156
+ className: "text-[11px] font-medium uppercase text-muted-foreground/70",
157
+ children: "Created"
158
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
159
+ className: "text-xs tabular-nums text-foreground",
160
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TimestampDisplay, { uuid: childBundle.id })
161
+ })]
162
+ })]
163
+ }),
164
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
165
+ className: "w-full",
166
+ onClick: () => onDetailClick(childBundle),
167
+ size: "sm",
168
+ type: "button",
169
+ variant: "outline",
170
+ children: "Detail"
171
+ })
172
+ ]
173
+ })
174
+ }, childBundle.id))
175
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
176
+ className: "overflow-x-auto rounded-md border bg-background",
177
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Table, {
178
+ className: "min-w-max",
179
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, {
180
+ className: "hover:bg-transparent",
181
+ children: [
182
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Patch Bundle" }),
183
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Relation" }),
184
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Artifact" }),
185
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Created" }),
186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
187
+ className: "w-24 text-right",
188
+ children: "Detail"
189
+ })
190
+ ]
191
+ }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableBody, { children: bundles.map((childBundle) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, { children: [
192
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
193
+ bundleId: childBundle.id,
194
+ fullOnMobile: true,
195
+ maxLength: 18
196
+ }) }),
197
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
198
+ className: "flex min-w-[280px] flex-col items-start gap-1 sm:flex-row sm:items-center sm:gap-2",
199
+ children: [
200
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
201
+ bundleId: bundle.id,
202
+ fullOnMobile: true,
203
+ maxLength: 12
204
+ }),
205
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowRight, { className: "size-4 shrink-0 rotate-90 text-muted-foreground sm:rotate-0" }),
206
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
207
+ bundleId: childBundle.id,
208
+ fullOnMobile: true,
209
+ maxLength: 12
210
+ })
211
+ ]
212
+ }) }),
213
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
214
+ variant: "secondary",
215
+ children: "bsdiff"
216
+ }) }),
217
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
218
+ className: "tabular-nums",
219
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TimestampDisplay, { uuid: childBundle.id })
220
+ }),
221
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
222
+ className: "text-right",
223
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
224
+ onClick: () => onDetailClick(childBundle),
225
+ size: "sm",
226
+ type: "button",
227
+ variant: "outline",
228
+ children: "Detail"
229
+ })
230
+ })
231
+ ] }, childBundle.id)) })]
232
+ })
233
+ })]
234
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
235
+ className: "flex flex-col gap-2",
236
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
237
+ className: "text-xs font-semibold uppercase text-muted-foreground/70",
238
+ children: "Patch bundles from this base"
239
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
240
+ className: "rounded-md border bg-background p-3 text-sm text-muted-foreground",
241
+ children: "No direct patch bundles."
242
+ })]
243
+ })]
244
+ })
245
+ });
246
+ }
247
+ function ChannelManagementDialog({ open, onOpenChange }) {
248
+ const [channelName, setChannelName] = (0, import_react.useState)("");
249
+ const [channelToDelete, setChannelToDelete] = (0, import_react.useState)(null);
250
+ const { data: channels = [], isPending: isLoadingChannels } = useChannelsQuery();
251
+ const createChannel = useCreateChannelMutation();
252
+ const deleteChannel = useDeleteChannelMutation();
253
+ const normalizedName = channelName.trim();
254
+ const handleCreate = async (event) => {
255
+ event.preventDefault();
256
+ if (!normalizedName || createChannel.isPending) return;
257
+ try {
258
+ const result = await createChannel.mutateAsync({
259
+ row: {
260
+ id: createUUIDv7(),
261
+ name: normalizedName
262
+ },
263
+ onConflict: "returnExisting"
264
+ });
265
+ setChannelName("");
266
+ if (result.inserted) toast.success(`Channel ${result.row.name} created`);
267
+ else toast.info(`Channel ${result.row.name} already exists`);
268
+ } catch (error) {
269
+ toast.error("Failed to create channel");
270
+ console.error(error);
271
+ }
272
+ };
273
+ const handleDelete = async () => {
274
+ if (!channelToDelete || deleteChannel.isPending) return;
275
+ try {
276
+ const result = await deleteChannel.mutateAsync({ id: channelToDelete.id });
277
+ if (result.deleted) toast.success(`Channel ${channelToDelete.name} deleted`);
278
+ else if (result.reason === "not_empty") toast.error(`Channel ${channelToDelete.name} is still in use and cannot be deleted`);
279
+ else toast.error(`Channel ${channelToDelete.name} no longer exists`);
280
+ setChannelToDelete(null);
281
+ } catch (error) {
282
+ toast.error("Failed to delete channel");
283
+ console.error(error);
284
+ }
285
+ };
286
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog$1, {
287
+ open,
288
+ onOpenChange,
289
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogContent, {
290
+ className: "sm:max-w-lg",
291
+ children: [
292
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogTitle$1, {
293
+ className: "flex items-center gap-2",
294
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tag, { className: "size-4 text-muted-foreground" }), "Channels"]
295
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogDescription$1, { children: "Create deployment channels and remove them when no Release uses them. Empty channels remain available until you delete them." })] }),
296
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("form", {
297
+ onSubmit: handleCreate,
298
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldGroup, {
299
+ className: "gap-2",
300
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Field, {
301
+ orientation: "horizontal",
302
+ children: [
303
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldLabel, {
304
+ htmlFor: "channel-name",
305
+ className: "sr-only",
306
+ children: "Channel name"
307
+ }),
308
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
309
+ id: "channel-name",
310
+ value: channelName,
311
+ onChange: (event) => setChannelName(event.target.value),
312
+ placeholder: "Channel name",
313
+ autoComplete: "off",
314
+ disabled: createChannel.isPending
315
+ }),
316
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
317
+ type: "submit",
318
+ disabled: !normalizedName || createChannel.isPending,
319
+ children: [createChannel.isPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoaderCircle, {
320
+ "data-icon": "inline-start",
321
+ className: "animate-spin"
322
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, { "data-icon": "inline-start" }), "Create"]
323
+ })
324
+ ]
325
+ })
326
+ })
327
+ }),
328
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
329
+ className: "max-h-72 overflow-y-auto rounded-lg border",
330
+ children: isLoadingChannels ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
331
+ className: "flex h-20 items-center justify-center text-muted-foreground",
332
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoaderCircle, { className: "mr-2 size-3.5 animate-spin" }), "Loading channels"]
333
+ }) : channels.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
334
+ className: "flex h-20 items-center justify-center text-muted-foreground",
335
+ children: "No channels yet"
336
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
337
+ className: "divide-y",
338
+ children: channels.map((channel) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", {
339
+ className: "flex min-h-12 items-center gap-3 px-3 py-2",
340
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
341
+ className: "min-w-0 flex-1",
342
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
343
+ className: "truncate text-xs font-medium",
344
+ children: channel.name
345
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
346
+ className: "truncate font-mono text-[10px] text-muted-foreground",
347
+ children: channel.id
348
+ })]
349
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
350
+ type: "button",
351
+ variant: "ghost",
352
+ size: "icon-sm",
353
+ "aria-label": `Delete ${channel.name}`,
354
+ title: `Delete ${channel.name}`,
355
+ disabled: deleteChannel.isPending,
356
+ onClick: () => setChannelToDelete(channel),
357
+ className: "text-muted-foreground hover:text-destructive",
358
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Trash2, { "data-icon": "inline-start" })
359
+ })]
360
+ }, channel.id))
361
+ })
362
+ })
363
+ ]
364
+ })
365
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialog$1, {
366
+ open: channelToDelete !== null,
367
+ onOpenChange: (nextOpen) => {
368
+ if (!nextOpen && !deleteChannel.isPending) setChannelToDelete(null);
369
+ },
370
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogContent, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogTitle, { children: [
371
+ "Delete ",
372
+ channelToDelete?.name,
373
+ "?"
374
+ ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogDescription, { children: "Only empty channels can be removed. The server checks this when you delete it, and the channel can be created again later with a new ID." })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogFooter, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogCancel, {
375
+ disabled: deleteChannel.isPending,
376
+ children: "Cancel"
377
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogAction, {
378
+ variant: "destructive",
379
+ disabled: deleteChannel.isPending,
380
+ onClick: (event) => {
381
+ event.preventDefault();
382
+ handleDelete();
383
+ },
384
+ children: deleteChannel.isPending ? "Deleting..." : "Delete channel"
385
+ })] })] })
386
+ })] });
387
+ }
388
+ function Metric({ label, tone, value }) {
389
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
390
+ className: "flex flex-col gap-1",
391
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dt", {
392
+ className: "flex items-center gap-2 text-xs text-muted-foreground",
393
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
394
+ "aria-hidden": "true",
395
+ className: tone === "applied" ? "size-2 rounded-full bg-chart-2" : "size-2 rounded-full bg-muted-foreground"
396
+ }), label]
397
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
398
+ className: "text-xl font-semibold tracking-tight tabular-nums",
399
+ children: value
400
+ })]
401
+ });
402
+ }
403
+ function SupportedBundleInsightsSummary({ bundleId }) {
404
+ const { data, error, isLoading } = useBundleEventInsightsQuery({
405
+ bundleId,
406
+ window: "30d",
407
+ limit: 1,
408
+ offset: 0
409
+ }, true);
410
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardHeader, {
411
+ className: "p-4 pb-3",
412
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
413
+ className: "text-sm font-medium",
414
+ children: "Activity · 30 days"
415
+ })
416
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, {
417
+ className: "flex flex-col gap-3 px-4 pb-4",
418
+ children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
419
+ "aria-label": "Loading reported bundle outcomes",
420
+ className: "flex flex-col gap-3",
421
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
422
+ className: "grid grid-cols-2 gap-4",
423
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" })]
424
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-24 w-full sm:h-32" })]
425
+ }) : error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InsightsErrorAlert, {
426
+ error: error instanceof Error ? error : /* @__PURE__ */ new Error("Failed to load bundle insights."),
427
+ fallbackTitle: "Insights unavailable"
428
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dl", {
429
+ className: "grid grid-cols-2 divide-x divide-border/70",
430
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
431
+ className: "pr-4",
432
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Metric, {
433
+ label: "Applied",
434
+ tone: "applied",
435
+ value: data?.summary.installed ?? 0
436
+ })
437
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
438
+ className: "pl-4",
439
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Metric, {
440
+ label: "Recovered",
441
+ tone: "recovered",
442
+ value: data?.summary.recovered ?? 0
443
+ })
444
+ })]
445
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleActivityChart, {
446
+ installed: data?.series.installed ?? [],
447
+ recovered: data?.series.recovered ?? [],
448
+ window: "30d"
449
+ })] })
450
+ })] });
451
+ }
452
+ function BundleInsightsSummary({ bundleId }) {
453
+ if (useInsightsCapability().status !== "supported") return null;
454
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SupportedBundleInsightsSummary, { bundleId });
455
+ }
456
+ function HashValueDisplay({ value, maxLength = 12, className }) {
457
+ const isTruncated = value.length > maxLength;
458
+ const truncated = isTruncated ? value.slice(0, maxLength) : value;
459
+ const copyValue = async () => {
460
+ try {
461
+ await navigator.clipboard.writeText(value);
462
+ toast.success("Copied to clipboard", { description: value });
463
+ } catch {
464
+ toast.error("Failed to copy value");
465
+ }
466
+ };
467
+ const handleClick = (event) => {
468
+ event.preventDefault();
469
+ event.stopPropagation();
470
+ copyValue();
471
+ };
472
+ const handleKeyDown = (event) => {
473
+ if (event.key === "Enter" || event.key === " ") {
474
+ event.preventDefault();
475
+ event.stopPropagation();
476
+ copyValue();
477
+ }
478
+ };
479
+ const content = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
480
+ translate: "no",
481
+ className: cn("font-mono text-xs tabular-nums whitespace-nowrap", className),
482
+ children: truncated
483
+ });
484
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Tooltip$1, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipTrigger, {
485
+ render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
486
+ type: "button",
487
+ translate: "no",
488
+ className: cn("ring-ring/30 bg-muted/40 border-border/70 inline-flex min-w-0 max-w-full cursor-pointer items-center rounded-md border px-1.5 py-0.5 align-top shadow-xs outline-none transition-[background-color,border-color,transform,box-shadow]", "hover:bg-muted/70 active:scale-[0.98] active:bg-muted/85 focus-visible:ring-[2px]", "touch-manipulation select-none", !isTruncated && "cursor-copy"),
489
+ onClick: handleClick,
490
+ onKeyDown: handleKeyDown
491
+ }),
492
+ children: content
493
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipContent, {
494
+ hidden: !isTruncated,
495
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
496
+ translate: "no",
497
+ className: "break-all font-mono text-xs tabular-nums",
498
+ children: value
499
+ })
500
+ })] });
501
+ }
502
+ var normalizeGitUrl = (gitUrl) => {
503
+ const trimmedGitUrl = gitUrl.trim();
504
+ if (trimmedGitUrl.startsWith("git@")) {
505
+ const sshMatch = trimmedGitUrl.match(/^git@([^:]+):(.+?)(?:\.git)?$/);
506
+ if (sshMatch) return `https://${sshMatch[1]}/${sshMatch[2]}`;
507
+ }
508
+ if (trimmedGitUrl.startsWith("ssh://")) try {
509
+ const url = new URL(trimmedGitUrl);
510
+ return `https://${url.hostname}${url.pathname}`.replace(/\/+$/, "").replace(/\.git$/, "");
511
+ } catch {
512
+ return trimmedGitUrl.replace(/\/+$/, "").replace(/\.git$/, "");
513
+ }
514
+ return trimmedGitUrl.replace(/\/+$/, "").replace(/\.git$/, "");
515
+ };
516
+ var getCommitUrl = (gitUrl, commitHash) => {
517
+ if (!gitUrl?.trim()) return null;
518
+ const normalizedGitUrl = normalizeGitUrl(gitUrl);
519
+ try {
520
+ const { hostname } = new URL(normalizedGitUrl);
521
+ if (hostname.includes("gitlab")) return `${normalizedGitUrl}/-/commit/${commitHash}`;
522
+ if (hostname.includes("bitbucket")) return `${normalizedGitUrl}/commits/${commitHash}`;
523
+ } catch {}
524
+ return `${normalizedGitUrl}/commit/${commitHash}`;
525
+ };
526
+ function Row({ label, value }) {
527
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
528
+ className: "flex flex-col gap-1.5 sm:flex-row sm:items-start sm:justify-between sm:gap-4",
529
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
530
+ className: "text-muted-foreground",
531
+ children: label
532
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
533
+ className: "min-w-0 text-left text-sm sm:text-right",
534
+ children: value
535
+ })]
536
+ });
537
+ }
538
+ function BundleMetadata({ bundle, release }) {
539
+ const { data: configData, isFetched } = useConfigQuery();
540
+ const patchBaseBundleId = bundle ? getPatchBaseBundleId(bundle) : null;
541
+ const hbcPatchFileHash = bundle ? getPatchFileHash(bundle) : null;
542
+ const hbcPatchBaseFileHash = bundle ? getPatchBaseFileHash(bundle) : null;
543
+ const gitCommitUrl = bundle?.gitCommitHash && isFetched ? getCommitUrl(configData?.console.gitUrl, bundle.gitCommitHash) : null;
544
+ const target = release.strategy === "APP_VERSION" ? release.target_app_version : release.fingerprint_hash;
545
+ if (!(target || bundle?.gitCommitHash || bundle?.fileHash || patchBaseBundleId || hbcPatchBaseFileHash || hbcPatchFileHash)) return null;
546
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardHeader, {
547
+ className: "p-4 pb-3",
548
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
549
+ className: "text-sm font-medium",
550
+ children: "Metadata"
551
+ })
552
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, {
553
+ className: "px-4 pb-4",
554
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dl", {
555
+ className: "flex flex-col gap-3 text-sm",
556
+ children: [
557
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
558
+ label: release.strategy === "APP_VERSION" ? "Target app version" : "Fingerprint target",
559
+ value: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
560
+ className: "break-all font-mono text-xs",
561
+ translate: "no",
562
+ children: target ?? "—"
563
+ })
564
+ }),
565
+ bundle?.gitCommitHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
566
+ label: "Git commit",
567
+ value: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
568
+ className: "flex items-center justify-start gap-1 sm:justify-end",
569
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HashValueDisplay, {
570
+ className: gitCommitUrl ? "text-primary" : void 0,
571
+ maxLength: 12,
572
+ value: bundle.gitCommitHash
573
+ }), gitCommitUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
574
+ "aria-label": "Open git commit",
575
+ className: "shrink-0 text-primary hover:underline",
576
+ href: gitCommitUrl,
577
+ rel: "noopener noreferrer",
578
+ target: "_blank",
579
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ExternalLink, { "aria-hidden": "true" })
580
+ }) : null]
581
+ })
582
+ }) : null,
583
+ bundle?.fileHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
584
+ label: "Bundle hash",
585
+ value: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HashValueDisplay, {
586
+ maxLength: 16,
587
+ value: bundle.fileHash
588
+ })
589
+ }) : null,
590
+ patchBaseBundleId ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
591
+ label: "Patch base",
592
+ value: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
593
+ bundleId: patchBaseBundleId,
594
+ fullOnMobile: true,
595
+ maxLength: 18
596
+ })
597
+ }) : null,
598
+ hbcPatchBaseFileHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
599
+ label: "Base hash",
600
+ value: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HashValueDisplay, {
601
+ maxLength: 16,
602
+ value: hbcPatchBaseFileHash
603
+ })
604
+ }) : null,
605
+ hbcPatchFileHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
606
+ label: "Patch hash",
607
+ value: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HashValueDisplay, {
608
+ maxLength: 16,
609
+ value: hbcPatchFileHash
610
+ })
611
+ }) : null
612
+ ]
613
+ })
614
+ })] });
615
+ }
616
+ var ALL_NUMERIC_COHORTS = Array.from({ length: NUMERIC_COHORT_SIZE }, (_, index) => index + 1);
617
+ function RolloutCohortsDialog({ releaseId, rolloutCohortCount, targetCohorts }) {
618
+ const normalizedRolloutCount = normalizeRolloutCohortCount(rolloutCohortCount);
619
+ const normalizedTargetCohorts = targetCohorts ?? [];
620
+ const hasTargetCohorts = normalizedTargetCohorts.length > 0;
621
+ const isPartialRollout = normalizedRolloutCount > 0 && normalizedRolloutCount < 1e3;
622
+ const isFullRollout = normalizedRolloutCount === NUMERIC_COHORT_SIZE;
623
+ const isMobile = useIsMobile();
624
+ const rolloutCohorts = isFullRollout ? ALL_NUMERIC_COHORTS : isPartialRollout ? ALL_NUMERIC_COHORTS.filter((cohortValue) => getNumericCohortRolloutPosition(releaseId, cohortValue) < normalizedRolloutCount) : [];
625
+ const rolloutPercentage = (normalizedRolloutCount / 10).toFixed(1);
626
+ const selectedCount = isFullRollout ? NUMERIC_COHORT_SIZE : rolloutCohorts.length;
627
+ const excludedCount = NUMERIC_COHORT_SIZE - selectedCount;
628
+ const dialogBody = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
629
+ className: "flex flex-col gap-4",
630
+ children: [
631
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
632
+ className: "grid grid-cols-2 gap-3",
633
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
634
+ className: "p-4",
635
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Selected cohorts" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
636
+ className: "font-mono text-2xl tabular-nums",
637
+ children: selectedCount
638
+ })]
639
+ }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
640
+ className: "p-4",
641
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Excluded cohorts" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
642
+ className: "font-mono text-2xl tabular-nums",
643
+ children: excludedCount
644
+ })]
645
+ }) })]
646
+ }),
647
+ hasTargetCohorts ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
648
+ className: "p-4 pb-3",
649
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
650
+ className: "text-sm",
651
+ children: "Additional cohorts"
652
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Always included in addition to the percentage rollout." })]
653
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, {
654
+ className: "p-4 pt-0",
655
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
656
+ className: "flex flex-wrap gap-2 rounded-lg border bg-muted/20 p-3",
657
+ children: normalizedTargetCohorts.map((cohort) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
658
+ className: "max-w-full font-mono",
659
+ variant: "secondary",
660
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
661
+ className: "truncate",
662
+ children: cohort
663
+ })
664
+ }, cohort))
665
+ })
666
+ })] }) : null,
667
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardHeader, {
668
+ className: "p-4 pb-3",
669
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
670
+ className: "text-sm",
671
+ children: "Numeric cohorts"
672
+ })
673
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, {
674
+ className: "p-4 pt-0",
675
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
676
+ className: "max-h-[50vh] overflow-y-auto overscroll-contain rounded-lg border bg-muted/20 p-3 sm:max-h-[45vh]",
677
+ children: rolloutCohorts.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
678
+ "aria-label": "Included numeric cohorts",
679
+ className: "grid grid-cols-3 gap-2 sm:grid-cols-6 lg:grid-cols-8",
680
+ role: "list",
681
+ children: rolloutCohorts.map((cohortValue) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
682
+ className: "justify-center font-mono tabular-nums",
683
+ role: "listitem",
684
+ variant: "outline",
685
+ children: cohortValue
686
+ }, cohortValue))
687
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
688
+ className: "text-sm text-muted-foreground",
689
+ children: "No numeric cohorts are included."
690
+ })
691
+ })
692
+ })] })
693
+ ]
694
+ });
695
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Dialog$1, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogTrigger, {
696
+ render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
697
+ size: "sm",
698
+ type: "button",
699
+ variant: "outline"
700
+ }),
701
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(List, { "data-icon": "inline-start" }), "Preview cohorts"]
702
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogContent, {
703
+ className: isMobile ? "top-0 left-0 h-dvh max-w-none translate-x-0 translate-y-0 rounded-none border-0 p-0" : "sm:max-w-3xl",
704
+ children: isMobile ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
705
+ className: "flex h-full flex-col overflow-hidden",
706
+ children: [
707
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, {
708
+ className: "shrink-0 border-b border-border/70 px-4 py-4",
709
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle$1, { children: "Rolled out cohorts" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogDescription$1, { children: [
710
+ rolloutPercentage,
711
+ "% includes ",
712
+ selectedCount,
713
+ " of",
714
+ " ",
715
+ NUMERIC_COHORT_SIZE,
716
+ " numeric cohorts.",
717
+ hasTargetCohorts ? " Additional cohorts are always included." : ""
718
+ ] })]
719
+ }),
720
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
721
+ className: "flex-1 overflow-y-auto overscroll-contain px-4 py-4",
722
+ children: dialogBody
723
+ }),
724
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogFooter, {
725
+ className: "shrink-0 border-t border-border/70 px-4 py-3",
726
+ showCloseButton: true
727
+ })
728
+ ]
729
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
730
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle$1, { children: "Rolled out cohorts" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogDescription$1, { children: [
731
+ rolloutPercentage,
732
+ "% includes ",
733
+ selectedCount,
734
+ " of",
735
+ " ",
736
+ NUMERIC_COHORT_SIZE,
737
+ " numeric cohorts.",
738
+ hasTargetCohorts ? " Additional cohorts are always included." : ""
739
+ ] })] }),
740
+ dialogBody,
741
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogFooter, { showCloseButton: true })
742
+ ] })
743
+ })] });
744
+ }
745
+ function AppleIcon({ className }) {
746
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
747
+ "aria-hidden": "true",
748
+ viewBox: "0 0 24 24",
749
+ xmlns: "http://www.w3.org/2000/svg",
750
+ className,
751
+ fill: "currentColor",
752
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701" })
753
+ });
754
+ }
755
+ function AndroidIcon({ className }) {
756
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
757
+ "aria-hidden": "true",
758
+ viewBox: "0 0 24 24",
759
+ xmlns: "http://www.w3.org/2000/svg",
760
+ className,
761
+ fill: "currentColor",
762
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M18.4395 5.5586c-.675 1.1664-1.352 2.3318-2.0274 3.498-.0366-.0155-.0742-.0286-.1113-.043-1.8249-.6957-3.484-.8-4.42-.787-1.8551.0185-3.3544.4643-4.2597.8203-.084-.1494-1.7526-3.021-2.0215-3.4864a1.1451 1.1451 0 0 0-.1406-.1914c-.3312-.364-.9054-.4859-1.379-.203-.475.282-.7136.9361-.3886 1.5019 1.9466 3.3696-.0966-.2158 1.9473 3.3593.0172.031-.4946.2642-1.3926 1.0177C2.8987 12.176.452 14.772 0 18.9902h24c-.119-1.1108-.3686-2.099-.7461-3.0683-.7438-1.9118-1.8435-3.2928-2.7402-4.1836a12.1048 12.1048 0 0 0-2.1309-1.6875c.6594-1.122 1.312-2.2559 1.9649-3.3848.2077-.3615.1886-.7956-.0079-1.1191a1.1001 1.1001 0 0 0-.8515-.5332c-.5225-.0536-.9392.3128-1.0488.5449zm-.0391 8.461c.3944.5926.324 1.3306-.1563 1.6503-.4799.3197-1.188.0985-1.582-.4941-.3944-.5927-.324-1.3307.1563-1.6504.4727-.315 1.1812-.1086 1.582.4941zM7.207 13.5273c.4803.3197.5506 1.0577.1563 1.6504-.394.5926-1.1038.8138-1.584.4941-.48-.3197-.5503-1.0577-.1563-1.6504.4008-.6021 1.1087-.8106 1.584-.4941z" })
763
+ });
764
+ }
765
+ function PlatformIcon({ platform, className }) {
766
+ if (platform === "ios") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppleIcon, { className });
767
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AndroidIcon, { className });
768
+ }
769
+ var Select$1 = SelectRoot;
770
+ function SelectGroup({ className, ...props }) {
771
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectGroup$1, {
772
+ "data-slot": "select-group",
773
+ className: cn("scroll-my-1 p-1", className),
774
+ ...props
775
+ });
776
+ }
777
+ function SelectValue({ className, ...props }) {
778
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue$1, {
779
+ "data-slot": "select-value",
780
+ className: cn("flex flex-1 text-left", className),
781
+ ...props
782
+ });
783
+ }
784
+ function SelectTrigger({ className, size = "default", children, ...props }) {
785
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectTrigger$1, {
786
+ "data-slot": "select-trigger",
787
+ "data-size": size,
788
+ className: cn("flex w-fit items-center justify-between gap-1.5 rounded-md border border-input bg-input/20 px-2 py-1.5 text-xs/relaxed whitespace-nowrap transition-colors outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-7 data-[size=sm]:h-6 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5", className),
789
+ ...props,
790
+ children: [children, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectIcon, { render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, { className: "pointer-events-none size-3.5 text-muted-foreground" }) })]
791
+ });
792
+ }
793
+ function SelectContent({ className, children, side = "bottom", sideOffset = 4, align = "center", alignOffset = 0, alignItemWithTrigger = true, ...props }) {
794
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectPortal, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectPositioner, {
795
+ side,
796
+ sideOffset,
797
+ align,
798
+ alignOffset,
799
+ alignItemWithTrigger,
800
+ className: "isolate z-50",
801
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectPopup, {
802
+ "data-slot": "select-content",
803
+ "data-align-trigger": alignItemWithTrigger,
804
+ className: cn("dark relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className),
805
+ ...props,
806
+ children: [
807
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectScrollUpButton, {}),
808
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectList, { children }),
809
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectScrollDownButton, {})
810
+ ]
811
+ })
812
+ }) });
813
+ }
814
+ function SelectItem({ className, children, ...props }) {
815
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectItem$1, {
816
+ "data-slot": "select-item",
817
+ className: cn("relative flex min-h-7 w-full cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", className),
818
+ ...props,
819
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItemText, {
820
+ className: "flex flex-1 shrink-0 gap-2 whitespace-nowrap",
821
+ children
822
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItemIndicator, {
823
+ render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pointer-events-none absolute right-2 flex items-center justify-center" }),
824
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: "pointer-events-none" })
825
+ })]
826
+ });
827
+ }
828
+ function SelectScrollUpButton({ className, ...props }) {
829
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectScrollUpArrow, {
830
+ "data-slot": "select-scroll-up-button",
831
+ className: cn("top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-3.5", className),
832
+ ...props,
833
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronUp, {})
834
+ });
835
+ }
836
+ function SelectScrollDownButton({ className, ...props }) {
837
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectScrollDownArrow, {
838
+ "data-slot": "select-scroll-down-button",
839
+ className: cn("bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-3.5", className),
840
+ ...props,
841
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, {})
842
+ });
843
+ }
844
+ function Sheet({ ...props }) {
845
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogRoot, {
846
+ "data-slot": "sheet",
847
+ ...props
848
+ });
849
+ }
850
+ function SheetPortal({ ...props }) {
851
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogPortal, {
852
+ "data-slot": "sheet-portal",
853
+ ...props
854
+ });
855
+ }
856
+ function SheetOverlay({ className, ...props }) {
857
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogBackdrop, {
858
+ "data-slot": "sheet-overlay",
859
+ className: cn("fixed inset-0 z-50 bg-black/80 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs", className),
860
+ ...props
861
+ });
862
+ }
863
+ function SheetContent({ className, children, side = "right", showCloseButton = true, ...props }) {
864
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SheetPortal, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SheetOverlay, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogPopup, {
865
+ "data-slot": "sheet-content",
866
+ "data-side": side,
867
+ className: cn("fixed z-50 flex flex-col bg-background bg-clip-padding text-xs/relaxed shadow-lg transition duration-200 ease-in-out data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm", className),
868
+ ...props,
869
+ children: [children, showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogClose, {
870
+ "data-slot": "sheet-close",
871
+ render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
872
+ variant: "ghost",
873
+ className: "absolute top-4 right-4",
874
+ size: "icon-sm"
875
+ }),
876
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
877
+ className: "sr-only",
878
+ children: "Close"
879
+ })]
880
+ })]
881
+ })] });
882
+ }
883
+ function SheetHeader({ className, ...props }) {
884
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
885
+ "data-slot": "sheet-header",
886
+ className: cn("flex flex-col gap-1.5 p-6", className),
887
+ ...props
888
+ });
889
+ }
890
+ function SheetTitle({ className, ...props }) {
891
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle, {
892
+ "data-slot": "sheet-title",
893
+ className: cn("text-sm font-medium text-foreground", className),
894
+ ...props
895
+ });
896
+ }
897
+ function SheetDescription({ className, ...props }) {
898
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogDescription, {
899
+ "data-slot": "sheet-description",
900
+ className: cn("text-xs/relaxed text-muted-foreground", className),
901
+ ...props
902
+ });
903
+ }
904
+ function Slider$1({ className, defaultValue, value, min = 0, max = 100, ...props }) {
905
+ const _values = Array.isArray(value) ? value : value !== void 0 ? [value] : Array.isArray(defaultValue) ? defaultValue : defaultValue !== void 0 ? [defaultValue] : [min];
906
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SliderRoot, {
907
+ className: cn("data-horizontal:w-full data-vertical:h-full", className),
908
+ "data-slot": "slider",
909
+ defaultValue,
910
+ value,
911
+ min,
912
+ max,
913
+ thumbAlignment: "edge",
914
+ ...props,
915
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SliderControl, {
916
+ className: "relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-vertical:h-full data-vertical:min-h-40 data-vertical:w-auto data-vertical:flex-col",
917
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SliderTrack, {
918
+ "data-slot": "slider-track",
919
+ className: "relative grow overflow-hidden rounded-full bg-primary/20 select-none data-horizontal:h-1.5 data-horizontal:w-full data-vertical:h-full data-vertical:w-1.5",
920
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SliderIndicator, {
921
+ "data-slot": "slider-range",
922
+ className: "bg-primary select-none data-horizontal:h-full data-vertical:w-full"
923
+ })
924
+ }), Array.from({ length: _values.length }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SliderThumb, {
925
+ "data-slot": "slider-thumb",
926
+ className: "relative block size-4 shrink-0 rounded-full border border-primary/50 bg-background shadow transition-colors select-none after:absolute after:-inset-2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring data-disabled:pointer-events-none data-disabled:opacity-50"
927
+ }, index))]
928
+ })
929
+ });
930
+ }
931
+ function Switch$1({ className, ...props }) {
932
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SwitchRoot, {
933
+ "data-slot": "switch",
934
+ className: cn("peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background data-checked:bg-primary data-unchecked:bg-input data-disabled:cursor-not-allowed data-disabled:opacity-50", className),
935
+ ...props,
936
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SwitchThumb, {
937
+ "data-slot": "switch-thumb",
938
+ className: "pointer-events-none block size-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-checked:translate-x-4 data-unchecked:translate-x-0"
939
+ })
940
+ });
941
+ }
942
+ var promoteActionItems = [{
943
+ label: "Move bundle",
944
+ value: "move"
945
+ }, {
946
+ label: "Copy bundle",
947
+ value: "copy"
948
+ }];
949
+ var draftFromRelease = (release) => ({
950
+ enabled: release.enabled,
951
+ message: release.message ?? "",
952
+ rolloutCohortCount: release.rollout_cohort_count,
953
+ shouldForceUpdate: release.should_force_update,
954
+ targetAppVersion: release.target_app_version ?? "",
955
+ targetCohorts: [...release.target_cohorts]
956
+ });
957
+ var patchFromDraft = (release, draft) => ({
958
+ enabled: draft.enabled,
959
+ message: draft.message.trim() || null,
960
+ rolloutCohortCount: draft.rolloutCohortCount,
961
+ shouldForceUpdate: draft.shouldForceUpdate,
962
+ ...release.strategy === "APP_VERSION" ? { targetAppVersion: draft.targetAppVersion.trim() } : {},
963
+ targetCohorts: draft.targetCohorts
964
+ });
965
+ var formatRolloutPercentage = (count) => (count / 10).toFixed(1);
966
+ var formatRolloutInput = (value) => {
967
+ const normalized = value.replace(",", ".");
968
+ const [integer = "", ...decimals] = normalized.split(".");
969
+ const integerDigits = integer.replace(/\D/g, "").slice(0, 3);
970
+ const decimalDigit = decimals.join("").replace(/\D/g, "").slice(0, 1);
971
+ return normalized.includes(".") ? `${integerDigits}.${decimalDigit}` : integerDigits;
972
+ };
973
+ var parseRolloutInput = (value) => {
974
+ const percentage = Number.parseFloat(value);
975
+ if (!Number.isFinite(percentage)) return null;
976
+ return Math.min(1e3, Math.max(0, Math.trunc(percentage * 10)));
977
+ };
978
+ function RolloutPercentageInput({ value, onChange }) {
979
+ const [inputValue, setInputValue] = (0, import_react.useState)(formatRolloutPercentage(value));
980
+ const [focused, setFocused] = (0, import_react.useState)(false);
981
+ (0, import_react.useEffect)(() => {
982
+ if (!focused) setInputValue(formatRolloutPercentage(value));
983
+ }, [focused, value]);
984
+ const commitInput = () => {
985
+ const nextValue = parseRolloutInput(inputValue) ?? value;
986
+ setFocused(false);
987
+ setInputValue(formatRolloutPercentage(nextValue));
988
+ onChange(nextValue);
989
+ };
990
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
991
+ className: "flex items-center gap-3",
992
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Slider$1, {
993
+ "aria-label": "Rollout percentage",
994
+ max: 1e3,
995
+ min: 0,
996
+ onValueChange: (nextValue) => onChange(Array.isArray(nextValue) ? nextValue[0] ?? value : nextValue),
997
+ step: 1,
998
+ value
999
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(InputGroup, {
1000
+ className: "w-24 shrink-0",
1001
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(InputGroupInput, {
1002
+ "aria-label": "Rollout percentage",
1003
+ autoComplete: "off",
1004
+ className: "text-right tabular-nums",
1005
+ id: "release-rollout-percentage",
1006
+ inputMode: "decimal",
1007
+ name: "rolloutPercentage",
1008
+ onBlur: commitInput,
1009
+ onChange: (event) => {
1010
+ const nextInput = formatRolloutInput(event.target.value);
1011
+ setInputValue(nextInput);
1012
+ const parsed = parseRolloutInput(nextInput);
1013
+ if (parsed !== null) onChange(parsed);
1014
+ },
1015
+ onFocus: () => setFocused(true),
1016
+ value: inputValue
1017
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InputGroupAddon, {
1018
+ align: "inline-end",
1019
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InputGroupText, { children: "%" })
1020
+ })]
1021
+ })]
1022
+ });
1023
+ }
1024
+ function ReleaseEditorSheet({ channels, onOpenChange, open, releaseId }) {
1025
+ const releaseQuery = useReleaseQuery(releaseId);
1026
+ const release = releaseQuery.data;
1027
+ const bundleQuery = useBundleQuery(release?.bundle_id ?? "");
1028
+ const bundle = bundleQuery.data;
1029
+ const diagnostics = useReleaseCatalogDiagnosticsQuery(release?.scope_key ?? "");
1030
+ const update = useUpdateReleaseMutation();
1031
+ const preflight = usePreflightReleaseMutation();
1032
+ const remove = useDeleteReleaseMutation();
1033
+ const promote = usePromoteReleaseMutation();
1034
+ const [draft, setDraft] = (0, import_react.useState)(null);
1035
+ const [newCohort, setNewCohort] = (0, import_react.useState)("");
1036
+ const [error, setError] = (0, import_react.useState)("");
1037
+ const [confirmDiscard, setConfirmDiscard] = (0, import_react.useState)(false);
1038
+ const [confirmDelete, setConfirmDelete] = (0, import_react.useState)(false);
1039
+ const [showPromote, setShowPromote] = (0, import_react.useState)(false);
1040
+ const [targetChannel, setTargetChannel] = (0, import_react.useState)("");
1041
+ const [promoteAction, setPromoteAction] = (0, import_react.useState)("move");
1042
+ (0, import_react.useEffect)(() => {
1043
+ setDraft(release ? draftFromRelease(release) : null);
1044
+ setError("");
1045
+ }, [release]);
1046
+ const initialDraft = release ? draftFromRelease(release) : null;
1047
+ const dirty = draft !== null && initialDraft !== null && JSON.stringify(draft) !== JSON.stringify(initialDraft);
1048
+ const busy = preflight.isPending || update.isPending || remove.isPending || promote.isPending;
1049
+ const requestClose = () => {
1050
+ if (busy) return;
1051
+ if (dirty) {
1052
+ setConfirmDiscard(true);
1053
+ return;
1054
+ }
1055
+ onOpenChange(false);
1056
+ };
1057
+ const addCohort = () => {
1058
+ if (!draft) return;
1059
+ const cohort = normalizeCohortValue(newCohort);
1060
+ if (!isValidCohort(cohort)) {
1061
+ setError(INVALID_COHORT_ERROR_MESSAGE);
1062
+ return;
1063
+ }
1064
+ setDraft({
1065
+ ...draft,
1066
+ targetCohorts: Array.from(new Set([...draft.targetCohorts, cohort]))
1067
+ });
1068
+ setNewCohort("");
1069
+ setError("");
1070
+ };
1071
+ const save = async () => {
1072
+ if (!release || !draft) return;
1073
+ const cohorts = draft.targetCohorts.map(normalizeCohortValue);
1074
+ if (cohorts.some((cohort) => !isValidCohort(cohort))) {
1075
+ setError(INVALID_COHORT_ERROR_MESSAGE);
1076
+ return;
1077
+ }
1078
+ if (release.strategy === "APP_VERSION" && !normalizeRange(draft.targetAppVersion.trim())) {
1079
+ setError("Enter a valid target app version range.");
1080
+ return;
1081
+ }
1082
+ const input = {
1083
+ expectedRevision: release.revision,
1084
+ patch: patchFromDraft(release, {
1085
+ ...draft,
1086
+ targetCohorts: cohorts
1087
+ }),
1088
+ releaseId: release.id
1089
+ };
1090
+ setError("");
1091
+ try {
1092
+ await preflight.mutateAsync(input);
1093
+ await update.mutateAsync(input);
1094
+ toast.success("Bundle settings saved");
1095
+ } catch (caught) {
1096
+ setError(caught instanceof Error ? caught.message : "Bundle settings could not be saved.");
1097
+ }
1098
+ };
1099
+ const channelName = channels.find((channel) => channel.id === release?.channel_id)?.name ?? release?.channel_id;
1100
+ const availableChannels = channels.filter((channel) => channel.id !== release?.channel_id);
1101
+ const availableChannelItems = availableChannels.map((channel) => ({
1102
+ label: channel.name,
1103
+ value: channel.name
1104
+ }));
1105
+ const normalizedTargetChannel = targetChannel.trim();
1106
+ const isCopyPromotion = promoteAction === "copy";
1107
+ const normalizedTargetAppVersion = draft ? normalizeRange(draft.targetAppVersion.trim()) : null;
1108
+ const resetPromoteDialog = () => {
1109
+ setTargetChannel("");
1110
+ setPromoteAction("move");
1111
+ };
1112
+ const handlePromoteOpenChange = (nextOpen) => {
1113
+ if (!nextOpen && promote.isPending) return;
1114
+ setShowPromote(nextOpen);
1115
+ if (!nextOpen) resetPromoteDialog();
1116
+ };
1117
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1118
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sheet, {
1119
+ onOpenChange: (nextOpen) => nextOpen ? void 0 : requestClose(),
1120
+ open,
1121
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SheetContent, {
1122
+ className: "min-w-0 overflow-hidden data-[side=right]:w-full sm:max-w-[600px]",
1123
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SheetHeader, {
1124
+ className: "shrink-0 pr-12",
1125
+ children: [
1126
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SheetTitle, { children: "Bundle Detail" }),
1127
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SheetDescription, {
1128
+ className: "sr-only",
1129
+ children: "Edit bundle delivery settings."
1130
+ }),
1131
+ release ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1132
+ className: "mt-1 flex min-w-0 flex-col gap-3 text-sm",
1133
+ children: [
1134
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1135
+ className: "flex flex-wrap items-center gap-2",
1136
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1137
+ className: "flex items-center gap-2",
1138
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlatformIcon, {
1139
+ className: "size-4",
1140
+ platform: release.platform
1141
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1142
+ className: "font-medium",
1143
+ children: release.platform === "ios" ? "iOS" : "Android"
1144
+ })]
1145
+ })
1146
+ }),
1147
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1148
+ className: "flex flex-wrap items-start gap-2",
1149
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1150
+ className: "font-medium text-muted-foreground",
1151
+ children: "Bundle"
1152
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1153
+ className: "min-w-0 basis-full sm:basis-auto",
1154
+ children: release.bundle_id ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
1155
+ bundleId: release.bundle_id,
1156
+ fullOnMobile: true,
1157
+ maxLength: 18
1158
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1159
+ className: "text-xs text-foreground",
1160
+ children: "Built-in app"
1161
+ })
1162
+ })]
1163
+ }),
1164
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1165
+ className: "flex flex-wrap items-center gap-2",
1166
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1167
+ className: "font-medium text-muted-foreground",
1168
+ children: "Channel"
1169
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1170
+ className: "text-xs text-foreground",
1171
+ translate: "no",
1172
+ children: channelName
1173
+ })]
1174
+ }),
1175
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1176
+ className: "flex flex-wrap items-center gap-2",
1177
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1178
+ className: "font-medium text-muted-foreground",
1179
+ children: "Platform"
1180
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1181
+ className: "text-xs text-foreground",
1182
+ children: release.platform === "ios" ? "iOS" : "Android"
1183
+ })]
1184
+ })
1185
+ ]
1186
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-12 w-full" })
1187
+ ]
1188
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1189
+ className: "min-h-0 flex-1 overflow-y-auto overscroll-contain",
1190
+ children: [releaseQuery.isError ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert, {
1191
+ className: "m-4 sm:m-6",
1192
+ variant: "destructive",
1193
+ children: [
1194
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriangleAlert, {}),
1195
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertTitle, { children: "Bundle details could not be loaded" }),
1196
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription, { children: releaseQuery.error.message })
1197
+ ]
1198
+ }) : null, release && draft ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1199
+ className: "flex flex-col gap-6 px-4 pb-4 sm:px-6 sm:pb-6",
1200
+ children: [
1201
+ release.bundle_id ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleInsightsSummary, { bundleId: release.bundle_id }) : null,
1202
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
1203
+ "aria-labelledby": "delivery-settings-heading",
1204
+ className: "flex flex-col gap-4",
1205
+ children: [
1206
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
1207
+ className: "text-sm font-medium",
1208
+ id: "delivery-settings-heading",
1209
+ children: "Delivery settings"
1210
+ }),
1211
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(FieldGroup, {
1212
+ className: "gap-6",
1213
+ children: [
1214
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Field, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldLabel, {
1215
+ htmlFor: "release-message",
1216
+ children: "Message"
1217
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Textarea, {
1218
+ autoComplete: "off",
1219
+ id: "release-message",
1220
+ name: "message",
1221
+ onChange: (event) => setDraft({
1222
+ ...draft,
1223
+ message: event.target.value
1224
+ }),
1225
+ rows: 3,
1226
+ value: draft.message
1227
+ })] }),
1228
+ release.strategy === "APP_VERSION" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Field, { children: [
1229
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldLabel, {
1230
+ htmlFor: "release-target",
1231
+ children: "Target app version"
1232
+ }),
1233
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
1234
+ autoComplete: "off",
1235
+ id: "release-target",
1236
+ name: "targetAppVersion",
1237
+ onChange: (event) => setDraft({
1238
+ ...draft,
1239
+ targetAppVersion: event.target.value
1240
+ }),
1241
+ value: draft.targetAppVersion
1242
+ }),
1243
+ normalizedTargetAppVersion ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldDescription, { children: normalizedTargetAppVersion }) : null
1244
+ ] }) : null,
1245
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Field, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1246
+ className: "flex items-center justify-between gap-4",
1247
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldLabel, {
1248
+ htmlFor: "release-enabled",
1249
+ children: "Enabled"
1250
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Switch$1, {
1251
+ "aria-label": "Enabled",
1252
+ checked: draft.enabled,
1253
+ id: "release-enabled",
1254
+ name: "enabled",
1255
+ onCheckedChange: (enabled) => setDraft({
1256
+ ...draft,
1257
+ enabled
1258
+ })
1259
+ })]
1260
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldDescription, { children: "Disabling rolls devices back to the previous enabled Release on their next check, or to the built-in app when none remains." })] }),
1261
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Field, {
1262
+ orientation: "horizontal",
1263
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldLabel, {
1264
+ htmlFor: "release-force-update",
1265
+ children: "Force update"
1266
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Switch$1, {
1267
+ "aria-label": "Force update",
1268
+ checked: draft.shouldForceUpdate,
1269
+ id: "release-force-update",
1270
+ name: "shouldForceUpdate",
1271
+ onCheckedChange: (shouldForceUpdate) => setDraft({
1272
+ ...draft,
1273
+ shouldForceUpdate
1274
+ })
1275
+ })]
1276
+ }),
1277
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Field, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldLabel, {
1278
+ htmlFor: "release-rollout-percentage",
1279
+ children: "Rollout percentage"
1280
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RolloutPercentageInput, {
1281
+ onChange: (rolloutCohortCount) => setDraft({
1282
+ ...draft,
1283
+ rolloutCohortCount
1284
+ }),
1285
+ value: draft.rolloutCohortCount
1286
+ })] }),
1287
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Field, { children: [
1288
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1289
+ className: "flex flex-wrap items-center justify-between gap-2",
1290
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldLabel, {
1291
+ htmlFor: "release-cohort",
1292
+ children: "Additional cohorts (optional)"
1293
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RolloutCohortsDialog, {
1294
+ releaseId: release.id,
1295
+ rolloutCohortCount: draft.rolloutCohortCount,
1296
+ targetCohorts: draft.targetCohorts
1297
+ })]
1298
+ }),
1299
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1300
+ className: "flex gap-2",
1301
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
1302
+ autoComplete: "off",
1303
+ id: "release-cohort",
1304
+ name: "targetCohort",
1305
+ onChange: (event) => setNewCohort(event.target.value),
1306
+ onKeyDown: (event) => {
1307
+ if (event.key === "Enter") {
1308
+ event.preventDefault();
1309
+ addCohort();
1310
+ }
1311
+ },
1312
+ placeholder: "e.g. qa-team…",
1313
+ value: newCohort
1314
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
1315
+ "aria-label": "Add cohort",
1316
+ disabled: !newCohort.trim(),
1317
+ onClick: addCohort,
1318
+ size: "icon",
1319
+ type: "button",
1320
+ variant: "outline",
1321
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, {})
1322
+ })]
1323
+ }),
1324
+ draft.targetCohorts.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1325
+ className: "flex flex-wrap gap-2",
1326
+ children: draft.targetCohorts.map((cohort) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
1327
+ className: "gap-1 font-mono",
1328
+ variant: "secondary",
1329
+ children: [cohort, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1330
+ "aria-label": `Remove cohort ${cohort}`,
1331
+ className: "rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1332
+ onClick: () => setDraft({
1333
+ ...draft,
1334
+ targetCohorts: draft.targetCohorts.filter((value) => value !== cohort)
1335
+ }),
1336
+ type: "button",
1337
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "size-3" })
1338
+ })]
1339
+ }, cohort))
1340
+ }) : null,
1341
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldDescription, { children: "Public names—no IDs or secrets." })
1342
+ ] })
1343
+ ]
1344
+ }),
1345
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldError, { children: error }) : null,
1346
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
1347
+ className: "w-full",
1348
+ disabled: !dirty || busy,
1349
+ onClick: save,
1350
+ size: "lg",
1351
+ children: preflight.isPending || update.isPending ? "Saving…" : "Save changes"
1352
+ })
1353
+ ]
1354
+ }),
1355
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Separator$1, { className: "my-2" }),
1356
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
1357
+ className: "flex flex-col gap-4",
1358
+ children: [
1359
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
1360
+ className: "text-sm font-medium",
1361
+ children: "Actions"
1362
+ }),
1363
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
1364
+ className: "w-full",
1365
+ disabled: release.kind !== "BUNDLE" || busy,
1366
+ onClick: () => setShowPromote(true),
1367
+ size: "sm",
1368
+ variant: "outline",
1369
+ children: "Promote to channel"
1370
+ }),
1371
+ release.bundle_id ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
1372
+ className: "w-full",
1373
+ disabled: busy,
1374
+ onClick: () => {
1375
+ window.open(`/api/bundles/${encodeURIComponent(release.bundle_id)}/download`, "_blank", "noopener,noreferrer");
1376
+ toast.success("Bundle download started");
1377
+ },
1378
+ size: "sm",
1379
+ variant: "outline",
1380
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Download, { "data-icon": "inline-start" }), "Download bundle"]
1381
+ }) : null,
1382
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
1383
+ className: "w-full",
1384
+ disabled: release.enabled || busy,
1385
+ onClick: () => setConfirmDelete(true),
1386
+ size: "sm",
1387
+ variant: "destructive",
1388
+ children: "Remove from channel"
1389
+ })
1390
+ ]
1391
+ }),
1392
+ release.bundle_id && bundleQuery.isPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-40 w-full" }) : null,
1393
+ release.bundle_id && bundleQuery.isError ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert, {
1394
+ variant: "destructive",
1395
+ children: [
1396
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriangleAlert, {}),
1397
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertTitle, { children: "Build details are unavailable" }),
1398
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription, { children: "Delivery settings are unaffected." })
1399
+ ]
1400
+ }) : null,
1401
+ !release.bundle_id || !bundleQuery.isPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleMetadata, {
1402
+ bundle,
1403
+ release
1404
+ }) : null,
1405
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", {
1406
+ className: "rounded-lg border bg-muted/10 p-4 text-xs",
1407
+ children: [
1408
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", {
1409
+ className: "cursor-pointer font-medium",
1410
+ children: "Advanced diagnostics"
1411
+ }),
1412
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dl", {
1413
+ className: "mt-4 grid gap-3 text-muted-foreground sm:grid-cols-2",
1414
+ children: [
1415
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1416
+ className: "sm:col-span-2",
1417
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Release ID" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
1418
+ className: "mt-1 break-all font-mono text-foreground",
1419
+ children: release.id
1420
+ })]
1421
+ }),
1422
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Revision" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
1423
+ className: "mt-1 font-mono text-foreground",
1424
+ children: release.revision
1425
+ })] }),
1426
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Catalog generation" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
1427
+ className: "mt-1 font-mono text-foreground",
1428
+ children: diagnostics.data?.generation ?? "—"
1429
+ })] }),
1430
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Catalog size" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
1431
+ className: "mt-1 font-mono text-foreground",
1432
+ children: diagnostics.data ? `${diagnostics.data.byte_size} bytes` : "—"
1433
+ })] }),
1434
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1435
+ className: "sm:col-span-2",
1436
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Scope key" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
1437
+ className: "mt-1 break-all font-mono text-foreground",
1438
+ children: release.scope_key
1439
+ })]
1440
+ })
1441
+ ]
1442
+ }),
1443
+ diagnostics.isError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1444
+ className: "mt-3 text-destructive",
1445
+ children: "Diagnostics are unavailable. Bundle settings are unaffected."
1446
+ }) : null
1447
+ ]
1448
+ })
1449
+ ]
1450
+ }) : null]
1451
+ })]
1452
+ })
1453
+ }),
1454
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialog$1, {
1455
+ onOpenChange: setConfirmDiscard,
1456
+ open: confirmDiscard,
1457
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogContent, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogTitle, { children: "Discard unsaved changes?" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogDescription, { children: "Your Bundle changes will be lost." })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogFooter, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogCancel, { children: "Keep editing" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogAction, {
1458
+ onClick: () => {
1459
+ setConfirmDiscard(false);
1460
+ onOpenChange(false);
1461
+ },
1462
+ variant: "destructive",
1463
+ children: "Discard changes"
1464
+ })] })] })
1465
+ }),
1466
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialog$1, {
1467
+ onOpenChange: setConfirmDelete,
1468
+ open: confirmDelete,
1469
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogContent, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogTitle, { children: [
1470
+ "Remove this Bundle from ",
1471
+ channelName ?? "the channel",
1472
+ "?"
1473
+ ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogDescription, { children: "The Bundle file stays available. This only removes it from the channel." })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogFooter, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogCancel, { children: "Cancel" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogAction, {
1474
+ onClick: async () => {
1475
+ if (!release) return;
1476
+ try {
1477
+ await remove.mutateAsync({
1478
+ expectedRevision: release.revision,
1479
+ releaseId: release.id
1480
+ });
1481
+ toast.success(`Bundle removed from ${channelName ?? "the channel"}`);
1482
+ setConfirmDelete(false);
1483
+ onOpenChange(false);
1484
+ } catch (caught) {
1485
+ setError(caught instanceof Error ? caught.message : "Bundle could not be removed from the channel.");
1486
+ }
1487
+ },
1488
+ variant: "destructive",
1489
+ children: "Remove from channel"
1490
+ })] })] })
1491
+ }),
1492
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog$1, {
1493
+ onOpenChange: handlePromoteOpenChange,
1494
+ open: showPromote,
1495
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogContent, {
1496
+ showCloseButton: !promote.isPending,
1497
+ children: [
1498
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle$1, { children: "Promote to Channel" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogDescription$1, { children: "Choose how to promote this Bundle, then select the target channel." })] }),
1499
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1500
+ className: "space-y-4 py-4",
1501
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1502
+ className: "space-y-2",
1503
+ children: [
1504
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
1505
+ htmlFor: "promote-action",
1506
+ children: "Action"
1507
+ }),
1508
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Select$1, {
1509
+ disabled: promote.isPending,
1510
+ items: promoteActionItems,
1511
+ onValueChange: (value) => setPromoteAction(value),
1512
+ value: promoteAction,
1513
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, {
1514
+ className: "w-full",
1515
+ id: "promote-action",
1516
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue, { placeholder: "Select an action" })
1517
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectGroup, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
1518
+ value: "move",
1519
+ children: "Move bundle"
1520
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
1521
+ value: "copy",
1522
+ children: "Copy bundle"
1523
+ })] }) })]
1524
+ }),
1525
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1526
+ className: "text-xs text-muted-foreground",
1527
+ children: isCopyPromotion ? "Make this Bundle available in the target channel and keep it in the current channel." : "Move this Bundle to the target channel. Devices in the current channel may fall back to an earlier Bundle."
1528
+ })
1529
+ ]
1530
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1531
+ className: "space-y-2",
1532
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
1533
+ htmlFor: "target-channel",
1534
+ children: "Target Channel"
1535
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Select$1, {
1536
+ disabled: promote.isPending,
1537
+ items: availableChannelItems,
1538
+ onValueChange: (value) => setTargetChannel(value ?? ""),
1539
+ value: targetChannel || null,
1540
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, {
1541
+ className: "w-full",
1542
+ id: "target-channel",
1543
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue, { placeholder: "Select a channel" })
1544
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectContent, {
1545
+ className: "max-h-64",
1546
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectGroup, { children: availableChannels.map((channel) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
1547
+ value: channel.name,
1548
+ children: channel.name
1549
+ }, channel.id)) })
1550
+ })]
1551
+ })]
1552
+ })]
1553
+ }),
1554
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogFooter, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
1555
+ disabled: promote.isPending,
1556
+ onClick: () => handlePromoteOpenChange(false),
1557
+ variant: "outline",
1558
+ children: "Cancel"
1559
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
1560
+ disabled: !normalizedTargetChannel || promote.isPending,
1561
+ onClick: async () => {
1562
+ if (!release) return;
1563
+ try {
1564
+ await promote.mutateAsync({
1565
+ action: promoteAction,
1566
+ expectedRevision: release.revision,
1567
+ releaseId: release.id,
1568
+ targetChannel: normalizedTargetChannel
1569
+ });
1570
+ handlePromoteOpenChange(false);
1571
+ toast.success(isCopyPromotion ? `Bundle copied to ${normalizedTargetChannel}` : `Bundle moved to ${normalizedTargetChannel}`, release.bundle_id ? { description: `bundleId: ${release.bundle_id}` } : void 0);
1572
+ } catch (caught) {
1573
+ toast.error(caught instanceof Error ? caught.message : "Bundle could not be promoted.");
1574
+ }
1575
+ },
1576
+ children: promote.isPending ? isCopyPromotion ? "Copying…" : "Moving…" : isCopyPromotion ? "Copy" : "Move"
1577
+ })] })
1578
+ ]
1579
+ })
1580
+ })
1581
+ ] });
1582
+ }
1583
+ function ReleaseStateBadge({ release }) {
1584
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
1585
+ className: "gap-1 font-normal text-muted-foreground",
1586
+ variant: release.enabled ? "outline" : "secondary",
1587
+ children: [release.enabled ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: "size-3" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CircleOff, { className: "size-3" }), release.enabled ? "Enabled" : "Disabled"]
1588
+ });
1589
+ }
1590
+ function RolloutPercentageBadge({ percentage, className }) {
1591
+ const isPartialRollout = percentage < 100;
1592
+ const formattedPercentage = percentage.toFixed(1);
1593
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
1594
+ className: cn("gap-1", className),
1595
+ variant: isPartialRollout ? "secondary" : "default",
1596
+ children: [
1597
+ isPartialRollout ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriangleAlert, { className: "h-3 w-3" }) : null,
1598
+ formattedPercentage,
1599
+ "%"
1600
+ ]
1601
+ });
1602
+ }
1603
+ var PAGE_SIZE = 20;
1604
+ var platformFilterItems = [
1605
+ {
1606
+ label: "All Platforms",
1607
+ value: "all"
1608
+ },
1609
+ {
1610
+ label: "iOS",
1611
+ value: "ios"
1612
+ },
1613
+ {
1614
+ label: "Android",
1615
+ value: "android"
1616
+ }
1617
+ ];
1618
+ var dateFormatter = new Intl.DateTimeFormat(void 0, {
1619
+ dateStyle: "medium",
1620
+ timeStyle: "short"
1621
+ });
1622
+ var tableDateFormatter = new Intl.DateTimeFormat(void 0, { dateStyle: "medium" });
1623
+ var shortId = (id) => id.length > 18 ? `${id.slice(0, 8)}…${id.slice(-4)}` : id;
1624
+ function BundleMovementSummary({ summary }) {
1625
+ if (summary === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1626
+ "aria-label": "30-day Bundle activity unavailable",
1627
+ className: "text-sm text-muted-foreground",
1628
+ title: "Insights are unavailable for this Bundle.",
1629
+ children: "—"
1630
+ });
1631
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1632
+ "aria-label": "Bundle movement over 30 days, distinct installations",
1633
+ className: "flex min-w-[140px] items-baseline gap-3 whitespace-nowrap",
1634
+ role: "group",
1635
+ title: "Distinct installations over the last 30 days.",
1636
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1637
+ className: "flex items-baseline gap-1.5",
1638
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1639
+ className: "text-xs text-muted-foreground",
1640
+ children: "Applied"
1641
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1642
+ className: "text-sm font-medium tabular-nums",
1643
+ children: summary.installed
1644
+ })]
1645
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1646
+ className: "flex items-baseline gap-1.5",
1647
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1648
+ className: "text-xs text-muted-foreground",
1649
+ children: "Recovered"
1650
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1651
+ className: "text-sm font-medium tabular-nums",
1652
+ children: summary.recovered
1653
+ })]
1654
+ })]
1655
+ });
1656
+ }
1657
+ function BundleFilterToolbar({ channels, onChange, onClear, onManageChannels, search }) {
1658
+ const [targetAppVersion, setTargetAppVersion] = (0, import_react.useState)(search.targetAppVersion ?? "");
1659
+ const hasFilters = Boolean(search.bundleId || search.channelId || search.enabled !== void 0 || search.platform || search.targetAppVersion);
1660
+ const channelFilterItems = [{
1661
+ label: "All Channels",
1662
+ value: "all"
1663
+ }, ...channels.map((channel) => ({
1664
+ label: channel.name,
1665
+ value: channel.id
1666
+ }))];
1667
+ const applyTargetAppVersion = () => {
1668
+ onChange({ targetAppVersion: targetAppVersion.trim() || void 0 });
1669
+ };
1670
+ (0, import_react.useEffect)(() => {
1671
+ setTargetAppVersion(search.targetAppVersion ?? "");
1672
+ }, [search.targetAppVersion]);
1673
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
1674
+ 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",
1675
+ children: [
1676
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarTrigger, { className: "-ml-1" }),
1677
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
1678
+ className: "sr-only",
1679
+ children: "Bundles"
1680
+ }),
1681
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1682
+ className: "ml-1 flex items-center gap-1.5 text-muted-foreground sm:ml-2",
1683
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Funnel, { className: "size-3.5" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1684
+ className: "text-xs font-medium",
1685
+ children: "Filters"
1686
+ })]
1687
+ }),
1688
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Select$1, {
1689
+ items: platformFilterItems,
1690
+ onValueChange: (value) => onChange({ platform: value === "all" ? void 0 : value }),
1691
+ value: search.platform ?? "all",
1692
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, {
1693
+ "aria-label": "Platform",
1694
+ className: "h-8 w-[calc(50%-0.25rem)] min-w-[132px] text-xs sm:w-[140px]",
1695
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue, {})
1696
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectGroup, { children: [
1697
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
1698
+ value: "all",
1699
+ children: "All Platforms"
1700
+ }),
1701
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
1702
+ value: "ios",
1703
+ children: "iOS"
1704
+ }),
1705
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
1706
+ value: "android",
1707
+ children: "Android"
1708
+ })
1709
+ ] }) })]
1710
+ }),
1711
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
1712
+ "aria-label": "Target app version",
1713
+ className: "h-8 w-full min-w-[132px] text-xs sm:w-[160px]",
1714
+ onBlur: applyTargetAppVersion,
1715
+ onChange: (event) => setTargetAppVersion(event.target.value),
1716
+ onKeyDown: (event) => {
1717
+ if (event.key === "Enter") applyTargetAppVersion();
1718
+ },
1719
+ placeholder: "Target version",
1720
+ value: targetAppVersion
1721
+ }),
1722
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Select$1, {
1723
+ items: channelFilterItems,
1724
+ onValueChange: (value) => onChange({ channelId: value === null || value === "all" ? void 0 : value }),
1725
+ value: search.channelId ?? "all",
1726
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, {
1727
+ "aria-label": "Channel",
1728
+ className: "h-8 w-[calc(50%-0.25rem)] min-w-[132px] text-xs sm:w-[140px]",
1729
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue, {})
1730
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectGroup, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
1731
+ value: "all",
1732
+ children: "All Channels"
1733
+ }), channels.map((channel) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
1734
+ value: channel.id,
1735
+ children: channel.name
1736
+ }, channel.id))] }) })]
1737
+ }),
1738
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
1739
+ "aria-label": "Manage channels",
1740
+ onClick: onManageChannels,
1741
+ size: "icon-sm",
1742
+ title: "Manage channels",
1743
+ variant: "outline",
1744
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tags, {})
1745
+ }),
1746
+ search.bundleId ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
1747
+ className: "max-w-48 gap-1",
1748
+ variant: "secondary",
1749
+ children: [
1750
+ "Bundle ",
1751
+ shortId(search.bundleId),
1752
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1753
+ "aria-label": "Clear Bundle filter",
1754
+ className: "rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1755
+ onClick: () => onChange({ bundleId: void 0 }),
1756
+ type: "button",
1757
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "size-3" })
1758
+ })
1759
+ ]
1760
+ }) : null,
1761
+ hasFilters ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
1762
+ className: "h-8 px-2 text-xs text-muted-foreground hover:text-foreground sm:ml-auto",
1763
+ onClick: onClear,
1764
+ size: "sm",
1765
+ variant: "ghost",
1766
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { "data-icon": "inline-start" }), "Clear"]
1767
+ }) : null
1768
+ ]
1769
+ });
1770
+ }
1771
+ function BundleEntry({ expanded, onOpen, onToggleExpand, release }) {
1772
+ const bundleLabel = release.bundle_id ? `bundle ${release.bundle_id}` : "the built-in app deployment";
1773
+ const operationLabel = release.operation === "PROMOTE" ? "Promoted" : release.operation === "ROLLBACK" ? "Rollback" : null;
1774
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1775
+ className: "flex min-w-[240px] items-center gap-3",
1776
+ children: [
1777
+ release.bundle_id ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
1778
+ "aria-controls": `bundle-lineage-panel-${release.id}`,
1779
+ "aria-expanded": expanded,
1780
+ "aria-label": expanded ? "Hide Lineage" : "Show Lineage",
1781
+ className: "size-8 shrink-0 touch-manipulation",
1782
+ onClick: onToggleExpand,
1783
+ size: "icon",
1784
+ type: "button",
1785
+ variant: "ghost",
1786
+ children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, {})
1787
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1788
+ "aria-hidden": "true",
1789
+ className: "size-8 shrink-0"
1790
+ }),
1791
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1792
+ "aria-label": `Open details for ${bundleLabel}`,
1793
+ className: "min-w-0 rounded-sm text-left text-foreground transition-colors underline-offset-4 hover:text-primary hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1794
+ onClick: onOpen,
1795
+ title: release.bundle_id ?? release.id,
1796
+ type: "button",
1797
+ children: release.bundle_id ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
1798
+ bundleId: release.bundle_id,
1799
+ fullOnMobile: true
1800
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1801
+ className: "inline-flex items-center gap-1.5 text-sm",
1802
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(RotateCcw, { className: "size-3.5" }), " Built-in app"]
1803
+ })
1804
+ }),
1805
+ operationLabel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
1806
+ className: "shrink-0 font-normal",
1807
+ variant: "secondary",
1808
+ children: operationLabel
1809
+ }) : null,
1810
+ release.currentlyUnreachable ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1811
+ "aria-label": "Currently unreachable. No catalog segment or cohort selects this release first.",
1812
+ className: "inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground",
1813
+ title: "No catalog segment or cohort selects this release first with the current delivery settings.",
1814
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CircleOff, { className: "size-3.5" }), "Unreachable"]
1815
+ }) : null
1816
+ ]
1817
+ });
1818
+ }
1819
+ function ReleaseBundleChildrenPanel({ onDetailClick, panelId, release }) {
1820
+ const bundleId = release.bundle_id ?? "";
1821
+ const bundleQuery = useBundleQuery(bundleId);
1822
+ const childrenQuery = useBundleChildrenQuery(bundleId);
1823
+ if (bundleQuery.isError || childrenQuery.isError) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1824
+ "aria-live": "polite",
1825
+ className: "border-t bg-muted/10 p-4 text-sm text-muted-foreground",
1826
+ id: panelId,
1827
+ children: "Patch lineage could not be loaded."
1828
+ });
1829
+ if (bundleQuery.isPending) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1830
+ "aria-live": "polite",
1831
+ className: "flex flex-col gap-2 border-t bg-muted/10 p-4",
1832
+ id: panelId,
1833
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" })]
1834
+ });
1835
+ if (!bundleQuery.data) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1836
+ "aria-live": "polite",
1837
+ className: "border-t bg-muted/10 p-4 text-sm text-muted-foreground",
1838
+ id: panelId,
1839
+ children: "Bundle details are unavailable."
1840
+ });
1841
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleChildrenPanel, {
1842
+ bundle: bundleQuery.data,
1843
+ bundles: childrenQuery.data ?? [],
1844
+ loading: childrenQuery.isPending,
1845
+ onDetailClick,
1846
+ panelId
1847
+ });
1848
+ }
1849
+ function BundlesPage() {
1850
+ const search = Route.useSearch();
1851
+ const navigate = Route.useNavigate();
1852
+ const isMobile = useIsMobile();
1853
+ const [channelsOpen, setChannelsOpen] = (0, import_react.useState)(false);
1854
+ const [expandedReleaseId, setExpandedReleaseId] = (0, import_react.useState)();
1855
+ const releasesQuery = useReleasesQuery({
1856
+ afterReleaseId: search.afterReleaseId,
1857
+ beforeReleaseId: search.beforeReleaseId,
1858
+ bundleId: search.bundleId,
1859
+ channelId: search.channelId,
1860
+ enabled: search.enabled,
1861
+ limit: PAGE_SIZE,
1862
+ page: search.page,
1863
+ platform: search.platform,
1864
+ targetAppVersion: search.targetAppVersion
1865
+ });
1866
+ const channels = useChannelsQuery().data ?? [];
1867
+ const releases = releasesQuery.data?.data ?? [];
1868
+ const patchCountsByBundleId = useBundleChildCountsQuery(Array.from(new Set(releases.flatMap((release) => release.bundle_id ? [release.bundle_id] : [])))).data ?? {};
1869
+ const pagination = releasesQuery.data?.pagination;
1870
+ const channelNames = new Map(channels.map((channel) => [channel.id, channel.name]));
1871
+ (0, import_react.useEffect)(() => {
1872
+ if (expandedReleaseId && !releases.some((release) => release.id === expandedReleaseId)) setExpandedReleaseId(void 0);
1873
+ }, [expandedReleaseId, releases]);
1874
+ const go = (nextSearch, resetScroll = true) => void navigate({
1875
+ resetScroll,
1876
+ search: nextSearch,
1877
+ to: "/"
1878
+ });
1879
+ const changeFilters = (filters) => go(updateReleaseFilters(search, filters));
1880
+ const openChildBundle = (bundle) => {
1881
+ const childRelease = releases.find((release) => release.bundle_id === bundle.id);
1882
+ setExpandedReleaseId(void 0);
1883
+ if (childRelease) {
1884
+ go({
1885
+ ...search,
1886
+ releaseId: childRelease.id
1887
+ }, false);
1888
+ return;
1889
+ }
1890
+ changeFilters({ bundleId: bundle.id });
1891
+ };
1892
+ const currentPage = pagination?.currentPage ?? 1;
1893
+ const startEntry = releases.length === 0 ? 0 : (currentPage - 1) * PAGE_SIZE + 1;
1894
+ const endEntry = startEntry === 0 ? 0 : startEntry + releases.length - 1;
1895
+ const previousPage = currentPage - 1;
1896
+ const firstReleaseId = releases[0]?.id;
1897
+ const lastReleaseId = releases.at(-1)?.id;
1898
+ const previousSearch = pagination?.hasPreviousPage && firstReleaseId ? {
1899
+ ...search,
1900
+ afterReleaseId: firstReleaseId,
1901
+ beforeReleaseId: void 0,
1902
+ page: previousPage > 1 ? previousPage : void 0,
1903
+ releaseId: void 0
1904
+ } : null;
1905
+ const nextSearch = pagination?.hasNextPage && lastReleaseId ? {
1906
+ ...search,
1907
+ afterReleaseId: void 0,
1908
+ beforeReleaseId: lastReleaseId,
1909
+ page: currentPage + 1,
1910
+ releaseId: void 0
1911
+ } : null;
1912
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1913
+ className: "flex h-svh min-h-0 min-w-0 flex-col",
1914
+ children: [
1915
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleFilterToolbar, {
1916
+ channels,
1917
+ onChange: changeFilters,
1918
+ onClear: () => go({}),
1919
+ onManageChannels: () => setChannelsOpen(true),
1920
+ search
1921
+ }),
1922
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1923
+ className: "flex min-h-0 min-w-0 flex-1 flex-col gap-6 bg-muted/5 p-3 sm:p-6",
1924
+ children: [releasesQuery.isError ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert, {
1925
+ variant: "destructive",
1926
+ children: [
1927
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriangleAlert, {}),
1928
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertTitle, { children: "Bundles could not be loaded" }),
1929
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription, { children: releasesQuery.error.message })
1930
+ ]
1931
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1932
+ className: "min-h-0 min-w-0 flex-1 overflow-hidden rounded-lg border bg-card text-card-foreground shadow-sm [&_[data-slot=table-container]]:h-full [&_[data-slot=table-container]]:overflow-auto",
1933
+ children: isMobile ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1934
+ className: "h-full overflow-y-auto",
1935
+ children: releasesQuery.isPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1936
+ className: "flex flex-col gap-3 p-4",
1937
+ children: Array.from({ length: 5 }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-44 w-full" }, index))
1938
+ }) : releases.length ? releases.map((release) => {
1939
+ const channelName = channelNames.get(release.channel_id) ?? release.channel_id;
1940
+ const patchCount = release.bundle_id ? patchCountsByBundleId[release.bundle_id] : 0;
1941
+ const isExpanded = expandedReleaseId === release.id;
1942
+ const panelId = `bundle-lineage-panel-${release.id}`;
1943
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("article", {
1944
+ className: cn("flex flex-col gap-4 border-b p-4 last:border-b-0", release.currentlyUnreachable && "bg-muted/35 saturate-50 transition-[background-color,filter] focus-within:saturate-100 motion-reduce:transition-none", isExpanded && "border-b-0 bg-primary/5"),
1945
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1946
+ className: "flex items-start justify-between gap-3",
1947
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleEntry, {
1948
+ expanded: isExpanded,
1949
+ onOpen: () => go({
1950
+ ...search,
1951
+ releaseId: release.id
1952
+ }, false),
1953
+ onToggleExpand: () => setExpandedReleaseId(isExpanded ? void 0 : release.id),
1954
+ release
1955
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChannelBadge, {
1956
+ channel: channelName,
1957
+ className: "shrink-0"
1958
+ })]
1959
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dl", {
1960
+ className: "grid grid-cols-2 gap-3 text-xs",
1961
+ children: [
1962
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1963
+ className: "rounded-md bg-muted/40 p-3",
1964
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
1965
+ className: "text-muted-foreground",
1966
+ children: "Platform"
1967
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dd", {
1968
+ className: "mt-1 flex items-center gap-2 font-medium",
1969
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlatformIcon, {
1970
+ className: "size-4",
1971
+ platform: release.platform
1972
+ }), release.platform === "ios" ? "iOS" : "Android"]
1973
+ })]
1974
+ }),
1975
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1976
+ className: "rounded-md bg-muted/40 p-3",
1977
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
1978
+ className: "text-muted-foreground",
1979
+ children: "Created"
1980
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
1981
+ className: "mt-1 tabular-nums",
1982
+ children: dateFormatter.format(release.created_at_ms)
1983
+ })]
1984
+ }),
1985
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1986
+ className: "rounded-md bg-muted/40 p-3",
1987
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
1988
+ className: "text-muted-foreground",
1989
+ children: "Rollout"
1990
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
1991
+ className: "mt-1",
1992
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RolloutPercentageBadge, { percentage: release.rollout_cohort_count / 10 })
1993
+ })]
1994
+ }),
1995
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1996
+ className: "rounded-md bg-muted/40 p-3",
1997
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
1998
+ className: "text-muted-foreground",
1999
+ children: "Patches"
2000
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
2001
+ className: "mt-1",
2002
+ children: patchCount === void 0 ? "Checking" : patchCount > 0 ? `${patchCount} ${patchCount === 1 ? "patch" : "patches"}` : "—"
2003
+ })]
2004
+ }),
2005
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2006
+ className: "col-span-2 rounded-md bg-muted/40 p-3",
2007
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
2008
+ className: "text-muted-foreground",
2009
+ children: "Activity · 30d"
2010
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
2011
+ className: "mt-1",
2012
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleMovementSummary, { summary: release.activity30d })
2013
+ })]
2014
+ }),
2015
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2016
+ className: "col-span-2 flex flex-wrap items-center gap-2 rounded-md bg-muted/40 p-3",
2017
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReleaseStateBadge, { release }), release.should_force_update ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
2018
+ variant: "secondary",
2019
+ children: "Force update"
2020
+ }) : null]
2021
+ }),
2022
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2023
+ className: "col-span-2 grid gap-2 rounded-md border bg-background/80 p-3",
2024
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2025
+ className: "flex items-start justify-between gap-3",
2026
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
2027
+ className: "text-muted-foreground",
2028
+ children: "Target"
2029
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
2030
+ className: "min-w-0 truncate text-right font-mono",
2031
+ children: release.target_app_version ?? release.fingerprint_hash ?? "—"
2032
+ })]
2033
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2034
+ className: "flex items-start justify-between gap-3",
2035
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
2036
+ className: "text-muted-foreground",
2037
+ children: "Message"
2038
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
2039
+ className: "min-w-0 text-right text-foreground",
2040
+ children: release.message || "—"
2041
+ })]
2042
+ })]
2043
+ })
2044
+ ]
2045
+ })]
2046
+ }), isExpanded && release.bundle_id ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReleaseBundleChildrenPanel, {
2047
+ onDetailClick: openChildBundle,
2048
+ panelId,
2049
+ release
2050
+ }) : null] }, release.id);
2051
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
2052
+ className: "p-8 text-center text-sm text-muted-foreground",
2053
+ children: search.bundleId || search.channelId || search.enabled !== void 0 || search.platform || search.targetAppVersion ? "No bundles match these filters." : "No bundles yet. Deploy a bundle to see it here."
2054
+ })
2055
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Table, {
2056
+ className: "min-w-max",
2057
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHeader, {
2058
+ className: "bg-muted/40",
2059
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, {
2060
+ className: "border-b border-border/60 hover:bg-transparent [&>th]:h-10 [&>th]:text-xs [&>th]:font-semibold [&>th]:uppercase [&>th]:text-muted-foreground/70",
2061
+ children: [
2062
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Bundle ID" }),
2063
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Channel" }),
2064
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Platform" }),
2065
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Patches" }),
2066
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Target" }),
2067
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Enabled" }),
2068
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Force update" }),
2069
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Rollout" }),
2070
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
2071
+ title: "Distinct installations over the last 30 days.",
2072
+ children: "Activity · 30d"
2073
+ }),
2074
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Message" }),
2075
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Created" })
2076
+ ]
2077
+ })
2078
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableBody, { children: [releasesQuery.isPending ? Array.from({ length: 7 }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableRow, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
2079
+ colSpan: 11,
2080
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-8 w-full" })
2081
+ }) }, index)) : releases.map((release) => {
2082
+ const isExpanded = expandedReleaseId === release.id;
2083
+ const panelId = `bundle-lineage-panel-${release.id}`;
2084
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, {
2085
+ "aria-label": `Open bundle ${release.bundle_id ?? release.id}`,
2086
+ className: cn("cursor-pointer transition-[background-color,filter] hover:bg-muted/10 focus-within:bg-muted/15 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring motion-reduce:transition-none data-[state=selected]:bg-muted/15 [&>td]:py-3", release.currentlyUnreachable && "bg-muted/35 saturate-50 hover:bg-muted/50 hover:saturate-100 focus-within:saturate-100", isExpanded && "bg-primary/5"),
2087
+ "data-state": search.releaseId === release.id ? "selected" : void 0,
2088
+ onClick: (event) => {
2089
+ if (event.target instanceof Element && event.target.closest("button, a, input, select, textarea")) return;
2090
+ go({
2091
+ ...search,
2092
+ releaseId: release.id
2093
+ }, false);
2094
+ },
2095
+ onKeyDown: (event) => {
2096
+ if (event.target !== event.currentTarget || event.key !== "Enter" && event.key !== " ") return;
2097
+ event.preventDefault();
2098
+ go({
2099
+ ...search,
2100
+ releaseId: release.id
2101
+ }, false);
2102
+ },
2103
+ tabIndex: 0,
2104
+ children: [
2105
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleEntry, {
2106
+ expanded: isExpanded,
2107
+ onOpen: () => go({
2108
+ ...search,
2109
+ releaseId: release.id
2110
+ }, false),
2111
+ onToggleExpand: () => setExpandedReleaseId(isExpanded ? void 0 : release.id),
2112
+ release
2113
+ }) }),
2114
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChannelBadge, { channel: channelNames.get(release.channel_id) ?? release.channel_id }) }),
2115
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
2116
+ className: "whitespace-nowrap",
2117
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2118
+ className: "flex items-center gap-2",
2119
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlatformIcon, {
2120
+ className: "size-4",
2121
+ platform: release.platform
2122
+ }), release.platform === "ios" ? "iOS" : "Android"]
2123
+ })
2124
+ }),
2125
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: release.bundle_id && patchCountsByBundleId[release.bundle_id] === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2126
+ className: "text-sm text-muted-foreground",
2127
+ children: "Checking"
2128
+ }) : release.bundle_id && patchCountsByBundleId[release.bundle_id] > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
2129
+ variant: "secondary",
2130
+ children: [
2131
+ patchCountsByBundleId[release.bundle_id],
2132
+ " ",
2133
+ patchCountsByBundleId[release.bundle_id] === 1 ? "patch" : "patches"
2134
+ ]
2135
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2136
+ className: "text-muted-foreground",
2137
+ children: "-"
2138
+ }) }),
2139
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: release.target_app_version ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2140
+ className: "flex items-center gap-2",
2141
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Package, { className: "size-4 shrink-0 text-muted-foreground" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2142
+ className: "font-medium",
2143
+ children: release.target_app_version
2144
+ })]
2145
+ }) : release.fingerprint_hash ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2146
+ className: "flex min-w-[160px] items-center gap-2",
2147
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FingerprintPattern, { className: "size-4 shrink-0 text-muted-foreground" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HashValueDisplay, {
2148
+ maxLength: 12,
2149
+ value: release.fingerprint_hash
2150
+ })]
2151
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2152
+ className: "text-muted-foreground",
2153
+ children: "-"
2154
+ }) }),
2155
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2156
+ className: "flex items-center",
2157
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnabledStatusIcon, { enabled: release.enabled }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2158
+ className: "sr-only",
2159
+ children: release.enabled ? "Enabled" : "Disabled"
2160
+ })]
2161
+ }) }),
2162
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2163
+ className: "flex items-center",
2164
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnabledStatusIcon, {
2165
+ enabled: release.should_force_update,
2166
+ falseIcon: "minus"
2167
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2168
+ className: "sr-only",
2169
+ children: release.should_force_update ? "Required" : "Optional"
2170
+ })]
2171
+ }) }),
2172
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RolloutPercentageBadge, { percentage: release.rollout_cohort_count / 10 }) }),
2173
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleMovementSummary, { summary: release.activity30d }) }),
2174
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
2175
+ className: "text-sm text-muted-foreground",
2176
+ title: release.message ?? void 0,
2177
+ children: release.message || "-"
2178
+ }),
2179
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
2180
+ className: "whitespace-nowrap text-xs text-muted-foreground",
2181
+ title: dateFormatter.format(release.created_at_ms),
2182
+ children: tableDateFormatter.format(release.created_at_ms)
2183
+ })
2184
+ ]
2185
+ }), isExpanded && release.bundle_id ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableRow, {
2186
+ className: "hover:bg-transparent",
2187
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
2188
+ className: "border-t-0 p-0",
2189
+ colSpan: 11,
2190
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReleaseBundleChildrenPanel, {
2191
+ onDetailClick: openChildBundle,
2192
+ panelId,
2193
+ release
2194
+ })
2195
+ })
2196
+ }) : null] }, release.id);
2197
+ }), !releasesQuery.isPending && releases.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableRow, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
2198
+ className: "h-40 text-center text-muted-foreground",
2199
+ colSpan: 10,
2200
+ children: search.bundleId || search.channelId || search.enabled !== void 0 || search.platform || search.targetAppVersion ? "No bundles match these filters." : "No bundles yet. Deploy a bundle to see it here."
2201
+ }) }) : null] })]
2202
+ })
2203
+ }), !releasesQuery.isError && !releasesQuery.isPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("nav", {
2204
+ "aria-label": "Bundle pagination",
2205
+ className: "flex flex-col gap-3 px-2 sm:flex-row sm:items-center sm:justify-between",
2206
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
2207
+ className: "text-xs font-medium text-muted-foreground",
2208
+ children: [
2209
+ "Showing ",
2210
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2211
+ className: "text-foreground",
2212
+ children: startEntry
2213
+ }),
2214
+ " to",
2215
+ " ",
2216
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2217
+ className: "text-foreground",
2218
+ children: endEntry
2219
+ }),
2220
+ " entries"
2221
+ ]
2222
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2223
+ className: "flex flex-wrap items-center gap-3 sm:justify-end",
2224
+ children: [
2225
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
2226
+ className: "text-xs font-medium text-muted-foreground",
2227
+ children: ["Page ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2228
+ className: "text-foreground",
2229
+ children: currentPage
2230
+ })]
2231
+ }),
2232
+ previousSearch ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
2233
+ className: buttonVariants({
2234
+ className: "h-8 flex-1 px-3 text-xs sm:flex-none",
2235
+ size: "sm",
2236
+ variant: "outline"
2237
+ }),
2238
+ search: previousSearch,
2239
+ to: "/",
2240
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronLeft, { "data-icon": "inline-start" }), "Previous"]
2241
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
2242
+ className: "h-8 flex-1 px-3 text-xs sm:flex-none",
2243
+ disabled: true,
2244
+ size: "sm",
2245
+ variant: "outline",
2246
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronLeft, { "data-icon": "inline-start" }), "Previous"]
2247
+ }),
2248
+ nextSearch ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
2249
+ className: buttonVariants({
2250
+ className: "h-8 flex-1 px-3 text-xs sm:flex-none",
2251
+ size: "sm",
2252
+ variant: "outline"
2253
+ }),
2254
+ search: nextSearch,
2255
+ to: "/",
2256
+ children: ["Next", /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { "data-icon": "inline-end" })]
2257
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
2258
+ className: "h-8 flex-1 px-3 text-xs sm:flex-none",
2259
+ disabled: true,
2260
+ size: "sm",
2261
+ variant: "outline",
2262
+ children: ["Next", /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { "data-icon": "inline-end" })]
2263
+ })
2264
+ ]
2265
+ })]
2266
+ }) : null]
2267
+ }),
2268
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReleaseEditorSheet, {
2269
+ channels,
2270
+ onOpenChange: (open) => !open && go({
2271
+ ...search,
2272
+ releaseId: void 0
2273
+ }, false),
2274
+ open: Boolean(search.releaseId),
2275
+ releaseId: search.releaseId ?? ""
2276
+ }),
2277
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChannelManagementDialog, {
2278
+ onOpenChange: setChannelsOpen,
2279
+ open: channelsOpen
2280
+ })
2281
+ ]
2282
+ });
2283
+ }
2284
+ //#endregion
2285
+ export { BundlesPage as component };