@hot-updater/console 0.30.12 → 0.31.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 (34) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/BaseTanStackRouterDevtoolsPanel-Bmws3ikM-q5p5qKUx.js +486 -0
  3. package/.output/public/assets/FloatingTanStackRouterDevtools-B7vy70jP-Bzs2Gthe.js +1 -0
  4. package/.output/public/assets/clsx-CbprLf2V.js +1 -0
  5. package/.output/public/assets/dist-B5egZOkC.js +9 -0
  6. package/.output/public/assets/main-DrVuFR7r.js +10 -0
  7. package/.output/public/assets/preload-helper-C5ST2IKa.js +1 -0
  8. package/.output/public/assets/routes-C_bgs7kg.js +54 -0
  9. package/.output/public/assets/styles-DZ0tCVA1.css +2 -0
  10. package/.output/server/{__tanstack-start-server-fn-resolver-rXMsqALt.mjs → __tanstack-start-server-fn-resolver-5wPQ8bZ3.mjs} +19 -11
  11. package/.output/server/_chunks/ssr-renderer.mjs +2 -2
  12. package/.output/server/_libs/@tanstack/react-form+[...].mjs +5 -5
  13. package/.output/server/_libs/lucide-react.mjs +8 -1
  14. package/.output/server/_libs/unctx.mjs +1 -1
  15. package/.output/server/_ssr/{api-rpc-DYKuUgOh.mjs → api-rpc-BhBKhZqY.mjs} +61 -18
  16. package/.output/server/_ssr/{config.server-8YQWTTc0.mjs → config.server-xu3W-WAK.mjs} +3 -1
  17. package/.output/server/_ssr/deleteBundle-D4jF5HeY.mjs +96 -0
  18. package/.output/server/_ssr/dist-CRiLZLfa.mjs +120 -0
  19. package/.output/server/_ssr/getBundleChildren-DFqZ6XMp.mjs +58 -0
  20. package/.output/server/_ssr/{router-D-WWsYjv.mjs → router-SkApCyud.mjs} +57 -15
  21. package/.output/server/_ssr/{routes-DyUhibF4.mjs → routes-BSs-dv4D.mjs} +1120 -472
  22. package/.output/server/_ssr/{sidebar-DXng0IOP.mjs → sidebar-CgbtXkE2.mjs} +65 -85
  23. package/.output/server/_ssr/ssr.mjs +4 -4
  24. package/.output/server/_ssr/start-D0X4LIsd.mjs +4 -0
  25. package/.output/server/_ssr/storageProfile-wICk5nZZ.mjs +9 -0
  26. package/.output/server/{_tanstack-start-manifest_v-xpdCj2Ct.mjs → _tanstack-start-manifest_v-D2MqgD3d.mjs} +9 -4
  27. package/.output/server/index.mjs +70 -42
  28. package/package.json +10 -6
  29. package/.output/public/assets/dist-BKho179_.js +0 -53
  30. package/.output/public/assets/main-5MdOCmsM.js +0 -10
  31. package/.output/public/assets/routes-BmbL4goz.js +0 -10
  32. package/.output/public/assets/styles-M2W42JQb.css +0 -2
  33. package/.output/server/_ssr/deleteBundle-BiJvjt0k.mjs +0 -22
  34. package/.output/server/_ssr/start-DsRb6TkZ.mjs +0 -4
@@ -1,28 +1,198 @@
1
1
  import { r as __toESM } from "../_runtime.mjs";
2
2
  import { c as createServerFn, i as TSS_SERVER_FUNCTION } from "./createServerFn-CdeRXnVy.mjs";
3
+ import { c as getPatchBaseFileHash, d as isValidCohort, f as normalizeCohortValue, l as getPatchFileHash, n as NUMERIC_COHORT_SIZE, o as getNumericCohortRolloutPosition, p as normalizeRolloutCohortCount, s as getPatchBaseBundleId, t as INVALID_COHORT_ERROR_MESSAGE } from "./dist-CRiLZLfa.mjs";
3
4
  import { A as Slot, P as require_jsx_runtime, a as Overlay2, c as Title2, d as Description, f as Overlay, g as Trigger, h as Title, i as Description2, l as Close, m as Root, n as Cancel, o as Portal2, p as Portal, r as Content2, s as Root2, t as Action, u as Content } from "../_libs/@radix-ui/react-alert-dialog+[...].mjs";
4
5
  import { u as require_react } from "../_libs/@floating-ui/react-dom+[...].mjs";
5
- import { S as Check, b as ChevronLeft, d as List, g as Download, h as ExternalLink, i as Plus, l as Minus, m as FingerprintPattern, n as TriangleAlert, o as Package, p as Funnel, t as X, v as ChevronUp, x as ChevronDown, y as ChevronRight } from "../_libs/lucide-react.mjs";
6
+ import { C as ArrowRight, S as Check, b as ChevronLeft, d as List, g as Download, h as ExternalLink, i as Plus, l as Minus, m as FingerprintPattern, n as TriangleAlert, o as Package, p as Funnel, t as X, v as ChevronUp, x as ChevronDown, y as ChevronRight } from "../_libs/lucide-react.mjs";
6
7
  import { t as cva } from "../_libs/class-variance-authority+clsx.mjs";
7
8
  import { a as ItemText, c as ScrollDownButton, d as Value, f as Viewport, i as ItemIndicator, l as ScrollUpButton, n as Icon, o as Portal$1, r as Item, s as Root2$1, t as Content2$1, u as Trigger$1 } from "../_libs/@radix-ui/react-select+[...].mjs";
8
9
  import { t as Root$1 } from "../_libs/radix-ui__react-label.mjs";
9
10
  import { i as Track, n as Root$2, r as Thumb, t as Range } from "../_libs/radix-ui__react-slider.mjs";
10
11
  import { n as Thumb$1, t as Root$3 } from "../_libs/radix-ui__react-switch.mjs";
11
- import { C as Tooltip$1, D as cn, E as TooltipTrigger, S as Skeleton, T as TooltipProvider, a as SheetContent, c as SheetTitle, i as Sheet, n as Input, o as SheetDescription, r as Separator$1, s as SheetHeader, t as Button, w as TooltipContent, x as SidebarTrigger } from "./sidebar-DXng0IOP.mjs";
12
+ import { C as useIsMobile, S as cn, _ as Skeleton, g as SidebarTrigger, n as Input, r as Separator$1, t as Button, v as Tooltip$1, x as TooltipTrigger, y as TooltipContent } from "./sidebar-CgbtXkE2.mjs";
12
13
  import { d as useNavigate, f as useSearch } from "../_libs/@tanstack/react-router+[...].mjs";
13
14
  import { n as useStore, t as useForm } from "../_libs/@tanstack/react-form+[...].mjs";
14
15
  import { i as useQueryClient, n as useQuery, t as useMutation } from "../_libs/tanstack__react-query.mjs";
15
16
  import { n as toast } from "../_libs/sonner.mjs";
16
- import { t as getServerFnById } from "../__tanstack-start-server-fn-resolver-rXMsqALt.mjs";
17
+ import { t as getServerFnById } from "../__tanstack-start-server-fn-resolver-5wPQ8bZ3.mjs";
17
18
  import { t as require_semver } from "../_libs/semver.mjs";
18
19
  import { i as getCoreRowModel, n as useReactTable, r as createColumnHelper, t as flexRender } from "../_libs/@tanstack/react-table+[...].mjs";
19
20
  import { n as require_dayjs_min, t as require_relativeTime } from "../_libs/dayjs.mjs";
20
- //#region node_modules/.nitro/vite/services/ssr/assets/routes-DyUhibF4.js
21
+ //#region node_modules/.nitro/vite/services/ssr/assets/routes-BSs-dv4D.js
21
22
  var import_jsx_runtime = require_jsx_runtime();
22
- var import_react = /* @__PURE__ */ __toESM(require_react());
23
23
  var import_semver = /* @__PURE__ */ __toESM(require_semver());
24
+ var import_react = /* @__PURE__ */ __toESM(require_react());
24
25
  var import_dayjs_min = /* @__PURE__ */ __toESM(require_dayjs_min());
25
26
  var import_relativeTime = /* @__PURE__ */ __toESM(require_relativeTime());
27
+ var extractTimestampFromUUIDv7 = (uuid) => {
28
+ const timestampHex = uuid.split("-").join("").slice(0, 12);
29
+ return Number.parseInt(timestampHex, 16);
30
+ };
31
+ function createUUIDv7FromTimestampHex(timestampHex) {
32
+ const randomBytes = new Uint8Array(10);
33
+ crypto.getRandomValues(randomBytes);
34
+ const randomHex = Array.from(randomBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
35
+ const randA = randomHex.slice(0, 3);
36
+ const randBHex = randomHex.slice(3, 19);
37
+ const versionAndRandA = `7${randA}`;
38
+ const variantAndFirstRandB = (128 | parseInt(randBHex.slice(0, 2), 16) & 63).toString(16).padStart(2, "0");
39
+ return [
40
+ timestampHex.slice(0, 8),
41
+ timestampHex.slice(8, 12),
42
+ versionAndRandA,
43
+ variantAndFirstRandB + randBHex.slice(2, 4),
44
+ randBHex.slice(4, 16)
45
+ ].join("-");
46
+ }
47
+ var createUUIDv7 = () => createUUIDv7FromTimestampHex(Date.now().toString(16).padStart(12, "0"));
48
+ function Dialog$1({ ...props }) {
49
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Root, {
50
+ "data-slot": "dialog",
51
+ ...props
52
+ });
53
+ }
54
+ function DialogTrigger({ ...props }) {
55
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Trigger, {
56
+ "data-slot": "dialog-trigger",
57
+ ...props
58
+ });
59
+ }
60
+ function DialogPortal({ ...props }) {
61
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Portal, {
62
+ "data-slot": "dialog-portal",
63
+ ...props
64
+ });
65
+ }
66
+ function DialogOverlay({ className, ...props }) {
67
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Overlay, {
68
+ "data-slot": "dialog-overlay",
69
+ className: cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/80 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50", className),
70
+ ...props
71
+ });
72
+ }
73
+ function DialogContent({ className, children, showCloseButton = true, ...props }) {
74
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogPortal, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogOverlay, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Content, {
75
+ "data-slot": "dialog-content",
76
+ className: cn("bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-4 rounded-xl p-4 text-xs/relaxed ring-1 duration-100 sm:max-w-sm fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2", className),
77
+ ...props,
78
+ children: [children, showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Close, {
79
+ "data-slot": "dialog-close",
80
+ asChild: true,
81
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
82
+ variant: "ghost",
83
+ className: "absolute top-2 right-2",
84
+ size: "icon-sm",
85
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
86
+ className: "sr-only",
87
+ children: "Close"
88
+ })]
89
+ })
90
+ })]
91
+ })] });
92
+ }
93
+ function DialogHeader({ className, ...props }) {
94
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
95
+ "data-slot": "dialog-header",
96
+ className: cn("gap-1 flex flex-col", className),
97
+ ...props
98
+ });
99
+ }
100
+ function DialogFooter({ className, showCloseButton = false, children, ...props }) {
101
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
102
+ "data-slot": "dialog-footer",
103
+ className: cn("gap-2 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className),
104
+ ...props,
105
+ children: [children, showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Close, {
106
+ asChild: true,
107
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
108
+ variant: "outline",
109
+ children: "Close"
110
+ })
111
+ })]
112
+ });
113
+ }
114
+ function DialogTitle({ className, ...props }) {
115
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Title, {
116
+ "data-slot": "dialog-title",
117
+ className: cn("text-sm font-medium", className),
118
+ ...props
119
+ });
120
+ }
121
+ function DialogDescription({ className, ...props }) {
122
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Description, {
123
+ "data-slot": "dialog-description",
124
+ className: cn("text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed *:[a]:underline *:[a]:underline-offset-3", className),
125
+ ...props
126
+ });
127
+ }
128
+ function Sheet({ ...props }) {
129
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Root, {
130
+ "data-slot": "sheet",
131
+ ...props
132
+ });
133
+ }
134
+ function SheetPortal({ ...props }) {
135
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Portal, {
136
+ "data-slot": "sheet-portal",
137
+ ...props
138
+ });
139
+ }
140
+ function SheetOverlay({ className, ...props }) {
141
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Overlay, {
142
+ "data-slot": "sheet-overlay",
143
+ className: cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/80 duration-100 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 z-50", className),
144
+ ...props
145
+ });
146
+ }
147
+ function SheetContent({ className, children, overlayClassName, side = "right", showCloseButton = true, ...props }) {
148
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SheetPortal, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SheetOverlay, { className: overlayClassName }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Content, {
149
+ "data-slot": "sheet-content",
150
+ "data-side": side,
151
+ className: cn("bg-background data-open:animate-in data-closed:animate-out data-[side=right]:data-closed:slide-out-to-right-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=top]:data-closed:slide-out-to-top-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:fade-out-0 data-open:fade-in-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=bottom]:data-open:slide-in-from-bottom-10 fixed z-50 flex flex-col bg-clip-padding text-xs/relaxed shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t 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=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=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm", className),
152
+ ...props,
153
+ children: [children, showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Close, {
154
+ "data-slot": "sheet-close",
155
+ asChild: true,
156
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
157
+ variant: "ghost",
158
+ className: "absolute top-4 right-4",
159
+ size: "icon-sm",
160
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
161
+ className: "sr-only",
162
+ children: "Close"
163
+ })]
164
+ })
165
+ })]
166
+ })] });
167
+ }
168
+ function SheetHeader({ className, ...props }) {
169
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
170
+ "data-slot": "sheet-header",
171
+ className: cn("gap-1.5 p-6 flex flex-col", className),
172
+ ...props
173
+ });
174
+ }
175
+ function SheetTitle({ className, ...props }) {
176
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Title, {
177
+ "data-slot": "sheet-title",
178
+ className: cn("text-foreground text-sm font-medium", className),
179
+ ...props
180
+ });
181
+ }
182
+ function SheetDescription({ className, ...props }) {
183
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Description, {
184
+ "data-slot": "sheet-description",
185
+ className: cn("text-muted-foreground text-xs/relaxed", className),
186
+ ...props
187
+ });
188
+ }
189
+ function BundleIdDisplay({ bundleId, className }) {
190
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
191
+ translate: "no",
192
+ className: cn("break-all font-mono text-xs tabular-nums", className),
193
+ children: bundleId
194
+ });
195
+ }
26
196
  function AppleIcon({ className }) {
27
197
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
28
198
  role: "img",
@@ -51,131 +221,59 @@ function PlatformIcon({ platform, className }) {
51
221
  }
52
222
  function BundleBasicInfo({ bundle }) {
53
223
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
54
- className: "flex flex-col gap-3 text-sm mt-1",
224
+ className: "mt-1 flex flex-col gap-3 text-sm",
55
225
  children: [
226
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
227
+ className: "flex flex-wrap items-center gap-2",
228
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
229
+ className: "flex items-center gap-2",
230
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlatformIcon, {
231
+ platform: bundle.platform,
232
+ className: "h-4 w-4"
233
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
234
+ className: "font-medium",
235
+ children: bundle.platform === "ios" ? "iOS" : "Android"
236
+ })]
237
+ })
238
+ }),
56
239
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
57
- className: "flex items-center gap-2",
58
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlatformIcon, {
59
- platform: bundle.platform,
60
- className: "h-4 w-4"
240
+ className: "flex flex-wrap items-start gap-2",
241
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
242
+ className: "font-medium text-muted-foreground",
243
+ children: "Bundle"
61
244
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
62
- className: "font-medium",
63
- children: bundle.platform === "ios" ? "iOS" : "Android"
245
+ className: "min-w-0 basis-full sm:basis-auto",
246
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
247
+ bundleId: bundle.id,
248
+ maxLength: 18,
249
+ fullOnMobile: true
250
+ })
64
251
  })]
65
252
  }),
66
253
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
67
- className: "flex items-center gap-2",
254
+ className: "flex flex-wrap items-center gap-2",
68
255
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
69
256
  className: "font-medium text-muted-foreground",
70
- children: "Bundle ID"
257
+ children: "Channel"
71
258
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
72
259
  className: "text-xs text-foreground",
73
- children: bundle.id
260
+ translate: "no",
261
+ children: bundle.channel
74
262
  })]
75
263
  }),
76
264
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
77
- className: "flex items-center gap-2",
265
+ className: "flex flex-wrap items-center gap-2",
78
266
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
79
267
  className: "font-medium text-muted-foreground",
80
- children: "Channel"
268
+ children: "Platform"
81
269
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
82
270
  className: "text-xs text-foreground",
83
- children: bundle.channel
271
+ children: bundle.platform === "ios" ? "iOS" : "Android"
84
272
  })]
85
273
  })
86
274
  ]
87
275
  });
88
276
  }
89
- var NUMERIC_COHORT_SIZE = 1e3;
90
- var DEFAULT_ROLLOUT_COHORT_COUNT = NUMERIC_COHORT_SIZE;
91
- var INVALID_COHORT_ERROR_MESSAGE = `Invalid cohort. Use 1-1000 or a lowercase slug without spaces, up to 64 characters.`;
92
- var CUSTOM_COHORT_PATTERN = /^[a-z0-9-]+$/;
93
- function parseNumericCohortValue(cohort) {
94
- if (!/^\d+$/.test(cohort)) return null;
95
- const parsed = Number.parseInt(cohort, 10);
96
- if (Number.isNaN(parsed) || parsed < 1 || parsed > 1e3) return null;
97
- return parsed;
98
- }
99
- function positiveMod(value, modulus) {
100
- return (value % modulus + modulus) % modulus;
101
- }
102
- function hashString(value) {
103
- let hash = 0;
104
- for (let i = 0; i < value.length; i++) {
105
- const char = value.charCodeAt(i);
106
- hash = (hash << 5) - hash + char;
107
- hash |= 0;
108
- }
109
- return hash;
110
- }
111
- function gcd(a, b) {
112
- let x = Math.abs(a);
113
- let y = Math.abs(b);
114
- while (y !== 0) {
115
- const next = x % y;
116
- x = y;
117
- y = next;
118
- }
119
- return x;
120
- }
121
- function modularInverse(value, modulus) {
122
- let t = 0;
123
- let newT = 1;
124
- let r = modulus;
125
- let newR = positiveMod(value, modulus);
126
- while (newR !== 0) {
127
- const quotient = Math.floor(r / newR);
128
- [t, newT] = [newT, t - quotient * newT];
129
- [r, newR] = [newR, r - quotient * newR];
130
- }
131
- if (r > 1) throw new Error(`No modular inverse for ${value} mod ${modulus}`);
132
- return positiveMod(t, modulus);
133
- }
134
- function getRolloutShuffleParameters(bundleId) {
135
- let multiplier = positiveMod(hashString(`${bundleId}:multiplier`), 997);
136
- if (multiplier === 0) multiplier = 1;
137
- while (gcd(multiplier, NUMERIC_COHORT_SIZE) !== 1) {
138
- multiplier = positiveMod(multiplier + 1, NUMERIC_COHORT_SIZE);
139
- if (multiplier === 0) multiplier = 1;
140
- }
141
- const offset = positiveMod(hashString(`${bundleId}:offset`), NUMERIC_COHORT_SIZE);
142
- return {
143
- multiplier,
144
- offset,
145
- inverseMultiplier: modularInverse(multiplier, NUMERIC_COHORT_SIZE)
146
- };
147
- }
148
- function normalizeRolloutCohortCount(rolloutCohortCount) {
149
- if (rolloutCohortCount === null || rolloutCohortCount === void 0) return DEFAULT_ROLLOUT_COHORT_COUNT;
150
- if (rolloutCohortCount <= 0) return 0;
151
- if (rolloutCohortCount >= 1e3) return NUMERIC_COHORT_SIZE;
152
- return Math.floor(rolloutCohortCount);
153
- }
154
- function normalizeCohortValue(cohort) {
155
- const normalized = cohort.trim().toLowerCase();
156
- const numericCohort = parseNumericCohortValue(normalized);
157
- if (numericCohort !== null) return String(numericCohort);
158
- return normalized;
159
- }
160
- function getNumericCohortValue(cohort) {
161
- return parseNumericCohortValue(normalizeCohortValue(cohort));
162
- }
163
- function isNumericCohort(cohort) {
164
- return getNumericCohortValue(cohort) !== null;
165
- }
166
- function isCustomCohort(cohort) {
167
- const normalized = normalizeCohortValue(cohort);
168
- return normalized.length > 0 && normalized.length <= 64 && !/^\d+$/.test(normalized) && CUSTOM_COHORT_PATTERN.test(normalized);
169
- }
170
- function isValidCohort(cohort) {
171
- const normalized = normalizeCohortValue(cohort);
172
- return isNumericCohort(normalized) || isCustomCohort(normalized);
173
- }
174
- function getNumericCohortRolloutPosition(bundleId, cohortValue) {
175
- if (cohortValue < 1 || cohortValue > 1e3) throw new Error(`Invalid numeric cohort: ${cohortValue}`);
176
- const { offset, inverseMultiplier } = getRolloutShuffleParameters(bundleId);
177
- return positiveMod(inverseMultiplier * (cohortValue - 1 - offset), NUMERIC_COHORT_SIZE);
178
- }
179
277
  var badgeVariants = cva("inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", {
180
278
  variants: { variant: {
181
279
  default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
@@ -309,6 +407,8 @@ var getChannels = createServerFn().handler(createSsrRpc("79ada05964de8f2123bfcd6
309
407
  createServerFn().handler(createSsrRpc("f8bdca3d0579adb812d6404f55d9261a89cdb025e920b1fcad82883646a5fe9e"));
310
408
  var getBundles = createServerFn({ method: "GET" }).inputValidator((input) => input).handler(createSsrRpc("00ccacb4a0212c83ec29f4d11719046ad91ea8291cfc557d514dbf00d3bd7f5f"));
311
409
  var getBundle = createServerFn({ method: "GET" }).inputValidator((input) => input).handler(createSsrRpc("1bd85c2a50e24785cb6abb023a247a9f048f37ddfa85cbb1c57e579563bad013"));
410
+ var getBundleChildren = createServerFn({ method: "GET" }).inputValidator((input) => input).handler(createSsrRpc("4370f1bf70b91f1168d85379d3008449a1bedb915e9878ca4ef89d5f3a80fa7f"));
411
+ var getBundleChildCounts = createServerFn({ method: "GET" }).inputValidator((input) => input).handler(createSsrRpc("4ca367070b00f58179a572f0bb8196a4ccdd3881e9b1dbfe33deee0cb6e1f549"));
312
412
  var getBundleDownloadUrl = createServerFn({ method: "GET" }).inputValidator((input) => input).handler(createSsrRpc("e000081a14772a0496dfdf615232fbbb1a23b89191d0e32f89a05dd67a1916f7"));
313
413
  var updateBundle = createServerFn({ method: "POST" }).inputValidator((input) => input).handler(createSsrRpc("67f90ce4cd10fd0226cd9d77cdcd8d0f25a59a6ac406360b655e44296bc4208b"));
314
414
  var promoteBundle = createServerFn({ method: "POST" }).inputValidator((input) => input).handler(createSsrRpc("c4ef3bbb77ea8a4410623fd18a4741e2ff0668b1a97510bcdd032a8428eaa5da"));
@@ -323,6 +423,15 @@ var queryKeys = {
323
423
  all: bundleListQueryKey,
324
424
  list: (filters) => [...bundleListQueryKey, filters ?? {}]
325
425
  },
426
+ bundleChildren: {
427
+ all: ["bundle-children"],
428
+ list: (baseBundleId) => ["bundle-children", baseBundleId],
429
+ counts: (bundleIds) => [
430
+ "bundle-children",
431
+ "counts",
432
+ ...bundleIds
433
+ ]
434
+ },
326
435
  bundle: (bundleId) => ["bundle", bundleId]
327
436
  };
328
437
  function replaceBundleInQueryData(data, updatedBundle) {
@@ -362,6 +471,23 @@ function useBundleQuery(bundleId) {
362
471
  enabled: !!bundleId
363
472
  });
364
473
  }
474
+ function useBundleChildrenQuery(baseBundleId) {
475
+ return useQuery({
476
+ queryKey: queryKeys.bundleChildren.list(baseBundleId),
477
+ queryFn: () => getBundleChildren({ data: { baseBundleId } }),
478
+ staleTime: Infinity,
479
+ enabled: !!baseBundleId
480
+ });
481
+ }
482
+ function useBundleChildCountsQuery(bundleIds) {
483
+ const normalizedBundleIds = [...bundleIds].sort((left, right) => left.localeCompare(right));
484
+ return useQuery({
485
+ queryKey: queryKeys.bundleChildren.counts(normalizedBundleIds),
486
+ queryFn: () => getBundleChildCounts({ data: { bundleIds: normalizedBundleIds } }),
487
+ staleTime: Infinity,
488
+ enabled: normalizedBundleIds.length > 0
489
+ });
490
+ }
365
491
  function useBundleDownloadUrlMutation() {
366
492
  return useMutation({ mutationFn: (params) => getBundleDownloadUrl({ data: params }) });
367
493
  }
@@ -374,6 +500,7 @@ function useUpdateBundleMutation() {
374
500
  queryClient.setQueriesData({ queryKey: queryKeys.bundles.all }, (data) => replaceBundleInQueryData(data, updatedBundle));
375
501
  await Promise.all([
376
502
  queryClient.invalidateQueries({ queryKey: queryKeys.bundles.all }),
503
+ queryClient.invalidateQueries({ queryKey: queryKeys.bundleChildren.all }),
377
504
  queryClient.invalidateQueries({ queryKey: queryKeys.bundle(vars.bundleId) }),
378
505
  queryClient.invalidateQueries({ queryKey: queryKeys.channels })
379
506
  ]);
@@ -388,6 +515,7 @@ function usePromoteBundleMutation() {
388
515
  queryClient.setQueryData(queryKeys.bundle(bundle.id), bundle);
389
516
  await Promise.all([
390
517
  queryClient.invalidateQueries({ queryKey: queryKeys.bundles.all }),
518
+ queryClient.invalidateQueries({ queryKey: queryKeys.bundleChildren.all }),
391
519
  queryClient.invalidateQueries({ queryKey: queryKeys.channels }),
392
520
  queryClient.invalidateQueries({ queryKey: queryKeys.bundle(bundle.id) })
393
521
  ]);
@@ -400,7 +528,11 @@ function useDeleteBundleMutation() {
400
528
  mutationFn: (params) => deleteBundle({ data: params }),
401
529
  onSuccess: async (_, vars) => {
402
530
  queryClient.removeQueries({ queryKey: queryKeys.bundle(vars.bundleId) });
403
- await Promise.all([queryClient.invalidateQueries({ queryKey: queryKeys.bundles.all }), queryClient.invalidateQueries({ queryKey: queryKeys.channels })]);
531
+ await Promise.all([
532
+ queryClient.invalidateQueries({ queryKey: queryKeys.bundles.all }),
533
+ queryClient.invalidateQueries({ queryKey: queryKeys.bundleChildren.all }),
534
+ queryClient.invalidateQueries({ queryKey: queryKeys.channels })
535
+ ]);
404
536
  }
405
537
  });
406
538
  }
@@ -548,107 +680,6 @@ function DeleteBundleDialog({ bundle, open, onOpenChange, onSuccess }) {
548
680
  })
549
681
  });
550
682
  }
551
- var extractTimestampFromUUIDv7 = (uuid) => {
552
- const timestampHex = uuid.split("-").join("").slice(0, 12);
553
- return Number.parseInt(timestampHex, 16);
554
- };
555
- function createUUIDv7FromTimestampHex(timestampHex) {
556
- const randomBytes = new Uint8Array(10);
557
- crypto.getRandomValues(randomBytes);
558
- const randomHex = Array.from(randomBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
559
- const randA = randomHex.slice(0, 3);
560
- const randBHex = randomHex.slice(3, 19);
561
- const versionAndRandA = `7${randA}`;
562
- const variantAndFirstRandB = (128 | parseInt(randBHex.slice(0, 2), 16) & 63).toString(16).padStart(2, "0");
563
- return [
564
- timestampHex.slice(0, 8),
565
- timestampHex.slice(8, 12),
566
- versionAndRandA,
567
- variantAndFirstRandB + randBHex.slice(2, 4),
568
- randBHex.slice(4, 16)
569
- ].join("-");
570
- }
571
- var createUUIDv7 = () => createUUIDv7FromTimestampHex(Date.now().toString(16).padStart(12, "0"));
572
- function Dialog$1({ ...props }) {
573
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Root, {
574
- "data-slot": "dialog",
575
- ...props
576
- });
577
- }
578
- function DialogTrigger({ ...props }) {
579
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Trigger, {
580
- "data-slot": "dialog-trigger",
581
- ...props
582
- });
583
- }
584
- function DialogPortal({ ...props }) {
585
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Portal, {
586
- "data-slot": "dialog-portal",
587
- ...props
588
- });
589
- }
590
- function DialogOverlay({ className, ...props }) {
591
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Overlay, {
592
- "data-slot": "dialog-overlay",
593
- className: cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/80 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50", className),
594
- ...props
595
- });
596
- }
597
- function DialogContent({ className, children, showCloseButton = true, ...props }) {
598
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogPortal, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogOverlay, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Content, {
599
- "data-slot": "dialog-content",
600
- className: cn("bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-4 rounded-xl p-4 text-xs/relaxed ring-1 duration-100 sm:max-w-sm fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2", className),
601
- ...props,
602
- children: [children, showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Close, {
603
- "data-slot": "dialog-close",
604
- asChild: true,
605
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
606
- variant: "ghost",
607
- className: "absolute top-2 right-2",
608
- size: "icon-sm",
609
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
610
- className: "sr-only",
611
- children: "Close"
612
- })]
613
- })
614
- })]
615
- })] });
616
- }
617
- function DialogHeader({ className, ...props }) {
618
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
619
- "data-slot": "dialog-header",
620
- className: cn("gap-1 flex flex-col", className),
621
- ...props
622
- });
623
- }
624
- function DialogFooter({ className, showCloseButton = false, children, ...props }) {
625
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
626
- "data-slot": "dialog-footer",
627
- className: cn("gap-2 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className),
628
- ...props,
629
- children: [children, showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Close, {
630
- asChild: true,
631
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
632
- variant: "outline",
633
- children: "Close"
634
- })
635
- })]
636
- });
637
- }
638
- function DialogTitle({ className, ...props }) {
639
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Title, {
640
- "data-slot": "dialog-title",
641
- className: cn("text-sm font-medium", className),
642
- ...props
643
- });
644
- }
645
- function DialogDescription({ className, ...props }) {
646
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Description, {
647
- "data-slot": "dialog-description",
648
- className: cn("text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed *:[a]:underline *:[a]:underline-offset-3", className),
649
- ...props
650
- });
651
- }
652
683
  function Select$1({ ...props }) {
653
684
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Root2$1, {
654
685
  "data-slot": "select",
@@ -728,10 +759,12 @@ function useFilterParams() {
728
759
  before: search.before
729
760
  };
730
761
  const bundleId = search.bundleId;
731
- const navigateWithSearch = (nextSearch) => {
762
+ const expandedBundleId = search.expandedBundleId;
763
+ const navigateWithSearch = (nextSearch, options) => {
732
764
  navigate({
733
765
  to: "/",
734
- search: nextSearch
766
+ search: nextSearch,
767
+ resetScroll: options?.resetScroll
735
768
  });
736
769
  };
737
770
  const getNextFilters = (newFilters) => {
@@ -753,14 +786,23 @@ function useFilterParams() {
753
786
  const setFilters = (newFilters) => {
754
787
  navigateWithSearch({
755
788
  ...getNextFilters(newFilters),
756
- bundleId: void 0
789
+ bundleId: void 0,
790
+ expandedBundleId: void 0
757
791
  });
758
792
  };
759
793
  const setBundleId = (nextBundleId, newFilters = {}) => {
760
794
  navigateWithSearch({
761
795
  ...getNextFilters(newFilters),
762
- bundleId: nextBundleId
763
- });
796
+ bundleId: nextBundleId,
797
+ expandedBundleId: void 0
798
+ }, { resetScroll: false });
799
+ };
800
+ const setExpandedBundleId = (nextExpandedBundleId, newFilters = {}) => {
801
+ navigateWithSearch({
802
+ ...getNextFilters(newFilters),
803
+ bundleId,
804
+ expandedBundleId: nextExpandedBundleId
805
+ }, { resetScroll: false });
764
806
  };
765
807
  const resetFilters = () => {
766
808
  navigateWithSearch({
@@ -769,14 +811,17 @@ function useFilterParams() {
769
811
  page: void 0,
770
812
  after: void 0,
771
813
  before: void 0,
772
- bundleId: void 0
814
+ bundleId: void 0,
815
+ expandedBundleId: void 0
773
816
  });
774
817
  };
775
818
  return {
776
819
  filters,
777
820
  bundleId,
821
+ expandedBundleId,
778
822
  setFilters,
779
823
  setBundleId,
824
+ setExpandedBundleId,
780
825
  resetFilters
781
826
  };
782
827
  }
@@ -1016,6 +1061,78 @@ function RolloutCohortsDialog({ bundleId, rolloutCohortCount, targetCohorts, tri
1016
1061
  const rolloutCohorts = Array.from({ length: NUMERIC_COHORT_SIZE }, (_, index) => index + 1).filter((cohortValue) => getNumericCohortRolloutPosition(bundleId, cohortValue) < normalizedRolloutCount);
1017
1062
  const rolloutPercentage = (normalizedRolloutCount / 10).toFixed(1);
1018
1063
  const excludedCount = NUMERIC_COHORT_SIZE - rolloutCohorts.length;
1064
+ const isMobile = useIsMobile();
1065
+ const dialogBody = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1066
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1067
+ className: "grid gap-3 grid-cols-2 sm:grid-cols-3",
1068
+ children: [
1069
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
1070
+ className: "p-4",
1071
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Selected Cohorts" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
1072
+ className: "font-mono text-xl",
1073
+ children: rolloutCohorts.length
1074
+ })]
1075
+ }) }),
1076
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
1077
+ className: "p-4",
1078
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Excluded Cohorts" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
1079
+ className: "font-mono text-xl",
1080
+ children: excludedCount
1081
+ })]
1082
+ }) }),
1083
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, {
1084
+ className: "col-span-2 sm:col-span-1",
1085
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
1086
+ className: "p-4",
1087
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Bundle ID" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
1088
+ className: "font-mono text-xs break-all leading-relaxed",
1089
+ children: bundleId
1090
+ })]
1091
+ })
1092
+ })
1093
+ ]
1094
+ }),
1095
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
1096
+ className: "p-4 pb-3",
1097
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
1098
+ className: "text-sm",
1099
+ children: "Numeric Cohorts"
1100
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Listed in ascending order for readability." })]
1101
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, {
1102
+ className: "p-4 pt-0",
1103
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1104
+ className: "max-h-[50vh] overflow-y-auto rounded-lg border bg-muted/20 p-3 sm:max-h-[45vh]",
1105
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1106
+ className: "grid grid-cols-3 gap-2 sm:grid-cols-6 lg:grid-cols-8",
1107
+ children: rolloutCohorts.map((cohortValue) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
1108
+ variant: "outline",
1109
+ className: "justify-center font-mono",
1110
+ children: cohortValue
1111
+ }, cohortValue))
1112
+ })
1113
+ })
1114
+ })] }),
1115
+ hasTargetCohorts ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
1116
+ className: "p-4 pb-3",
1117
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
1118
+ className: "text-sm",
1119
+ children: "Target Cohorts"
1120
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "These cohorts are also included, even if they are outside the numeric rollout." })]
1121
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, {
1122
+ className: "p-4 pt-0",
1123
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1124
+ className: "flex flex-wrap gap-2 rounded-lg border bg-muted/20 p-3",
1125
+ children: normalizedTargetCohorts.map((cohort) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
1126
+ variant: "secondary",
1127
+ className: "max-w-full font-mono",
1128
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1129
+ className: "truncate",
1130
+ children: cohort
1131
+ })
1132
+ }, cohort))
1133
+ })
1134
+ })] }) : null
1135
+ ] });
1019
1136
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Dialog$1, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTrigger, {
1020
1137
  asChild: true,
1021
1138
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
@@ -1026,84 +1143,50 @@ function RolloutCohortsDialog({ bundleId, rolloutCohortCount, targetCohorts, tri
1026
1143
  onClick: (event) => event.stopPropagation(),
1027
1144
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(List, { className: "h-3.5 w-3.5" }), triggerLabel]
1028
1145
  })
1029
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogContent, {
1030
- className: "sm:max-w-3xl",
1031
- children: [
1032
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle, { children: "Rolled Out Cohorts" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogDescription, { children: [
1033
- rolloutPercentage,
1034
- "% rollout currently targets",
1035
- " ",
1036
- rolloutCohorts.length,
1037
- " of ",
1038
- NUMERIC_COHORT_SIZE,
1039
- " numeric cohorts. The selected set stays stable for this bundle as you expand or shrink rollout.",
1040
- hasTargetCohorts ? " Target Cohorts are added on top of this numeric rollout." : ""
1041
- ] })] }),
1042
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1043
- className: "grid gap-3 sm:grid-cols-3",
1044
- children: [
1045
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
1046
- className: "p-4",
1047
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Selected Cohorts" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
1048
- className: "font-mono text-xl",
1049
- children: rolloutCohorts.length
1050
- })]
1051
- }) }),
1052
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
1053
- className: "p-4",
1054
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Excluded Cohorts" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
1055
- className: "font-mono text-xl",
1056
- children: excludedCount
1057
- })]
1058
- }) }),
1059
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
1060
- className: "p-4",
1061
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Bundle ID" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
1062
- className: "font-mono text-xs break-all leading-relaxed",
1063
- children: bundleId
1064
- })]
1065
- }) })
1066
- ]
1067
- }),
1068
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
1069
- className: "p-4 pb-3",
1070
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
1071
- className: "text-sm",
1072
- children: "Numeric Cohorts"
1073
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Listed in ascending order for readability." })]
1074
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, {
1075
- className: "p-4 pt-0",
1076
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1077
- className: "max-h-[50vh] overflow-y-auto rounded-lg border bg-muted/20 p-3",
1146
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogContent, {
1147
+ 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",
1148
+ children: isMobile ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1149
+ className: "flex h-full flex-col overflow-hidden",
1150
+ children: [
1151
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, {
1152
+ className: "shrink-0 border-b border-border/70 px-4 py-4",
1153
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle, { children: "Rolled Out Cohorts" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogDescription, { children: [
1154
+ rolloutPercentage,
1155
+ "% rollout currently targets",
1156
+ " ",
1157
+ rolloutCohorts.length,
1158
+ " of ",
1159
+ NUMERIC_COHORT_SIZE,
1160
+ " numeric cohorts. The selected set stays stable for this bundle as you expand or shrink rollout.",
1161
+ hasTargetCohorts ? " Target Cohorts are added on top of this numeric rollout." : ""
1162
+ ] })]
1163
+ }),
1164
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1165
+ className: "flex-1 overflow-y-auto px-4 py-4",
1078
1166
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1079
- className: "grid grid-cols-4 gap-2 sm:grid-cols-6 lg:grid-cols-8",
1080
- children: rolloutCohorts.map((cohortValue) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
1081
- variant: "outline",
1082
- className: "justify-center font-mono",
1083
- children: cohortValue
1084
- }, cohortValue))
1167
+ className: "flex flex-col gap-4",
1168
+ children: dialogBody
1085
1169
  })
1170
+ }),
1171
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogFooter, {
1172
+ className: "shrink-0 border-t border-border/70 px-4 py-3",
1173
+ showCloseButton: true
1086
1174
  })
1087
- })] }),
1088
- hasTargetCohorts ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
1089
- className: "p-4 pb-3",
1090
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
1091
- className: "text-sm",
1092
- children: "Target Cohorts"
1093
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "These cohorts are also included, even if they are outside the numeric rollout." })]
1094
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, {
1095
- className: "p-4 pt-0",
1096
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1097
- className: "flex flex-wrap gap-2 rounded-lg border bg-muted/20 p-3",
1098
- children: normalizedTargetCohorts.map((cohort) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
1099
- variant: "secondary",
1100
- className: "font-mono",
1101
- children: cohort
1102
- }, cohort))
1103
- })
1104
- })] }) : null,
1175
+ ]
1176
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1177
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle, { children: "Rolled Out Cohorts" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogDescription, { children: [
1178
+ rolloutPercentage,
1179
+ "% rollout currently targets",
1180
+ " ",
1181
+ rolloutCohorts.length,
1182
+ " of ",
1183
+ NUMERIC_COHORT_SIZE,
1184
+ " numeric cohorts. The selected set stays stable for this bundle as you expand or shrink rollout.",
1185
+ hasTargetCohorts ? " Target Cohorts are added on top of this numeric rollout." : ""
1186
+ ] })] }),
1187
+ dialogBody,
1105
1188
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogFooter, { showCloseButton: true })
1106
- ]
1189
+ ] })
1107
1190
  })] });
1108
1191
  }
1109
1192
  var MIN_ROLLOUT_COHORT_COUNT = 10;
@@ -1532,6 +1615,53 @@ function BundleEditorForm({ bundle, onClose, onBusyChange }) {
1532
1615
  ]
1533
1616
  });
1534
1617
  }
1618
+ function HashValueDisplay({ value, maxLength = 12, className }) {
1619
+ const isTruncated = value.length > maxLength;
1620
+ const truncated = isTruncated ? value.slice(0, maxLength) : value;
1621
+ const copyValue = async () => {
1622
+ try {
1623
+ await navigator.clipboard.writeText(value);
1624
+ toast.success("Copied to clipboard", { description: value });
1625
+ } catch {
1626
+ toast.error("Failed to copy value");
1627
+ }
1628
+ };
1629
+ const handleClick = (event) => {
1630
+ event.preventDefault();
1631
+ event.stopPropagation();
1632
+ copyValue();
1633
+ };
1634
+ const handleKeyDown = (event) => {
1635
+ if (event.key === "Enter" || event.key === " ") {
1636
+ event.preventDefault();
1637
+ event.stopPropagation();
1638
+ copyValue();
1639
+ }
1640
+ };
1641
+ const content = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1642
+ translate: "no",
1643
+ className: cn("font-mono text-xs tabular-nums whitespace-nowrap", className),
1644
+ children: truncated
1645
+ });
1646
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Tooltip$1, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipTrigger, {
1647
+ asChild: true,
1648
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1649
+ type: "button",
1650
+ translate: "no",
1651
+ 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"),
1652
+ onClick: handleClick,
1653
+ onKeyDown: handleKeyDown,
1654
+ children: content
1655
+ })
1656
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipContent, {
1657
+ hidden: !isTruncated,
1658
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1659
+ translate: "no",
1660
+ className: "break-all font-mono text-xs tabular-nums",
1661
+ children: value
1662
+ })
1663
+ })] });
1664
+ }
1535
1665
  var normalizeGitUrl = (gitUrl) => {
1536
1666
  const trimmedGitUrl = gitUrl.trim();
1537
1667
  if (trimmedGitUrl.startsWith("git@")) {
@@ -1556,71 +1686,115 @@ var getCommitUrl = (gitUrl, commitHash) => {
1556
1686
  } catch {}
1557
1687
  return `${normalizedGitUrl}/commit/${commitHash}`;
1558
1688
  };
1689
+ function Row({ label, value }) {
1690
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1691
+ className: "flex flex-col gap-1.5 sm:flex-row sm:items-start sm:justify-between sm:gap-4",
1692
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1693
+ className: "text-muted-foreground",
1694
+ children: label
1695
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1696
+ className: "min-w-0 text-left text-sm sm:text-right",
1697
+ children: value
1698
+ })]
1699
+ });
1700
+ }
1559
1701
  function BundleMetadata({ bundle }) {
1560
1702
  const { data: configData, isFetched } = useConfigQuery();
1561
- const hasMetadata = bundle.targetAppVersion || bundle.fingerprintHash || bundle.gitCommitHash || bundle.fileHash;
1703
+ const patchBaseBundleId = getPatchBaseBundleId(bundle);
1704
+ const hbcPatchFileHash = getPatchFileHash(bundle);
1705
+ const hbcPatchBaseFileHash = getPatchBaseFileHash(bundle);
1706
+ const hasMetadata = bundle.targetAppVersion || bundle.fingerprintHash || bundle.gitCommitHash || bundle.fileHash || patchBaseBundleId || hbcPatchBaseFileHash || hbcPatchFileHash;
1562
1707
  const gitCommitUrl = bundle.gitCommitHash && isFetched ? getCommitUrl(configData?.console.gitUrl, bundle.gitCommitHash) : null;
1563
1708
  if (!hasMetadata) return null;
1564
1709
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
1565
1710
  className: "text-sm font-medium",
1566
1711
  children: "Metadata"
1567
1712
  }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardContent, {
1568
- className: "space-y-3 text-sm",
1713
+ className: "flex flex-col gap-3 text-sm",
1569
1714
  children: [
1570
- bundle.targetAppVersion && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1571
- className: "flex items-center justify-between",
1572
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1573
- className: "text-muted-foreground",
1574
- children: "App Version"
1575
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1715
+ bundle.targetAppVersion ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
1716
+ label: "App Version",
1717
+ value: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1718
+ translate: "no",
1576
1719
  className: "font-mono",
1577
1720
  children: bundle.targetAppVersion
1578
- })]
1579
- }),
1580
- bundle.fingerprintHash && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1581
- className: "flex items-center justify-between",
1582
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1583
- className: "text-muted-foreground",
1584
- children: "Fingerprint"
1585
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1586
- className: "font-mono text-xs",
1587
- children: [bundle.fingerprintHash.slice(0, 16), "..."]
1588
- })]
1589
- }),
1590
- bundle.gitCommitHash && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1591
- className: "flex items-center justify-between",
1592
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1593
- className: "text-muted-foreground",
1594
- children: "Git Commit"
1595
- }), gitCommitUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("a", {
1596
- href: gitCommitUrl,
1597
- target: "_blank",
1598
- rel: "noopener noreferrer",
1599
- className: "flex items-center gap-1 text-primary hover:underline font-mono text-xs",
1600
- children: [bundle.gitCommitHash.slice(0, 8), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ExternalLink, { className: "h-3 w-3" })]
1601
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1602
- className: "font-mono text-xs",
1603
- children: bundle.gitCommitHash.slice(0, 8)
1604
- })]
1605
- }),
1606
- bundle.fileHash && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1607
- className: "flex items-center justify-between",
1608
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1609
- className: "text-muted-foreground",
1610
- children: "File Hash"
1611
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1612
- className: "font-mono text-xs",
1613
- children: [bundle.fileHash.slice(0, 16), "..."]
1614
- })]
1615
- })
1721
+ })
1722
+ }) : null,
1723
+ bundle.fingerprintHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
1724
+ label: "Fingerprint",
1725
+ value: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HashValueDisplay, {
1726
+ value: bundle.fingerprintHash,
1727
+ maxLength: 16
1728
+ })
1729
+ }) : null,
1730
+ bundle.gitCommitHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
1731
+ label: "Git Commit",
1732
+ value: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1733
+ className: "flex items-center justify-start gap-1 sm:justify-end",
1734
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HashValueDisplay, {
1735
+ value: bundle.gitCommitHash,
1736
+ maxLength: 12,
1737
+ className: gitCommitUrl ? "text-primary" : void 0
1738
+ }), gitCommitUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
1739
+ href: gitCommitUrl,
1740
+ target: "_blank",
1741
+ rel: "noopener noreferrer",
1742
+ className: "shrink-0 text-primary hover:underline",
1743
+ "aria-label": "Open git commit",
1744
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ExternalLink, {
1745
+ "aria-hidden": "true",
1746
+ className: "h-3 w-3"
1747
+ })
1748
+ }) : null]
1749
+ })
1750
+ }) : null,
1751
+ bundle.fileHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
1752
+ label: "Bundle Hash",
1753
+ value: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HashValueDisplay, {
1754
+ value: bundle.fileHash,
1755
+ maxLength: 16
1756
+ })
1757
+ }) : null,
1758
+ patchBaseBundleId ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
1759
+ label: "Patch Base",
1760
+ value: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
1761
+ bundleId: patchBaseBundleId,
1762
+ maxLength: 18,
1763
+ fullOnMobile: true
1764
+ })
1765
+ }) : null,
1766
+ hbcPatchBaseFileHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
1767
+ label: "Base Hash",
1768
+ value: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HashValueDisplay, {
1769
+ value: hbcPatchBaseFileHash,
1770
+ maxLength: 16
1771
+ })
1772
+ }) : null,
1773
+ hbcPatchFileHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
1774
+ label: "Patch Hash",
1775
+ value: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HashValueDisplay, {
1776
+ value: hbcPatchFileHash,
1777
+ maxLength: 16
1778
+ })
1779
+ }) : null
1616
1780
  ]
1617
1781
  })] });
1618
1782
  }
1619
1783
  function BundleEditorSheet({ bundleId, bundle, loading = false, open, onOpenChange }) {
1620
1784
  const [isSaving, setIsSaving] = (0, import_react.useState)(false);
1785
+ const isMobile = useIsMobile();
1621
1786
  (0, import_react.useEffect)(() => {
1622
1787
  if (!open) setIsSaving(false);
1623
1788
  }, [open]);
1789
+ (0, import_react.useEffect)(() => {
1790
+ if (typeof window === "undefined") return;
1791
+ const refreshThemeChrome = () => {
1792
+ window.dispatchEvent(new Event("hot-updater:refresh-theme-chrome"));
1793
+ };
1794
+ refreshThemeChrome();
1795
+ const timeoutId = window.setTimeout(refreshThemeChrome, 180);
1796
+ return () => window.clearTimeout(timeoutId);
1797
+ }, [open]);
1624
1798
  const handleOpenChange = (nextOpen) => {
1625
1799
  if (!nextOpen && isSaving) return;
1626
1800
  if (!nextOpen) setIsSaving(false);
@@ -1630,11 +1804,70 @@ function BundleEditorSheet({ bundleId, bundle, loading = false, open, onOpenChan
1630
1804
  setIsSaving(false);
1631
1805
  onOpenChange(false);
1632
1806
  };
1807
+ const headerContent = bundle ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleBasicInfo, { bundle }) : loading ? bundleId ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1808
+ translate: "no",
1809
+ className: "font-mono text-xs",
1810
+ children: [
1811
+ "Loading ",
1812
+ bundleId,
1813
+ "…"
1814
+ ]
1815
+ }) : "Loading bundle details…" : bundleId ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1816
+ translate: "no",
1817
+ className: "font-mono text-xs",
1818
+ children: ["Bundle not found: ", bundleId]
1819
+ }) : "Bundle details unavailable";
1820
+ const bodyContent = bundle ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1821
+ className: "flex flex-col gap-6 px-4 pb-4 sm:px-6 sm:pb-6",
1822
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleEditorForm, {
1823
+ bundle,
1824
+ onClose: closeSheet,
1825
+ onBusyChange: setIsSaving
1826
+ }, bundle.id), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleMetadata, { bundle })]
1827
+ }) : loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1828
+ className: "flex flex-col gap-4 px-4 pb-4 sm:px-6 sm:pb-6",
1829
+ children: [
1830
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" }),
1831
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-28 w-full" }),
1832
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" }),
1833
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" })
1834
+ ]
1835
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1836
+ className: "px-4 pb-4 text-sm text-muted-foreground sm:px-6 sm:pb-6",
1837
+ children: "The requested bundle could not be loaded."
1838
+ });
1839
+ if (isMobile) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog$1, {
1840
+ open,
1841
+ onOpenChange: handleOpenChange,
1842
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogContent, {
1843
+ className: "top-0 left-0 h-dvh max-w-none translate-x-0 translate-y-0 rounded-none border-0 p-0",
1844
+ showCloseButton: !isSaving,
1845
+ onEscapeKeyDown: (event) => {
1846
+ if (isSaving) event.preventDefault();
1847
+ },
1848
+ onInteractOutside: (event) => {
1849
+ if (isSaving) event.preventDefault();
1850
+ },
1851
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1852
+ className: "flex h-full flex-col overflow-hidden",
1853
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, {
1854
+ className: "shrink-0 border-b border-border/70 px-4 py-4 sm:px-6",
1855
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle, { children: bundle ? "Bundle Detail" : "Bundle Details" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogDescription, {
1856
+ asChild: true,
1857
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: headerContent })
1858
+ })]
1859
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1860
+ className: "flex-1 overflow-y-auto",
1861
+ children: bodyContent
1862
+ })]
1863
+ })
1864
+ })
1865
+ });
1633
1866
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sheet, {
1634
1867
  open,
1635
1868
  onOpenChange: handleOpenChange,
1636
1869
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SheetContent, {
1637
- className: "w-[600px] sm:max-w-[600px] overflow-y-auto",
1870
+ className: "w-[600px] overflow-y-auto sm:max-w-[600px]",
1638
1871
  showCloseButton: !isSaving,
1639
1872
  onEscapeKeyDown: (event) => {
1640
1873
  if (isSaving) event.preventDefault();
@@ -1642,34 +1875,63 @@ function BundleEditorSheet({ bundleId, bundle, loading = false, open, onOpenChan
1642
1875
  onInteractOutside: (event) => {
1643
1876
  if (isSaving) event.preventDefault();
1644
1877
  },
1645
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SheetHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SheetTitle, { children: bundle ? "Edit Bundle" : "Bundle Details" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SheetDescription, { children: bundle ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleBasicInfo, { bundle }) : loading ? bundleId ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1646
- className: "font-mono text-xs",
1647
- children: ["Loading ", bundleId]
1648
- }) : "Loading bundle details" : bundleId ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1649
- className: "font-mono text-xs",
1650
- children: ["Bundle not found: ", bundleId]
1651
- }) : "Bundle details unavailable" })] }), bundle ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1652
- className: "px-6 pb-6 space-y-6",
1653
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleEditorForm, {
1654
- bundle,
1655
- onClose: closeSheet,
1656
- onBusyChange: setIsSaving
1657
- }, bundle.id), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleMetadata, { bundle })]
1658
- }) : loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1659
- className: "px-6 pb-6 space-y-4",
1660
- children: [
1661
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" }),
1662
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-28 w-full" }),
1663
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" }),
1664
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" })
1665
- ]
1666
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1667
- className: "px-6 pb-6 text-sm text-muted-foreground",
1668
- children: "The requested bundle could not be loaded."
1669
- })]
1878
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SheetHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SheetTitle, { children: bundle ? "Bundle Detail" : "Bundle Details" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SheetDescription, { children: headerContent })] }), bodyContent]
1670
1879
  })
1671
1880
  });
1672
1881
  }
1882
+ var channelColors = {
1883
+ production: "bg-green-500/10 text-green-700 dark:text-green-400 border-green-500/20",
1884
+ dev: "bg-blue-500/10 text-blue-700 dark:text-blue-400 border-blue-500/20",
1885
+ staging: "bg-yellow-500/10 text-yellow-700 dark:text-yellow-400 border-yellow-500/20"
1886
+ };
1887
+ function ChannelBadge({ channel, className }) {
1888
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
1889
+ variant: "outline",
1890
+ className: cn(channelColors[channel.toLowerCase()] || "bg-gray-500/10 text-gray-700 dark:text-gray-400 border-gray-500/20", className),
1891
+ children: channel
1892
+ });
1893
+ }
1894
+ function EnabledStatusIcon({ enabled, className, falseIcon = "x", colorMode = "semantic" }) {
1895
+ const iconColorClassName = colorMode === "inherit" ? "text-current" : void 0;
1896
+ 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) });
1897
+ if (falseIcon === "minus") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Minus, { className: cn("h-4 w-4", iconColorClassName ?? "text-muted-foreground", className) });
1898
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: cn("h-4 w-4", iconColorClassName ?? "text-red-600 dark:text-red-400", className) });
1899
+ }
1900
+ function RolloutPercentageBadge({ percentage, className }) {
1901
+ const isPartialRollout = percentage < 100;
1902
+ const formattedPercentage = percentage.toFixed(1);
1903
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
1904
+ variant: isPartialRollout ? "secondary" : "default",
1905
+ className: cn("gap-1", className),
1906
+ children: [
1907
+ isPartialRollout && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriangleAlert, { className: "h-3 w-3" }),
1908
+ formattedPercentage,
1909
+ "%"
1910
+ ]
1911
+ });
1912
+ }
1913
+ import_dayjs_min.default.extend(import_relativeTime.default);
1914
+ function TimestampDisplay({ uuid, format = "relative" }) {
1915
+ const date = (0, import_dayjs_min.default)(extractTimestampFromUUIDv7(uuid));
1916
+ if (format === "relative") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1917
+ className: "text-sm text-muted-foreground",
1918
+ children: date.fromNow()
1919
+ });
1920
+ if (format === "absolute") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1921
+ className: "text-sm text-muted-foreground",
1922
+ children: date.format("YYYY-MM-DD HH:mm:ss")
1923
+ });
1924
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1925
+ className: "flex flex-col",
1926
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1927
+ className: "text-sm",
1928
+ children: date.fromNow()
1929
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1930
+ className: "text-xs text-muted-foreground",
1931
+ children: date.format("YYYY-MM-DD HH:mm:ss")
1932
+ })]
1933
+ });
1934
+ }
1673
1935
  function Table({ className, ...props }) {
1674
1936
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1675
1937
  "data-slot": "table-container",
@@ -1716,80 +1978,230 @@ function TableCell({ className, ...props }) {
1716
1978
  ...props
1717
1979
  });
1718
1980
  }
1719
- function BundleIdDisplay({ bundleId, maxLength = 12 }) {
1720
- const truncated = bundleId.length > maxLength ? bundleId.slice(-maxLength) : bundleId;
1721
- if (bundleId.length <= maxLength) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1722
- className: "font-mono text-xs",
1723
- children: bundleId
1724
- });
1725
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Tooltip$1, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipTrigger, {
1726
- asChild: true,
1727
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1728
- className: "font-mono text-xs cursor-help",
1729
- children: truncated
1981
+ function BundleChildrenPanel({ panelId, bundle, bundles, loading, onDetailClick }) {
1982
+ const isMobile = useIsMobile();
1983
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1984
+ id: panelId,
1985
+ className: "border-t bg-muted/10 p-3 sm:p-4",
1986
+ "aria-live": "polite",
1987
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1988
+ className: "flex flex-col gap-4",
1989
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1990
+ className: "flex flex-wrap items-center justify-between gap-3",
1991
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1992
+ className: "flex min-w-0 items-start gap-2 text-sm sm:items-center",
1993
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1994
+ className: "text-muted-foreground",
1995
+ children: "Base bundle"
1996
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
1997
+ bundleId: bundle.id,
1998
+ maxLength: 18,
1999
+ fullOnMobile: true
2000
+ })]
2001
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
2002
+ variant: "outline",
2003
+ children: [
2004
+ bundles.length,
2005
+ " ",
2006
+ bundles.length === 1 ? "patch" : "patches"
2007
+ ]
2008
+ })]
2009
+ }), loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2010
+ className: "flex flex-col gap-2",
2011
+ 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" })]
2012
+ }) : bundles.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2013
+ className: "flex flex-col gap-2",
2014
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2015
+ className: "text-xs font-semibold uppercase text-muted-foreground/70",
2016
+ children: "Patch bundles from this base"
2017
+ }), isMobile ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2018
+ className: "flex flex-col gap-2",
2019
+ children: bundles.map((childBundle) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2020
+ className: "rounded-md border bg-background p-3",
2021
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2022
+ className: "flex flex-col gap-3",
2023
+ children: [
2024
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2025
+ className: "space-y-1",
2026
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2027
+ className: "text-[11px] font-medium uppercase text-muted-foreground/70",
2028
+ children: "Patch Bundle"
2029
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
2030
+ bundleId: childBundle.id,
2031
+ maxLength: 18,
2032
+ fullOnMobile: true
2033
+ })]
2034
+ }),
2035
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2036
+ className: "space-y-1",
2037
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2038
+ className: "text-[11px] font-medium uppercase text-muted-foreground/70",
2039
+ children: "Relation"
2040
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2041
+ className: "flex flex-col items-start gap-1 text-sm",
2042
+ children: [
2043
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
2044
+ bundleId: bundle.id,
2045
+ maxLength: 12,
2046
+ fullOnMobile: true
2047
+ }),
2048
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowRight, { className: "h-4 w-4 rotate-90 text-muted-foreground" }),
2049
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
2050
+ bundleId: childBundle.id,
2051
+ maxLength: 12,
2052
+ fullOnMobile: true
2053
+ })
2054
+ ]
2055
+ })]
2056
+ }),
2057
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2058
+ className: "flex items-center justify-between gap-3",
2059
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2060
+ className: "space-y-1",
2061
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2062
+ className: "text-[11px] font-medium uppercase text-muted-foreground/70",
2063
+ children: "Artifact"
2064
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
2065
+ variant: "secondary",
2066
+ children: "bsdiff"
2067
+ })]
2068
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2069
+ className: "space-y-1 text-right",
2070
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2071
+ className: "text-[11px] font-medium uppercase text-muted-foreground/70",
2072
+ children: "Created"
2073
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2074
+ className: "text-xs tabular-nums text-foreground",
2075
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TimestampDisplay, { uuid: childBundle.id })
2076
+ })]
2077
+ })]
2078
+ }),
2079
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
2080
+ type: "button",
2081
+ variant: "outline",
2082
+ size: "sm",
2083
+ className: "w-full",
2084
+ onClick: () => onDetailClick(childBundle),
2085
+ children: "Detail"
2086
+ })
2087
+ ]
2088
+ })
2089
+ }, childBundle.id))
2090
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2091
+ className: "overflow-x-auto rounded-md border bg-background",
2092
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Table, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, {
2093
+ className: "hover:bg-transparent",
2094
+ children: [
2095
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Patch Bundle" }),
2096
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Relation" }),
2097
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Artifact" }),
2098
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Created" }),
2099
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
2100
+ className: "w-[96px] text-right",
2101
+ children: "Detail"
2102
+ })
2103
+ ]
2104
+ }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableBody, { children: bundles.map((childBundle) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, { children: [
2105
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
2106
+ bundleId: childBundle.id,
2107
+ maxLength: 18,
2108
+ fullOnMobile: true
2109
+ }) }),
2110
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2111
+ className: "flex min-w-[280px] flex-col items-start gap-1 sm:flex-row sm:items-center sm:gap-2",
2112
+ children: [
2113
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
2114
+ bundleId: bundle.id,
2115
+ maxLength: 12,
2116
+ fullOnMobile: true
2117
+ }),
2118
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowRight, { className: "h-4 w-4 shrink-0 rotate-90 text-muted-foreground sm:rotate-0" }),
2119
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
2120
+ bundleId: childBundle.id,
2121
+ maxLength: 12,
2122
+ fullOnMobile: true
2123
+ })
2124
+ ]
2125
+ }) }),
2126
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
2127
+ variant: "secondary",
2128
+ children: "bsdiff"
2129
+ }) }),
2130
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
2131
+ className: "tabular-nums",
2132
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TimestampDisplay, { uuid: childBundle.id })
2133
+ }),
2134
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
2135
+ className: "text-right",
2136
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
2137
+ type: "button",
2138
+ variant: "outline",
2139
+ size: "sm",
2140
+ onClick: () => onDetailClick(childBundle),
2141
+ children: "Detail"
2142
+ })
2143
+ })
2144
+ ] }, childBundle.id)) })] })
2145
+ })]
2146
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2147
+ className: "flex flex-col gap-2",
2148
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2149
+ className: "text-xs font-semibold uppercase text-muted-foreground/70",
2150
+ children: "Patch bundles from this base"
2151
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2152
+ className: "rounded-md border bg-background p-3 text-sm text-muted-foreground",
2153
+ children: "No direct patch bundles."
2154
+ })]
2155
+ })]
1730
2156
  })
1731
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1732
- className: "font-mono text-xs",
1733
- children: bundleId
1734
- }) })] }) });
1735
- }
1736
- var channelColors = {
1737
- production: "bg-green-500/10 text-green-700 dark:text-green-400 border-green-500/20",
1738
- dev: "bg-blue-500/10 text-blue-700 dark:text-blue-400 border-blue-500/20",
1739
- staging: "bg-yellow-500/10 text-yellow-700 dark:text-yellow-400 border-yellow-500/20"
1740
- };
1741
- function ChannelBadge({ channel, className }) {
1742
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
1743
- variant: "outline",
1744
- className: cn(channelColors[channel.toLowerCase()] || "bg-gray-500/10 text-gray-700 dark:text-gray-400 border-gray-500/20", className),
1745
- children: channel
1746
- });
1747
- }
1748
- function EnabledStatusIcon({ enabled, className, falseIcon = "x" }) {
1749
- if (enabled) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: cn("h-4 w-4 text-green-600 dark:text-green-400", className) });
1750
- if (falseIcon === "minus") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Minus, { className: cn("h-4 w-4 text-muted-foreground", className) });
1751
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: cn("h-4 w-4 text-red-600 dark:text-red-400", className) });
1752
- }
1753
- function RolloutPercentageBadge({ percentage, className }) {
1754
- const isPartialRollout = percentage < 100;
1755
- const formattedPercentage = percentage.toFixed(1);
1756
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
1757
- variant: isPartialRollout ? "secondary" : "default",
1758
- className: cn("gap-1", className),
1759
- children: [
1760
- isPartialRollout && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriangleAlert, { className: "h-3 w-3" }),
1761
- formattedPercentage,
1762
- "%"
1763
- ]
1764
2157
  });
1765
2158
  }
1766
- import_dayjs_min.default.extend(import_relativeTime.default);
1767
- function TimestampDisplay({ uuid, format = "relative" }) {
1768
- const date = (0, import_dayjs_min.default)(extractTimestampFromUUIDv7(uuid));
1769
- if (format === "relative") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1770
- className: "text-sm text-muted-foreground",
1771
- children: date.fromNow()
1772
- });
1773
- if (format === "absolute") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1774
- className: "text-sm text-muted-foreground",
1775
- children: date.format("YYYY-MM-DD HH:mm:ss")
1776
- });
2159
+ var columnHelper = createColumnHelper();
2160
+ function BundleIdCell({ bundle, expandedBundleId, onDetailClick, onToggleExpand }) {
2161
+ const isExpanded = bundle.id === expandedBundleId;
2162
+ const panelId = `bundle-lineage-panel-${bundle.id}`;
1777
2163
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1778
- className: "flex flex-col",
1779
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1780
- className: "text-sm",
1781
- children: date.fromNow()
1782
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1783
- className: "text-xs text-muted-foreground",
1784
- children: date.format("YYYY-MM-DD HH:mm:ss")
2164
+ className: "flex min-w-[240px] items-center gap-3",
2165
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
2166
+ type: "button",
2167
+ variant: "ghost",
2168
+ size: "icon",
2169
+ className: "size-8 shrink-0 touch-manipulation",
2170
+ "aria-label": isExpanded ? "Hide Lineage" : "Show Lineage",
2171
+ "aria-controls": panelId,
2172
+ "aria-expanded": isExpanded,
2173
+ onClick: (event) => {
2174
+ event.stopPropagation();
2175
+ onToggleExpand(bundle);
2176
+ },
2177
+ children: isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, { "aria-hidden": "true" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { "aria-hidden": "true" })
2178
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
2179
+ type: "button",
2180
+ className: cn("flex min-w-0 flex-col items-start rounded-sm text-left transition-colors", "focus-visible:ring-ring/30 focus-visible:ring-[2px] outline-none", "text-muted-foreground hover:text-foreground"),
2181
+ "aria-label": `Open details for bundle ${bundle.id}`,
2182
+ onClick: (event) => {
2183
+ event.stopPropagation();
2184
+ onDetailClick(bundle);
2185
+ },
2186
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2187
+ className: "min-w-0 text-foreground",
2188
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
2189
+ bundleId: bundle.id,
2190
+ fullOnMobile: true
2191
+ })
2192
+ })
1785
2193
  })]
1786
2194
  });
1787
2195
  }
1788
- var columnHelper = createColumnHelper();
1789
- var bundleColumns = [
2196
+ var createBundleColumns = ({ expandedBundleId, patchCountsByBundleId, onDetailClick, onToggleExpand }) => [
1790
2197
  columnHelper.accessor("id", {
1791
2198
  header: "Bundle ID",
1792
- cell: (info) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, { bundleId: info.getValue() })
2199
+ cell: (info) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdCell, {
2200
+ bundle: info.row.original,
2201
+ expandedBundleId,
2202
+ onDetailClick,
2203
+ onToggleExpand
2204
+ })
1793
2205
  }),
1794
2206
  columnHelper.accessor("channel", {
1795
2207
  header: "Channel",
@@ -1805,27 +2217,45 @@ var bundleColumns = [
1805
2217
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: info.getValue() === "ios" ? "iOS" : "Android" })]
1806
2218
  })
1807
2219
  }),
2220
+ columnHelper.display({
2221
+ id: "patches",
2222
+ header: "Patches",
2223
+ cell: (info) => {
2224
+ const count = patchCountsByBundleId[info.row.original.id];
2225
+ if (count === void 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2226
+ className: "text-sm text-muted-foreground",
2227
+ children: "Checking"
2228
+ });
2229
+ if (count === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2230
+ className: "text-sm text-muted-foreground",
2231
+ children: "-"
2232
+ });
2233
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
2234
+ variant: "secondary",
2235
+ children: [
2236
+ count,
2237
+ " ",
2238
+ count === 1 ? "patch" : "patches"
2239
+ ]
2240
+ });
2241
+ }
2242
+ }),
1808
2243
  columnHelper.display({
1809
2244
  id: "target",
1810
2245
  header: "Target",
1811
2246
  cell: (info) => {
1812
2247
  const row = info.row.original;
1813
- if (row.fingerprintHash) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Tooltip$1, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipTrigger, {
1814
- asChild: true,
1815
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1816
- className: "flex items-center gap-2 cursor-help",
1817
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FingerprintPattern, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1818
- className: "font-mono text-xs",
1819
- children: row.fingerprintHash.slice(0, 8)
1820
- })]
1821
- })
1822
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1823
- className: "font-mono text-xs",
1824
- children: row.fingerprintHash
1825
- }) })] });
2248
+ if (row.fingerprintHash) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2249
+ className: "flex min-w-[220px] items-start gap-2",
2250
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FingerprintPattern, { className: "mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HashValueDisplay, {
2251
+ value: row.fingerprintHash,
2252
+ maxLength: 12
2253
+ })]
2254
+ });
1826
2255
  if (row.targetAppVersion) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1827
2256
  className: "flex items-center gap-2",
1828
2257
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Package, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2258
+ translate: "no",
1829
2259
  className: "text-sm",
1830
2260
  children: row.targetAppVersion
1831
2261
  })]
@@ -1866,8 +2296,29 @@ var bundleColumns = [
1866
2296
  cell: (info) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TimestampDisplay, { uuid: info.getValue() })
1867
2297
  })
1868
2298
  ];
1869
- function BundlesTable({ bundles, pagination, selectedBundleId, onRowClick }) {
2299
+ function MobileStatusBadge({ enabled, trueLabel, falseLabel, falseIcon = "x", trueTone = "success" }) {
2300
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2301
+ className: cn("inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-medium", enabled ? trueTone === "warning" ? "bg-amber-500/14 text-amber-700 dark:text-amber-300" : "bg-emerald-500/12 text-emerald-700 dark:text-emerald-300" : falseIcon === "minus" ? "bg-muted text-muted-foreground" : "bg-red-500/12 text-red-700 dark:text-red-300"),
2302
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnabledStatusIcon, {
2303
+ enabled,
2304
+ falseIcon,
2305
+ colorMode: "inherit",
2306
+ className: "h-3.5 w-3.5"
2307
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: enabled ? trueLabel : falseLabel })]
2308
+ });
2309
+ }
2310
+ function BundlesTable({ bundles, pagination, expandedBundleId, selectedBundleId, onExpandedBundleChange, onDetailClick }) {
1870
2311
  const { setFilters } = useFilterParams();
2312
+ const isMobile = useIsMobile();
2313
+ const cursorPagination = pagination;
2314
+ const { data: patchCountsByBundleId = {} } = useBundleChildCountsQuery(bundles.map((bundle) => bundle.id));
2315
+ const { data: childBundles = [], isLoading: isChildBundlesLoading } = useBundleChildrenQuery(expandedBundleId ?? "");
2316
+ const bundleColumns = createBundleColumns({
2317
+ expandedBundleId,
2318
+ patchCountsByBundleId,
2319
+ onDetailClick,
2320
+ onToggleExpand: (bundle) => onExpandedBundleChange(expandedBundleId === bundle.id ? void 0 : bundle.id)
2321
+ });
1871
2322
  const table = useReactTable({
1872
2323
  data: bundles,
1873
2324
  columns: bundleColumns,
@@ -1878,7 +2329,7 @@ function BundlesTable({ bundles, pagination, selectedBundleId, onRowClick }) {
1878
2329
  const currentPage = pagination?.currentPage ?? 1;
1879
2330
  const totalPages = pagination?.totalPages ?? 0;
1880
2331
  const handlePreviousPage = () => {
1881
- const previousCursor = pagination?.previousCursor ?? bundles[0]?.id;
2332
+ const previousCursor = cursorPagination?.previousCursor ?? bundles[0]?.id;
1882
2333
  if (!previousCursor) return;
1883
2334
  setFilters({
1884
2335
  page: Math.max(1, currentPage - 1),
@@ -1887,7 +2338,7 @@ function BundlesTable({ bundles, pagination, selectedBundleId, onRowClick }) {
1887
2338
  });
1888
2339
  };
1889
2340
  const handleNextPage = () => {
1890
- const nextCursor = pagination?.nextCursor ?? bundles.at(-1)?.id;
2341
+ const nextCursor = cursorPagination?.nextCursor ?? bundles.at(-1)?.id;
1891
2342
  if (!nextCursor) return;
1892
2343
  setFilters({
1893
2344
  page: currentPage + 1,
@@ -1898,35 +2349,223 @@ function BundlesTable({ bundles, pagination, selectedBundleId, onRowClick }) {
1898
2349
  const startEntry = bundles.length === 0 ? 0 : (currentPage - 1) * 20 + 1;
1899
2350
  const endEntry = startEntry === 0 ? 0 : startEntry + bundles.length - 1;
1900
2351
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1901
- className: "space-y-4",
2352
+ className: "flex flex-col gap-4",
1902
2353
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1903
- className: "rounded-lg border bg-card text-card-foreground shadow-sm overflow-hidden",
1904
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Table, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHeader, {
2354
+ className: "overflow-hidden rounded-lg border bg-card text-card-foreground shadow-sm",
2355
+ children: isMobile ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2356
+ className: "flex flex-col",
2357
+ children: bundles.length ? bundles.map((bundle) => {
2358
+ const isExpanded = bundle.id === expandedBundleId;
2359
+ const panelId = `bundle-lineage-panel-${bundle.id}`;
2360
+ const rolloutPercentage = (bundle.rolloutCohortCount ?? 1e3) / 10;
2361
+ const patchCount = patchCountsByBundleId[bundle.id];
2362
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2363
+ "data-state": bundle.id === selectedBundleId ? "selected" : void 0,
2364
+ className: cn("border-b border-border/60 last:border-b-0 data-[state=selected]:bg-muted/20", isExpanded && "bg-primary/5"),
2365
+ children: [
2366
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
2367
+ type: "button",
2368
+ className: "flex w-full flex-col gap-4 p-4 text-left",
2369
+ onClick: () => onDetailClick(bundle),
2370
+ children: [
2371
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2372
+ className: "flex items-start justify-between gap-3",
2373
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2374
+ className: "min-w-0 space-y-2",
2375
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2376
+ className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground/70",
2377
+ children: "Bundle"
2378
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2379
+ className: "min-w-0 text-sm font-medium",
2380
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
2381
+ bundleId: bundle.id,
2382
+ maxLength: 18,
2383
+ fullOnMobile: true
2384
+ })
2385
+ })]
2386
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2387
+ className: "shrink-0",
2388
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChannelBadge, { channel: bundle.channel })
2389
+ })]
2390
+ }),
2391
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2392
+ className: "grid grid-cols-2 gap-3 text-sm",
2393
+ children: [
2394
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2395
+ className: "rounded-md bg-muted/40 p-3",
2396
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2397
+ className: "mb-1 text-[11px] font-medium uppercase text-muted-foreground/70",
2398
+ children: "Platform"
2399
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2400
+ className: "flex items-center gap-2",
2401
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlatformIcon, {
2402
+ platform: bundle.platform,
2403
+ className: "h-4 w-4"
2404
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: bundle.platform === "ios" ? "iOS" : "Android" })]
2405
+ })]
2406
+ }),
2407
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2408
+ className: "rounded-md bg-muted/40 p-3",
2409
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2410
+ className: "mb-1 text-[11px] font-medium uppercase text-muted-foreground/70",
2411
+ children: "Created"
2412
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2413
+ className: "text-xs text-foreground",
2414
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TimestampDisplay, { uuid: bundle.id })
2415
+ })]
2416
+ }),
2417
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2418
+ className: "rounded-md bg-muted/40 p-3",
2419
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2420
+ className: "mb-1 text-[11px] font-medium uppercase text-muted-foreground/70",
2421
+ children: "Rollout"
2422
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RolloutPercentageBadge, { percentage: rolloutPercentage })]
2423
+ }),
2424
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2425
+ className: "rounded-md bg-muted/40 p-3",
2426
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2427
+ className: "mb-1 text-[11px] font-medium uppercase text-muted-foreground/70",
2428
+ children: "Patches"
2429
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2430
+ className: "text-xs text-foreground",
2431
+ children: patchCount === void 0 ? "Checking" : patchCount > 0 ? `${patchCount} ${patchCount === 1 ? "patch" : "patches"}` : "-"
2432
+ })]
2433
+ }),
2434
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2435
+ className: "rounded-md bg-muted/40 p-3",
2436
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2437
+ className: "mb-1 text-[11px] font-medium uppercase text-muted-foreground/70",
2438
+ children: "Status"
2439
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2440
+ className: "flex flex-wrap items-center gap-2",
2441
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MobileStatusBadge, {
2442
+ enabled: bundle.enabled,
2443
+ trueLabel: "Enabled",
2444
+ falseLabel: "Disabled"
2445
+ }), bundle.shouldForceUpdate ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MobileStatusBadge, {
2446
+ enabled: bundle.shouldForceUpdate,
2447
+ trueLabel: "Force update",
2448
+ falseLabel: "Optional",
2449
+ falseIcon: "minus",
2450
+ trueTone: "warning"
2451
+ }) : null]
2452
+ })]
2453
+ })
2454
+ ]
2455
+ }),
2456
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2457
+ className: "grid gap-2 rounded-md border border-border/70 bg-background/80 p-3 text-sm",
2458
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2459
+ className: "flex items-center justify-between gap-3",
2460
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2461
+ className: "text-muted-foreground",
2462
+ children: "Target"
2463
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2464
+ className: "min-w-0 text-right",
2465
+ children: bundle.fingerprintHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2466
+ translate: "no",
2467
+ className: "inline-flex min-w-0 items-start gap-2 font-mono text-xs",
2468
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FingerprintPattern, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HashValueDisplay, {
2469
+ value: bundle.fingerprintHash,
2470
+ maxLength: 12
2471
+ })]
2472
+ }) : bundle.targetAppVersion ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2473
+ translate: "no",
2474
+ className: "inline-flex items-center gap-2 text-xs",
2475
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Package, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }), bundle.targetAppVersion]
2476
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2477
+ className: "text-xs text-muted-foreground",
2478
+ children: "-"
2479
+ })
2480
+ })]
2481
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2482
+ className: "flex items-start justify-between gap-3",
2483
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2484
+ className: "text-muted-foreground",
2485
+ children: "Message"
2486
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2487
+ className: "min-w-0 text-right text-xs text-foreground/80",
2488
+ children: bundle.message || "-"
2489
+ })]
2490
+ })]
2491
+ })
2492
+ ]
2493
+ }),
2494
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2495
+ className: "border-t border-border/60 px-4 py-3",
2496
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
2497
+ type: "button",
2498
+ variant: "ghost",
2499
+ className: "h-9 w-full justify-between px-3 text-sm",
2500
+ "aria-label": isExpanded ? "Hide Lineage" : "Show Lineage",
2501
+ "aria-controls": panelId,
2502
+ "aria-expanded": isExpanded,
2503
+ onClick: () => onExpandedBundleChange(isExpanded ? void 0 : bundle.id),
2504
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: patchCount === void 0 ? "Patch lineage" : `Patch lineage (${patchCount})` }), isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronUp, {
2505
+ className: "h-4 w-4",
2506
+ "aria-hidden": "true"
2507
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, {
2508
+ className: "h-4 w-4",
2509
+ "aria-hidden": "true"
2510
+ })]
2511
+ })
2512
+ }),
2513
+ isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleChildrenPanel, {
2514
+ panelId,
2515
+ bundle,
2516
+ bundles: childBundles,
2517
+ loading: isChildBundlesLoading,
2518
+ onDetailClick
2519
+ }) : null
2520
+ ]
2521
+ }, bundle.id);
2522
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2523
+ className: "flex h-32 items-center justify-center px-6 text-center text-sm text-muted-foreground",
2524
+ children: "No bundles found matching your filters."
2525
+ })
2526
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Table, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHeader, {
1905
2527
  className: "bg-muted/40",
1906
2528
  children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableRow, {
1907
- className: "hover:bg-transparent border-b border-border/60",
2529
+ className: "border-b border-border/60 hover:bg-transparent",
1908
2530
  children: headerGroup.headers.map((header) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
1909
2531
  className: "h-10 text-xs font-semibold uppercase text-muted-foreground/70",
1910
2532
  children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())
1911
2533
  }, header.id))
1912
2534
  }, headerGroup.id))
1913
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableBody, { children: table.getRowModel().rows?.length ? table.getRowModel().rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableRow, {
1914
- "data-state": row.original.id === selectedBundleId ? "selected" : void 0,
1915
- onClick: () => onRowClick(row.original),
1916
- className: "cursor-pointer hover:bg-muted/30 transition-colors data-[state=selected]:bg-muted",
1917
- children: row.getVisibleCells().map((cell) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
1918
- className: "py-3",
1919
- children: flexRender(cell.column.columnDef.cell, cell.getContext())
1920
- }, cell.id))
1921
- }, row.id)) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableRow, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
2535
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableBody, { children: table.getRowModel().rows?.length ? table.getRowModel().rows.map((row) => {
2536
+ const isExpanded = row.original.id === expandedBundleId;
2537
+ const panelId = `bundle-lineage-panel-${row.original.id}`;
2538
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableRow, {
2539
+ "data-state": row.original.id === selectedBundleId ? "selected" : void 0,
2540
+ className: cn("cursor-pointer transition-colors hover:bg-muted/10 focus-within:bg-muted/15 data-[state=selected]:bg-muted/15", isExpanded && "bg-primary/5"),
2541
+ onClick: () => onDetailClick(row.original),
2542
+ children: row.getVisibleCells().map((cell) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
2543
+ className: "py-3",
2544
+ children: flexRender(cell.column.columnDef.cell, cell.getContext())
2545
+ }, cell.id))
2546
+ }), isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableRow, {
2547
+ className: "hover:bg-transparent",
2548
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
2549
+ colSpan: bundleColumns.length,
2550
+ className: "border-t-0 p-0",
2551
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleChildrenPanel, {
2552
+ panelId,
2553
+ bundle: row.original,
2554
+ bundles: childBundles,
2555
+ loading: isChildBundlesLoading,
2556
+ onDetailClick
2557
+ })
2558
+ })
2559
+ }) : null] }, row.original.id);
2560
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableRow, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
1922
2561
  colSpan: bundleColumns.length,
1923
2562
  className: "h-32 text-center text-muted-foreground",
1924
2563
  children: "No bundles found matching your filters."
1925
2564
  }) }) })] })
1926
2565
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1927
- className: "flex items-center justify-between px-2",
2566
+ className: "flex flex-col gap-3 px-2 sm:flex-row sm:items-center sm:justify-between",
1928
2567
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1929
- className: "text-xs text-muted-foreground font-medium",
2568
+ className: "text-xs font-medium text-muted-foreground",
1930
2569
  children: [
1931
2570
  "Showing ",
1932
2571
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
@@ -1942,10 +2581,10 @@ function BundlesTable({ bundles, pagination, selectedBundleId, onRowClick }) {
1942
2581
  " entries"
1943
2582
  ]
1944
2583
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1945
- className: "flex items-center gap-3",
2584
+ className: "flex flex-wrap items-center gap-3 sm:justify-end",
1946
2585
  children: [
1947
2586
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1948
- className: "text-xs text-muted-foreground font-medium",
2587
+ className: "text-xs font-medium text-muted-foreground",
1949
2588
  children: [
1950
2589
  "Page ",
1951
2590
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
@@ -1967,16 +2606,16 @@ function BundlesTable({ bundles, pagination, selectedBundleId, onRowClick }) {
1967
2606
  size: "sm",
1968
2607
  onClick: handlePreviousPage,
1969
2608
  disabled: !hasPreviousPage,
1970
- className: "h-8 px-3 text-xs",
1971
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronLeft, { className: "h-3.5 w-3.5 mr-1" }), "Previous"]
2609
+ className: "h-8 flex-1 px-3 text-xs sm:flex-none",
2610
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronLeft, { "data-icon": "inline-start" }), "Previous"]
1972
2611
  }),
1973
2612
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
1974
2613
  variant: "outline",
1975
2614
  size: "sm",
1976
2615
  onClick: handleNextPage,
1977
2616
  disabled: !hasNextPage,
1978
- className: "h-8 px-3 text-xs",
1979
- children: ["Next", /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { className: "h-3.5 w-3.5 ml-1" })]
2617
+ className: "h-8 flex-1 px-3 text-xs sm:flex-none",
2618
+ children: ["Next", /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { "data-icon": "inline-end" })]
1980
2619
  })
1981
2620
  ]
1982
2621
  })]
@@ -1988,11 +2627,11 @@ function FilterToolbar() {
1988
2627
  const { data: channels = [] } = useChannelsQuery();
1989
2628
  const hasActiveFilters = filters.channel || filters.platform;
1990
2629
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
1991
- className: "flex h-12 shrink-0 items-center gap-2 border-b px-4 bg-card/50 backdrop-blur-sm sticky top-0 z-10",
2630
+ 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",
1992
2631
  children: [
1993
2632
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarTrigger, { className: "-ml-1" }),
1994
2633
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1995
- className: "flex items-center gap-1.5 text-muted-foreground ml-2",
2634
+ className: "ml-1 flex items-center gap-1.5 text-muted-foreground sm:ml-2",
1996
2635
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Funnel, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1997
2636
  className: "text-xs font-medium",
1998
2637
  children: "Filters"
@@ -2002,7 +2641,7 @@ function FilterToolbar() {
2002
2641
  value: filters.platform || "all",
2003
2642
  onValueChange: (value) => setFilters({ platform: value === "all" ? void 0 : value }),
2004
2643
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, {
2005
- className: "w-[140px] h-8 text-xs",
2644
+ className: "h-8 w-[calc(50%-0.25rem)] min-w-[132px] text-xs sm:w-[140px]",
2006
2645
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue, { placeholder: "All Platforms" })
2007
2646
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectContent, { children: [
2008
2647
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
@@ -2023,7 +2662,7 @@ function FilterToolbar() {
2023
2662
  value: filters.channel || "all",
2024
2663
  onValueChange: (value) => setFilters({ channel: value === "all" ? void 0 : value }),
2025
2664
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, {
2026
- className: "w-[140px] h-8 text-xs",
2665
+ className: "h-8 w-[calc(50%-0.25rem)] min-w-[132px] text-xs sm:w-[140px]",
2027
2666
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue, { placeholder: "All Channels" })
2028
2667
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectContent, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
2029
2668
  value: "all",
@@ -2037,7 +2676,7 @@ function FilterToolbar() {
2037
2676
  variant: "ghost",
2038
2677
  size: "sm",
2039
2678
  onClick: resetFilters,
2040
- className: "h-8 px-2 text-xs text-muted-foreground hover:text-foreground",
2679
+ className: "h-8 px-2 text-xs text-muted-foreground hover:text-foreground sm:ml-auto",
2041
2680
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "h-3.5 w-3.5 mr-1" }), "Clear"]
2042
2681
  })
2043
2682
  ]
@@ -2045,6 +2684,7 @@ function FilterToolbar() {
2045
2684
  }
2046
2685
  function BundlesPage() {
2047
2686
  const { filters, bundleId, setBundleId } = useFilterParams();
2687
+ const [expandedBundleId, setExpandedBundleId] = (0, import_react.useState)();
2048
2688
  const activeBundleId = bundleId ?? "";
2049
2689
  const { data: bundlesData, isLoading } = useBundlesQuery({
2050
2690
  channel: filters.channel,
@@ -2060,10 +2700,13 @@ function BundlesPage() {
2060
2700
  const { data: selectedBundleFromQuery, isPending: isSelectedBundlePending } = useBundleQuery(activeBundleId);
2061
2701
  const selectedBundle = selectedBundleFromQuery ?? selectedBundleFromList;
2062
2702
  const isSelectedBundleLoading = Boolean(activeBundleId) && !selectedBundle && isSelectedBundlePending;
2703
+ (0, import_react.useEffect)(() => {
2704
+ if (expandedBundleId && !bundles.some((bundle) => bundle.id === expandedBundleId)) setExpandedBundleId(void 0);
2705
+ }, [bundles, expandedBundleId]);
2063
2706
  if (isLoading) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2064
2707
  className: "flex flex-col h-full",
2065
2708
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FilterToolbar, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2066
- className: "flex-1 p-6 space-y-4 bg-muted/5",
2709
+ className: "flex flex-1 flex-col gap-4 bg-muted/5 p-3 sm:p-6",
2067
2710
  children: [
2068
2711
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-12 w-full" }),
2069
2712
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-12 w-full" }),
@@ -2077,12 +2720,17 @@ function BundlesPage() {
2077
2720
  children: [
2078
2721
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FilterToolbar, {}),
2079
2722
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2080
- className: "flex-1 p-6 space-y-6 bg-muted/5",
2723
+ className: "flex flex-1 flex-col gap-6 bg-muted/5 p-3 sm:p-6",
2081
2724
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundlesTable, {
2082
2725
  bundles,
2083
2726
  pagination,
2727
+ expandedBundleId,
2084
2728
  selectedBundleId: bundleId,
2085
- onRowClick: (bundle) => setBundleId(bundle.id)
2729
+ onExpandedBundleChange: setExpandedBundleId,
2730
+ onDetailClick: (bundle) => {
2731
+ setExpandedBundleId(void 0);
2732
+ setBundleId(bundle.id);
2733
+ }
2086
2734
  })
2087
2735
  }),
2088
2736
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleEditorSheet, {