@comergehq/studio 0.1.26 → 0.1.28

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.
package/dist/index.js CHANGED
@@ -37,12 +37,12 @@ __export(index_exports, {
37
37
  module.exports = __toCommonJS(index_exports);
38
38
 
39
39
  // src/studio/ComergeStudio.tsx
40
- var React47 = __toESM(require("react"));
41
- var import_react_native57 = require("react-native");
40
+ var React49 = __toESM(require("react"));
41
+ var import_react_native60 = require("react-native");
42
42
  var import_bottom_sheet6 = require("@gorhom/bottom-sheet");
43
43
 
44
44
  // src/studio/bootstrap/StudioBootstrap.tsx
45
- var import_react_native3 = require("react-native");
45
+ var import_react_native4 = require("react-native");
46
46
 
47
47
  // src/components/primitives/Text.tsx
48
48
  var import_react_native2 = require("react-native");
@@ -309,6 +309,109 @@ async function ensureAnonymousSession() {
309
309
  return { user: data.user, isNew: true };
310
310
  }
311
311
 
312
+ // src/studio/analytics/client.ts
313
+ var import_react_native3 = require("react-native");
314
+ var import_mixpanel_react_native = require("mixpanel-react-native");
315
+
316
+ // src/core/logger.ts
317
+ var import_react_native_logs = require("react-native-logs");
318
+ var log = import_react_native_logs.logger.createLogger(
319
+ {
320
+ levels: {
321
+ debug: 0,
322
+ info: 1,
323
+ warn: 2,
324
+ error: 3
325
+ },
326
+ severity: "debug",
327
+ transport: import_react_native_logs.consoleTransport,
328
+ transportOptions: {
329
+ colors: {
330
+ info: "blueBright",
331
+ warn: "yellowBright",
332
+ error: "redBright"
333
+ }
334
+ },
335
+ async: true,
336
+ dateFormat: "time",
337
+ printLevel: true,
338
+ printDate: true,
339
+ fixedExtLvlLength: false,
340
+ enabled: true
341
+ }
342
+ );
343
+
344
+ // src/studio/analytics/client.ts
345
+ var studioMixpanel = null;
346
+ var studioAnalyticsEnabled = false;
347
+ var initPromise = null;
348
+ async function initStudioAnalytics(options) {
349
+ if (initPromise) return initPromise;
350
+ initPromise = (async () => {
351
+ if (!options.enabled) {
352
+ studioAnalyticsEnabled = false;
353
+ return;
354
+ }
355
+ const token = (options.token ?? "").trim();
356
+ if (!token) {
357
+ studioAnalyticsEnabled = false;
358
+ log.warn("[studio-analytics] disabled: missing Mixpanel token");
359
+ return;
360
+ }
361
+ try {
362
+ const trackAutomaticEvents = false;
363
+ const useNative = false;
364
+ const serverUrl = (options.serverUrl ?? "").trim() || "https://api.mixpanel.com";
365
+ const superProperties = {
366
+ runtime: "comerge-studio",
367
+ platform: import_react_native3.Platform.OS
368
+ };
369
+ studioMixpanel = new import_mixpanel_react_native.Mixpanel(token, trackAutomaticEvents, useNative);
370
+ await studioMixpanel.init(false, superProperties, serverUrl);
371
+ studioMixpanel.setLoggingEnabled(Boolean(options.debug));
372
+ studioMixpanel.setFlushBatchSize(50);
373
+ studioAnalyticsEnabled = true;
374
+ } catch (error) {
375
+ studioMixpanel = null;
376
+ studioAnalyticsEnabled = false;
377
+ log.warn("[studio-analytics] init failed", error);
378
+ }
379
+ })();
380
+ return initPromise;
381
+ }
382
+ async function trackStudioEvent(eventName, properties) {
383
+ if (!studioAnalyticsEnabled || !studioMixpanel) return;
384
+ try {
385
+ await studioMixpanel.track(eventName, properties);
386
+ } catch (error) {
387
+ log.warn("[studio-analytics] track failed", { eventName, error });
388
+ }
389
+ }
390
+ async function flushStudioAnalytics() {
391
+ if (!studioAnalyticsEnabled || !studioMixpanel) return;
392
+ try {
393
+ await studioMixpanel.flush();
394
+ } catch (error) {
395
+ log.warn("[studio-analytics] flush failed", error);
396
+ }
397
+ }
398
+ async function identifyStudioUser(userId) {
399
+ if (!studioAnalyticsEnabled || !studioMixpanel || !userId) return;
400
+ try {
401
+ await studioMixpanel.identify(userId);
402
+ } catch (error) {
403
+ log.warn("[studio-analytics] identify failed", error);
404
+ }
405
+ }
406
+ async function resetStudioAnalytics() {
407
+ if (!studioAnalyticsEnabled || !studioMixpanel) return;
408
+ try {
409
+ await studioMixpanel.reset();
410
+ } catch (error) {
411
+ log.warn("[studio-analytics] reset failed", error);
412
+ }
413
+ }
414
+
312
415
  // src/studio/bootstrap/useStudioBootstrap.ts
313
416
  var SUPABASE_URL = "https://xtfxwbckjpfmqubnsusu.supabase.co";
314
417
  var SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh0Znh3YmNranBmbXF1Ym5zdXN1Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjA2MDEyMzAsImV4cCI6MjA3NjE3NzIzMH0.dzWGAWrK4CvrmHVHzf8w7JlUZohdap0ZPnLZnABMV8s";
@@ -323,11 +426,23 @@ function useStudioBootstrap(options) {
323
426
  (async () => {
324
427
  try {
325
428
  setClientKey(options.clientKey);
326
- const requireAuth = isSupabaseClientInjected();
429
+ const hasInjectedSupabase = isSupabaseClientInjected();
430
+ const requireAuth = hasInjectedSupabase;
431
+ const analyticsEnabled = options.analyticsEnabled ?? hasInjectedSupabase;
432
+ await initStudioAnalytics({
433
+ enabled: analyticsEnabled,
434
+ token: process.env.EXPO_PUBLIC_MIXPANEL_TOKEN,
435
+ serverUrl: process.env.EXPO_PUBLIC_MIXPANEL_SERVER_URL,
436
+ debug: __DEV__
437
+ });
327
438
  if (!requireAuth) {
328
439
  setSupabaseConfig({ url: SUPABASE_URL, anonKey: SUPABASE_ANON_KEY });
440
+ await resetStudioAnalytics();
329
441
  }
330
442
  const { user } = requireAuth ? await ensureAuthenticatedSession() : await ensureAnonymousSession();
443
+ if (requireAuth) {
444
+ await identifyStudioUser(user.id);
445
+ }
331
446
  if (cancelled) return;
332
447
  setState({ ready: true, userId: user.id, error: null });
333
448
  } catch (e) {
@@ -339,19 +454,25 @@ function useStudioBootstrap(options) {
339
454
  return () => {
340
455
  cancelled = true;
341
456
  };
342
- }, [options.clientKey]);
457
+ }, [options.analyticsEnabled, options.clientKey]);
343
458
  return state;
344
459
  }
345
460
 
346
461
  // src/studio/bootstrap/StudioBootstrap.tsx
347
462
  var import_jsx_runtime2 = require("react/jsx-runtime");
348
- function StudioBootstrap({ children, fallback, renderError, clientKey: clientKey2 }) {
349
- const { ready, error, userId } = useStudioBootstrap({ clientKey: clientKey2 });
463
+ function StudioBootstrap({
464
+ children,
465
+ fallback,
466
+ renderError,
467
+ clientKey: clientKey2,
468
+ analyticsEnabled
469
+ }) {
470
+ const { ready, error, userId } = useStudioBootstrap({ clientKey: clientKey2, analyticsEnabled });
350
471
  if (error) {
351
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { style: { flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, children: renderError ? renderError(error) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { variant: "bodyMuted", children: error.message }) });
472
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native4.View, { style: { flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, children: renderError ? renderError(error) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { variant: "bodyMuted", children: error.message }) });
352
473
  }
353
474
  if (!ready) {
354
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { style: { flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, children: fallback ?? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { variant: "bodyMuted", children: "Loading\u2026" }) });
475
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native4.View, { style: { flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, children: fallback ?? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { variant: "bodyMuted", children: "Loading\u2026" }) });
355
476
  }
356
477
  if (typeof children === "function") {
357
478
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: children({ userId: userId ?? "" }) });
@@ -364,36 +485,6 @@ var React3 = __toESM(require("react"));
364
485
 
365
486
  // src/core/services/http/index.ts
366
487
  var import_axios2 = __toESM(require("axios"));
367
-
368
- // src/core/logger.ts
369
- var import_react_native_logs = require("react-native-logs");
370
- var log = import_react_native_logs.logger.createLogger(
371
- {
372
- levels: {
373
- debug: 0,
374
- info: 1,
375
- warn: 2,
376
- error: 3
377
- },
378
- severity: "debug",
379
- transport: import_react_native_logs.consoleTransport,
380
- transportOptions: {
381
- colors: {
382
- info: "blueBright",
383
- warn: "yellowBright",
384
- error: "redBright"
385
- }
386
- },
387
- async: true,
388
- dateFormat: "time",
389
- printLevel: true,
390
- printDate: true,
391
- fixedExtLvlLength: false,
392
- enabled: true
393
- }
394
- );
395
-
396
- // src/core/services/http/index.ts
397
488
  var RETRYABLE_MAX_ATTEMPTS = 3;
398
489
  var RETRYABLE_BASE_DELAY_MS = 500;
399
490
  var RETRYABLE_MAX_DELAY_MS = 4e3;
@@ -842,13 +933,13 @@ var appsRepository = new AppsRepositoryImpl(appsRemoteDataSource);
842
933
 
843
934
  // src/studio/hooks/useForegroundSignal.ts
844
935
  var React2 = __toESM(require("react"));
845
- var import_react_native4 = require("react-native");
936
+ var import_react_native5 = require("react-native");
846
937
  function useForegroundSignal(enabled = true) {
847
938
  const [signal, setSignal] = React2.useState(0);
848
- const lastStateRef = React2.useRef(import_react_native4.AppState.currentState);
939
+ const lastStateRef = React2.useRef(import_react_native5.AppState.currentState);
849
940
  React2.useEffect(() => {
850
941
  if (!enabled) return;
851
- const sub = import_react_native4.AppState.addEventListener("change", (nextState) => {
942
+ const sub = import_react_native5.AppState.addEventListener("change", (nextState) => {
852
943
  const prevState = lastStateRef.current;
853
944
  lastStateRef.current = nextState;
854
945
  const didResume = (prevState === "background" || prevState === "inactive") && nextState === "active";
@@ -1208,6 +1299,154 @@ var BundlesRepositoryImpl = class extends BaseRepository {
1208
1299
  };
1209
1300
  var bundlesRepository = new BundlesRepositoryImpl(bundlesRemoteDataSource);
1210
1301
 
1302
+ // src/studio/analytics/events.ts
1303
+ var STUDIO_ANALYTICS_EVENT_VERSION = 1;
1304
+
1305
+ // src/studio/analytics/track.ts
1306
+ function baseProps() {
1307
+ return { event_version: STUDIO_ANALYTICS_EVENT_VERSION };
1308
+ }
1309
+ function normalizeError(error) {
1310
+ if (!error) return {};
1311
+ if (typeof error === "string") {
1312
+ return { error_code: error.slice(0, 120), error_domain: "string" };
1313
+ }
1314
+ if (error instanceof Error) {
1315
+ return {
1316
+ error_code: error.message.slice(0, 120),
1317
+ error_domain: error.name || "Error"
1318
+ };
1319
+ }
1320
+ if (typeof error === "object") {
1321
+ const candidate = error;
1322
+ return {
1323
+ error_code: String(candidate.code ?? candidate.message ?? "unknown_error").slice(0, 120),
1324
+ error_domain: candidate.name ?? "object"
1325
+ };
1326
+ }
1327
+ return { error_code: "unknown_error", error_domain: typeof error };
1328
+ }
1329
+ async function trackMutationEvent(name, payload) {
1330
+ await trackStudioEvent(name, payload);
1331
+ await flushStudioAnalytics();
1332
+ }
1333
+ var lastOpenCommentsKey = null;
1334
+ var lastOpenCommentsAt = 0;
1335
+ async function trackRemixApp(params) {
1336
+ const errorProps = params.success ? {} : normalizeError(params.error);
1337
+ await trackMutationEvent("remix_app", {
1338
+ app_id: params.appId,
1339
+ source_app_id: params.sourceAppId,
1340
+ thread_id: params.threadId,
1341
+ success: params.success,
1342
+ ...errorProps,
1343
+ ...baseProps()
1344
+ });
1345
+ }
1346
+ async function trackEditApp(params) {
1347
+ const errorProps = params.success ? {} : normalizeError(params.error);
1348
+ await trackMutationEvent("edit_app", {
1349
+ app_id: params.appId,
1350
+ thread_id: params.threadId,
1351
+ prompt_length: params.promptLength,
1352
+ success: params.success,
1353
+ ...errorProps,
1354
+ ...baseProps()
1355
+ });
1356
+ }
1357
+ async function trackShareApp(params) {
1358
+ const errorProps = params.success ? {} : normalizeError(params.error);
1359
+ await trackMutationEvent("share_app", {
1360
+ app_id: params.appId,
1361
+ success: params.success,
1362
+ ...errorProps,
1363
+ ...baseProps()
1364
+ });
1365
+ }
1366
+ async function trackOpenMergeRequest(params) {
1367
+ const errorProps = params.success ? {} : normalizeError(params.error);
1368
+ await trackMutationEvent("open_merge_request", {
1369
+ app_id: params.appId,
1370
+ merge_request_id: params.mergeRequestId,
1371
+ success: params.success,
1372
+ ...errorProps,
1373
+ ...baseProps()
1374
+ });
1375
+ }
1376
+ async function trackApproveMergeRequest(params) {
1377
+ const errorProps = params.success ? {} : normalizeError(params.error);
1378
+ await trackMutationEvent("approve_merge_request", {
1379
+ app_id: params.appId,
1380
+ merge_request_id: params.mergeRequestId,
1381
+ success: params.success,
1382
+ ...errorProps,
1383
+ ...baseProps()
1384
+ });
1385
+ }
1386
+ async function trackRejectMergeRequest(params) {
1387
+ const errorProps = params.success ? {} : normalizeError(params.error);
1388
+ await trackMutationEvent("reject_merge_request", {
1389
+ app_id: params.appId,
1390
+ merge_request_id: params.mergeRequestId,
1391
+ success: params.success,
1392
+ ...errorProps,
1393
+ ...baseProps()
1394
+ });
1395
+ }
1396
+ async function trackTestBundle(params) {
1397
+ const errorProps = params.success ? {} : normalizeError(params.error);
1398
+ await trackMutationEvent("test_bundle", {
1399
+ app_id: params.appId,
1400
+ commit_id: params.commitId,
1401
+ success: params.success,
1402
+ ...errorProps,
1403
+ ...baseProps()
1404
+ });
1405
+ }
1406
+ async function trackLikeApp(params) {
1407
+ const errorProps = params.success ? {} : normalizeError(params.error);
1408
+ await trackMutationEvent("like_app", {
1409
+ app_id: params.appId,
1410
+ source: params.source ?? "unknown",
1411
+ success: params.success,
1412
+ ...errorProps,
1413
+ ...baseProps()
1414
+ });
1415
+ }
1416
+ async function trackUnlikeApp(params) {
1417
+ const errorProps = params.success ? {} : normalizeError(params.error);
1418
+ await trackMutationEvent("unlike_app", {
1419
+ app_id: params.appId,
1420
+ source: params.source ?? "unknown",
1421
+ success: params.success,
1422
+ ...errorProps,
1423
+ ...baseProps()
1424
+ });
1425
+ }
1426
+ async function trackOpenComments(params) {
1427
+ const key = `${params.appId}:${params.source ?? "unknown"}`;
1428
+ const now = Date.now();
1429
+ if (lastOpenCommentsKey === key && now - lastOpenCommentsAt < 1e3) return;
1430
+ lastOpenCommentsKey = key;
1431
+ lastOpenCommentsAt = now;
1432
+ await trackStudioEvent("open_comments", {
1433
+ app_id: params.appId,
1434
+ source: params.source ?? "unknown",
1435
+ ...baseProps()
1436
+ });
1437
+ }
1438
+ async function trackSubmitComment(params) {
1439
+ const errorProps = params.success ? {} : normalizeError(params.error);
1440
+ await trackMutationEvent("submit_comment", {
1441
+ app_id: params.appId,
1442
+ comment_type: "general",
1443
+ comment_length: params.commentLength,
1444
+ success: params.success,
1445
+ ...errorProps,
1446
+ ...baseProps()
1447
+ });
1448
+ }
1449
+
1211
1450
  // src/studio/hooks/useBundleManager.ts
1212
1451
  function sleep2(ms) {
1213
1452
  return new Promise((r) => setTimeout(r, ms));
@@ -1524,7 +1763,7 @@ async function pollBundle(appId, bundleId, opts) {
1524
1763
  await sleep2(opts.intervalMs);
1525
1764
  }
1526
1765
  }
1527
- async function resolveBundlePath(src, platform, mode) {
1766
+ async function resolveBundlePath(src, platform, mode, onStatus) {
1528
1767
  const { appId, commitId } = src;
1529
1768
  const dir = bundlesCacheDir();
1530
1769
  await ensureDir(dir);
@@ -1538,7 +1777,9 @@ async function resolveBundlePath(src, platform, mode) {
1538
1777
  },
1539
1778
  { attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
1540
1779
  );
1780
+ onStatus == null ? void 0 : onStatus(initiate.status);
1541
1781
  const finalBundle = initiate.status === "succeeded" || initiate.status === "failed" ? initiate : await pollBundle(appId, initiate.id, { timeoutMs: 3 * 60 * 1e3, intervalMs: 1200 });
1782
+ onStatus == null ? void 0 : onStatus(finalBundle.status);
1542
1783
  if (finalBundle.status === "failed") {
1543
1784
  throw new Error("Bundle build failed.");
1544
1785
  }
@@ -1580,6 +1821,7 @@ function useBundleManager({
1580
1821
  const [renderToken, setRenderToken] = React5.useState(0);
1581
1822
  const [loading, setLoading] = React5.useState(false);
1582
1823
  const [loadingMode, setLoadingMode] = React5.useState(null);
1824
+ const [bundleStatus, setBundleStatus] = React5.useState(null);
1583
1825
  const [statusLabel, setStatusLabel] = React5.useState(null);
1584
1826
  const [error, setError] = React5.useState(null);
1585
1827
  const [isTesting, setIsTesting] = React5.useState(false);
@@ -1691,16 +1933,20 @@ function useBundleManager({
1691
1933
  activeLoadModeRef.current = mode;
1692
1934
  setLoading(true);
1693
1935
  setLoadingMode(mode);
1936
+ setBundleStatus(null);
1694
1937
  setError(null);
1695
1938
  setStatusLabel(mode === "test" ? "Loading test bundle\u2026" : "Loading latest build\u2026");
1696
1939
  if (mode === "base" && desiredModeRef.current === "base") {
1697
1940
  void activateCachedBase(src.appId);
1698
1941
  }
1699
1942
  try {
1700
- const { bundlePath: path, bundle } = await resolveBundlePath(src, platform, mode);
1943
+ const { bundlePath: path, bundle } = await resolveBundlePath(src, platform, mode, (status) => {
1944
+ setBundleStatus(status);
1945
+ });
1701
1946
  if (mode === "base" && opId !== baseOpIdRef.current) return;
1702
1947
  if (mode === "test" && opId !== testOpIdRef.current) return;
1703
1948
  if (desiredModeRef.current !== mode) return;
1949
+ setBundleStatus(bundle.status);
1704
1950
  setBundlePath(path);
1705
1951
  const fingerprint = bundle.checksumSha256 ?? `id:${bundle.id}`;
1706
1952
  const shouldSkipInitialBaseRemount = mode === "base" && initialHydratedBaseFromDiskRef.current && !hasCompletedFirstNetworkBaseLoadRef.current && Boolean(lastBaseFingerprintRef.current) && lastBaseFingerprintRef.current === fingerprint;
@@ -1736,6 +1982,7 @@ function useBundleManager({
1736
1982
  } catch (e) {
1737
1983
  if (mode === "base" && opId !== baseOpIdRef.current) return;
1738
1984
  if (mode === "test" && opId !== testOpIdRef.current) return;
1985
+ setBundleStatus("failed");
1739
1986
  const msg = e instanceof Error ? e.message : String(e);
1740
1987
  setError(msg);
1741
1988
  setStatusLabel(null);
@@ -1751,7 +1998,22 @@ function useBundleManager({
1751
1998
  await load(baseRef.current, "base");
1752
1999
  }, [load]);
1753
2000
  const loadTest = React5.useCallback(async (src) => {
1754
- await load(src, "test");
2001
+ try {
2002
+ await load(src, "test");
2003
+ await trackTestBundle({
2004
+ appId: src.appId,
2005
+ commitId: src.commitId ?? void 0,
2006
+ success: true
2007
+ });
2008
+ } catch (error2) {
2009
+ await trackTestBundle({
2010
+ appId: src.appId,
2011
+ commitId: src.commitId ?? void 0,
2012
+ success: false,
2013
+ error: error2
2014
+ });
2015
+ throw error2;
2016
+ }
1755
2017
  }, [load]);
1756
2018
  const restoreBase = React5.useCallback(async () => {
1757
2019
  const src = baseRef.current;
@@ -1767,7 +2029,19 @@ function useBundleManager({
1767
2029
  if (!canRequestLatest) return;
1768
2030
  void loadBase();
1769
2031
  }, [base.appId, base.commitId, platform, canRequestLatest, loadBase]);
1770
- return { bundlePath, renderToken, loading, loadingMode, statusLabel, error, isTesting, loadBase, loadTest, restoreBase };
2032
+ return {
2033
+ bundlePath,
2034
+ renderToken,
2035
+ loading,
2036
+ loadingMode,
2037
+ bundleStatus,
2038
+ statusLabel,
2039
+ error,
2040
+ isTesting,
2041
+ loadBase,
2042
+ loadTest,
2043
+ restoreBase
2044
+ };
1771
2045
  }
1772
2046
 
1773
2047
  // src/studio/hooks/useMergeRequests.ts
@@ -1977,24 +2251,68 @@ function useMergeRequests(params) {
1977
2251
  }, [appId]);
1978
2252
  React6.useEffect(() => {
1979
2253
  void refresh();
1980
- }, [refresh]);
2254
+ }, [appId, refresh]);
1981
2255
  const openMergeRequest = React6.useCallback(async (sourceAppId) => {
1982
- const mr = await mergeRequestsRepository.open({ sourceAppId });
1983
- await refresh();
1984
- return mr;
2256
+ try {
2257
+ const mr = await mergeRequestsRepository.open({ sourceAppId });
2258
+ await refresh();
2259
+ await trackOpenMergeRequest({
2260
+ appId,
2261
+ mergeRequestId: mr.id,
2262
+ success: true
2263
+ });
2264
+ return mr;
2265
+ } catch (error2) {
2266
+ await trackOpenMergeRequest({
2267
+ appId,
2268
+ success: false,
2269
+ error: error2
2270
+ });
2271
+ throw error2;
2272
+ }
1985
2273
  }, [refresh]);
1986
2274
  const approve = React6.useCallback(async (mrId) => {
1987
- const mr = await mergeRequestsRepository.update(mrId, { status: "approved" });
1988
- await refresh();
1989
- const merged = await pollUntilMerged(mrId);
1990
- await refresh();
1991
- return merged ?? mr;
1992
- }, [pollUntilMerged, refresh]);
2275
+ try {
2276
+ const mr = await mergeRequestsRepository.update(mrId, { status: "approved" });
2277
+ await refresh();
2278
+ const merged = await pollUntilMerged(mrId);
2279
+ await refresh();
2280
+ await trackApproveMergeRequest({
2281
+ appId,
2282
+ mergeRequestId: mrId,
2283
+ success: true
2284
+ });
2285
+ return merged ?? mr;
2286
+ } catch (error2) {
2287
+ await trackApproveMergeRequest({
2288
+ appId,
2289
+ mergeRequestId: mrId,
2290
+ success: false,
2291
+ error: error2
2292
+ });
2293
+ throw error2;
2294
+ }
2295
+ }, [appId, pollUntilMerged, refresh]);
1993
2296
  const reject = React6.useCallback(async (mrId) => {
1994
- const mr = await mergeRequestsRepository.update(mrId, { status: "rejected" });
1995
- await refresh();
1996
- return mr;
1997
- }, [refresh]);
2297
+ try {
2298
+ const mr = await mergeRequestsRepository.update(mrId, { status: "rejected" });
2299
+ await refresh();
2300
+ await trackRejectMergeRequest({
2301
+ appId,
2302
+ mergeRequestId: mrId,
2303
+ success: true
2304
+ });
2305
+ return mr;
2306
+ } catch (error2) {
2307
+ await trackRejectMergeRequest({
2308
+ appId,
2309
+ mergeRequestId: mrId,
2310
+ success: false,
2311
+ error: error2
2312
+ });
2313
+ throw error2;
2314
+ }
2315
+ }, [appId, refresh]);
1998
2316
  const toSummary = React6.useCallback((mr) => {
1999
2317
  const stats = creatorStatsById[mr.createdBy];
2000
2318
  return {
@@ -2030,7 +2348,7 @@ function useMergeRequests(params) {
2030
2348
 
2031
2349
  // src/studio/hooks/useAttachmentUpload.ts
2032
2350
  var React7 = __toESM(require("react"));
2033
- var import_react_native5 = require("react-native");
2351
+ var import_react_native6 = require("react-native");
2034
2352
  var FileSystem2 = __toESM(require("expo-file-system/legacy"));
2035
2353
 
2036
2354
  // src/data/attachment/remote.ts
@@ -2117,7 +2435,7 @@ function useAttachmentUpload() {
2117
2435
  const blobs = await Promise.all(
2118
2436
  dataUrls.map(async (dataUrl, idx) => {
2119
2437
  const normalized = dataUrl.startsWith("data:") ? dataUrl : `data:image/png;base64,${dataUrl}`;
2120
- const blob = import_react_native5.Platform.OS === "android" ? await dataUrlToBlobAndroid(normalized) : await (await fetch(normalized)).blob();
2438
+ const blob = import_react_native6.Platform.OS === "android" ? await dataUrlToBlobAndroid(normalized) : await (await fetch(normalized)).blob();
2121
2439
  const mimeType = getMimeTypeFromDataUrl(normalized);
2122
2440
  return { blob, idx, mimeType };
2123
2441
  })
@@ -2235,13 +2553,21 @@ function useStudioActions({
2235
2553
  if (sending) return;
2236
2554
  setSending(true);
2237
2555
  setError(null);
2556
+ let forkSucceeded = false;
2238
2557
  try {
2239
2558
  let targetApp = app;
2559
+ const sourceAppId = app.id;
2240
2560
  if (shouldForkOnEdit) {
2241
2561
  setForking(true);
2242
- const sourceAppId = app.id;
2243
2562
  const forked = await appsRepository.fork(app.id, {});
2244
2563
  targetApp = forked;
2564
+ await trackRemixApp({
2565
+ appId: forked.id,
2566
+ sourceAppId,
2567
+ threadId: forked.threadId ?? void 0,
2568
+ success: true
2569
+ });
2570
+ forkSucceeded = true;
2245
2571
  onForkedApp == null ? void 0 : onForkedApp(forked.id, { keepRenderingAppId: sourceAppId });
2246
2572
  }
2247
2573
  setForking(false);
@@ -2269,9 +2595,33 @@ function useStudioActions({
2269
2595
  queueItemId: editResult.queueItemId ?? null,
2270
2596
  queuePosition: editResult.queuePosition ?? null
2271
2597
  });
2598
+ await trackEditApp({
2599
+ appId: targetApp.id,
2600
+ threadId,
2601
+ promptLength: prompt.trim().length,
2602
+ success: true
2603
+ });
2272
2604
  } catch (e) {
2273
2605
  const err = e instanceof Error ? e : new Error(String(e));
2274
2606
  setError(err);
2607
+ if (shouldForkOnEdit && !forkSucceeded && (app == null ? void 0 : app.id)) {
2608
+ await trackRemixApp({
2609
+ appId: app.id,
2610
+ sourceAppId: app.id,
2611
+ threadId: app.threadId ?? void 0,
2612
+ success: false,
2613
+ error: err
2614
+ });
2615
+ }
2616
+ if ((app == null ? void 0 : app.id) && app.threadId) {
2617
+ await trackEditApp({
2618
+ appId: app.id,
2619
+ threadId: app.threadId,
2620
+ promptLength: prompt.trim().length,
2621
+ success: false,
2622
+ error: err
2623
+ });
2624
+ }
2275
2625
  throw err;
2276
2626
  } finally {
2277
2627
  setForking(false);
@@ -2286,12 +2636,13 @@ function useStudioActions({
2286
2636
 
2287
2637
  // src/studio/ui/RuntimeRenderer.tsx
2288
2638
  var React9 = __toESM(require("react"));
2289
- var import_react_native6 = require("react-native");
2639
+ var import_react_native7 = require("react-native");
2290
2640
  var import_runtime = require("@comergehq/runtime");
2291
2641
  var import_jsx_runtime3 = require("react/jsx-runtime");
2292
2642
  function RuntimeRenderer({
2293
2643
  appKey,
2294
2644
  bundlePath,
2645
+ preparingText,
2295
2646
  forcePreparing,
2296
2647
  renderToken,
2297
2648
  style,
@@ -2305,11 +2656,11 @@ function RuntimeRenderer({
2305
2656
  }, [bundlePath]);
2306
2657
  if (!bundlePath || forcePreparing) {
2307
2658
  if (!hasRenderedOnce && !forcePreparing && !allowInitialPreparing) {
2308
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native6.View, { style: [{ flex: 1 }, style] });
2659
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native7.View, { style: [{ flex: 1 }, style] });
2309
2660
  }
2310
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native6.View, { style: [{ flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { variant: "bodyMuted", children: "Preparing app\u2026" }) });
2661
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native7.View, { style: [{ flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { variant: "bodyMuted", children: preparingText ?? "Preparing app\u2026" }) });
2311
2662
  }
2312
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native6.View, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2663
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native7.View, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2313
2664
  import_runtime.ComergeRuntimeRenderer,
2314
2665
  {
2315
2666
  appKey,
@@ -2321,17 +2672,17 @@ function RuntimeRenderer({
2321
2672
  }
2322
2673
 
2323
2674
  // src/studio/ui/StudioOverlay.tsx
2324
- var React44 = __toESM(require("react"));
2325
- var import_react_native56 = require("react-native");
2675
+ var React45 = __toESM(require("react"));
2676
+ var import_react_native59 = require("react-native");
2326
2677
 
2327
2678
  // src/components/studio-sheet/StudioBottomSheet.tsx
2328
2679
  var React12 = __toESM(require("react"));
2329
- var import_react_native9 = require("react-native");
2680
+ var import_react_native10 = require("react-native");
2330
2681
  var import_bottom_sheet = require("@gorhom/bottom-sheet");
2331
2682
  var import_react_native_safe_area_context = require("react-native-safe-area-context");
2332
2683
 
2333
2684
  // src/components/studio-sheet/StudioSheetBackground.tsx
2334
- var import_react_native8 = require("react-native");
2685
+ var import_react_native9 = require("react-native");
2335
2686
  var import_liquid_glass2 = require("@callstack/liquid-glass");
2336
2687
 
2337
2688
  // src/components/utils/ResettableLiquidGlassView.tsx
@@ -2340,7 +2691,7 @@ var import_liquid_glass = require("@callstack/liquid-glass");
2340
2691
 
2341
2692
  // src/components/utils/liquidGlassReset.tsx
2342
2693
  var React10 = __toESM(require("react"));
2343
- var import_react_native7 = require("react-native");
2694
+ var import_react_native8 = require("react-native");
2344
2695
  var import_jsx_runtime4 = require("react/jsx-runtime");
2345
2696
  var LiquidGlassResetContext = React10.createContext(0);
2346
2697
  function LiquidGlassResetProvider({
@@ -2349,11 +2700,11 @@ function LiquidGlassResetProvider({
2349
2700
  }) {
2350
2701
  const [token, setToken] = React10.useState(0);
2351
2702
  React10.useEffect(() => {
2352
- if (import_react_native7.Platform.OS !== "ios") return;
2703
+ if (import_react_native8.Platform.OS !== "ios") return;
2353
2704
  const onChange = (state) => {
2354
2705
  if (state === "active") setToken((t) => t + 1);
2355
2706
  };
2356
- const sub = import_react_native7.AppState.addEventListener("change", onChange);
2707
+ const sub = import_react_native8.AppState.addEventListener("change", onChange);
2357
2708
  return () => sub.remove();
2358
2709
  }, []);
2359
2710
  React10.useEffect(() => {
@@ -2390,7 +2741,7 @@ function StudioSheetBackground({
2390
2741
  renderBackground
2391
2742
  }) {
2392
2743
  const theme = useTheme();
2393
- const radius = import_react_native8.Platform.OS === "ios" ? 39 : 16;
2744
+ const radius = import_react_native9.Platform.OS === "ios" ? 39 : 16;
2394
2745
  const fallbackBgColor = theme.scheme === "dark" ? "rgba(11, 8, 15, 0.85)" : "rgba(255, 255, 255, 0.85)";
2395
2746
  const secondaryBgBaseColor = theme.scheme === "dark" ? "rgb(24, 24, 27)" : "rgb(173, 173, 173)";
2396
2747
  const containerStyle = {
@@ -2411,7 +2762,7 @@ function StudioSheetBackground({
2411
2762
  }
2412
2763
  ),
2413
2764
  import_liquid_glass2.isLiquidGlassSupported && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2414
- import_react_native8.View,
2765
+ import_react_native9.View,
2415
2766
  {
2416
2767
  style: [
2417
2768
  containerStyle,
@@ -2448,12 +2799,12 @@ function StudioBottomSheet({
2448
2799
  const resolvedSheetRef = sheetRef ?? internalSheetRef;
2449
2800
  const resolvedSnapPoints = React12.useMemo(() => [...snapPoints], [snapPoints]);
2450
2801
  const currentIndexRef = React12.useRef(open ? resolvedSnapPoints.length - 1 : -1);
2451
- const lastAppStateRef = React12.useRef(import_react_native9.AppState.currentState);
2802
+ const lastAppStateRef = React12.useRef(import_react_native10.AppState.currentState);
2452
2803
  React12.useEffect(() => {
2453
- const sub = import_react_native9.AppState.addEventListener("change", (state) => {
2804
+ const sub = import_react_native10.AppState.addEventListener("change", (state) => {
2454
2805
  lastAppStateRef.current = state;
2455
2806
  if (state === "background" || state === "inactive") {
2456
- import_react_native9.Keyboard.dismiss();
2807
+ import_react_native10.Keyboard.dismiss();
2457
2808
  return;
2458
2809
  }
2459
2810
  });
@@ -2494,19 +2845,19 @@ function StudioBottomSheet({
2494
2845
  ...bottomSheetProps,
2495
2846
  onChange: handleChange,
2496
2847
  onDismiss: () => onOpenChange == null ? void 0 : onOpenChange(false),
2497
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native9.View, { style: { flex: 1, overflow: "hidden" }, children })
2848
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native10.View, { style: { flex: 1, overflow: "hidden" }, children })
2498
2849
  }
2499
2850
  );
2500
2851
  }
2501
2852
 
2502
2853
  // src/components/studio-sheet/StudioSheetPager.tsx
2503
2854
  var React13 = __toESM(require("react"));
2504
- var import_react_native10 = require("react-native");
2855
+ var import_react_native11 = require("react-native");
2505
2856
  var import_jsx_runtime8 = require("react/jsx-runtime");
2506
2857
  function StudioSheetPager({ activePage, width, preview, chat, style }) {
2507
- const anim = React13.useRef(new import_react_native10.Animated.Value(activePage === "chat" ? 1 : 0)).current;
2858
+ const anim = React13.useRef(new import_react_native11.Animated.Value(activePage === "chat" ? 1 : 0)).current;
2508
2859
  React13.useEffect(() => {
2509
- import_react_native10.Animated.spring(anim, {
2860
+ import_react_native11.Animated.spring(anim, {
2510
2861
  toValue: activePage === "chat" ? 1 : 0,
2511
2862
  useNativeDriver: true,
2512
2863
  tension: 65,
@@ -2515,9 +2866,9 @@ function StudioSheetPager({ activePage, width, preview, chat, style }) {
2515
2866
  }, [activePage, anim]);
2516
2867
  const previewTranslateX = anim.interpolate({ inputRange: [0, 1], outputRange: [0, -width] });
2517
2868
  const chatTranslateX = anim.interpolate({ inputRange: [0, 1], outputRange: [width, 0] });
2518
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native10.Animated.View, { style: [{ flex: 1 }, style], children: [
2869
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native11.Animated.View, { style: [{ flex: 1 }, style], children: [
2519
2870
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2520
- import_react_native10.Animated.View,
2871
+ import_react_native11.Animated.View,
2521
2872
  {
2522
2873
  style: [
2523
2874
  {
@@ -2534,7 +2885,7 @@ function StudioSheetPager({ activePage, width, preview, chat, style }) {
2534
2885
  }
2535
2886
  ),
2536
2887
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2537
- import_react_native10.Animated.View,
2888
+ import_react_native11.Animated.View,
2538
2889
  {
2539
2890
  style: [
2540
2891
  {
@@ -2555,7 +2906,7 @@ function StudioSheetPager({ activePage, width, preview, chat, style }) {
2555
2906
 
2556
2907
  // src/components/bubble/Bubble.tsx
2557
2908
  var import_react = require("react");
2558
- var import_react_native11 = require("react-native");
2909
+ var import_react_native12 = require("react-native");
2559
2910
  var Haptics = __toESM(require("expo-haptics"));
2560
2911
  var import_react_native_reanimated = __toESM(require("react-native-reanimated"));
2561
2912
  var import_liquid_glass3 = require("@callstack/liquid-glass");
@@ -2627,7 +2978,7 @@ function Bubble({
2627
2978
  backgroundColor
2628
2979
  }) {
2629
2980
  const theme = useTheme();
2630
- const { width, height } = (0, import_react_native11.useWindowDimensions)();
2981
+ const { width, height } = (0, import_react_native12.useWindowDimensions)();
2631
2982
  const isDanger = variant === "danger";
2632
2983
  const onPressRef = (0, import_react.useRef)(onPress);
2633
2984
  (0, import_react.useEffect)(() => {
@@ -2720,7 +3071,7 @@ function Bubble({
2720
3071
  }
2721
3072
  }, [forceShowTrigger, visible, animateIn]);
2722
3073
  const panResponder = (0, import_react.useRef)(
2723
- import_react_native11.PanResponder.create({
3074
+ import_react_native12.PanResponder.create({
2724
3075
  onStartShouldSetPanResponder: () => true,
2725
3076
  onMoveShouldSetPanResponder: () => true,
2726
3077
  onPanResponderGrant: () => {
@@ -2783,24 +3134,24 @@ function Bubble({
2783
3134
  interactive: true,
2784
3135
  effect: "clear",
2785
3136
  children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2786
- import_react_native11.Pressable,
3137
+ import_react_native12.Pressable,
2787
3138
  {
2788
3139
  onPress: () => {
2789
3140
  if (!disabled) animateOut();
2790
3141
  },
2791
3142
  style: styles.buttonInner,
2792
3143
  android_ripple: { color: "rgba(255, 255, 255, 0.3)", borderless: true },
2793
- children: children ?? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native11.View, {})
3144
+ children: children ?? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native12.View, {})
2794
3145
  }
2795
3146
  )
2796
3147
  }
2797
3148
  ) }),
2798
- badgeCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native11.View, { style: [styles.badge, { backgroundColor: theme.colors.danger }], children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native11.Text, { style: [styles.badgeText, { color: theme.colors.onDanger }], children: badgeCount > 99 ? "99+" : badgeCount }) })
3149
+ badgeCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native12.View, { style: [styles.badge, { backgroundColor: theme.colors.danger }], children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native12.Text, { style: [styles.badgeText, { color: theme.colors.onDanger }], children: badgeCount > 99 ? "99+" : badgeCount }) })
2799
3150
  ]
2800
3151
  }
2801
3152
  );
2802
3153
  }
2803
- var styles = import_react_native11.StyleSheet.create({
3154
+ var styles = import_react_native12.StyleSheet.create({
2804
3155
  floatingButton: {
2805
3156
  position: "absolute",
2806
3157
  justifyContent: "center",
@@ -2837,7 +3188,7 @@ var styles = import_react_native11.StyleSheet.create({
2837
3188
 
2838
3189
  // src/components/overlays/EdgeGlowFrame.tsx
2839
3190
  var React14 = __toESM(require("react"));
2840
- var import_react_native12 = require("react-native");
3191
+ var import_react_native13 = require("react-native");
2841
3192
  var import_expo_linear_gradient = require("expo-linear-gradient");
2842
3193
  var import_jsx_runtime10 = require("react/jsx-runtime");
2843
3194
  function baseColor(role, theme) {
@@ -2862,9 +3213,9 @@ function EdgeGlowFrame({
2862
3213
  }) {
2863
3214
  const theme = useTheme();
2864
3215
  const alpha = Math.max(0, Math.min(1, intensity));
2865
- const anim = React14.useRef(new import_react_native12.Animated.Value(visible ? 1 : 0)).current;
3216
+ const anim = React14.useRef(new import_react_native13.Animated.Value(visible ? 1 : 0)).current;
2866
3217
  React14.useEffect(() => {
2867
- import_react_native12.Animated.timing(anim, {
3218
+ import_react_native13.Animated.timing(anim, {
2868
3219
  toValue: visible ? 1 : 0,
2869
3220
  duration: 300,
2870
3221
  useNativeDriver: true
@@ -2873,8 +3224,8 @@ function EdgeGlowFrame({
2873
3224
  const c = baseColor(role, theme);
2874
3225
  const strong = withAlpha(c, 0.6 * alpha);
2875
3226
  const soft = withAlpha(c, 0.22 * alpha);
2876
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native12.Animated.View, { pointerEvents: "none", style: [{ position: "absolute", inset: 0, opacity: anim }, style], children: [
2877
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native12.View, { style: { position: "absolute", top: 0, left: 0, right: 0, height: thickness }, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3227
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native13.Animated.View, { pointerEvents: "none", style: [{ position: "absolute", inset: 0, opacity: anim }, style], children: [
3228
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native13.View, { style: { position: "absolute", top: 0, left: 0, right: 0, height: thickness }, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2878
3229
  import_expo_linear_gradient.LinearGradient,
2879
3230
  {
2880
3231
  colors: [strong, soft, "transparent"],
@@ -2883,7 +3234,7 @@ function EdgeGlowFrame({
2883
3234
  style: { width: "100%", height: "100%" }
2884
3235
  }
2885
3236
  ) }),
2886
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native12.View, { style: { position: "absolute", bottom: 0, left: 0, right: 0, height: thickness }, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3237
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native13.View, { style: { position: "absolute", bottom: 0, left: 0, right: 0, height: thickness }, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2887
3238
  import_expo_linear_gradient.LinearGradient,
2888
3239
  {
2889
3240
  colors: ["transparent", soft, strong],
@@ -2892,7 +3243,7 @@ function EdgeGlowFrame({
2892
3243
  style: { width: "100%", height: "100%" }
2893
3244
  }
2894
3245
  ) }),
2895
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native12.View, { style: { position: "absolute", top: 0, bottom: 0, left: 0, width: thickness }, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3246
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native13.View, { style: { position: "absolute", top: 0, bottom: 0, left: 0, width: thickness }, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2896
3247
  import_expo_linear_gradient.LinearGradient,
2897
3248
  {
2898
3249
  colors: [strong, soft, "transparent"],
@@ -2901,7 +3252,7 @@ function EdgeGlowFrame({
2901
3252
  style: { width: "100%", height: "100%" }
2902
3253
  }
2903
3254
  ) }),
2904
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native12.View, { style: { position: "absolute", top: 0, bottom: 0, right: 0, width: thickness }, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3255
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native13.View, { style: { position: "absolute", top: 0, bottom: 0, right: 0, width: thickness }, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2905
3256
  import_expo_linear_gradient.LinearGradient,
2906
3257
  {
2907
3258
  colors: ["transparent", soft, strong],
@@ -2915,12 +3266,12 @@ function EdgeGlowFrame({
2915
3266
 
2916
3267
  // src/components/draw/DrawModeOverlay.tsx
2917
3268
  var React17 = __toESM(require("react"));
2918
- var import_react_native16 = require("react-native");
3269
+ var import_react_native17 = require("react-native");
2919
3270
  var import_react_native_view_shot = require("react-native-view-shot");
2920
3271
 
2921
3272
  // src/components/draw/DrawSurface.tsx
2922
3273
  var React15 = __toESM(require("react"));
2923
- var import_react_native13 = require("react-native");
3274
+ var import_react_native14 = require("react-native");
2924
3275
  var import_react_native_svg = __toESM(require("react-native-svg"));
2925
3276
 
2926
3277
  // src/components/draw/strokes.ts
@@ -2991,7 +3342,7 @@ function DrawSurface({
2991
3342
  triggerRender();
2992
3343
  }, [color, onAddStroke, strokeWidth, triggerRender]);
2993
3344
  const panResponder = React15.useMemo(
2994
- () => import_react_native13.PanResponder.create({
3345
+ () => import_react_native14.PanResponder.create({
2995
3346
  onStartShouldSetPanResponder: () => true,
2996
3347
  onMoveShouldSetPanResponder: () => true,
2997
3348
  onPanResponderGrant: onStart,
@@ -3003,7 +3354,7 @@ function DrawSurface({
3003
3354
  );
3004
3355
  const currentPath = pointsToSmoothPath(currentPointsRef.current);
3005
3356
  void renderTick;
3006
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native13.View, { style: [import_react_native13.StyleSheet.absoluteFill, styles2.container, style], ...panResponder.panHandlers, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react_native_svg.default, { style: import_react_native13.StyleSheet.absoluteFill, width: "100%", height: "100%", children: [
3357
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native14.View, { style: [import_react_native14.StyleSheet.absoluteFill, styles2.container, style], ...panResponder.panHandlers, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react_native_svg.default, { style: import_react_native14.StyleSheet.absoluteFill, width: "100%", height: "100%", children: [
3007
3358
  strokes.map((s, idx) => {
3008
3359
  const d = pointsToSmoothPath(s.points);
3009
3360
  if (!d) return null;
@@ -3033,7 +3384,7 @@ function DrawSurface({
3033
3384
  ) : null
3034
3385
  ] }) });
3035
3386
  }
3036
- var styles2 = import_react_native13.StyleSheet.create({
3387
+ var styles2 = import_react_native14.StyleSheet.create({
3037
3388
  container: {
3038
3389
  zIndex: 5
3039
3390
  }
@@ -3041,7 +3392,7 @@ var styles2 = import_react_native13.StyleSheet.create({
3041
3392
 
3042
3393
  // src/components/draw/DrawToolbar.tsx
3043
3394
  var React16 = __toESM(require("react"));
3044
- var import_react_native15 = require("react-native");
3395
+ var import_react_native16 = require("react-native");
3045
3396
  var import_react_native_safe_area_context2 = require("react-native-safe-area-context");
3046
3397
  var import_lucide_react_native = require("lucide-react-native");
3047
3398
 
@@ -3060,7 +3411,7 @@ async function impact(style) {
3060
3411
  }
3061
3412
 
3062
3413
  // src/components/draw/DrawColorPicker.tsx
3063
- var import_react_native14 = require("react-native");
3414
+ var import_react_native15 = require("react-native");
3064
3415
  var import_jsx_runtime12 = require("react/jsx-runtime");
3065
3416
  function DrawColorPicker({
3066
3417
  colors,
@@ -3095,10 +3446,10 @@ function DrawColorPicker({
3095
3446
  return { ...base, ...selectedStyle, ...whiteStyle };
3096
3447
  };
3097
3448
  if (!expanded) {
3098
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native14.Pressable, { onPress: onToggle, style: [swatchStyle(selected, true), style] });
3449
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native15.Pressable, { onPress: onToggle, style: [swatchStyle(selected, true), style] });
3099
3450
  }
3100
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native14.View, { style: [{ flexDirection: "row", alignItems: "center", gap: 8 }, style], children: colors.map((c, idx) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3101
- import_react_native14.Pressable,
3451
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native15.View, { style: [{ flexDirection: "row", alignItems: "center", gap: 8 }, style], children: colors.map((c, idx) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3452
+ import_react_native15.Pressable,
3102
3453
  {
3103
3454
  onPress: () => {
3104
3455
  onSelect(c);
@@ -3129,14 +3480,14 @@ function DrawToolbar({
3129
3480
  style
3130
3481
  }) {
3131
3482
  const insets = (0, import_react_native_safe_area_context2.useSafeAreaInsets)();
3132
- const { width: screenWidth, height: screenHeight } = (0, import_react_native15.useWindowDimensions)();
3483
+ const { width: screenWidth, height: screenHeight } = (0, import_react_native16.useWindowDimensions)();
3133
3484
  const [expanded, setExpanded] = React16.useState(false);
3134
- const pos = React16.useRef(new import_react_native15.Animated.ValueXY({ x: screenWidth / 2 - 110, y: -140 })).current;
3485
+ const pos = React16.useRef(new import_react_native16.Animated.ValueXY({ x: screenWidth / 2 - 110, y: -140 })).current;
3135
3486
  const start = React16.useRef({ x: 0, y: 0 });
3136
3487
  const currentPos = React16.useRef({ x: 0, y: 0 });
3137
3488
  React16.useEffect(() => {
3138
3489
  if (hidden) return;
3139
- import_react_native15.Animated.spring(pos.y, {
3490
+ import_react_native16.Animated.spring(pos.y, {
3140
3491
  toValue: insets.top + 60,
3141
3492
  useNativeDriver: true,
3142
3493
  damping: 12,
@@ -3163,7 +3514,7 @@ function DrawToolbar({
3163
3514
  [insets.top, screenHeight, screenWidth]
3164
3515
  );
3165
3516
  const panResponder = React16.useMemo(
3166
- () => import_react_native15.PanResponder.create({
3517
+ () => import_react_native16.PanResponder.create({
3167
3518
  onStartShouldSetPanResponder: () => false,
3168
3519
  onMoveShouldSetPanResponder: (_e, g) => Math.abs(g.dx) > 5 || Math.abs(g.dy) > 5,
3169
3520
  onPanResponderGrant: () => {
@@ -3175,7 +3526,7 @@ function DrawToolbar({
3175
3526
  },
3176
3527
  onPanResponderRelease: () => {
3177
3528
  const next = clamp2(currentPos.current.x, currentPos.current.y);
3178
- import_react_native15.Animated.spring(pos, { toValue: next, useNativeDriver: true }).start();
3529
+ import_react_native16.Animated.spring(pos, { toValue: next, useNativeDriver: true }).start();
3179
3530
  }
3180
3531
  }),
3181
3532
  [clamp2, pos]
@@ -3192,7 +3543,7 @@ function DrawToolbar({
3192
3543
  const isDisabled = Boolean(disabled) || Boolean(capturingDisabled);
3193
3544
  const [pressed, setPressed] = React16.useState(false);
3194
3545
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3195
- import_react_native15.View,
3546
+ import_react_native16.View,
3196
3547
  {
3197
3548
  style: {
3198
3549
  width: 28,
@@ -3204,7 +3555,7 @@ function DrawToolbar({
3204
3555
  opacity: isDisabled ? 0.5 : pressed ? 0.85 : 1
3205
3556
  },
3206
3557
  children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3207
- import_react_native15.Pressable,
3558
+ import_react_native16.Pressable,
3208
3559
  {
3209
3560
  accessibilityRole: "button",
3210
3561
  accessibilityLabel,
@@ -3221,7 +3572,7 @@ function DrawToolbar({
3221
3572
  );
3222
3573
  }
3223
3574
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3224
- import_react_native15.Animated.View,
3575
+ import_react_native16.Animated.View,
3225
3576
  {
3226
3577
  style: [
3227
3578
  {
@@ -3238,7 +3589,7 @@ function DrawToolbar({
3238
3589
  ],
3239
3590
  ...panResponder.panHandlers,
3240
3591
  children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3241
- import_react_native15.View,
3592
+ import_react_native16.View,
3242
3593
  {
3243
3594
  style: {
3244
3595
  backgroundColor: "#F43F5E",
@@ -3246,7 +3597,7 @@ function DrawToolbar({
3246
3597
  padding: 12,
3247
3598
  minWidth: 220
3248
3599
  },
3249
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_react_native15.View, { style: { flexDirection: "row", alignItems: "center", gap: 8 }, children: [
3600
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_react_native16.View, { style: { flexDirection: "row", alignItems: "center", gap: 8 }, children: [
3250
3601
  renderDragHandle ? renderDragHandle() : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react_native.GripVertical, { size: 20, color: "rgba(255, 255, 255, 0.6)" }),
3251
3602
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3252
3603
  DrawColorPicker,
@@ -3264,7 +3615,7 @@ function DrawToolbar({
3264
3615
  }
3265
3616
  }
3266
3617
  ),
3267
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native15.View, { style: { width: 1, height: 20, backgroundColor: "rgba(255, 255, 255, 0.3)", marginHorizontal: 4 } }),
3618
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native16.View, { style: { width: 1, height: 20, backgroundColor: "rgba(255, 255, 255, 0.3)", marginHorizontal: 4 } }),
3268
3619
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3269
3620
  CircleActionButton,
3270
3621
  {
@@ -3302,7 +3653,7 @@ function DrawToolbar({
3302
3653
  void impact("medium");
3303
3654
  onDone();
3304
3655
  },
3305
- children: capturing ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native15.ActivityIndicator, { color: "#FFFFFF", size: "small" }) : renderDoneIcon ? renderDoneIcon() : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react_native.Check, { size: 16, color: "#FFFFFF" })
3656
+ children: capturing ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native16.ActivityIndicator, { color: "#FFFFFF", size: "small" }) : renderDoneIcon ? renderDoneIcon() : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react_native.Check, { size: 16, color: "#FFFFFF" })
3306
3657
  }
3307
3658
  )
3308
3659
  ] })
@@ -3387,7 +3738,7 @@ function DrawModeOverlay({
3387
3738
  }
3388
3739
  }, [captureTargetRef, capturing, onCapture]);
3389
3740
  if (!visible) return null;
3390
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native16.View, { style: [import_react_native16.StyleSheet.absoluteFill, styles3.root, style], pointerEvents: "box-none", children: [
3741
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native17.View, { style: [import_react_native17.StyleSheet.absoluteFill, styles3.root, style], pointerEvents: "box-none", children: [
3391
3742
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(EdgeGlowFrame, { visible: !hideUi, role: "danger", thickness: 50, intensity: 1 }),
3392
3743
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3393
3744
  DrawSurface,
@@ -3418,7 +3769,7 @@ function DrawModeOverlay({
3418
3769
  )
3419
3770
  ] });
3420
3771
  }
3421
- var styles3 = import_react_native16.StyleSheet.create({
3772
+ var styles3 = import_react_native17.StyleSheet.create({
3422
3773
  root: {
3423
3774
  zIndex: 9999
3424
3775
  }
@@ -3426,7 +3777,7 @@ var styles3 = import_react_native16.StyleSheet.create({
3426
3777
 
3427
3778
  // src/components/comments/AppCommentsSheet.tsx
3428
3779
  var React24 = __toESM(require("react"));
3429
- var import_react_native22 = require("react-native");
3780
+ var import_react_native23 = require("react-native");
3430
3781
  var import_bottom_sheet3 = require("@gorhom/bottom-sheet");
3431
3782
  var import_react_native_safe_area_context3 = require("react-native-safe-area-context");
3432
3783
  var import_liquid_glass5 = require("@callstack/liquid-glass");
@@ -3434,13 +3785,13 @@ var import_lucide_react_native4 = require("lucide-react-native");
3434
3785
 
3435
3786
  // src/components/chat/ChatComposer.tsx
3436
3787
  var React19 = __toESM(require("react"));
3437
- var import_react_native18 = require("react-native");
3788
+ var import_react_native19 = require("react-native");
3438
3789
  var import_liquid_glass4 = require("@callstack/liquid-glass");
3439
3790
  var import_lucide_react_native3 = require("lucide-react-native");
3440
3791
 
3441
3792
  // src/components/chat/MultilineTextInput.tsx
3442
3793
  var React18 = __toESM(require("react"));
3443
- var import_react_native17 = require("react-native");
3794
+ var import_react_native18 = require("react-native");
3444
3795
  var import_bottom_sheet2 = require("@gorhom/bottom-sheet");
3445
3796
  var import_jsx_runtime15 = require("react/jsx-runtime");
3446
3797
  var MultilineTextInput = React18.forwardRef(function MultilineTextInput2({ useBottomSheetTextInput = false, placeholder, placeholderTextColor, style, ...props }, ref) {
@@ -3463,7 +3814,7 @@ var MultilineTextInput = React18.forwardRef(function MultilineTextInput2({ useBo
3463
3814
  style: [baseStyle, style],
3464
3815
  textAlignVertical: "top"
3465
3816
  };
3466
- return useBottomSheetTextInput ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_bottom_sheet2.BottomSheetTextInput, { ref, ...commonProps }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native17.TextInput, { ref, ...commonProps });
3817
+ return useBottomSheetTextInput ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_bottom_sheet2.BottomSheetTextInput, { ref, ...commonProps }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native18.TextInput, { ref, ...commonProps });
3467
3818
  });
3468
3819
 
3469
3820
  // src/components/icons/StudioIcons.tsx
@@ -3517,9 +3868,9 @@ function AspectRatioThumbnail({
3517
3868
  renderRemoveIcon
3518
3869
  }) {
3519
3870
  const [aspectRatio, setAspectRatio] = React19.useState(1);
3520
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_react_native18.View, { style: { height: THUMBNAIL_HEIGHT, aspectRatio, position: "relative" }, children: [
3521
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native18.View, { style: { flex: 1, borderRadius: 8, overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3522
- import_react_native18.Image,
3871
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_react_native19.View, { style: { height: THUMBNAIL_HEIGHT, aspectRatio, position: "relative" }, children: [
3872
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native19.View, { style: { flex: 1, borderRadius: 8, overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3873
+ import_react_native19.Image,
3523
3874
  {
3524
3875
  source: { uri },
3525
3876
  style: { width: "100%", height: "100%" },
@@ -3532,7 +3883,7 @@ function AspectRatioThumbnail({
3532
3883
  }
3533
3884
  ) }),
3534
3885
  onRemove ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3535
- import_react_native18.Pressable,
3886
+ import_react_native19.Pressable,
3536
3887
  {
3537
3888
  style: {
3538
3889
  position: "absolute",
@@ -3579,17 +3930,17 @@ function ChatComposer({
3579
3930
  const hasText = text.trim().length > 0;
3580
3931
  const composerMinHeight = hasAttachments ? THUMBNAIL_HEIGHT + 44 + 24 : 44;
3581
3932
  const isButtonDisabled = sending || disabled || sendDisabled;
3582
- const maxInputHeight = React19.useMemo(() => import_react_native18.Dimensions.get("window").height * 0.5, []);
3583
- const shakeAnim = React19.useRef(new import_react_native18.Animated.Value(0)).current;
3933
+ const maxInputHeight = React19.useMemo(() => import_react_native19.Dimensions.get("window").height * 0.5, []);
3934
+ const shakeAnim = React19.useRef(new import_react_native19.Animated.Value(0)).current;
3584
3935
  const [sendPressed, setSendPressed] = React19.useState(false);
3585
3936
  const triggerShake = React19.useCallback(() => {
3586
3937
  shakeAnim.setValue(0);
3587
- import_react_native18.Animated.sequence([
3588
- import_react_native18.Animated.timing(shakeAnim, { toValue: 10, duration: 50, useNativeDriver: true }),
3589
- import_react_native18.Animated.timing(shakeAnim, { toValue: -10, duration: 50, useNativeDriver: true }),
3590
- import_react_native18.Animated.timing(shakeAnim, { toValue: 10, duration: 50, useNativeDriver: true }),
3591
- import_react_native18.Animated.timing(shakeAnim, { toValue: -10, duration: 50, useNativeDriver: true }),
3592
- import_react_native18.Animated.timing(shakeAnim, { toValue: 0, duration: 50, useNativeDriver: true })
3938
+ import_react_native19.Animated.sequence([
3939
+ import_react_native19.Animated.timing(shakeAnim, { toValue: 10, duration: 50, useNativeDriver: true }),
3940
+ import_react_native19.Animated.timing(shakeAnim, { toValue: -10, duration: 50, useNativeDriver: true }),
3941
+ import_react_native19.Animated.timing(shakeAnim, { toValue: 10, duration: 50, useNativeDriver: true }),
3942
+ import_react_native19.Animated.timing(shakeAnim, { toValue: -10, duration: 50, useNativeDriver: true }),
3943
+ import_react_native19.Animated.timing(shakeAnim, { toValue: 0, duration: 50, useNativeDriver: true })
3593
3944
  ]).start();
3594
3945
  }, [shakeAnim]);
3595
3946
  const handleSend = React19.useCallback(async () => {
@@ -3606,12 +3957,12 @@ function ChatComposer({
3606
3957
  const placeholderTextColor = theme.scheme === "dark" ? "#A1A1AA" : "#71717A";
3607
3958
  const sendBg = withAlpha(theme.colors.primary, isButtonDisabled ? 0.6 : sendPressed ? 0.9 : 1);
3608
3959
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3609
- import_react_native18.View,
3960
+ import_react_native19.View,
3610
3961
  {
3611
3962
  style: [{ paddingHorizontal: 16, paddingBottom: 12, paddingTop: 8 }, style],
3612
3963
  onLayout: (e) => onLayout == null ? void 0 : onLayout({ height: e.nativeEvent.layout.height }),
3613
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_react_native18.View, { style: { flexDirection: "row", alignItems: "flex-end", gap: 8 }, children: [
3614
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native18.Animated.View, { style: { flex: 1, transform: [{ translateX: shakeAnim }] }, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3964
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_react_native19.View, { style: { flexDirection: "row", alignItems: "flex-end", gap: 8 }, children: [
3965
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native19.Animated.View, { style: { flex: 1, transform: [{ translateX: shakeAnim }] }, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3615
3966
  import_liquid_glass4.LiquidGlassView,
3616
3967
  {
3617
3968
  style: [
@@ -3624,7 +3975,7 @@ function ChatComposer({
3624
3975
  effect: "clear",
3625
3976
  children: [
3626
3977
  hasAttachments ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3627
- import_react_native18.ScrollView,
3978
+ import_react_native19.ScrollView,
3628
3979
  {
3629
3980
  horizontal: true,
3630
3981
  showsHorizontalScrollIndicator: false,
@@ -3641,7 +3992,7 @@ function ChatComposer({
3641
3992
  `attachment-${index}`
3642
3993
  )),
3643
3994
  onAddAttachment ? renderAddAttachment ? renderAddAttachment() : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3644
- import_react_native18.Pressable,
3995
+ import_react_native19.Pressable,
3645
3996
  {
3646
3997
  style: {
3647
3998
  height: THUMBNAIL_HEIGHT,
@@ -3692,7 +4043,7 @@ function ChatComposer({
3692
4043
  interactive: true,
3693
4044
  effect: "clear",
3694
4045
  children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3695
- import_react_native18.View,
4046
+ import_react_native19.View,
3696
4047
  {
3697
4048
  style: {
3698
4049
  width: 44,
@@ -3702,7 +4053,7 @@ function ChatComposer({
3702
4053
  backgroundColor: sendBg
3703
4054
  },
3704
4055
  children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3705
- import_react_native18.Pressable,
4056
+ import_react_native19.Pressable,
3706
4057
  {
3707
4058
  accessibilityRole: "button",
3708
4059
  accessibilityLabel: "Send",
@@ -3711,7 +4062,7 @@ function ChatComposer({
3711
4062
  onPressIn: () => setSendPressed(true),
3712
4063
  onPressOut: () => setSendPressed(false),
3713
4064
  style: { flex: 1, alignItems: "center", justifyContent: "center" },
3714
- children: sending ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native18.ActivityIndicator, {}) : renderSendIcon ? renderSendIcon() : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconChevronRight, { size: 20, colorToken: "onPrimary" })
4065
+ children: sending ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native19.ActivityIndicator, {}) : renderSendIcon ? renderSendIcon() : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconChevronRight, { size: 20, colorToken: "onPrimary" })
3715
4066
  }
3716
4067
  )
3717
4068
  }
@@ -3725,10 +4076,10 @@ function ChatComposer({
3725
4076
 
3726
4077
  // src/components/comments/CommentRow.tsx
3727
4078
  var React20 = __toESM(require("react"));
3728
- var import_react_native20 = require("react-native");
4079
+ var import_react_native21 = require("react-native");
3729
4080
 
3730
4081
  // src/components/primitives/Avatar.tsx
3731
- var import_react_native19 = require("react-native");
4082
+ var import_react_native20 = require("react-native");
3732
4083
  var import_jsx_runtime18 = require("react/jsx-runtime");
3733
4084
  function initialsFrom(name) {
3734
4085
  var _a, _b;
@@ -3748,7 +4099,7 @@ function Avatar({
3748
4099
  const radius = size / 2;
3749
4100
  const fallbackBg = fallbackBackgroundColor ?? theme.colors.neutral;
3750
4101
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3751
- import_react_native19.View,
4102
+ import_react_native20.View,
3752
4103
  {
3753
4104
  style: [
3754
4105
  {
@@ -3763,7 +4114,7 @@ function Avatar({
3763
4114
  style
3764
4115
  ],
3765
4116
  children: uri ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3766
- import_react_native19.Image,
4117
+ import_react_native20.Image,
3767
4118
  {
3768
4119
  source: { uri },
3769
4120
  style: [{ width: size, height: size }, imageStyle],
@@ -3814,7 +4165,7 @@ function CommentRow({ comment, showDivider }) {
3814
4165
  };
3815
4166
  }, [comment.authorId]);
3816
4167
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
3817
- import_react_native20.View,
4168
+ import_react_native21.View,
3818
4169
  {
3819
4170
  style: {
3820
4171
  flexDirection: "row",
@@ -3825,8 +4176,8 @@ function CommentRow({ comment, showDivider }) {
3825
4176
  },
3826
4177
  children: [
3827
4178
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Avatar, { size: 32, uri: authorAvatar, name: authorName ?? comment.authorId, style: { marginTop: 6 } }),
3828
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_react_native20.View, { style: { flex: 1, minWidth: 0, gap: 4 }, children: [
3829
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_react_native20.View, { style: { flexDirection: "row", alignItems: "center", gap: theme.spacing.sm }, children: [
4179
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_react_native21.View, { style: { flex: 1, minWidth: 0, gap: 4 }, children: [
4180
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_react_native21.View, { style: { flexDirection: "row", alignItems: "center", gap: theme.spacing.sm }, children: [
3830
4181
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Text, { style: { fontSize: 14, lineHeight: 18, fontWeight: theme.typography.fontWeight.bold, color: theme.colors.text }, children: authorName ?? "Unknown User" }),
3831
4182
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Text, { style: { fontSize: 12, lineHeight: 16, color: theme.colors.textMuted }, children: formatTimeAgo(comment.createdAt) })
3832
4183
  ] }),
@@ -3951,7 +4302,18 @@ function useAppComments(appId) {
3951
4302
  try {
3952
4303
  const newComment = await appCommentsRepository.create(appId, { body: trimmed, commentType: "general" });
3953
4304
  setComments((prev) => sortByCreatedAtAsc([...prev, newComment]));
4305
+ await trackSubmitComment({
4306
+ appId,
4307
+ commentLength: trimmed.length,
4308
+ success: true
4309
+ });
3954
4310
  } catch (e) {
4311
+ await trackSubmitComment({
4312
+ appId,
4313
+ commentLength: trimmed.length,
4314
+ success: false,
4315
+ error: e
4316
+ });
3955
4317
  setError(e instanceof Error ? e : new Error(String(e)));
3956
4318
  throw e;
3957
4319
  } finally {
@@ -3994,13 +4356,13 @@ function useAppDetails(appId) {
3994
4356
 
3995
4357
  // src/components/comments/useIosKeyboardSnapFix.ts
3996
4358
  var React23 = __toESM(require("react"));
3997
- var import_react_native21 = require("react-native");
4359
+ var import_react_native22 = require("react-native");
3998
4360
  function useIosKeyboardSnapFix(sheetRef, options) {
3999
4361
  const [keyboardVisible, setKeyboardVisible] = React23.useState(false);
4000
4362
  React23.useEffect(() => {
4001
- if (import_react_native21.Platform.OS !== "ios") return;
4002
- const show = import_react_native21.Keyboard.addListener("keyboardWillShow", () => setKeyboardVisible(true));
4003
- const hide = import_react_native21.Keyboard.addListener("keyboardWillHide", () => {
4363
+ if (import_react_native22.Platform.OS !== "ios") return;
4364
+ const show = import_react_native22.Keyboard.addListener("keyboardWillShow", () => setKeyboardVisible(true));
4365
+ const hide = import_react_native22.Keyboard.addListener("keyboardWillHide", () => {
4004
4366
  var _a;
4005
4367
  setKeyboardVisible(false);
4006
4368
  const target = (options == null ? void 0 : options.targetIndex) ?? 1;
@@ -4076,17 +4438,17 @@ function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
4076
4438
  onChange: handleChange,
4077
4439
  backgroundStyle: {
4078
4440
  backgroundColor: theme.scheme === "dark" ? "#0B080F" : "#FFFFFF",
4079
- borderTopLeftRadius: import_react_native22.Platform.OS === "ios" ? 39 : 16,
4080
- borderTopRightRadius: import_react_native22.Platform.OS === "ios" ? 39 : 16
4441
+ borderTopLeftRadius: import_react_native23.Platform.OS === "ios" ? 39 : 16,
4442
+ borderTopRightRadius: import_react_native23.Platform.OS === "ios" ? 39 : 16
4081
4443
  },
4082
4444
  handleIndicatorStyle: { backgroundColor: theme.colors.handleIndicator },
4083
4445
  keyboardBehavior: "interactive",
4084
4446
  keyboardBlurBehavior: "restore",
4085
4447
  android_keyboardInputMode: "adjustResize",
4086
4448
  topInset: insets.top,
4087
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_native22.View, { style: { flex: 1 }, children: [
4449
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_native23.View, { style: { flex: 1 }, children: [
4088
4450
  /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
4089
- import_react_native22.View,
4451
+ import_react_native23.View,
4090
4452
  {
4091
4453
  style: {
4092
4454
  flexDirection: "row",
@@ -4122,7 +4484,7 @@ function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
4122
4484
  interactive: true,
4123
4485
  effect: "clear",
4124
4486
  children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4125
- import_react_native22.View,
4487
+ import_react_native23.View,
4126
4488
  {
4127
4489
  style: {
4128
4490
  width: 32,
@@ -4133,7 +4495,7 @@ function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
4133
4495
  justifyContent: "center"
4134
4496
  },
4135
4497
  children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4136
- import_react_native22.Pressable,
4498
+ import_react_native23.Pressable,
4137
4499
  {
4138
4500
  disabled: !appId,
4139
4501
  onPress: () => void handlePlay(),
@@ -4168,13 +4530,13 @@ function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
4168
4530
  },
4169
4531
  keyboardShouldPersistTaps: "handled",
4170
4532
  children: [
4171
- loading && comments.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native22.View, { style: { flex: 1, alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native22.ActivityIndicator, {}) }) : comments.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native22.View, { style: { flex: 1, alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Text, { variant: "bodyMuted", style: { textAlign: "center" }, children: "No comments yet" }) }) : comments.map((c, idx) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(CommentRow, { comment: c, showDivider: idx < comments.length - 1 }, c.id)),
4533
+ loading && comments.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native23.View, { style: { flex: 1, alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native23.ActivityIndicator, {}) }) : comments.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native23.View, { style: { flex: 1, alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Text, { variant: "bodyMuted", style: { textAlign: "center" }, children: "No comments yet" }) }) : comments.map((c, idx) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(CommentRow, { comment: c, showDivider: idx < comments.length - 1 }, c.id)),
4172
4534
  error ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Text, { variant: "captionMuted", style: { marginTop: theme.spacing.lg }, children: "Failed to load comments." }) : null
4173
4535
  ]
4174
4536
  }
4175
4537
  ),
4176
4538
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4177
- import_react_native22.View,
4539
+ import_react_native23.View,
4178
4540
  {
4179
4541
  style: {
4180
4542
  position: "absolute",
@@ -4183,7 +4545,7 @@ function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
4183
4545
  bottom: 0,
4184
4546
  paddingHorizontal: theme.spacing.lg,
4185
4547
  paddingTop: theme.spacing.sm,
4186
- paddingBottom: import_react_native22.Platform.OS === "ios" ? keyboardVisible ? theme.spacing.lg : insets.bottom : insets.bottom + 10,
4548
+ paddingBottom: import_react_native23.Platform.OS === "ios" ? keyboardVisible ? theme.spacing.lg : insets.bottom : insets.bottom + 10,
4187
4549
  borderTopWidth: 1,
4188
4550
  borderTopColor: withAlpha(theme.colors.border, 0.1),
4189
4551
  backgroundColor: withAlpha(theme.colors.background, 0.8)
@@ -4197,7 +4559,7 @@ function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
4197
4559
  useBottomSheetTextInput: true,
4198
4560
  onSend: async (text) => {
4199
4561
  await create(text);
4200
- import_react_native22.Keyboard.dismiss();
4562
+ import_react_native23.Keyboard.dismiss();
4201
4563
  }
4202
4564
  }
4203
4565
  )
@@ -4210,16 +4572,16 @@ function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
4210
4572
 
4211
4573
  // src/studio/ui/PreviewPanel.tsx
4212
4574
  var React35 = __toESM(require("react"));
4213
- var import_react_native43 = require("react-native");
4575
+ var import_react_native44 = require("react-native");
4214
4576
 
4215
4577
  // src/components/preview/PreviewPage.tsx
4216
- var import_react_native23 = require("react-native");
4578
+ var import_react_native24 = require("react-native");
4217
4579
  var import_bottom_sheet4 = require("@gorhom/bottom-sheet");
4218
4580
  var import_jsx_runtime21 = require("react/jsx-runtime");
4219
4581
  function PreviewPage({ header, children, contentStyle }) {
4220
4582
  const theme = useTheme();
4221
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_react_native23.View, { style: { flex: 1 }, children: [
4222
- header ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native23.View, { children: header }) : null,
4583
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_react_native24.View, { style: { flex: 1 }, children: [
4584
+ header ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native24.View, { children: header }) : null,
4223
4585
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4224
4586
  import_bottom_sheet4.BottomSheetScrollView,
4225
4587
  {
@@ -4239,15 +4601,15 @@ function PreviewPage({ header, children, contentStyle }) {
4239
4601
  }
4240
4602
 
4241
4603
  // src/studio/ui/preview-panel/PreviewPanelHeader.tsx
4242
- var import_react_native26 = require("react-native");
4604
+ var import_react_native27 = require("react-native");
4243
4605
 
4244
4606
  // src/components/studio-sheet/StudioSheetHeader.tsx
4245
- var import_react_native24 = require("react-native");
4607
+ var import_react_native25 = require("react-native");
4246
4608
  var import_jsx_runtime22 = require("react/jsx-runtime");
4247
4609
  function StudioSheetHeader({ left, center, right, style }) {
4248
4610
  const theme = useTheme();
4249
4611
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
4250
- import_react_native24.View,
4612
+ import_react_native25.View,
4251
4613
  {
4252
4614
  style: [
4253
4615
  {
@@ -4260,9 +4622,9 @@ function StudioSheetHeader({ left, center, right, style }) {
4260
4622
  style
4261
4623
  ],
4262
4624
  children: [
4263
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native24.View, { style: { flexDirection: "row", alignItems: "center" }, children: left }),
4264
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native24.View, { style: { flex: 1, alignItems: "center" }, children: center }),
4265
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native24.View, { style: { flexDirection: "row", alignItems: "center" }, children: right })
4625
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native25.View, { style: { flexDirection: "row", alignItems: "center" }, children: left }),
4626
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native25.View, { style: { flex: 1, alignItems: "center" }, children: center }),
4627
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native25.View, { style: { flexDirection: "row", alignItems: "center" }, children: right })
4266
4628
  ]
4267
4629
  }
4268
4630
  );
@@ -4270,7 +4632,7 @@ function StudioSheetHeader({ left, center, right, style }) {
4270
4632
 
4271
4633
  // src/components/studio-sheet/StudioSheetHeaderIconButton.tsx
4272
4634
  var React25 = __toESM(require("react"));
4273
- var import_react_native25 = require("react-native");
4635
+ var import_react_native26 = require("react-native");
4274
4636
  var import_liquid_glass6 = require("@callstack/liquid-glass");
4275
4637
  var import_jsx_runtime23 = require("react/jsx-runtime");
4276
4638
  function StudioSheetHeaderIconButton({
@@ -4290,14 +4652,14 @@ function StudioSheetHeaderIconButton({
4290
4652
  const glassInnerBg = intent === "danger" ? theme.colors.danger : theme.colors.primary;
4291
4653
  const resolvedOpacity = disabled ? 0.6 : pressed ? 0.9 : 1;
4292
4654
  const glassBg = withAlpha(glassInnerBg, resolvedOpacity);
4293
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_native25.View, { style, children: appearance === "glass" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4655
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_native26.View, { style, children: appearance === "glass" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4294
4656
  ResettableLiquidGlassView,
4295
4657
  {
4296
4658
  style: [{ borderRadius: 100 }, !import_liquid_glass6.isLiquidGlassSupported && { backgroundColor: glassFallbackBg }],
4297
4659
  interactive: true,
4298
4660
  effect: "clear",
4299
4661
  children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4300
- import_react_native25.View,
4662
+ import_react_native26.View,
4301
4663
  {
4302
4664
  style: {
4303
4665
  width: size,
@@ -4308,7 +4670,7 @@ function StudioSheetHeaderIconButton({
4308
4670
  backgroundColor: glassBg
4309
4671
  },
4310
4672
  children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4311
- import_react_native25.Pressable,
4673
+ import_react_native26.Pressable,
4312
4674
  {
4313
4675
  accessibilityRole: "button",
4314
4676
  accessibilityLabel,
@@ -4327,7 +4689,7 @@ function StudioSheetHeaderIconButton({
4327
4689
  )
4328
4690
  }
4329
4691
  ) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4330
- import_react_native25.View,
4692
+ import_react_native26.View,
4331
4693
  {
4332
4694
  style: {
4333
4695
  width: size,
@@ -4339,7 +4701,7 @@ function StudioSheetHeaderIconButton({
4339
4701
  opacity: resolvedOpacity
4340
4702
  },
4341
4703
  children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4342
- import_react_native25.Pressable,
4704
+ import_react_native26.Pressable,
4343
4705
  {
4344
4706
  accessibilityRole: "button",
4345
4707
  accessibilityLabel,
@@ -4373,7 +4735,7 @@ function PreviewPanelHeader({
4373
4735
  {
4374
4736
  left: onNavigateHome ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(StudioSheetHeaderIconButton, { onPress: onNavigateHome, accessibilityLabel: "Home", appearance: "glass", intent: "primary", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(IconHome, { size: 20, colorToken: "onPrimary" }) }) : null,
4375
4737
  center: null,
4376
- right: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_react_native26.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
4738
+ right: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_react_native27.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
4377
4739
  isOwner ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4378
4740
  StudioSheetHeaderIconButton,
4379
4741
  {
@@ -4403,10 +4765,10 @@ function PreviewPanelHeader({
4403
4765
  }
4404
4766
 
4405
4767
  // src/components/preview/PreviewHeroCard.tsx
4406
- var import_react_native28 = require("react-native");
4768
+ var import_react_native29 = require("react-native");
4407
4769
 
4408
4770
  // src/components/primitives/Surface.tsx
4409
- var import_react_native27 = require("react-native");
4771
+ var import_react_native28 = require("react-native");
4410
4772
  var import_jsx_runtime25 = require("react/jsx-runtime");
4411
4773
  function backgroundFor(variant, theme) {
4412
4774
  const { colors } = theme;
@@ -4425,7 +4787,7 @@ function backgroundFor(variant, theme) {
4425
4787
  function Surface({ variant = "surface", border = false, style, ...props }) {
4426
4788
  const theme = useTheme();
4427
4789
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
4428
- import_react_native27.View,
4790
+ import_react_native28.View,
4429
4791
  {
4430
4792
  ...props,
4431
4793
  style: [
@@ -4481,11 +4843,11 @@ function PreviewHeroCard({
4481
4843
  },
4482
4844
  style
4483
4845
  ],
4484
- children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_react_native28.View, { style: { flex: 1 }, children: [
4485
- background ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react_native28.View, { style: { position: "absolute", inset: 0 }, children: background }) : null,
4486
- image ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react_native28.View, { style: { position: "absolute", inset: 0 }, children: image }) : null,
4487
- overlayTopLeft ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react_native28.View, { style: { position: "absolute", top: theme.spacing.sm, left: theme.spacing.sm, zIndex: 2 }, children: overlayTopLeft }) : null,
4488
- overlayBottom ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react_native28.View, { style: { flex: 1, justifyContent: "flex-end" }, children: overlayBottom }) : null
4846
+ children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_react_native29.View, { style: { flex: 1 }, children: [
4847
+ background ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react_native29.View, { style: { position: "absolute", inset: 0 }, children: background }) : null,
4848
+ image ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react_native29.View, { style: { position: "absolute", inset: 0 }, children: image }) : null,
4849
+ overlayTopLeft ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react_native29.View, { style: { position: "absolute", top: theme.spacing.sm, left: theme.spacing.sm, zIndex: 2 }, children: overlayTopLeft }) : null,
4850
+ overlayBottom ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react_native29.View, { style: { flex: 1, justifyContent: "flex-end" }, children: overlayBottom }) : null
4489
4851
  ] })
4490
4852
  }
4491
4853
  );
@@ -4493,20 +4855,20 @@ function PreviewHeroCard({
4493
4855
 
4494
4856
  // src/components/preview/PreviewPlaceholder.tsx
4495
4857
  var React26 = __toESM(require("react"));
4496
- var import_react_native29 = require("react-native");
4858
+ var import_react_native30 = require("react-native");
4497
4859
  var import_expo_linear_gradient2 = require("expo-linear-gradient");
4498
4860
  var import_jsx_runtime28 = require("react/jsx-runtime");
4499
4861
  function PreviewPlaceholder({ visible, style }) {
4500
4862
  if (!visible) return null;
4501
- const opacityAnim = React26.useRef(new import_react_native29.Animated.Value(0)).current;
4863
+ const opacityAnim = React26.useRef(new import_react_native30.Animated.Value(0)).current;
4502
4864
  React26.useEffect(() => {
4503
4865
  if (!visible) return;
4504
- const animation = import_react_native29.Animated.loop(
4505
- import_react_native29.Animated.sequence([
4506
- import_react_native29.Animated.timing(opacityAnim, { toValue: 1, duration: 1500, useNativeDriver: true }),
4507
- import_react_native29.Animated.timing(opacityAnim, { toValue: 2, duration: 1500, useNativeDriver: true }),
4508
- import_react_native29.Animated.timing(opacityAnim, { toValue: 3, duration: 1500, useNativeDriver: true }),
4509
- import_react_native29.Animated.timing(opacityAnim, { toValue: 0, duration: 1500, useNativeDriver: true })
4866
+ const animation = import_react_native30.Animated.loop(
4867
+ import_react_native30.Animated.sequence([
4868
+ import_react_native30.Animated.timing(opacityAnim, { toValue: 1, duration: 1500, useNativeDriver: true }),
4869
+ import_react_native30.Animated.timing(opacityAnim, { toValue: 2, duration: 1500, useNativeDriver: true }),
4870
+ import_react_native30.Animated.timing(opacityAnim, { toValue: 3, duration: 1500, useNativeDriver: true }),
4871
+ import_react_native30.Animated.timing(opacityAnim, { toValue: 0, duration: 1500, useNativeDriver: true })
4510
4872
  ])
4511
4873
  );
4512
4874
  animation.start();
@@ -4517,7 +4879,7 @@ function PreviewPlaceholder({ visible, style }) {
4517
4879
  const opacity3 = opacityAnim.interpolate({ inputRange: [0, 1, 2, 3], outputRange: [0, 0, 1, 0] });
4518
4880
  const opacity4 = opacityAnim.interpolate({ inputRange: [0, 1, 2, 3], outputRange: [0, 0, 0, 1] });
4519
4881
  return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
4520
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react_native29.Animated.View, { style: [{ position: "absolute", inset: 0, opacity: opacity1 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
4882
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react_native30.Animated.View, { style: [{ position: "absolute", inset: 0, opacity: opacity1 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
4521
4883
  import_expo_linear_gradient2.LinearGradient,
4522
4884
  {
4523
4885
  colors: ["rgba(98, 0, 238, 0.45)", "rgba(168, 85, 247, 0.35)"],
@@ -4526,7 +4888,7 @@ function PreviewPlaceholder({ visible, style }) {
4526
4888
  style: { width: "100%", height: "100%" }
4527
4889
  }
4528
4890
  ) }),
4529
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react_native29.Animated.View, { style: [{ position: "absolute", inset: 0, opacity: opacity2 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
4891
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react_native30.Animated.View, { style: [{ position: "absolute", inset: 0, opacity: opacity2 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
4530
4892
  import_expo_linear_gradient2.LinearGradient,
4531
4893
  {
4532
4894
  colors: ["rgba(168, 85, 247, 0.45)", "rgba(139, 92, 246, 0.35)"],
@@ -4535,7 +4897,7 @@ function PreviewPlaceholder({ visible, style }) {
4535
4897
  style: { width: "100%", height: "100%" }
4536
4898
  }
4537
4899
  ) }),
4538
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react_native29.Animated.View, { style: [{ position: "absolute", inset: 0, opacity: opacity3 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
4900
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react_native30.Animated.View, { style: [{ position: "absolute", inset: 0, opacity: opacity3 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
4539
4901
  import_expo_linear_gradient2.LinearGradient,
4540
4902
  {
4541
4903
  colors: ["rgba(139, 92, 246, 0.45)", "rgba(126, 34, 206, 0.35)"],
@@ -4544,7 +4906,7 @@ function PreviewPlaceholder({ visible, style }) {
4544
4906
  style: { width: "100%", height: "100%" }
4545
4907
  }
4546
4908
  ) }),
4547
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react_native29.Animated.View, { style: [{ position: "absolute", inset: 0, opacity: opacity4 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
4909
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react_native30.Animated.View, { style: [{ position: "absolute", inset: 0, opacity: opacity4 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
4548
4910
  import_expo_linear_gradient2.LinearGradient,
4549
4911
  {
4550
4912
  colors: ["rgba(126, 34, 206, 0.45)", "rgba(98, 0, 238, 0.35)"],
@@ -4557,12 +4919,12 @@ function PreviewPlaceholder({ visible, style }) {
4557
4919
  }
4558
4920
 
4559
4921
  // src/components/preview/PreviewImage.tsx
4560
- var import_react_native30 = require("react-native");
4922
+ var import_react_native31 = require("react-native");
4561
4923
  var import_jsx_runtime29 = require("react/jsx-runtime");
4562
4924
  function PreviewImage({ uri, onLoad, style }) {
4563
4925
  if (!uri) return null;
4564
4926
  return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
4565
- import_react_native30.Image,
4927
+ import_react_native31.Image,
4566
4928
  {
4567
4929
  source: { uri },
4568
4930
  resizeMode: "cover",
@@ -4573,7 +4935,7 @@ function PreviewImage({ uri, onLoad, style }) {
4573
4935
  }
4574
4936
 
4575
4937
  // src/components/preview/StatsBar.tsx
4576
- var import_react_native31 = require("react-native");
4938
+ var import_react_native32 = require("react-native");
4577
4939
  var import_liquid_glass7 = require("@callstack/liquid-glass");
4578
4940
  var import_lucide_react_native5 = require("lucide-react-native");
4579
4941
 
@@ -4606,7 +4968,7 @@ function StatsBar({
4606
4968
  const theme = useTheme();
4607
4969
  const statsBgColor = theme.scheme === "dark" ? "rgba(24, 24, 27, 0.5)" : "rgba(255, 255, 255, 0.5)";
4608
4970
  return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
4609
- import_react_native31.View,
4971
+ import_react_native32.View,
4610
4972
  {
4611
4973
  style: [
4612
4974
  { position: "absolute", bottom: 12, width: "100%", paddingHorizontal: 12 },
@@ -4622,15 +4984,15 @@ function StatsBar({
4622
4984
  !import_liquid_glass7.isLiquidGlassSupported && { backgroundColor: statsBgColor }
4623
4985
  ],
4624
4986
  effect: "clear",
4625
- children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_react_native31.View, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", paddingHorizontal: 16 }, children: [
4987
+ children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_react_native32.View, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", paddingHorizontal: 16 }, children: [
4626
4988
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
4627
- import_react_native31.Pressable,
4989
+ import_react_native32.Pressable,
4628
4990
  {
4629
4991
  disabled: !onPressLike,
4630
4992
  onPress: onPressLike,
4631
4993
  hitSlop: 8,
4632
4994
  style: { paddingVertical: 8 },
4633
- children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_react_native31.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
4995
+ children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_react_native32.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
4634
4996
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
4635
4997
  import_lucide_react_native5.Heart,
4636
4998
  {
@@ -4640,7 +5002,7 @@ function StatsBar({
4640
5002
  fill: isLiked ? theme.colors.danger : "transparent"
4641
5003
  }
4642
5004
  ),
4643
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react_native31.View, { style: { width: 4 } }),
5005
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react_native32.View, { style: { width: 4 } }),
4644
5006
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
4645
5007
  Text,
4646
5008
  {
@@ -4656,22 +5018,22 @@ function StatsBar({
4656
5018
  }
4657
5019
  ),
4658
5020
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
4659
- import_react_native31.Pressable,
5021
+ import_react_native32.Pressable,
4660
5022
  {
4661
5023
  disabled: !onPressComments,
4662
5024
  onPress: onPressComments,
4663
5025
  hitSlop: 8,
4664
5026
  style: { paddingVertical: 8 },
4665
- children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_react_native31.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
5027
+ children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_react_native32.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
4666
5028
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react_native5.MessageCircle, { size: 16, strokeWidth: 2.5, color: "#FFFFFF" }),
4667
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react_native31.View, { style: { width: 4 } }),
5029
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react_native32.View, { style: { width: 4 } }),
4668
5030
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Text, { variant: "caption", style: { color: "#FFFFFF", fontWeight: theme.typography.fontWeight.bold }, children: commentCount })
4669
5031
  ] })
4670
5032
  }
4671
5033
  ),
4672
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_react_native31.View, { style: { flexDirection: "row", alignItems: "center", paddingVertical: 8 }, children: [
4673
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react_native31.View, { style: { transform: [{ scaleY: -1 }] }, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(MergeIcon, { width: 14, height: 14, color: "#FFFFFF" }) }),
4674
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react_native31.View, { style: { width: 4 } }),
5034
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_react_native32.View, { style: { flexDirection: "row", alignItems: "center", paddingVertical: 8 }, children: [
5035
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react_native32.View, { style: { transform: [{ scaleY: -1 }] }, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(MergeIcon, { width: 14, height: 14, color: "#FFFFFF" }) }),
5036
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react_native32.View, { style: { width: 4 } }),
4675
5037
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Text, { variant: "caption", style: { color: "#FFFFFF", fontWeight: theme.typography.fontWeight.bold }, children: forkCount })
4676
5038
  ] })
4677
5039
  ] })
@@ -4682,7 +5044,7 @@ function StatsBar({
4682
5044
  }
4683
5045
 
4684
5046
  // src/components/preview/PreviewStatusBadge.tsx
4685
- var import_react_native32 = require("react-native");
5047
+ var import_react_native33 = require("react-native");
4686
5048
  var import_lucide_react_native6 = require("lucide-react-native");
4687
5049
 
4688
5050
  // src/data/apps/types.ts
@@ -4727,7 +5089,7 @@ function PreviewStatusBadge({ status }) {
4727
5089
  const IconComp = STATUS_ICON[status];
4728
5090
  const label = APP_STATUS_LABEL[status] ?? status;
4729
5091
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
4730
- import_react_native32.View,
5092
+ import_react_native33.View,
4731
5093
  {
4732
5094
  style: {
4733
5095
  flexDirection: "row",
@@ -4780,10 +5142,10 @@ function PreviewHeroSection({
4780
5142
  }
4781
5143
 
4782
5144
  // src/studio/ui/preview-panel/PreviewMetaSection.tsx
4783
- var import_react_native34 = require("react-native");
5145
+ var import_react_native35 = require("react-native");
4784
5146
 
4785
5147
  // src/components/preview/PreviewMetaRow.tsx
4786
- var import_react_native33 = require("react-native");
5148
+ var import_react_native34 = require("react-native");
4787
5149
  var import_jsx_runtime34 = require("react/jsx-runtime");
4788
5150
  function PreviewMetaRow({
4789
5151
  avatarUri,
@@ -4795,10 +5157,10 @@ function PreviewMetaRow({
4795
5157
  style
4796
5158
  }) {
4797
5159
  const theme = useTheme();
4798
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_react_native33.View, { style: [{ alignSelf: "stretch" }, style], children: [
4799
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_react_native33.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
5160
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_react_native34.View, { style: [{ alignSelf: "stretch" }, style], children: [
5161
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_react_native34.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
4800
5162
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(Avatar, { uri: avatarUri, name: creatorName, size: 24, style: { marginRight: theme.spacing.sm } }),
4801
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_react_native33.View, { style: { flexDirection: "row", alignItems: "center", flex: 1, minWidth: 0, marginRight: theme.spacing.sm }, children: [
5163
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_react_native34.View, { style: { flexDirection: "row", alignItems: "center", flex: 1, minWidth: 0, marginRight: theme.spacing.sm }, children: [
4802
5164
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
4803
5165
  Text,
4804
5166
  {
@@ -4813,9 +5175,9 @@ function PreviewMetaRow({
4813
5175
  children: title
4814
5176
  }
4815
5177
  ),
4816
- tag ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react_native33.View, { style: { marginLeft: theme.spacing.sm }, children: tag }) : null
5178
+ tag ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react_native34.View, { style: { marginLeft: theme.spacing.sm }, children: tag }) : null
4817
5179
  ] }),
4818
- rightMetric ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react_native33.View, { children: rightMetric }) : null
5180
+ rightMetric ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react_native34.View, { children: rightMetric }) : null
4819
5181
  ] }),
4820
5182
  subtitle ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
4821
5183
  Text,
@@ -4871,9 +5233,9 @@ function PreviewMetaSection({ app, isOwner, creator, downloadsCount }) {
4871
5233
  subtitle: app.description,
4872
5234
  avatarUri: (creator == null ? void 0 : creator.avatar) ?? null,
4873
5235
  creatorName: (creator == null ? void 0 : creator.name) ?? null,
4874
- tag: isOwner || app.forkedFromAppId ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_react_native34.View, { style: { paddingHorizontal: 8, paddingVertical: 2, borderRadius: 999, backgroundColor: "#007A75" }, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(Text, { variant: "caption", style: { color: "#fff", fontWeight: theme.typography.fontWeight.semibold }, children: app.forkedFromAppId ? "Remix" : "Owner" }) }) : null,
5236
+ tag: isOwner || app.forkedFromAppId ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_react_native35.View, { style: { paddingHorizontal: 8, paddingVertical: 2, borderRadius: 999, backgroundColor: "#007A75" }, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(Text, { variant: "caption", style: { color: "#fff", fontWeight: theme.typography.fontWeight.semibold }, children: app.forkedFromAppId ? "Remix" : "Owner" }) }) : null,
4875
5237
  rightMetric: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
4876
- import_react_native34.View,
5238
+ import_react_native35.View,
4877
5239
  {
4878
5240
  style: {
4879
5241
  flexDirection: "row",
@@ -4907,10 +5269,10 @@ function PreviewMetaSection({ app, isOwner, creator, downloadsCount }) {
4907
5269
  }
4908
5270
 
4909
5271
  // src/studio/ui/preview-panel/PreviewCustomizeSection.tsx
4910
- var import_react_native36 = require("react-native");
5272
+ var import_react_native37 = require("react-native");
4911
5273
 
4912
5274
  // src/studio/ui/preview-panel/PressableCardRow.tsx
4913
- var import_react_native35 = require("react-native");
5275
+ var import_react_native36 = require("react-native");
4914
5276
  var import_jsx_runtime36 = require("react/jsx-runtime");
4915
5277
  function PressableCardRow({
4916
5278
  accessibilityLabel,
@@ -4923,20 +5285,20 @@ function PressableCardRow({
4923
5285
  style
4924
5286
  }) {
4925
5287
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
4926
- import_react_native35.Pressable,
5288
+ import_react_native36.Pressable,
4927
5289
  {
4928
5290
  accessibilityRole: "button",
4929
5291
  accessibilityLabel,
4930
5292
  disabled,
4931
5293
  onPress,
4932
5294
  style: ({ pressed }) => ({ opacity: disabled ? 0.6 : pressed ? 0.85 : 1 }),
4933
- children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(Card, { padded: false, border: false, style, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_react_native35.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
5295
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(Card, { padded: false, border: false, style, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_react_native36.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
4934
5296
  left,
4935
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_react_native35.View, { style: { flex: 1, minWidth: 0 }, children: [
5297
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_react_native36.View, { style: { flex: 1, minWidth: 0 }, children: [
4936
5298
  title,
4937
5299
  subtitle ? subtitle : null
4938
5300
  ] }),
4939
- right ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_react_native35.View, { style: { marginLeft: 16 }, children: right }) : null
5301
+ right ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_react_native36.View, { style: { marginLeft: 16 }, children: right }) : null
4940
5302
  ] }) })
4941
5303
  }
4942
5304
  );
@@ -4978,7 +5340,7 @@ function PreviewCustomizeSection({
4978
5340
  return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
4979
5341
  /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(SectionTitle, { children: "Customize" }),
4980
5342
  showProcessing ? /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
4981
- import_react_native36.View,
5343
+ import_react_native37.View,
4982
5344
  {
4983
5345
  style: {
4984
5346
  flexDirection: "row",
@@ -4992,7 +5354,7 @@ function PreviewCustomizeSection({
4992
5354
  },
4993
5355
  children: [
4994
5356
  /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
4995
- import_react_native36.View,
5357
+ import_react_native37.View,
4996
5358
  {
4997
5359
  style: {
4998
5360
  width: 40,
@@ -5003,10 +5365,10 @@ function PreviewCustomizeSection({
5003
5365
  backgroundColor: withAlpha(theme.colors.warning, 0.1),
5004
5366
  marginRight: theme.spacing.lg
5005
5367
  },
5006
- children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_react_native36.ActivityIndicator, { color: theme.colors.warning, size: "small" })
5368
+ children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_react_native37.ActivityIndicator, { color: theme.colors.warning, size: "small" })
5007
5369
  }
5008
5370
  ),
5009
- /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_react_native36.View, { style: { flex: 1, minWidth: 0 }, children: [
5371
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_react_native37.View, { style: { flex: 1, minWidth: 0 }, children: [
5010
5372
  /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(Text, { style: { color: theme.colors.text, fontSize: 16, lineHeight: 20, fontWeight: theme.typography.fontWeight.semibold }, children: app.status === "error" ? "Error" : "Processing" }),
5011
5373
  /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(Text, { style: { color: theme.colors.textMuted, fontSize: 12, lineHeight: 16, marginTop: 2 }, children: statusDescription(app.status, app.statusError) })
5012
5374
  ] })
@@ -5027,7 +5389,7 @@ function PreviewCustomizeSection({
5027
5389
  marginBottom: theme.spacing.sm
5028
5390
  },
5029
5391
  left: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
5030
- import_react_native36.View,
5392
+ import_react_native37.View,
5031
5393
  {
5032
5394
  style: {
5033
5395
  width: 40,
@@ -5060,7 +5422,7 @@ function PreviewCustomizeSection({
5060
5422
  marginBottom: theme.spacing.sm
5061
5423
  },
5062
5424
  left: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
5063
- import_react_native36.View,
5425
+ import_react_native37.View,
5064
5426
  {
5065
5427
  style: {
5066
5428
  width: 40,
@@ -5084,16 +5446,16 @@ function PreviewCustomizeSection({
5084
5446
 
5085
5447
  // src/studio/ui/preview-panel/PreviewCollaborateSection.tsx
5086
5448
  var React32 = __toESM(require("react"));
5087
- var import_react_native42 = require("react-native");
5449
+ var import_react_native43 = require("react-native");
5088
5450
  var import_lucide_react_native9 = require("lucide-react-native");
5089
5451
 
5090
5452
  // src/components/merge-requests/MergeRequestStatusCard.tsx
5091
5453
  var React28 = __toESM(require("react"));
5092
- var import_react_native38 = require("react-native");
5454
+ var import_react_native39 = require("react-native");
5093
5455
  var import_lucide_react_native7 = require("lucide-react-native");
5094
5456
 
5095
5457
  // src/components/primitives/MarkdownText.tsx
5096
- var import_react_native37 = require("react-native");
5458
+ var import_react_native38 = require("react-native");
5097
5459
  var import_react_native_markdown_display = __toESM(require("react-native-markdown-display"));
5098
5460
  var import_react2 = require("react");
5099
5461
  var import_jsx_runtime39 = require("react/jsx-runtime");
@@ -5137,7 +5499,7 @@ function MarkdownText({ markdown, variant = "chat", bodyColor, style }) {
5137
5499
  const codeTextColor = isDark ? "#FFFFFF" : theme.colors.text;
5138
5500
  const paragraphBottom = variant === "mergeRequest" ? 8 : 6;
5139
5501
  const baseLineHeight = variant === "mergeRequest" ? 22 : 20;
5140
- const screen = import_react_native37.Dimensions.get("window");
5502
+ const screen = import_react_native38.Dimensions.get("window");
5141
5503
  const tooltipPadding = theme.spacing.sm;
5142
5504
  const tooltipYOffset = theme.spacing.lg + 32;
5143
5505
  const minTooltipY = theme.spacing.xl;
@@ -5167,7 +5529,7 @@ function MarkdownText({ markdown, variant = "chat", bodyColor, style }) {
5167
5529
  setShowCopied(false);
5168
5530
  }, 1200);
5169
5531
  };
5170
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_react_native37.Pressable, { style, onPress: import_react_native37.Keyboard.dismiss, onLongPress: handleLongPress, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_react_native37.View, { style: { position: "relative" }, children: [
5532
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_react_native38.Pressable, { style, onPress: import_react_native38.Keyboard.dismiss, onLongPress: handleLongPress, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_react_native38.View, { style: { position: "relative" }, children: [
5171
5533
  /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
5172
5534
  import_react_native_markdown_display.default,
5173
5535
  {
@@ -5181,7 +5543,7 @@ function MarkdownText({ markdown, variant = "chat", bodyColor, style }) {
5181
5543
  paddingHorizontal: variant === "mergeRequest" ? 6 : 4,
5182
5544
  paddingVertical: variant === "mergeRequest" ? 2 : 0,
5183
5545
  borderRadius: variant === "mergeRequest" ? 6 : 4,
5184
- fontFamily: import_react_native37.Platform.OS === "ios" ? "Menlo" : "monospace",
5546
+ fontFamily: import_react_native38.Platform.OS === "ios" ? "Menlo" : "monospace",
5185
5547
  fontSize: 13
5186
5548
  },
5187
5549
  code_block: {
@@ -5202,8 +5564,8 @@ function MarkdownText({ markdown, variant = "chat", bodyColor, style }) {
5202
5564
  children: markdown
5203
5565
  }
5204
5566
  ),
5205
- showCopied && tooltipAnchor ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_react_native37.Modal, { transparent: true, visible: true, statusBarTranslucent: true, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_react_native37.View, { pointerEvents: "none", style: { flex: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
5206
- import_react_native37.View,
5567
+ showCopied && tooltipAnchor ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_react_native38.Modal, { transparent: true, visible: true, statusBarTranslucent: true, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_react_native38.View, { pointerEvents: "none", style: { flex: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
5568
+ import_react_native38.View,
5207
5569
  {
5208
5570
  pointerEvents: "none",
5209
5571
  style: {
@@ -5217,7 +5579,7 @@ function MarkdownText({ markdown, variant = "chat", bodyColor, style }) {
5217
5579
  },
5218
5580
  onLayout: (event) => setTooltipWidth(event.nativeEvent.layout.width),
5219
5581
  children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
5220
- import_react_native37.Text,
5582
+ import_react_native38.Text,
5221
5583
  {
5222
5584
  style: {
5223
5585
  color: theme.colors.onSuccess,
@@ -5321,15 +5683,15 @@ function MergeRequestStatusCard({
5321
5683
  const createdIso = toIsoString(mergeRequest.createdAt ?? null);
5322
5684
  const headerTimeAgo = updatedIso ? formatTimeAgo(updatedIso) : "";
5323
5685
  const createdTimeAgo = createdIso ? formatTimeAgo(createdIso) : "";
5324
- const rotate = React28.useRef(new import_react_native38.Animated.Value(expanded ? 1 : 0)).current;
5686
+ const rotate = React28.useRef(new import_react_native39.Animated.Value(expanded ? 1 : 0)).current;
5325
5687
  React28.useEffect(() => {
5326
- import_react_native38.Animated.timing(rotate, {
5688
+ import_react_native39.Animated.timing(rotate, {
5327
5689
  toValue: expanded ? 1 : 0,
5328
5690
  duration: 200,
5329
5691
  useNativeDriver: true
5330
5692
  }).start();
5331
5693
  }, [expanded, rotate]);
5332
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_react_native38.Pressable, { onPress: () => setExpanded(!expanded), style: ({ pressed }) => [{ opacity: pressed ? 0.95 : 1 }], children: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
5694
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_react_native39.Pressable, { onPress: () => setExpanded(!expanded), style: ({ pressed }) => [{ opacity: pressed ? 0.95 : 1 }], children: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
5333
5695
  Card,
5334
5696
  {
5335
5697
  padded: false,
@@ -5342,10 +5704,10 @@ function MergeRequestStatusCard({
5342
5704
  style
5343
5705
  ],
5344
5706
  children: [
5345
- /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_react_native38.View, { style: { flexDirection: "row", alignItems: "center", gap: theme.spacing.lg }, children: [
5346
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_react_native38.View, { style: { width: 40, height: 40, borderRadius: 999, alignItems: "center", justifyContent: "center", backgroundColor: bgColor }, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(StatusIcon, { size: 20, color: iconColor }) }),
5347
- /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_react_native38.View, { style: { flex: 1, minWidth: 0 }, children: [
5348
- /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_react_native38.View, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between" }, children: [
5707
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_react_native39.View, { style: { flexDirection: "row", alignItems: "center", gap: theme.spacing.lg }, children: [
5708
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_react_native39.View, { style: { width: 40, height: 40, borderRadius: 999, alignItems: "center", justifyContent: "center", backgroundColor: bgColor }, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(StatusIcon, { size: 20, color: iconColor }) }),
5709
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_react_native39.View, { style: { flex: 1, minWidth: 0 }, children: [
5710
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_react_native39.View, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between" }, children: [
5349
5711
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
5350
5712
  Text,
5351
5713
  {
@@ -5364,8 +5726,8 @@ function MergeRequestStatusCard({
5364
5726
  ] }),
5365
5727
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Text, { style: { fontSize: 12, lineHeight: 16, color: theme.colors.textMuted }, numberOfLines: 1, children: mergeRequest.title ?? "Untitled merge request" })
5366
5728
  ] }),
5367
- headerRight ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_react_native38.View, { children: headerRight }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
5368
- import_react_native38.Animated.View,
5729
+ headerRight ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_react_native39.View, { children: headerRight }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
5730
+ import_react_native39.Animated.View,
5369
5731
  {
5370
5732
  style: {
5371
5733
  transform: [
@@ -5378,7 +5740,7 @@ function MergeRequestStatusCard({
5378
5740
  }
5379
5741
  )
5380
5742
  ] }),
5381
- expanded ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_react_native38.View, { style: { marginTop: 16, marginLeft: 56 }, children: [
5743
+ expanded ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_react_native39.View, { style: { marginTop: 16, marginLeft: 56 }, children: [
5382
5744
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
5383
5745
  Text,
5384
5746
  {
@@ -5414,16 +5776,16 @@ function MergeRequestStatusCard({
5414
5776
 
5415
5777
  // src/components/merge-requests/ReviewMergeRequestCarousel.tsx
5416
5778
  var React31 = __toESM(require("react"));
5417
- var import_react_native41 = require("react-native");
5779
+ var import_react_native42 = require("react-native");
5418
5780
 
5419
5781
  // src/components/merge-requests/ReviewMergeRequestCard.tsx
5420
5782
  var React30 = __toESM(require("react"));
5421
- var import_react_native40 = require("react-native");
5783
+ var import_react_native41 = require("react-native");
5422
5784
  var import_lucide_react_native8 = require("lucide-react-native");
5423
5785
 
5424
5786
  // src/components/merge-requests/ReviewMergeRequestActionButton.tsx
5425
5787
  var React29 = __toESM(require("react"));
5426
- var import_react_native39 = require("react-native");
5788
+ var import_react_native40 = require("react-native");
5427
5789
  var import_jsx_runtime41 = require("react/jsx-runtime");
5428
5790
  function ReviewMergeRequestActionButton({
5429
5791
  accessibilityLabel,
@@ -5440,7 +5802,7 @@ function ReviewMergeRequestActionButton({
5440
5802
  const paddingVertical = iconOnly ? 0 : 8;
5441
5803
  const opacity = disabled ? 0.5 : pressed ? 0.9 : 1;
5442
5804
  return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
5443
- import_react_native39.View,
5805
+ import_react_native40.View,
5444
5806
  {
5445
5807
  style: {
5446
5808
  width,
@@ -5455,7 +5817,7 @@ function ReviewMergeRequestActionButton({
5455
5817
  justifyContent: "center"
5456
5818
  },
5457
5819
  children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
5458
- import_react_native39.Pressable,
5820
+ import_react_native40.Pressable,
5459
5821
  {
5460
5822
  accessibilityRole: "button",
5461
5823
  accessibilityLabel,
@@ -5497,12 +5859,12 @@ function ReviewMergeRequestCard({
5497
5859
  const theme = useTheme();
5498
5860
  const status = React30.useMemo(() => getMergeRequestStatusDisplay(mr.status), [mr.status]);
5499
5861
  const canAct = mr.status === "open";
5500
- const rotate = React30.useRef(new import_react_native40.Animated.Value(isExpanded ? 1 : 0)).current;
5862
+ const rotate = React30.useRef(new import_react_native41.Animated.Value(isExpanded ? 1 : 0)).current;
5501
5863
  React30.useEffect(() => {
5502
- import_react_native40.Animated.timing(rotate, { toValue: isExpanded ? 1 : 0, duration: 200, useNativeDriver: true }).start();
5864
+ import_react_native41.Animated.timing(rotate, { toValue: isExpanded ? 1 : 0, duration: 200, useNativeDriver: true }).start();
5503
5865
  }, [isExpanded, rotate]);
5504
5866
  const position = total > 1 ? `${index + 1}/${total}` : "Merge request";
5505
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_react_native40.Pressable, { onPress: onToggle, style: ({ pressed }) => ({ opacity: pressed ? 0.95 : 1 }), children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
5867
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_react_native41.Pressable, { onPress: onToggle, style: ({ pressed }) => ({ opacity: pressed ? 0.95 : 1 }), children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
5506
5868
  Card,
5507
5869
  {
5508
5870
  padded: false,
@@ -5515,9 +5877,9 @@ function ReviewMergeRequestCard({
5515
5877
  }
5516
5878
  ],
5517
5879
  children: [
5518
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react_native40.View, { style: { flexDirection: "row", alignItems: "center", gap: 12 }, children: [
5880
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react_native41.View, { style: { flexDirection: "row", alignItems: "center", gap: 12 }, children: [
5519
5881
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Avatar, { size: 40, uri: (creator == null ? void 0 : creator.avatar) ?? null, name: (creator == null ? void 0 : creator.name) ?? void 0 }),
5520
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react_native40.View, { style: { flex: 1, minWidth: 0 }, children: [
5882
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react_native41.View, { style: { flex: 1, minWidth: 0 }, children: [
5521
5883
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
5522
5884
  Text,
5523
5885
  {
@@ -5533,7 +5895,7 @@ function ReviewMergeRequestCard({
5533
5895
  ] })
5534
5896
  ] }),
5535
5897
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
5536
- import_react_native40.Animated.View,
5898
+ import_react_native41.Animated.View,
5537
5899
  {
5538
5900
  style: {
5539
5901
  transform: [{ rotate: rotate.interpolate({ inputRange: [0, 1], outputRange: ["0deg", "180deg"] }) }]
@@ -5542,7 +5904,7 @@ function ReviewMergeRequestCard({
5542
5904
  }
5543
5905
  )
5544
5906
  ] }),
5545
- isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react_native40.View, { style: { marginTop: 16 }, children: [
5907
+ isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react_native41.View, { style: { marginTop: 16 }, children: [
5546
5908
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
5547
5909
  Text,
5548
5910
  {
@@ -5560,9 +5922,9 @@ function ReviewMergeRequestCard({
5560
5922
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Text, { style: { color: theme.colors.textMuted, fontSize: 12, lineHeight: 16, marginBottom: 12 }, children: creator ? `${creator.approvedOrMergedMergeRequests} approved merge${creator.approvedOrMergedMergeRequests !== 1 ? "s" : ""}` : "Loading stats..." }),
5561
5923
  mr.description ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(MarkdownText, { markdown: mr.description, variant: "mergeRequest" }) : null
5562
5924
  ] }) : null,
5563
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_react_native40.View, { style: { height: 1, backgroundColor: withAlpha(theme.colors.borderStrong, 0.5), marginTop: 12, marginBottom: 12 } }),
5564
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react_native40.View, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between" }, children: [
5565
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react_native40.View, { style: { flexDirection: "row", gap: 8 }, children: [
5925
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_react_native41.View, { style: { height: 1, backgroundColor: withAlpha(theme.colors.borderStrong, 0.5), marginTop: 12, marginBottom: 12 } }),
5926
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react_native41.View, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between" }, children: [
5927
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react_native41.View, { style: { flexDirection: "row", gap: 8 }, children: [
5566
5928
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
5567
5929
  ReviewMergeRequestActionButton,
5568
5930
  {
@@ -5571,7 +5933,7 @@ function ReviewMergeRequestCard({
5571
5933
  disabled: !canAct || isAnyProcessing,
5572
5934
  onPress: onReject,
5573
5935
  iconOnly: !isExpanded,
5574
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react_native40.View, { style: { flexDirection: "row", alignItems: "center", gap: isExpanded ? 4 : 0 }, children: [
5936
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react_native41.View, { style: { flexDirection: "row", alignItems: "center", gap: isExpanded ? 4 : 0 }, children: [
5575
5937
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react_native8.X, { size: 18, color: "#FFFFFF" }),
5576
5938
  isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Text, { style: { fontSize: 13, color: "#FFFFFF", fontWeight: theme.typography.fontWeight.semibold }, children: "Reject" }) : null
5577
5939
  ] })
@@ -5585,10 +5947,10 @@ function ReviewMergeRequestCard({
5585
5947
  disabled: !canAct || isAnyProcessing,
5586
5948
  onPress: onApprove,
5587
5949
  iconOnly: !isExpanded,
5588
- children: isProcessing ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react_native40.View, { style: { flexDirection: "row", alignItems: "center", gap: isExpanded ? 4 : 0 }, children: [
5589
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_react_native40.ActivityIndicator, { size: "small", color: "#FFFFFF" }),
5950
+ children: isProcessing ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react_native41.View, { style: { flexDirection: "row", alignItems: "center", gap: isExpanded ? 4 : 0 }, children: [
5951
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_react_native41.ActivityIndicator, { size: "small", color: "#FFFFFF" }),
5590
5952
  isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Text, { style: { fontSize: 13, color: "#FFFFFF", fontWeight: theme.typography.fontWeight.semibold }, children: "Processing" }) : null
5591
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react_native40.View, { style: { flexDirection: "row", alignItems: "center", gap: isExpanded ? 4 : 0 }, children: [
5953
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react_native41.View, { style: { flexDirection: "row", alignItems: "center", gap: isExpanded ? 4 : 0 }, children: [
5592
5954
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react_native8.Check, { size: 18, color: "#FFFFFF" }),
5593
5955
  isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Text, { style: { fontSize: 13, color: "#FFFFFF", fontWeight: theme.typography.fontWeight.semibold }, children: "Approve" }) : null
5594
5956
  ] })
@@ -5603,7 +5965,7 @@ function ReviewMergeRequestCard({
5603
5965
  disabled: isBuilding || isTestingThis,
5604
5966
  onPress: onTest,
5605
5967
  iconOnly: !isExpanded,
5606
- children: isTestingThis ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_react_native40.ActivityIndicator, { size: "small", color: "#888" }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react_native40.View, { style: { flexDirection: "row", alignItems: "center", gap: isExpanded ? 4 : 0 }, children: [
5968
+ children: isTestingThis ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_react_native41.ActivityIndicator, { size: "small", color: "#888" }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react_native41.View, { style: { flexDirection: "row", alignItems: "center", gap: isExpanded ? 4 : 0 }, children: [
5607
5969
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react_native8.Play, { size: 14, color: theme.colors.text }),
5608
5970
  isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Text, { style: { fontSize: 13, color: theme.colors.text, fontWeight: theme.typography.fontWeight.semibold }, children: "Test" }) : null
5609
5971
  ] })
@@ -5629,32 +5991,32 @@ function ReviewMergeRequestCarousel({
5629
5991
  style
5630
5992
  }) {
5631
5993
  const theme = useTheme();
5632
- const { width } = (0, import_react_native41.useWindowDimensions)();
5994
+ const { width } = (0, import_react_native42.useWindowDimensions)();
5633
5995
  const [expanded, setExpanded] = React31.useState({});
5634
- const carouselScrollX = React31.useRef(new import_react_native41.Animated.Value(0)).current;
5996
+ const carouselScrollX = React31.useRef(new import_react_native42.Animated.Value(0)).current;
5635
5997
  const peekAmount = 24;
5636
5998
  const gap = 16;
5637
5999
  const cardWidth = React31.useMemo(() => Math.max(1, width - theme.spacing.lg * 2 - peekAmount), [peekAmount, theme.spacing.lg, width]);
5638
6000
  const snapInterval = cardWidth + gap;
5639
6001
  const dotColor = theme.scheme === "dark" ? "#FFFFFF" : "#000000";
5640
6002
  if (mergeRequests.length === 0) return null;
5641
- return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(import_react_native41.View, { style: [{ marginHorizontal: -theme.spacing.lg }, style], children: [
6003
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(import_react_native42.View, { style: [{ marginHorizontal: -theme.spacing.lg }, style], children: [
5642
6004
  /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
5643
- import_react_native41.FlatList,
6005
+ import_react_native42.FlatList,
5644
6006
  {
5645
6007
  horizontal: true,
5646
6008
  data: mergeRequests,
5647
6009
  keyExtractor: (mr) => mr.id,
5648
6010
  showsHorizontalScrollIndicator: false,
5649
6011
  contentContainerStyle: { paddingHorizontal: theme.spacing.lg, paddingVertical: theme.spacing.sm },
5650
- ItemSeparatorComponent: () => /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_react_native41.View, { style: { width: gap } }),
6012
+ ItemSeparatorComponent: () => /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_react_native42.View, { style: { width: gap } }),
5651
6013
  snapToAlignment: "start",
5652
6014
  decelerationRate: "fast",
5653
6015
  snapToInterval: snapInterval,
5654
6016
  disableIntervalMomentum: true,
5655
6017
  style: { paddingRight: peekAmount },
5656
- ListFooterComponent: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_react_native41.View, { style: { width: peekAmount } }),
5657
- onScroll: import_react_native41.Animated.event([{ nativeEvent: { contentOffset: { x: carouselScrollX } } }], {
6018
+ ListFooterComponent: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_react_native42.View, { style: { width: peekAmount } }),
6019
+ onScroll: import_react_native42.Animated.event([{ nativeEvent: { contentOffset: { x: carouselScrollX } } }], {
5658
6020
  useNativeDriver: false
5659
6021
  }),
5660
6022
  scrollEventThrottle: 16,
@@ -5665,7 +6027,7 @@ function ReviewMergeRequestCarousel({
5665
6027
  const isProcessing = Boolean(processingMrId && processingMrId === item.id);
5666
6028
  const isAnyProcessing = Boolean(processingMrId);
5667
6029
  const isTestingThis = Boolean(testingMrId && testingMrId === item.id);
5668
- return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_react_native41.View, { style: { width: cardWidth }, children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
6030
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_react_native42.View, { style: { width: cardWidth }, children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
5669
6031
  ReviewMergeRequestCard,
5670
6032
  {
5671
6033
  mr: item,
@@ -5686,7 +6048,7 @@ function ReviewMergeRequestCarousel({
5686
6048
  }
5687
6049
  }
5688
6050
  ),
5689
- mergeRequests.length >= 1 ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_react_native41.View, { style: { flexDirection: "row", justifyContent: "center", columnGap: 8, marginTop: theme.spacing.md }, children: mergeRequests.map((mr, index) => {
6051
+ mergeRequests.length >= 1 ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_react_native42.View, { style: { flexDirection: "row", justifyContent: "center", columnGap: 8, marginTop: theme.spacing.md }, children: mergeRequests.map((mr, index) => {
5690
6052
  const inputRange = [(index - 1) * snapInterval, index * snapInterval, (index + 1) * snapInterval];
5691
6053
  const scale = carouselScrollX.interpolate({
5692
6054
  inputRange,
@@ -5699,7 +6061,7 @@ function ReviewMergeRequestCarousel({
5699
6061
  extrapolate: "clamp"
5700
6062
  });
5701
6063
  return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
5702
- import_react_native41.Animated.View,
6064
+ import_react_native42.Animated.View,
5703
6065
  {
5704
6066
  style: {
5705
6067
  width: 8,
@@ -5766,7 +6128,7 @@ function PreviewCollaborateSection({
5766
6128
  accessibilityLabel: "Submit merge request",
5767
6129
  disabled: submittingMr,
5768
6130
  onPress: () => {
5769
- import_react_native42.Alert.alert(
6131
+ import_react_native43.Alert.alert(
5770
6132
  "Submit Merge Request",
5771
6133
  "Are you sure you want to submit your changes to the original app?",
5772
6134
  [
@@ -5792,7 +6154,7 @@ function PreviewCollaborateSection({
5792
6154
  marginBottom: theme.spacing.sm
5793
6155
  },
5794
6156
  left: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
5795
- import_react_native42.View,
6157
+ import_react_native43.View,
5796
6158
  {
5797
6159
  style: {
5798
6160
  width: 40,
@@ -5803,7 +6165,7 @@ function PreviewCollaborateSection({
5803
6165
  backgroundColor: withAlpha("#03DAC6", 0.1),
5804
6166
  marginRight: theme.spacing.lg
5805
6167
  },
5806
- children: submittingMr ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_react_native42.ActivityIndicator, { color: "#03DAC6", size: "small" }) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(MergeIcon, { width: 20, height: 20, color: "#03DAC6" })
6168
+ children: submittingMr ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_react_native43.ActivityIndicator, { color: "#03DAC6", size: "small" }) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(MergeIcon, { width: 20, height: 20, color: "#03DAC6" })
5807
6169
  }
5808
6170
  ),
5809
6171
  title: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(Text, { style: { color: theme.colors.text, fontSize: 16, lineHeight: 20, fontWeight: theme.typography.fontWeight.semibold }, children: "Submit your new changes" }),
@@ -5817,7 +6179,7 @@ function PreviewCollaborateSection({
5817
6179
  accessibilityLabel: "Sync from original",
5818
6180
  disabled: isSyncing,
5819
6181
  onPress: () => {
5820
- import_react_native42.Alert.alert(
6182
+ import_react_native43.Alert.alert(
5821
6183
  "Sync from Original",
5822
6184
  "This will pull the latest upstream changes into your remix.",
5823
6185
  [
@@ -5829,12 +6191,12 @@ function PreviewCollaborateSection({
5829
6191
  setSyncingLocal(true);
5830
6192
  Promise.resolve(onSyncUpstream()).then((result) => {
5831
6193
  if ((result == null ? void 0 : result.status) === "up-to-date") {
5832
- import_react_native42.Alert.alert("Up to date", "Your remix already includes the latest upstream changes.");
6194
+ import_react_native43.Alert.alert("Up to date", "Your remix already includes the latest upstream changes.");
5833
6195
  } else {
5834
- import_react_native42.Alert.alert("Sync started", "Upstream changes are being merged into your remix.");
6196
+ import_react_native43.Alert.alert("Sync started", "Upstream changes are being merged into your remix.");
5835
6197
  }
5836
6198
  }).catch(() => {
5837
- import_react_native42.Alert.alert("Sync failed", "We could not start the sync. Please try again.");
6199
+ import_react_native43.Alert.alert("Sync failed", "We could not start the sync. Please try again.");
5838
6200
  }).finally(() => setSyncingLocal(false));
5839
6201
  }
5840
6202
  }
@@ -5850,7 +6212,7 @@ function PreviewCollaborateSection({
5850
6212
  marginBottom: theme.spacing.sm
5851
6213
  },
5852
6214
  left: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
5853
- import_react_native42.View,
6215
+ import_react_native43.View,
5854
6216
  {
5855
6217
  style: {
5856
6218
  width: 40,
@@ -5861,7 +6223,7 @@ function PreviewCollaborateSection({
5861
6223
  backgroundColor: withAlpha(theme.colors.primary, 0.12),
5862
6224
  marginRight: theme.spacing.lg
5863
6225
  },
5864
- children: isSyncing ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_react_native42.ActivityIndicator, { color: theme.colors.primary, size: "small" }) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react_native9.RefreshCw, { size: 18, color: theme.colors.primary })
6226
+ children: isSyncing ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_react_native43.ActivityIndicator, { color: theme.colors.primary, size: "small" }) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react_native9.RefreshCw, { size: 18, color: theme.colors.primary })
5865
6227
  }
5866
6228
  ),
5867
6229
  title: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(Text, { style: { color: theme.colors.text, fontSize: 16, lineHeight: 20, fontWeight: theme.typography.fontWeight.semibold }, children: "Sync from Original" }),
@@ -5899,7 +6261,7 @@ function PreviewCollaborateSection({
5899
6261
  children: "History"
5900
6262
  }
5901
6263
  ),
5902
- outgoingMergeRequests.map((mr) => /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_react_native42.View, { style: { marginBottom: theme.spacing.sm }, children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(MergeRequestStatusCard, { mergeRequest: toMergeRequestSummary(mr) }) }, mr.id))
6264
+ outgoingMergeRequests.map((mr) => /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_react_native43.View, { style: { marginBottom: theme.spacing.sm }, children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(MergeRequestStatusCard, { mergeRequest: toMergeRequestSummary(mr) }) }, mr.id))
5903
6265
  ] }) : null
5904
6266
  ] });
5905
6267
  }
@@ -6023,7 +6385,8 @@ function useAppStats({
6023
6385
  initialComments = 0,
6024
6386
  initialForks = 0,
6025
6387
  initialIsLiked = false,
6026
- onOpenComments
6388
+ onOpenComments,
6389
+ interactionSource = "unknown"
6027
6390
  }) {
6028
6391
  const [likeCount, setLikeCount] = React33.useState(initialLikes);
6029
6392
  const [commentCount, setCommentCount] = React33.useState(initialComments);
@@ -6067,23 +6430,31 @@ function useAppStats({
6067
6430
  if (newIsLiked) {
6068
6431
  const res = await appLikesRepository.create(appId, {});
6069
6432
  if (typeof ((_a = res.stats) == null ? void 0 : _a.total) === "number") setLikeCount(Math.max(0, res.stats.total));
6433
+ await trackLikeApp({ appId, source: interactionSource, success: true });
6070
6434
  } else {
6071
6435
  const res = await appLikesRepository.removeMine(appId);
6072
6436
  if (typeof ((_b = res.stats) == null ? void 0 : _b.total) === "number") setLikeCount(Math.max(0, res.stats.total));
6437
+ await trackUnlikeApp({ appId, source: interactionSource, success: true });
6073
6438
  }
6074
6439
  } catch (e) {
6075
6440
  setIsLiked(!newIsLiked);
6076
6441
  setLikeCount((prev) => Math.max(0, prev + (newIsLiked ? -1 : 1)));
6442
+ if (newIsLiked) {
6443
+ await trackLikeApp({ appId, source: interactionSource, success: false, error: e });
6444
+ } else {
6445
+ await trackUnlikeApp({ appId, source: interactionSource, success: false, error: e });
6446
+ }
6077
6447
  }
6078
- }, [appId, isLiked, likeCount]);
6448
+ }, [appId, interactionSource, isLiked, likeCount]);
6079
6449
  const handleOpenComments = React33.useCallback(() => {
6080
6450
  if (!appId) return;
6081
6451
  try {
6082
6452
  void Haptics2.impactAsync(Haptics2.ImpactFeedbackStyle.Light);
6083
6453
  } catch {
6084
6454
  }
6455
+ void trackOpenComments({ appId, source: interactionSource });
6085
6456
  onOpenComments == null ? void 0 : onOpenComments();
6086
- }, [appId, onOpenComments]);
6457
+ }, [appId, interactionSource, onOpenComments]);
6087
6458
  return { likeCount, commentCount, forkCount, isLiked, setCommentCount, handleLike, handleOpenComments };
6088
6459
  }
6089
6460
 
@@ -6166,7 +6537,8 @@ function usePreviewPanelData(params) {
6166
6537
  initialForks: insights.forks,
6167
6538
  initialComments: commentCountOverride ?? insights.comments,
6168
6539
  initialIsLiked: Boolean(app == null ? void 0 : app.isLiked),
6169
- onOpenComments
6540
+ onOpenComments,
6541
+ interactionSource: "preview_panel"
6170
6542
  });
6171
6543
  const canSubmitMergeRequest = React34.useMemo(() => {
6172
6544
  if (!isOwner) return false;
@@ -6232,7 +6604,7 @@ ${shareUrl}` : `Check out this app on Remix
6232
6604
  ${shareUrl}`;
6233
6605
  try {
6234
6606
  const title = app.name ?? "Remix app";
6235
- const payload = import_react_native43.Platform.OS === "ios" ? {
6607
+ const payload = import_react_native44.Platform.OS === "ios" ? {
6236
6608
  title,
6237
6609
  message
6238
6610
  } : {
@@ -6240,9 +6612,18 @@ ${shareUrl}`;
6240
6612
  message,
6241
6613
  url: shareUrl
6242
6614
  };
6243
- await import_react_native43.Share.share(payload);
6615
+ await import_react_native44.Share.share(payload);
6616
+ await trackShareApp({
6617
+ appId: app.id,
6618
+ success: true
6619
+ });
6244
6620
  } catch (error) {
6245
6621
  log.warn("PreviewPanel share failed", error);
6622
+ await trackShareApp({
6623
+ appId: app.id,
6624
+ success: false,
6625
+ error
6626
+ });
6246
6627
  }
6247
6628
  }, [app]);
6248
6629
  const {
@@ -6274,9 +6655,9 @@ ${shareUrl}`;
6274
6655
  }
6275
6656
  );
6276
6657
  if (loading || !app) {
6277
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(PreviewPage, { header, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(import_react_native43.View, { style: { flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, children: [
6278
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_react_native43.ActivityIndicator, {}),
6279
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_react_native43.View, { style: { height: 12 } }),
6658
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(PreviewPage, { header, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(import_react_native44.View, { style: { flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, children: [
6659
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_react_native44.ActivityIndicator, {}),
6660
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_react_native44.View, { style: { height: 12 } }),
6280
6661
  /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Text, { variant: "bodyMuted", children: "Loading app\u2026" })
6281
6662
  ] }) });
6282
6663
  }
@@ -6336,25 +6717,26 @@ ${shareUrl}`;
6336
6717
  }
6337
6718
 
6338
6719
  // src/studio/ui/ChatPanel.tsx
6339
- var React41 = __toESM(require("react"));
6340
- var import_react_native53 = require("react-native");
6720
+ var React42 = __toESM(require("react"));
6721
+ var import_react_native56 = require("react-native");
6341
6722
 
6342
6723
  // src/components/chat/ChatPage.tsx
6343
- var React38 = __toESM(require("react"));
6344
- var import_react_native48 = require("react-native");
6724
+ var React39 = __toESM(require("react"));
6725
+ var import_react_native49 = require("react-native");
6345
6726
  var import_react_native_safe_area_context4 = require("react-native-safe-area-context");
6346
6727
 
6347
6728
  // src/components/chat/ChatMessageList.tsx
6348
- var React37 = __toESM(require("react"));
6349
- var import_react_native47 = require("react-native");
6729
+ var React38 = __toESM(require("react"));
6730
+ var import_react_native48 = require("react-native");
6350
6731
  var import_bottom_sheet5 = require("@gorhom/bottom-sheet");
6351
6732
 
6352
6733
  // src/components/chat/ChatMessageBubble.tsx
6353
- var import_react_native45 = require("react-native");
6734
+ var React36 = __toESM(require("react"));
6735
+ var import_react_native46 = require("react-native");
6354
6736
  var import_lucide_react_native10 = require("lucide-react-native");
6355
6737
 
6356
6738
  // src/components/primitives/Button.tsx
6357
- var import_react_native44 = require("react-native");
6739
+ var import_react_native45 = require("react-native");
6358
6740
  var import_jsx_runtime46 = require("react/jsx-runtime");
6359
6741
  function backgroundFor2(variant, theme, pressed, disabled) {
6360
6742
  const { colors } = theme;
@@ -6392,7 +6774,7 @@ function Button({
6392
6774
  const theme = useTheme();
6393
6775
  const isDisabled = disabled ?? void 0;
6394
6776
  return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
6395
- import_react_native44.Pressable,
6777
+ import_react_native45.Pressable,
6396
6778
  {
6397
6779
  ...props,
6398
6780
  disabled: isDisabled,
@@ -6418,7 +6800,19 @@ function Button({
6418
6800
 
6419
6801
  // src/components/chat/ChatMessageBubble.tsx
6420
6802
  var import_jsx_runtime47 = require("react/jsx-runtime");
6421
- function ChatMessageBubble({ message, renderContent, isLast, retrying, onRetry, style }) {
6803
+ function areMessageMetaEqual(a, b) {
6804
+ if (a === b) return true;
6805
+ if (!a || !b) return a === b;
6806
+ return a.kind === b.kind && a.event === b.event && a.status === b.status && a.mergeRequestId === b.mergeRequestId && a.sourceAppId === b.sourceAppId && a.targetAppId === b.targetAppId && a.appId === b.appId && a.threadId === b.threadId;
6807
+ }
6808
+ function ChatMessageBubbleInner({
6809
+ message,
6810
+ renderContent,
6811
+ isLast,
6812
+ retrying,
6813
+ onRetryMessage,
6814
+ style
6815
+ }) {
6422
6816
  var _a, _b;
6423
6817
  const theme = useTheme();
6424
6818
  const metaEvent = ((_a = message.meta) == null ? void 0 : _a.event) ?? null;
@@ -6433,9 +6827,12 @@ function ChatMessageBubble({ message, renderContent, isLast, retrying, onRetry,
6433
6827
  const bubbleVariant = isHuman ? "surface" : "surfaceRaised";
6434
6828
  const cornerStyle = isHuman ? { borderTopRightRadius: 0 } : { borderTopLeftRadius: 0 };
6435
6829
  const bodyColor = metaStatus === "success" ? theme.colors.success : metaStatus === "error" ? theme.colors.danger : void 0;
6436
- const showRetry = Boolean(onRetry) && isLast && metaStatus === "error" && message.author === "human";
6830
+ const showRetry = Boolean(onRetryMessage) && isLast && metaStatus === "error" && message.author === "human";
6437
6831
  const retryLabel = retrying ? "Retrying..." : "Retry";
6438
- return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(import_react_native45.View, { style: [align, style], children: [
6832
+ const handleRetryPress = React36.useCallback(() => {
6833
+ onRetryMessage == null ? void 0 : onRetryMessage(message.id);
6834
+ }, [message.id, onRetryMessage]);
6835
+ return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(import_react_native46.View, { style: [align, style], children: [
6439
6836
  /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
6440
6837
  Surface,
6441
6838
  {
@@ -6451,23 +6848,23 @@ function ChatMessageBubble({ message, renderContent, isLast, retrying, onRetry,
6451
6848
  },
6452
6849
  cornerStyle
6453
6850
  ],
6454
- children: /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(import_react_native45.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
6851
+ children: /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(import_react_native46.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
6455
6852
  isMergeCompleted || isSyncCompleted ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react_native10.CheckCheck, { size: 16, color: theme.colors.success, style: { marginRight: theme.spacing.sm } }) : null,
6456
6853
  isMergeApproved || isSyncStarted ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react_native10.GitMerge, { size: 16, color: theme.colors.text, style: { marginRight: theme.spacing.sm } }) : null,
6457
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_react_native45.View, { style: { flexShrink: 1, minWidth: 0 }, children: renderContent ? renderContent(message) : /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(MarkdownText, { markdown: message.content, variant: "chat", bodyColor }) })
6854
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_react_native46.View, { style: { flexShrink: 1, minWidth: 0 }, children: renderContent ? renderContent(message) : /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(MarkdownText, { markdown: message.content, variant: "chat", bodyColor }) })
6458
6855
  ] })
6459
6856
  }
6460
6857
  ),
6461
- showRetry ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_react_native45.View, { style: { marginTop: theme.spacing.xs, alignSelf: align.alignSelf }, children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
6858
+ showRetry ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_react_native46.View, { style: { marginTop: theme.spacing.xs, alignSelf: align.alignSelf }, children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
6462
6859
  Button,
6463
6860
  {
6464
6861
  variant: "ghost",
6465
6862
  size: "sm",
6466
- onPress: onRetry,
6863
+ onPress: handleRetryPress,
6467
6864
  disabled: retrying,
6468
6865
  style: { borderColor: theme.colors.danger },
6469
6866
  accessibilityLabel: "Retry send",
6470
- children: /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(import_react_native45.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
6867
+ children: /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(import_react_native46.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
6471
6868
  !retrying ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react_native10.RotateCcw, { size: 14, color: theme.colors.danger }) : null,
6472
6869
  /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
6473
6870
  Text,
@@ -6484,26 +6881,31 @@ function ChatMessageBubble({ message, renderContent, isLast, retrying, onRetry,
6484
6881
  ) }) : null
6485
6882
  ] });
6486
6883
  }
6884
+ function areEqual(prev, next) {
6885
+ return prev.message.id === next.message.id && prev.message.author === next.message.author && prev.message.content === next.message.content && prev.message.kind === next.message.kind && String(prev.message.createdAt) === String(next.message.createdAt) && areMessageMetaEqual(prev.message.meta, next.message.meta) && prev.renderContent === next.renderContent && prev.isLast === next.isLast && prev.retrying === next.retrying && prev.onRetryMessage === next.onRetryMessage && prev.style === next.style;
6886
+ }
6887
+ var ChatMessageBubble = React36.memo(ChatMessageBubbleInner, areEqual);
6888
+ ChatMessageBubble.displayName = "ChatMessageBubble";
6487
6889
 
6488
6890
  // src/components/chat/TypingIndicator.tsx
6489
- var React36 = __toESM(require("react"));
6490
- var import_react_native46 = require("react-native");
6891
+ var React37 = __toESM(require("react"));
6892
+ var import_react_native47 = require("react-native");
6491
6893
  var import_jsx_runtime48 = require("react/jsx-runtime");
6492
6894
  function TypingIndicator({ style }) {
6493
6895
  const theme = useTheme();
6494
6896
  const dotColor = theme.colors.textSubtle;
6495
- const anims = React36.useMemo(
6496
- () => [new import_react_native46.Animated.Value(0.3), new import_react_native46.Animated.Value(0.3), new import_react_native46.Animated.Value(0.3)],
6897
+ const anims = React37.useMemo(
6898
+ () => [new import_react_native47.Animated.Value(0.3), new import_react_native47.Animated.Value(0.3), new import_react_native47.Animated.Value(0.3)],
6497
6899
  []
6498
6900
  );
6499
- React36.useEffect(() => {
6901
+ React37.useEffect(() => {
6500
6902
  const loops = [];
6501
6903
  anims.forEach((a, idx) => {
6502
- const seq = import_react_native46.Animated.sequence([
6503
- import_react_native46.Animated.timing(a, { toValue: 1, duration: 420, useNativeDriver: true, delay: idx * 140 }),
6504
- import_react_native46.Animated.timing(a, { toValue: 0.3, duration: 420, useNativeDriver: true })
6904
+ const seq = import_react_native47.Animated.sequence([
6905
+ import_react_native47.Animated.timing(a, { toValue: 1, duration: 420, useNativeDriver: true, delay: idx * 140 }),
6906
+ import_react_native47.Animated.timing(a, { toValue: 0.3, duration: 420, useNativeDriver: true })
6505
6907
  ]);
6506
- const loop = import_react_native46.Animated.loop(seq);
6908
+ const loop = import_react_native47.Animated.loop(seq);
6507
6909
  loops.push(loop);
6508
6910
  loop.start();
6509
6911
  });
@@ -6511,8 +6913,8 @@ function TypingIndicator({ style }) {
6511
6913
  loops.forEach((l) => l.stop());
6512
6914
  };
6513
6915
  }, [anims]);
6514
- return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_react_native46.View, { style: [{ flexDirection: "row", alignItems: "center" }, style], children: anims.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
6515
- import_react_native46.Animated.View,
6916
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_react_native47.View, { style: [{ flexDirection: "row", alignItems: "center" }, style], children: anims.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
6917
+ import_react_native47.Animated.View,
6516
6918
  {
6517
6919
  style: {
6518
6920
  width: 8,
@@ -6521,7 +6923,7 @@ function TypingIndicator({ style }) {
6521
6923
  marginHorizontal: 3,
6522
6924
  backgroundColor: dotColor,
6523
6925
  opacity: a,
6524
- transform: [{ translateY: import_react_native46.Animated.multiply(import_react_native46.Animated.subtract(a, 0.3), 2) }]
6926
+ transform: [{ translateY: import_react_native47.Animated.multiply(import_react_native47.Animated.subtract(a, 0.3), 2) }]
6525
6927
  }
6526
6928
  },
6527
6929
  i
@@ -6530,7 +6932,7 @@ function TypingIndicator({ style }) {
6530
6932
 
6531
6933
  // src/components/chat/ChatMessageList.tsx
6532
6934
  var import_jsx_runtime49 = require("react/jsx-runtime");
6533
- var ChatMessageList = React37.forwardRef(
6935
+ var ChatMessageList = React38.forwardRef(
6534
6936
  ({
6535
6937
  messages,
6536
6938
  showTypingIndicator = false,
@@ -6543,21 +6945,22 @@ var ChatMessageList = React37.forwardRef(
6543
6945
  nearBottomThreshold = 200
6544
6946
  }, ref) => {
6545
6947
  const theme = useTheme();
6546
- const listRef = React37.useRef(null);
6547
- const nearBottomRef = React37.useRef(true);
6548
- const initialScrollDoneRef = React37.useRef(false);
6549
- const lastMessageIdRef = React37.useRef(null);
6550
- const data = React37.useMemo(() => {
6948
+ const listRef = React38.useRef(null);
6949
+ const nearBottomRef = React38.useRef(true);
6950
+ const initialScrollDoneRef = React38.useRef(false);
6951
+ const lastMessageIdRef = React38.useRef(null);
6952
+ const data = React38.useMemo(() => {
6551
6953
  return [...messages].reverse();
6552
6954
  }, [messages]);
6553
6955
  const lastMessageId = messages.length > 0 ? messages[messages.length - 1].id : null;
6554
- const scrollToBottom = React37.useCallback((options) => {
6956
+ const keyExtractor = React38.useCallback((m) => m.id, []);
6957
+ const scrollToBottom = React38.useCallback((options) => {
6555
6958
  var _a;
6556
6959
  const animated = (options == null ? void 0 : options.animated) ?? true;
6557
6960
  (_a = listRef.current) == null ? void 0 : _a.scrollToOffset({ offset: 0, animated });
6558
6961
  }, []);
6559
- React37.useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);
6560
- const handleScroll = React37.useCallback(
6962
+ React38.useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);
6963
+ const handleScroll = React38.useCallback(
6561
6964
  (e) => {
6562
6965
  const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent;
6563
6966
  const distanceFromBottom = Math.max(contentOffset.y - Math.max(bottomInset, 0), 0);
@@ -6569,7 +6972,7 @@ var ChatMessageList = React37.forwardRef(
6569
6972
  },
6570
6973
  [bottomInset, nearBottomThreshold, onNearBottomChange]
6571
6974
  );
6572
- React37.useEffect(() => {
6975
+ React38.useEffect(() => {
6573
6976
  if (!initialScrollDoneRef.current) return;
6574
6977
  const lastId = messages.length > 0 ? messages[messages.length - 1].id : null;
6575
6978
  const prevLastId = lastMessageIdRef.current;
@@ -6579,54 +6982,68 @@ var ChatMessageList = React37.forwardRef(
6579
6982
  const id = requestAnimationFrame(() => scrollToBottom({ animated: true }));
6580
6983
  return () => cancelAnimationFrame(id);
6581
6984
  }, [messages, scrollToBottom]);
6582
- React37.useEffect(() => {
6985
+ React38.useEffect(() => {
6583
6986
  if (showTypingIndicator && nearBottomRef.current) {
6584
6987
  const id = requestAnimationFrame(() => scrollToBottom({ animated: true }));
6585
6988
  return () => cancelAnimationFrame(id);
6586
6989
  }
6587
6990
  return void 0;
6588
6991
  }, [showTypingIndicator, scrollToBottom]);
6992
+ const handleContentSizeChange = React38.useCallback(() => {
6993
+ if (initialScrollDoneRef.current) return;
6994
+ initialScrollDoneRef.current = true;
6995
+ lastMessageIdRef.current = messages.length > 0 ? messages[messages.length - 1].id : null;
6996
+ nearBottomRef.current = true;
6997
+ onNearBottomChange == null ? void 0 : onNearBottomChange(true);
6998
+ requestAnimationFrame(() => scrollToBottom({ animated: false }));
6999
+ }, [messages, onNearBottomChange, scrollToBottom]);
7000
+ const contentContainerStyle = React38.useMemo(
7001
+ () => [
7002
+ {
7003
+ paddingHorizontal: theme.spacing.lg,
7004
+ paddingVertical: theme.spacing.sm
7005
+ },
7006
+ contentStyle
7007
+ ],
7008
+ [contentStyle, theme.spacing.lg, theme.spacing.sm]
7009
+ );
7010
+ const renderSeparator = React38.useCallback(() => /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_react_native48.View, { style: { height: theme.spacing.sm } }), [theme.spacing.sm]);
7011
+ const listHeader = React38.useMemo(
7012
+ () => /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_react_native48.View, { children: [
7013
+ showTypingIndicator ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_react_native48.View, { style: { marginTop: theme.spacing.sm, alignSelf: "flex-start", paddingHorizontal: theme.spacing.lg }, children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(TypingIndicator, {}) }) : null,
7014
+ bottomInset > 0 ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_react_native48.View, { style: { height: bottomInset } }) : null
7015
+ ] }),
7016
+ [bottomInset, showTypingIndicator, theme.spacing.lg, theme.spacing.sm]
7017
+ );
7018
+ const renderItem = React38.useCallback(
7019
+ ({ item }) => /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
7020
+ ChatMessageBubble,
7021
+ {
7022
+ message: item,
7023
+ renderContent: renderMessageContent,
7024
+ isLast: Boolean(lastMessageId && item.id === lastMessageId),
7025
+ retrying: (isRetryingMessage == null ? void 0 : isRetryingMessage(item.id)) ?? false,
7026
+ onRetryMessage
7027
+ }
7028
+ ),
7029
+ [isRetryingMessage, lastMessageId, onRetryMessage, renderMessageContent]
7030
+ );
6589
7031
  return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
6590
7032
  import_bottom_sheet5.BottomSheetFlatList,
6591
7033
  {
6592
7034
  ref: listRef,
6593
7035
  inverted: true,
6594
7036
  data,
6595
- keyExtractor: (m) => m.id,
7037
+ keyExtractor,
6596
7038
  keyboardShouldPersistTaps: "handled",
6597
7039
  onScroll: handleScroll,
6598
7040
  scrollEventThrottle: 16,
6599
7041
  showsVerticalScrollIndicator: false,
6600
- onContentSizeChange: () => {
6601
- if (initialScrollDoneRef.current) return;
6602
- initialScrollDoneRef.current = true;
6603
- lastMessageIdRef.current = messages.length > 0 ? messages[messages.length - 1].id : null;
6604
- nearBottomRef.current = true;
6605
- onNearBottomChange == null ? void 0 : onNearBottomChange(true);
6606
- requestAnimationFrame(() => scrollToBottom({ animated: false }));
6607
- },
6608
- contentContainerStyle: [
6609
- {
6610
- paddingHorizontal: theme.spacing.lg,
6611
- paddingVertical: theme.spacing.sm
6612
- },
6613
- contentStyle
6614
- ],
6615
- ItemSeparatorComponent: () => /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_react_native47.View, { style: { height: theme.spacing.sm } }),
6616
- renderItem: ({ item }) => /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
6617
- ChatMessageBubble,
6618
- {
6619
- message: item,
6620
- renderContent: renderMessageContent,
6621
- isLast: Boolean(lastMessageId && item.id === lastMessageId),
6622
- retrying: (isRetryingMessage == null ? void 0 : isRetryingMessage(item.id)) ?? false,
6623
- onRetry: onRetryMessage ? () => onRetryMessage(item.id) : void 0
6624
- }
6625
- ),
6626
- ListHeaderComponent: /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_react_native47.View, { children: [
6627
- showTypingIndicator ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_react_native47.View, { style: { marginTop: theme.spacing.sm, alignSelf: "flex-start", paddingHorizontal: theme.spacing.lg }, children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(TypingIndicator, {}) }) : null,
6628
- bottomInset > 0 ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_react_native47.View, { style: { height: bottomInset } }) : null
6629
- ] })
7042
+ onContentSizeChange: handleContentSizeChange,
7043
+ contentContainerStyle,
7044
+ ItemSeparatorComponent: renderSeparator,
7045
+ renderItem,
7046
+ ListHeaderComponent: listHeader
6630
7047
  }
6631
7048
  );
6632
7049
  }
@@ -6653,31 +7070,31 @@ function ChatPage({
6653
7070
  }) {
6654
7071
  const theme = useTheme();
6655
7072
  const insets = (0, import_react_native_safe_area_context4.useSafeAreaInsets)();
6656
- const [composerHeight, setComposerHeight] = React38.useState(0);
6657
- const [composerTopHeight, setComposerTopHeight] = React38.useState(0);
6658
- const footerBottomPadding = import_react_native48.Platform.OS === "ios" ? insets.bottom - 24 : insets.bottom + 10;
7073
+ const [composerHeight, setComposerHeight] = React39.useState(0);
7074
+ const [composerTopHeight, setComposerTopHeight] = React39.useState(0);
7075
+ const footerBottomPadding = import_react_native49.Platform.OS === "ios" ? insets.bottom - 24 : insets.bottom + 10;
6659
7076
  const totalComposerHeight = composerHeight + composerTopHeight;
6660
7077
  const overlayBottom = totalComposerHeight + footerBottomPadding + theme.spacing.lg;
6661
7078
  const bottomInset = totalComposerHeight + footerBottomPadding + theme.spacing.xl;
6662
- const resolvedOverlay = React38.useMemo(() => {
7079
+ const resolvedOverlay = React39.useMemo(() => {
6663
7080
  var _a;
6664
7081
  if (!overlay) return null;
6665
- if (!React38.isValidElement(overlay)) return overlay;
7082
+ if (!React39.isValidElement(overlay)) return overlay;
6666
7083
  const prevStyle = (_a = overlay.props) == null ? void 0 : _a.style;
6667
- return React38.cloneElement(overlay, {
7084
+ return React39.cloneElement(overlay, {
6668
7085
  style: [prevStyle, { bottom: overlayBottom }]
6669
7086
  });
6670
7087
  }, [overlay, overlayBottom]);
6671
- React38.useEffect(() => {
7088
+ React39.useEffect(() => {
6672
7089
  if (composerTop) return;
6673
7090
  setComposerTopHeight(0);
6674
7091
  }, [composerTop]);
6675
- return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_react_native48.View, { style: [{ flex: 1 }, style], children: [
6676
- header ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_react_native48.View, { children: header }) : null,
6677
- topBanner ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_react_native48.View, { style: { paddingHorizontal: theme.spacing.lg, paddingTop: theme.spacing.sm }, children: topBanner }) : null,
6678
- /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_react_native48.View, { style: { flex: 1 }, children: [
7092
+ return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_react_native49.View, { style: [{ flex: 1 }, style], children: [
7093
+ header ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_react_native49.View, { children: header }) : null,
7094
+ topBanner ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_react_native49.View, { style: { paddingHorizontal: theme.spacing.lg, paddingTop: theme.spacing.sm }, children: topBanner }) : null,
7095
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_react_native49.View, { style: { flex: 1 }, children: [
6679
7096
  /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(
6680
- import_react_native48.View,
7097
+ import_react_native49.View,
6681
7098
  {
6682
7099
  style: { flex: 1 },
6683
7100
  children: [
@@ -6699,7 +7116,7 @@ function ChatPage({
6699
7116
  }
6700
7117
  ),
6701
7118
  /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(
6702
- import_react_native48.View,
7119
+ import_react_native49.View,
6703
7120
  {
6704
7121
  style: {
6705
7122
  position: "absolute",
@@ -6712,7 +7129,7 @@ function ChatPage({
6712
7129
  },
6713
7130
  children: [
6714
7131
  composerTop ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
6715
- import_react_native48.View,
7132
+ import_react_native49.View,
6716
7133
  {
6717
7134
  style: { marginBottom: theme.spacing.sm },
6718
7135
  onLayout: (e) => setComposerTopHeight(e.nativeEvent.layout.height),
@@ -6735,15 +7152,15 @@ function ChatPage({
6735
7152
  }
6736
7153
 
6737
7154
  // src/components/chat/ScrollToBottomButton.tsx
6738
- var React39 = __toESM(require("react"));
6739
- var import_react_native49 = require("react-native");
7155
+ var React40 = __toESM(require("react"));
7156
+ var import_react_native50 = require("react-native");
6740
7157
  var import_react_native_reanimated2 = __toESM(require("react-native-reanimated"));
6741
7158
  var import_jsx_runtime51 = require("react/jsx-runtime");
6742
7159
  function ScrollToBottomButton({ visible, onPress, children, style }) {
6743
7160
  const theme = useTheme();
6744
7161
  const progress = (0, import_react_native_reanimated2.useSharedValue)(visible ? 1 : 0);
6745
- const [pressed, setPressed] = React39.useState(false);
6746
- React39.useEffect(() => {
7162
+ const [pressed, setPressed] = React40.useState(false);
7163
+ React40.useEffect(() => {
6747
7164
  progress.value = (0, import_react_native_reanimated2.withTiming)(visible ? 1 : 0, { duration: 200, easing: import_react_native_reanimated2.Easing.out(import_react_native_reanimated2.Easing.ease) });
6748
7165
  }, [progress, visible]);
6749
7166
  const animStyle = (0, import_react_native_reanimated2.useAnimatedStyle)(() => ({
@@ -6767,7 +7184,7 @@ function ScrollToBottomButton({ visible, onPress, children, style }) {
6767
7184
  animStyle
6768
7185
  ],
6769
7186
  children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
6770
- import_react_native49.View,
7187
+ import_react_native50.View,
6771
7188
  {
6772
7189
  style: {
6773
7190
  width: 44,
@@ -6786,7 +7203,7 @@ function ScrollToBottomButton({ visible, onPress, children, style }) {
6786
7203
  opacity: pressed ? 0.85 : 1
6787
7204
  },
6788
7205
  children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
6789
- import_react_native49.Pressable,
7206
+ import_react_native50.Pressable,
6790
7207
  {
6791
7208
  onPress,
6792
7209
  onPressIn: () => setPressed(true),
@@ -6803,10 +7220,10 @@ function ScrollToBottomButton({ visible, onPress, children, style }) {
6803
7220
  }
6804
7221
 
6805
7222
  // src/components/chat/ChatHeader.tsx
6806
- var import_react_native50 = require("react-native");
7223
+ var import_react_native51 = require("react-native");
6807
7224
  var import_jsx_runtime52 = require("react/jsx-runtime");
6808
7225
  function ChatHeader({ left, right, center, style }) {
6809
- const flattenedStyle = import_react_native50.StyleSheet.flatten([
7226
+ const flattenedStyle = import_react_native51.StyleSheet.flatten([
6810
7227
  {
6811
7228
  paddingTop: 0
6812
7229
  },
@@ -6824,7 +7241,7 @@ function ChatHeader({ left, right, center, style }) {
6824
7241
  }
6825
7242
 
6826
7243
  // src/components/chat/ForkNoticeBanner.tsx
6827
- var import_react_native51 = require("react-native");
7244
+ var import_react_native52 = require("react-native");
6828
7245
  var import_jsx_runtime53 = require("react/jsx-runtime");
6829
7246
  function ForkNoticeBanner({ isOwner = true, title, description, style }) {
6830
7247
  const theme = useTheme();
@@ -6845,7 +7262,7 @@ function ForkNoticeBanner({ isOwner = true, title, description, style }) {
6845
7262
  },
6846
7263
  style
6847
7264
  ],
6848
- children: /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_react_native51.View, { style: { minWidth: 0 }, children: [
7265
+ children: /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_react_native52.View, { style: { minWidth: 0 }, children: [
6849
7266
  /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
6850
7267
  Text,
6851
7268
  {
@@ -6878,16 +7295,16 @@ function ForkNoticeBanner({ isOwner = true, title, description, style }) {
6878
7295
  }
6879
7296
 
6880
7297
  // src/components/chat/ChatQueue.tsx
6881
- var React40 = __toESM(require("react"));
6882
- var import_react_native52 = require("react-native");
7298
+ var React41 = __toESM(require("react"));
7299
+ var import_react_native53 = require("react-native");
6883
7300
  var import_jsx_runtime54 = require("react/jsx-runtime");
6884
7301
  function ChatQueue({ items, onRemove }) {
6885
7302
  const theme = useTheme();
6886
- const [expanded, setExpanded] = React40.useState({});
6887
- const [canExpand, setCanExpand] = React40.useState({});
6888
- const [collapsedText, setCollapsedText] = React40.useState({});
6889
- const [removing, setRemoving] = React40.useState({});
6890
- const buildCollapsedText = React40.useCallback((lines) => {
7303
+ const [expanded, setExpanded] = React41.useState({});
7304
+ const [canExpand, setCanExpand] = React41.useState({});
7305
+ const [collapsedText, setCollapsedText] = React41.useState({});
7306
+ const [removing, setRemoving] = React41.useState({});
7307
+ const buildCollapsedText = React41.useCallback((lines) => {
6891
7308
  var _a, _b;
6892
7309
  const line1 = ((_a = lines[0]) == null ? void 0 : _a.text) ?? "";
6893
7310
  const line2 = ((_b = lines[1]) == null ? void 0 : _b.text) ?? "";
@@ -6903,7 +7320,7 @@ function ChatQueue({ items, onRemove }) {
6903
7320
  return `${line1}
6904
7321
  ${trimmedLine2}\u2026 `;
6905
7322
  }, []);
6906
- React40.useEffect(() => {
7323
+ React41.useEffect(() => {
6907
7324
  if (items.length === 0) return;
6908
7325
  const ids = new Set(items.map((item) => item.id));
6909
7326
  setExpanded((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
@@ -6913,7 +7330,7 @@ ${trimmedLine2}\u2026 `;
6913
7330
  }, [items]);
6914
7331
  if (items.length === 0) return null;
6915
7332
  return /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(
6916
- import_react_native52.View,
7333
+ import_react_native53.View,
6917
7334
  {
6918
7335
  style: {
6919
7336
  borderWidth: 1,
@@ -6925,7 +7342,7 @@ ${trimmedLine2}\u2026 `;
6925
7342
  },
6926
7343
  children: [
6927
7344
  /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(Text, { variant: "caption", style: { marginBottom: theme.spacing.sm }, children: "Queue" }),
6928
- /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_react_native52.View, { style: { gap: theme.spacing.sm }, children: items.map((item) => {
7345
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_react_native53.View, { style: { gap: theme.spacing.sm }, children: items.map((item) => {
6929
7346
  const isExpanded = Boolean(expanded[item.id]);
6930
7347
  const showToggle = Boolean(canExpand[item.id]);
6931
7348
  const prompt = item.prompt ?? "";
@@ -6933,7 +7350,7 @@ ${trimmedLine2}\u2026 `;
6933
7350
  const displayPrompt = !isExpanded && showToggle && collapsedText[item.id] ? collapsedText[item.id] : prompt;
6934
7351
  const isRemoving = Boolean(removing[item.id]);
6935
7352
  return /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(
6936
- import_react_native52.View,
7353
+ import_react_native53.View,
6937
7354
  {
6938
7355
  style: {
6939
7356
  flexDirection: "row",
@@ -6945,7 +7362,7 @@ ${trimmedLine2}\u2026 `;
6945
7362
  backgroundColor: withAlpha(theme.colors.surface, theme.scheme === "dark" ? 0.8 : 0.9)
6946
7363
  },
6947
7364
  children: [
6948
- /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_react_native52.View, { style: { flex: 1 }, children: [
7365
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_react_native53.View, { style: { flex: 1 }, children: [
6949
7366
  !canExpand[item.id] ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
6950
7367
  Text,
6951
7368
  {
@@ -6985,7 +7402,7 @@ ${trimmedLine2}\u2026 `;
6985
7402
  }
6986
7403
  ),
6987
7404
  showToggle && isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
6988
- import_react_native52.Pressable,
7405
+ import_react_native53.Pressable,
6989
7406
  {
6990
7407
  onPress: () => setExpanded((prev) => ({ ...prev, [item.id]: false })),
6991
7408
  hitSlop: 6,
@@ -6995,7 +7412,7 @@ ${trimmedLine2}\u2026 `;
6995
7412
  ) : null
6996
7413
  ] }),
6997
7414
  /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
6998
- import_react_native52.Pressable,
7415
+ import_react_native53.Pressable,
6999
7416
  {
7000
7417
  onPress: () => {
7001
7418
  if (!onRemove || isRemoving) return;
@@ -7010,7 +7427,7 @@ ${trimmedLine2}\u2026 `;
7010
7427
  },
7011
7428
  hitSlop: 8,
7012
7429
  style: { alignSelf: "center" },
7013
- children: isRemoving ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_react_native52.ActivityIndicator, { size: "small", color: theme.colors.text }) : /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(IconClose, { size: 14, colorToken: "text" })
7430
+ children: isRemoving ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_react_native53.ActivityIndicator, { size: "small", color: theme.colors.text }) : /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(IconClose, { size: 14, colorToken: "text" })
7014
7431
  }
7015
7432
  )
7016
7433
  ]
@@ -7023,8 +7440,137 @@ ${trimmedLine2}\u2026 `;
7023
7440
  );
7024
7441
  }
7025
7442
 
7026
- // src/studio/ui/ChatPanel.tsx
7443
+ // src/components/chat/AgentProgressCard.tsx
7444
+ var import_react_native54 = require("react-native");
7027
7445
  var import_jsx_runtime55 = require("react/jsx-runtime");
7446
+ function titleForPhase(phase) {
7447
+ if (phase === "planning") return "Planning";
7448
+ if (phase === "reasoning") return "Reasoning";
7449
+ if (phase === "analyzing") return "Analyzing";
7450
+ if (phase === "editing") return "Editing";
7451
+ if (phase === "executing") return "Executing";
7452
+ if (phase === "validating") return "Validating";
7453
+ if (phase === "finalizing") return "Finalizing";
7454
+ if (phase === "working") return "Working";
7455
+ return "Working";
7456
+ }
7457
+ function titleForStatus(status) {
7458
+ if (status === "succeeded") return "Completed";
7459
+ if (status === "failed") return "Failed";
7460
+ if (status === "cancelled") return "Cancelled";
7461
+ return "In Progress";
7462
+ }
7463
+ function AgentProgressCard({ progress }) {
7464
+ const theme = useTheme();
7465
+ const statusLabel = titleForStatus(progress.status);
7466
+ const phaseLabel = titleForPhase(progress.phase);
7467
+ const subtitle = progress.latestMessage || `Agent is ${phaseLabel.toLowerCase()}...`;
7468
+ const todo = progress.todoSummary;
7469
+ return /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(
7470
+ import_react_native54.View,
7471
+ {
7472
+ style: {
7473
+ borderWidth: 1,
7474
+ borderColor: theme.colors.border,
7475
+ borderRadius: theme.radii.lg,
7476
+ marginHorizontal: theme.spacing.md,
7477
+ padding: theme.spacing.md,
7478
+ backgroundColor: withAlpha(theme.colors.surface, theme.scheme === "dark" ? 0.84 : 0.94)
7479
+ },
7480
+ children: [
7481
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(import_react_native54.View, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginBottom: 4 }, children: [
7482
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Text, { variant: "caption", children: statusLabel }),
7483
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Text, { variant: "captionMuted", children: phaseLabel })
7484
+ ] }),
7485
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Text, { variant: "bodyMuted", children: subtitle }),
7486
+ progress.changedFilesCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(Text, { variant: "captionMuted", style: { marginTop: 8 }, children: [
7487
+ "Updated files: ",
7488
+ progress.changedFilesCount
7489
+ ] }) : null,
7490
+ progress.recentFiles.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(import_react_native54.View, { style: { marginTop: 6 }, children: progress.recentFiles.map((path) => /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(Text, { variant: "captionMuted", numberOfLines: 1, children: [
7491
+ "\u2022 ",
7492
+ path
7493
+ ] }, path)) }) : null,
7494
+ todo ? /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(Text, { variant: "captionMuted", style: { marginTop: 8 }, children: [
7495
+ "Todos: ",
7496
+ todo.completed,
7497
+ "/",
7498
+ todo.total,
7499
+ " complete",
7500
+ todo.currentTask ? ` \u2022 ${todo.currentTask}` : ""
7501
+ ] }) : null
7502
+ ]
7503
+ }
7504
+ );
7505
+ }
7506
+
7507
+ // src/components/chat/BundleProgressCard.tsx
7508
+ var import_react_native55 = require("react-native");
7509
+ var import_jsx_runtime56 = require("react/jsx-runtime");
7510
+ function titleForStatus2(status) {
7511
+ if (status === "succeeded") return "Completed";
7512
+ if (status === "failed") return "Failed";
7513
+ return "In Progress";
7514
+ }
7515
+ function BundleProgressCard({ progress }) {
7516
+ const theme = useTheme();
7517
+ const statusLabel = titleForStatus2(progress.status);
7518
+ const percent = Math.round(Math.max(0, Math.min(1, progress.progressValue)) * 100);
7519
+ const fillColor = progress.status === "failed" ? theme.colors.danger : progress.status === "succeeded" ? theme.colors.success : theme.colors.warning;
7520
+ const detail = progress.errorMessage || progress.phaseLabel;
7521
+ return /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(
7522
+ import_react_native55.View,
7523
+ {
7524
+ accessible: true,
7525
+ accessibilityRole: "progressbar",
7526
+ accessibilityLabel: `Bundle progress ${statusLabel}`,
7527
+ accessibilityValue: { min: 0, max: 100, now: percent, text: `${percent}%` },
7528
+ style: {
7529
+ borderWidth: 1,
7530
+ borderColor: theme.colors.border,
7531
+ borderRadius: theme.radii.lg,
7532
+ marginHorizontal: theme.spacing.md,
7533
+ padding: theme.spacing.md,
7534
+ backgroundColor: withAlpha(theme.colors.surface, theme.scheme === "dark" ? 0.84 : 0.94)
7535
+ },
7536
+ children: [
7537
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(import_react_native55.View, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }, children: [
7538
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Text, { variant: "caption", children: statusLabel }),
7539
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(Text, { variant: "captionMuted", children: [
7540
+ percent,
7541
+ "%"
7542
+ ] })
7543
+ ] }),
7544
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
7545
+ import_react_native55.View,
7546
+ {
7547
+ style: {
7548
+ width: "100%",
7549
+ height: 8,
7550
+ borderRadius: 999,
7551
+ backgroundColor: withAlpha(theme.colors.border, theme.scheme === "dark" ? 0.5 : 0.6),
7552
+ overflow: "hidden"
7553
+ },
7554
+ children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
7555
+ import_react_native55.View,
7556
+ {
7557
+ style: {
7558
+ width: `${percent}%`,
7559
+ height: "100%",
7560
+ backgroundColor: fillColor
7561
+ }
7562
+ }
7563
+ )
7564
+ }
7565
+ ),
7566
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Text, { variant: "captionMuted", numberOfLines: 1, style: { marginTop: 8, minHeight: 16 }, children: detail })
7567
+ ]
7568
+ }
7569
+ );
7570
+ }
7571
+
7572
+ // src/studio/ui/ChatPanel.tsx
7573
+ var import_jsx_runtime57 = require("react/jsx-runtime");
7028
7574
  function ChatPanel({
7029
7575
  title = "Chat",
7030
7576
  messages,
@@ -7045,11 +7591,13 @@ function ChatPanel({
7045
7591
  onRetryMessage,
7046
7592
  isRetryingMessage,
7047
7593
  queueItems = [],
7048
- onRemoveQueueItem
7594
+ onRemoveQueueItem,
7595
+ progress = null
7049
7596
  }) {
7050
- const listRef = React41.useRef(null);
7051
- const [nearBottom, setNearBottom] = React41.useState(true);
7052
- const handleSend = React41.useCallback(
7597
+ const theme = useTheme();
7598
+ const listRef = React42.useRef(null);
7599
+ const [nearBottom, setNearBottom] = React42.useState(true);
7600
+ const handleSend = React42.useCallback(
7053
7601
  async (text, composerAttachments) => {
7054
7602
  const all = composerAttachments ?? attachments;
7055
7603
  await onSend(text, all.length > 0 ? all : void 0);
@@ -7063,25 +7611,25 @@ function ChatPanel({
7063
7611
  },
7064
7612
  [attachments, nearBottom, onClearAttachments, onSend]
7065
7613
  );
7066
- const handleScrollToBottom = React41.useCallback(() => {
7614
+ const handleScrollToBottom = React42.useCallback(() => {
7067
7615
  var _a;
7068
7616
  (_a = listRef.current) == null ? void 0 : _a.scrollToBottom({ animated: true });
7069
7617
  }, []);
7070
- const header = /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
7618
+ const header = /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7071
7619
  ChatHeader,
7072
7620
  {
7073
- left: /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(import_react_native53.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
7074
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(StudioSheetHeaderIconButton, { onPress: onBack, accessibilityLabel: "Back", style: { marginRight: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(IconBack, { size: 20, colorToken: "floatingContent" }) }),
7075
- onNavigateHome ? /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(StudioSheetHeaderIconButton, { onPress: onNavigateHome, accessibilityLabel: "Home", children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(IconHome, { size: 20, colorToken: "floatingContent" }) }) : null
7621
+ left: /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(import_react_native56.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
7622
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(StudioSheetHeaderIconButton, { onPress: onBack, accessibilityLabel: "Back", style: { marginRight: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(IconBack, { size: 20, colorToken: "floatingContent" }) }),
7623
+ onNavigateHome ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(StudioSheetHeaderIconButton, { onPress: onNavigateHome, accessibilityLabel: "Home", children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(IconHome, { size: 20, colorToken: "floatingContent" }) }) : null
7076
7624
  ] }),
7077
- right: /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(import_react_native53.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
7078
- onStartDraw ? /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(StudioSheetHeaderIconButton, { onPress: onStartDraw, accessibilityLabel: "Draw", intent: "danger", style: { marginRight: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(IconDraw, { size: 20, colorToken: "onDanger" }) }) : null,
7079
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(StudioSheetHeaderIconButton, { onPress: onClose, accessibilityLabel: "Close", children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(IconClose, { size: 20, colorToken: "floatingContent" }) })
7625
+ right: /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(import_react_native56.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
7626
+ onStartDraw ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(StudioSheetHeaderIconButton, { onPress: onStartDraw, accessibilityLabel: "Draw", intent: "danger", style: { marginRight: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(IconDraw, { size: 20, colorToken: "onDanger" }) }) : null,
7627
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(StudioSheetHeaderIconButton, { onPress: onClose, accessibilityLabel: "Close", children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(IconClose, { size: 20, colorToken: "floatingContent" }) })
7080
7628
  ] }),
7081
7629
  center: null
7082
7630
  }
7083
7631
  );
7084
- const topBanner = shouldForkOnEdit ? /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
7632
+ const topBanner = shouldForkOnEdit ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7085
7633
  ForkNoticeBanner,
7086
7634
  {
7087
7635
  isOwner: !shouldForkOnEdit,
@@ -7090,18 +7638,22 @@ function ChatPanel({
7090
7638
  ) : null;
7091
7639
  const showMessagesLoading = Boolean(loading) && messages.length === 0 || forking;
7092
7640
  if (showMessagesLoading) {
7093
- return /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(import_react_native53.View, { style: { flex: 1 }, children: [
7094
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(import_react_native53.View, { children: header }),
7095
- topBanner ? /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(import_react_native53.View, { style: { paddingHorizontal: 16, paddingTop: 8 }, children: topBanner }) : null,
7096
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(import_react_native53.View, { style: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 24, paddingVertical: 12 }, children: [
7097
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(import_react_native53.ActivityIndicator, {}),
7098
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(import_react_native53.View, { style: { height: 12 } }),
7099
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Text, { variant: "bodyMuted", children: forking ? "Creating your copy\u2026" : "Loading messages\u2026" })
7641
+ return /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(import_react_native56.View, { style: { flex: 1 }, children: [
7642
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(import_react_native56.View, { children: header }),
7643
+ topBanner ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(import_react_native56.View, { style: { paddingHorizontal: 16, paddingTop: 8 }, children: topBanner }) : null,
7644
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(import_react_native56.View, { style: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 24, paddingVertical: 12 }, children: [
7645
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(import_react_native56.ActivityIndicator, {}),
7646
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(import_react_native56.View, { style: { height: 12 } }),
7647
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(Text, { variant: "bodyMuted", children: forking ? "Creating your copy\u2026" : "Loading messages\u2026" })
7100
7648
  ] })
7101
7649
  ] });
7102
7650
  }
7103
- const queueTop = queueItems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(ChatQueue, { items: queueItems, onRemove: onRemoveQueueItem }) : null;
7104
- return /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
7651
+ const bundleProgress = (progress == null ? void 0 : progress.bundle) ?? null;
7652
+ const queueTop = progress || queueItems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(import_react_native56.View, { style: { gap: theme.spacing.sm }, children: [
7653
+ progress ? bundleProgress ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(BundleProgressCard, { progress: bundleProgress }) : /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(AgentProgressCard, { progress }) : null,
7654
+ queueItems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(ChatQueue, { items: queueItems, onRemove: onRemoveQueueItem }) : null
7655
+ ] }) : null;
7656
+ return /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7105
7657
  ChatPage,
7106
7658
  {
7107
7659
  header,
@@ -7114,13 +7666,13 @@ function ChatPanel({
7114
7666
  composerHorizontalPadding: 0,
7115
7667
  listRef,
7116
7668
  onNearBottomChange: setNearBottom,
7117
- overlay: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
7669
+ overlay: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7118
7670
  ScrollToBottomButton,
7119
7671
  {
7120
7672
  visible: !nearBottom,
7121
7673
  onPress: handleScrollToBottom,
7122
7674
  style: { bottom: 80 },
7123
- children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(IconArrowDown, { size: 20, colorToken: "floatingContent" })
7675
+ children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(IconArrowDown, { size: 20, colorToken: "floatingContent" })
7124
7676
  }
7125
7677
  ),
7126
7678
  composer: {
@@ -7140,12 +7692,12 @@ function ChatPanel({
7140
7692
  }
7141
7693
 
7142
7694
  // src/components/dialogs/ConfirmMergeRequestDialog.tsx
7143
- var React42 = __toESM(require("react"));
7144
- var import_react_native55 = require("react-native");
7695
+ var React43 = __toESM(require("react"));
7696
+ var import_react_native58 = require("react-native");
7145
7697
 
7146
7698
  // src/components/primitives/Modal.tsx
7147
- var import_react_native54 = require("react-native");
7148
- var import_jsx_runtime56 = require("react/jsx-runtime");
7699
+ var import_react_native57 = require("react-native");
7700
+ var import_jsx_runtime58 = require("react/jsx-runtime");
7149
7701
  function Modal2({
7150
7702
  visible,
7151
7703
  onRequestClose,
@@ -7154,30 +7706,30 @@ function Modal2({
7154
7706
  contentStyle
7155
7707
  }) {
7156
7708
  const theme = useTheme();
7157
- return /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
7158
- import_react_native54.Modal,
7709
+ return /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
7710
+ import_react_native57.Modal,
7159
7711
  {
7160
7712
  visible,
7161
7713
  transparent: true,
7162
7714
  animationType: "fade",
7163
7715
  onRequestClose,
7164
- children: /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(import_react_native54.View, { style: { flex: 1, backgroundColor: theme.colors.backdrop, justifyContent: "center", padding: theme.spacing.lg }, children: [
7165
- /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
7166
- import_react_native54.Pressable,
7716
+ children: /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(import_react_native57.View, { style: { flex: 1, backgroundColor: theme.colors.backdrop, justifyContent: "center", padding: theme.spacing.lg }, children: [
7717
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
7718
+ import_react_native57.Pressable,
7167
7719
  {
7168
7720
  accessibilityRole: "button",
7169
7721
  onPress: dismissOnBackdropPress ? onRequestClose : void 0,
7170
7722
  style: { position: "absolute", inset: 0 }
7171
7723
  }
7172
7724
  ),
7173
- /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Card, { variant: "surfaceRaised", padded: true, style: [{ borderRadius: theme.radii.xl }, contentStyle], children })
7725
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(Card, { variant: "surfaceRaised", padded: true, style: [{ borderRadius: theme.radii.xl }, contentStyle], children })
7174
7726
  ] })
7175
7727
  }
7176
7728
  );
7177
7729
  }
7178
7730
 
7179
7731
  // src/components/dialogs/ConfirmMergeRequestDialog.tsx
7180
- var import_jsx_runtime57 = require("react/jsx-runtime");
7732
+ var import_jsx_runtime59 = require("react/jsx-runtime");
7181
7733
  function ConfirmMergeRequestDialog({
7182
7734
  visible,
7183
7735
  onOpenChange,
@@ -7188,14 +7740,14 @@ function ConfirmMergeRequestDialog({
7188
7740
  onTestFirst
7189
7741
  }) {
7190
7742
  const theme = useTheme();
7191
- const close = React42.useCallback(() => onOpenChange(false), [onOpenChange]);
7743
+ const close = React43.useCallback(() => onOpenChange(false), [onOpenChange]);
7192
7744
  const canConfirm = Boolean(mergeRequest) && !approveDisabled;
7193
- const handleConfirm = React42.useCallback(() => {
7745
+ const handleConfirm = React43.useCallback(() => {
7194
7746
  if (!mergeRequest) return;
7195
7747
  onOpenChange(false);
7196
7748
  void onConfirm();
7197
7749
  }, [mergeRequest, onConfirm, onOpenChange]);
7198
- const handleTestFirst = React42.useCallback(() => {
7750
+ const handleTestFirst = React43.useCallback(() => {
7199
7751
  if (!mergeRequest) return;
7200
7752
  onOpenChange(false);
7201
7753
  void onTestFirst(mergeRequest);
@@ -7207,7 +7759,7 @@ function ConfirmMergeRequestDialog({
7207
7759
  justifyContent: "center",
7208
7760
  alignSelf: "stretch"
7209
7761
  };
7210
- return /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(
7762
+ return /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(
7211
7763
  Modal2,
7212
7764
  {
7213
7765
  visible,
@@ -7218,7 +7770,7 @@ function ConfirmMergeRequestDialog({
7218
7770
  backgroundColor: theme.colors.background
7219
7771
  },
7220
7772
  children: [
7221
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(import_react_native55.View, { children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7773
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(import_react_native58.View, { children: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
7222
7774
  Text,
7223
7775
  {
7224
7776
  style: {
@@ -7230,9 +7782,9 @@ function ConfirmMergeRequestDialog({
7230
7782
  children: "Are you sure you want to approve this merge request?"
7231
7783
  }
7232
7784
  ) }),
7233
- /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(import_react_native55.View, { style: { marginTop: 16 }, children: [
7234
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7235
- import_react_native55.View,
7785
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(import_react_native58.View, { style: { marginTop: 16 }, children: [
7786
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
7787
+ import_react_native58.View,
7236
7788
  {
7237
7789
  style: [
7238
7790
  fullWidthButtonBase,
@@ -7241,22 +7793,22 @@ function ConfirmMergeRequestDialog({
7241
7793
  opacity: canConfirm ? 1 : 0.5
7242
7794
  }
7243
7795
  ],
7244
- children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7245
- import_react_native55.Pressable,
7796
+ children: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
7797
+ import_react_native58.Pressable,
7246
7798
  {
7247
7799
  accessibilityRole: "button",
7248
7800
  accessibilityLabel: "Approve Merge",
7249
7801
  disabled: !canConfirm,
7250
7802
  onPress: handleConfirm,
7251
7803
  style: [fullWidthButtonBase, { flex: 1 }],
7252
- children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(Text, { style: { textAlign: "center", color: theme.colors.onPrimary }, children: "Approve Merge" })
7804
+ children: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(Text, { style: { textAlign: "center", color: theme.colors.onPrimary }, children: "Approve Merge" })
7253
7805
  }
7254
7806
  )
7255
7807
  }
7256
7808
  ),
7257
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(import_react_native55.View, { style: { height: 8 } }),
7258
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7259
- import_react_native55.View,
7809
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(import_react_native58.View, { style: { height: 8 } }),
7810
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
7811
+ import_react_native58.View,
7260
7812
  {
7261
7813
  style: [
7262
7814
  fullWidthButtonBase,
@@ -7267,22 +7819,22 @@ function ConfirmMergeRequestDialog({
7267
7819
  opacity: isBuilding || !mergeRequest ? 0.5 : 1
7268
7820
  }
7269
7821
  ],
7270
- children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7271
- import_react_native55.Pressable,
7822
+ children: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
7823
+ import_react_native58.Pressable,
7272
7824
  {
7273
7825
  accessibilityRole: "button",
7274
7826
  accessibilityLabel: isBuilding ? "Preparing\u2026" : "Test edits first",
7275
7827
  disabled: isBuilding || !mergeRequest,
7276
7828
  onPress: handleTestFirst,
7277
7829
  style: [fullWidthButtonBase, { flex: 1 }],
7278
- children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(Text, { style: { textAlign: "center", color: theme.colors.text }, children: isBuilding ? "Preparing\u2026" : "Test edits first" })
7830
+ children: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(Text, { style: { textAlign: "center", color: theme.colors.text }, children: isBuilding ? "Preparing\u2026" : "Test edits first" })
7279
7831
  }
7280
7832
  )
7281
7833
  }
7282
7834
  ),
7283
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(import_react_native55.View, { style: { height: 8 } }),
7284
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7285
- import_react_native55.View,
7835
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(import_react_native58.View, { style: { height: 8 } }),
7836
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
7837
+ import_react_native58.View,
7286
7838
  {
7287
7839
  style: [
7288
7840
  fullWidthButtonBase,
@@ -7292,14 +7844,14 @@ function ConfirmMergeRequestDialog({
7292
7844
  borderColor: theme.colors.border
7293
7845
  }
7294
7846
  ],
7295
- children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
7296
- import_react_native55.Pressable,
7847
+ children: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
7848
+ import_react_native58.Pressable,
7297
7849
  {
7298
7850
  accessibilityRole: "button",
7299
7851
  accessibilityLabel: "Cancel",
7300
7852
  onPress: close,
7301
7853
  style: [fullWidthButtonBase, { flex: 1 }],
7302
- children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(Text, { style: { textAlign: "center", color: theme.colors.text }, children: "Cancel" })
7854
+ children: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(Text, { style: { textAlign: "center", color: theme.colors.text }, children: "Cancel" })
7303
7855
  }
7304
7856
  )
7305
7857
  }
@@ -7311,7 +7863,7 @@ function ConfirmMergeRequestDialog({
7311
7863
  }
7312
7864
 
7313
7865
  // src/studio/ui/ConfirmMergeFlow.tsx
7314
- var import_jsx_runtime58 = require("react/jsx-runtime");
7866
+ var import_jsx_runtime60 = require("react/jsx-runtime");
7315
7867
  function ConfirmMergeFlow({
7316
7868
  visible,
7317
7869
  onOpenChange,
@@ -7322,7 +7874,7 @@ function ConfirmMergeFlow({
7322
7874
  onConfirm,
7323
7875
  onTestFirst
7324
7876
  }) {
7325
- return /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
7877
+ return /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
7326
7878
  ConfirmMergeRequestDialog,
7327
7879
  {
7328
7880
  visible,
@@ -7344,7 +7896,7 @@ function ConfirmMergeFlow({
7344
7896
  }
7345
7897
 
7346
7898
  // src/studio/hooks/useOptimisticChatMessages.ts
7347
- var React43 = __toESM(require("react"));
7899
+ var React44 = __toESM(require("react"));
7348
7900
  function makeOptimisticId() {
7349
7901
  return `optimistic:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 10)}`;
7350
7902
  }
@@ -7383,11 +7935,11 @@ function useOptimisticChatMessages({
7383
7935
  chatMessages,
7384
7936
  onSendChat
7385
7937
  }) {
7386
- const [optimisticChat, setOptimisticChat] = React43.useState([]);
7387
- React43.useEffect(() => {
7938
+ const [optimisticChat, setOptimisticChat] = React44.useState([]);
7939
+ React44.useEffect(() => {
7388
7940
  setOptimisticChat([]);
7389
7941
  }, [threadId]);
7390
- const messages = React43.useMemo(() => {
7942
+ const messages = React44.useMemo(() => {
7391
7943
  if (!optimisticChat || optimisticChat.length === 0) return chatMessages;
7392
7944
  const unresolved = optimisticChat.filter((o) => !isOptimisticResolvedByServer(chatMessages, o));
7393
7945
  if (unresolved.length === 0) return chatMessages;
@@ -7403,7 +7955,7 @@ function useOptimisticChatMessages({
7403
7955
  merged.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
7404
7956
  return merged;
7405
7957
  }, [chatMessages, optimisticChat]);
7406
- React43.useEffect(() => {
7958
+ React44.useEffect(() => {
7407
7959
  if (optimisticChat.length === 0) return;
7408
7960
  setOptimisticChat((prev) => {
7409
7961
  if (prev.length === 0) return prev;
@@ -7411,7 +7963,7 @@ function useOptimisticChatMessages({
7411
7963
  return next.length === prev.length ? prev : next;
7412
7964
  });
7413
7965
  }, [chatMessages, optimisticChat.length]);
7414
- const onSend = React43.useCallback(
7966
+ const onSend = React44.useCallback(
7415
7967
  async (text, attachments) => {
7416
7968
  if (shouldForkOnEdit || disableOptimistic) {
7417
7969
  await onSendChat(text, attachments);
@@ -7439,7 +7991,7 @@ function useOptimisticChatMessages({
7439
7991
  },
7440
7992
  [chatMessages, disableOptimistic, onSendChat, shouldForkOnEdit]
7441
7993
  );
7442
- const onRetry = React43.useCallback(
7994
+ const onRetry = React44.useCallback(
7443
7995
  async (messageId) => {
7444
7996
  if (shouldForkOnEdit || disableOptimistic) return;
7445
7997
  const target = optimisticChat.find((m) => m.id === messageId);
@@ -7463,7 +8015,7 @@ function useOptimisticChatMessages({
7463
8015
  },
7464
8016
  [chatMessages, disableOptimistic, onSendChat, optimisticChat, shouldForkOnEdit]
7465
8017
  );
7466
- const isRetrying = React43.useCallback(
8018
+ const isRetrying = React44.useCallback(
7467
8019
  (messageId) => {
7468
8020
  return optimisticChat.some((m) => m.id === messageId && m.retrying);
7469
8021
  },
@@ -7474,7 +8026,7 @@ function useOptimisticChatMessages({
7474
8026
 
7475
8027
  // src/studio/ui/StudioOverlay.tsx
7476
8028
  var import_studio_control = require("@comergehq/studio-control");
7477
- var import_jsx_runtime59 = require("react/jsx-runtime");
8029
+ var import_jsx_runtime61 = require("react/jsx-runtime");
7478
8030
  function StudioOverlay({
7479
8031
  captureTargetRef,
7480
8032
  app,
@@ -7507,19 +8059,20 @@ function StudioOverlay({
7507
8059
  onSendChat,
7508
8060
  chatQueueItems,
7509
8061
  onRemoveQueueItem,
8062
+ chatProgress,
7510
8063
  onNavigateHome,
7511
8064
  showBubble,
7512
8065
  studioControlOptions
7513
8066
  }) {
7514
8067
  const theme = useTheme();
7515
- const { width } = (0, import_react_native56.useWindowDimensions)();
7516
- const [sheetOpen, setSheetOpen] = React44.useState(false);
7517
- const sheetOpenRef = React44.useRef(sheetOpen);
7518
- const [activePage, setActivePage] = React44.useState("preview");
7519
- const [drawing, setDrawing] = React44.useState(false);
7520
- const [chatAttachments, setChatAttachments] = React44.useState([]);
7521
- const [commentsAppId, setCommentsAppId] = React44.useState(null);
7522
- const [commentsCount, setCommentsCount] = React44.useState(null);
8068
+ const { width } = (0, import_react_native59.useWindowDimensions)();
8069
+ const [sheetOpen, setSheetOpen] = React45.useState(false);
8070
+ const sheetOpenRef = React45.useRef(sheetOpen);
8071
+ const [activePage, setActivePage] = React45.useState("preview");
8072
+ const [drawing, setDrawing] = React45.useState(false);
8073
+ const [chatAttachments, setChatAttachments] = React45.useState([]);
8074
+ const [commentsAppId, setCommentsAppId] = React45.useState(null);
8075
+ const [commentsCount, setCommentsCount] = React45.useState(null);
7523
8076
  const threadId = (app == null ? void 0 : app.threadId) ?? null;
7524
8077
  const isForking = chatForking || (app == null ? void 0 : app.status) === "forking";
7525
8078
  const queueItemsForChat = isForking ? [] : chatQueueItems;
@@ -7531,26 +8084,26 @@ function StudioOverlay({
7531
8084
  chatMessages,
7532
8085
  onSendChat
7533
8086
  });
7534
- const [confirmMrId, setConfirmMrId] = React44.useState(null);
7535
- const confirmMr = React44.useMemo(
8087
+ const [confirmMrId, setConfirmMrId] = React45.useState(null);
8088
+ const confirmMr = React45.useMemo(
7536
8089
  () => confirmMrId ? incomingMergeRequests.find((m) => m.id === confirmMrId) ?? null : null,
7537
8090
  [confirmMrId, incomingMergeRequests]
7538
8091
  );
7539
- const handleSheetOpenChange = React44.useCallback((open) => {
8092
+ const handleSheetOpenChange = React45.useCallback((open) => {
7540
8093
  setSheetOpen(open);
7541
- if (!open) import_react_native56.Keyboard.dismiss();
8094
+ if (!open) import_react_native59.Keyboard.dismiss();
7542
8095
  }, []);
7543
- const closeSheet = React44.useCallback(() => {
8096
+ const closeSheet = React45.useCallback(() => {
7544
8097
  handleSheetOpenChange(false);
7545
8098
  }, [handleSheetOpenChange]);
7546
- const openSheet = React44.useCallback(() => setSheetOpen(true), []);
7547
- const goToChat = React44.useCallback(() => {
8099
+ const openSheet = React45.useCallback(() => setSheetOpen(true), []);
8100
+ const goToChat = React45.useCallback(() => {
7548
8101
  setActivePage("chat");
7549
8102
  openSheet();
7550
8103
  }, [openSheet]);
7551
- const backToPreview = React44.useCallback(() => {
7552
- if (import_react_native56.Platform.OS !== "ios") {
7553
- import_react_native56.Keyboard.dismiss();
8104
+ const backToPreview = React45.useCallback(() => {
8105
+ if (import_react_native59.Platform.OS !== "ios") {
8106
+ import_react_native59.Keyboard.dismiss();
7554
8107
  setActivePage("preview");
7555
8108
  return;
7556
8109
  }
@@ -7562,15 +8115,15 @@ function StudioOverlay({
7562
8115
  clearTimeout(t);
7563
8116
  setActivePage("preview");
7564
8117
  };
7565
- const sub = import_react_native56.Keyboard.addListener("keyboardDidHide", finalize);
8118
+ const sub = import_react_native59.Keyboard.addListener("keyboardDidHide", finalize);
7566
8119
  const t = setTimeout(finalize, 350);
7567
- import_react_native56.Keyboard.dismiss();
8120
+ import_react_native59.Keyboard.dismiss();
7568
8121
  }, []);
7569
- const startDraw = React44.useCallback(() => {
8122
+ const startDraw = React45.useCallback(() => {
7570
8123
  setDrawing(true);
7571
8124
  closeSheet();
7572
8125
  }, [closeSheet]);
7573
- const handleDrawCapture = React44.useCallback(
8126
+ const handleDrawCapture = React45.useCallback(
7574
8127
  (dataUrl) => {
7575
8128
  setChatAttachments((prev) => [...prev, dataUrl]);
7576
8129
  setDrawing(false);
@@ -7579,7 +8132,7 @@ function StudioOverlay({
7579
8132
  },
7580
8133
  [openSheet]
7581
8134
  );
7582
- const toggleSheet = React44.useCallback(async () => {
8135
+ const toggleSheet = React45.useCallback(async () => {
7583
8136
  if (!sheetOpen) {
7584
8137
  const shouldExitTest = Boolean(testingMrId) || isTesting;
7585
8138
  if (shouldExitTest) {
@@ -7591,7 +8144,7 @@ function StudioOverlay({
7591
8144
  closeSheet();
7592
8145
  }
7593
8146
  }, [closeSheet, isTesting, onRestoreBase, sheetOpen, testingMrId]);
7594
- const handleTestMr = React44.useCallback(
8147
+ const handleTestMr = React45.useCallback(
7595
8148
  async (mr) => {
7596
8149
  if (!onTestMr) return;
7597
8150
  await onTestMr(mr);
@@ -7599,10 +8152,10 @@ function StudioOverlay({
7599
8152
  },
7600
8153
  [closeSheet, onTestMr]
7601
8154
  );
7602
- React44.useEffect(() => {
8155
+ React45.useEffect(() => {
7603
8156
  sheetOpenRef.current = sheetOpen;
7604
8157
  }, [sheetOpen]);
7605
- React44.useEffect(() => {
8158
+ React45.useEffect(() => {
7606
8159
  const poller = (0, import_studio_control.startStudioControlPolling)((action) => {
7607
8160
  if (action === "show" && !sheetOpenRef.current) openSheet();
7608
8161
  if (action === "hide" && sheetOpenRef.current) closeSheet();
@@ -7610,17 +8163,17 @@ function StudioOverlay({
7610
8163
  }, studioControlOptions);
7611
8164
  return () => poller.stop();
7612
8165
  }, [closeSheet, openSheet, studioControlOptions, toggleSheet]);
7613
- React44.useEffect(() => {
8166
+ React45.useEffect(() => {
7614
8167
  void (0, import_studio_control.publishComergeStudioUIState)(sheetOpen, studioControlOptions);
7615
8168
  }, [sheetOpen, studioControlOptions]);
7616
- return /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(import_jsx_runtime59.Fragment, { children: [
7617
- /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(EdgeGlowFrame, { visible: isTesting, role: "accent", thickness: 40, intensity: 1 }),
7618
- /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(StudioBottomSheet, { open: sheetOpen, onOpenChange: handleSheetOpenChange, children: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
8169
+ return /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)(import_jsx_runtime61.Fragment, { children: [
8170
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(EdgeGlowFrame, { visible: isTesting, role: "accent", thickness: 40, intensity: 1 }),
8171
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(StudioBottomSheet, { open: sheetOpen, onOpenChange: handleSheetOpenChange, children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
7619
8172
  StudioSheetPager,
7620
8173
  {
7621
8174
  activePage,
7622
8175
  width,
7623
- preview: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
8176
+ preview: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
7624
8177
  PreviewPanel,
7625
8178
  {
7626
8179
  app,
@@ -7649,7 +8202,7 @@ function StudioOverlay({
7649
8202
  commentCountOverride: commentsCount ?? void 0
7650
8203
  }
7651
8204
  ),
7652
- chat: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
8205
+ chat: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
7653
8206
  ChatPanel,
7654
8207
  {
7655
8208
  messages: optimistic.messages,
@@ -7670,12 +8223,13 @@ function StudioOverlay({
7670
8223
  onRetryMessage: optimistic.onRetry,
7671
8224
  isRetryingMessage: optimistic.isRetrying,
7672
8225
  queueItems: queueItemsForChat,
7673
- onRemoveQueueItem
8226
+ onRemoveQueueItem,
8227
+ progress: chatProgress
7674
8228
  }
7675
8229
  )
7676
8230
  }
7677
8231
  ) }),
7678
- showBubble && /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
8232
+ showBubble && /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
7679
8233
  Bubble,
7680
8234
  {
7681
8235
  visible: !sheetOpen && !drawing,
@@ -7684,10 +8238,10 @@ function StudioOverlay({
7684
8238
  onPress: toggleSheet,
7685
8239
  isLoading: (app == null ? void 0 : app.status) === "editing" || isBaseBundleDownloading,
7686
8240
  loadingBorderTone: isBaseBundleDownloading ? "warning" : "default",
7687
- children: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(import_react_native56.View, { style: { width: 28, height: 28, alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(MergeIcon, { width: 24, height: 24, color: theme.colors.floatingContent }) })
8241
+ children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(import_react_native59.View, { style: { width: 28, height: 28, alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(MergeIcon, { width: 24, height: 24, color: theme.colors.floatingContent }) })
7688
8242
  }
7689
8243
  ),
7690
- /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
8244
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
7691
8245
  DrawModeOverlay,
7692
8246
  {
7693
8247
  visible: drawing,
@@ -7696,7 +8250,7 @@ function StudioOverlay({
7696
8250
  onCapture: handleDrawCapture
7697
8251
  }
7698
8252
  ),
7699
- /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
8253
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
7700
8254
  ConfirmMergeFlow,
7701
8255
  {
7702
8256
  visible: Boolean(confirmMr),
@@ -7710,7 +8264,7 @@ function StudioOverlay({
7710
8264
  onTestFirst: handleTestMr
7711
8265
  }
7712
8266
  ),
7713
- /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
8267
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
7714
8268
  AppCommentsSheet,
7715
8269
  {
7716
8270
  appId: commentsAppId,
@@ -7723,7 +8277,7 @@ function StudioOverlay({
7723
8277
  }
7724
8278
 
7725
8279
  // src/studio/hooks/useEditQueue.ts
7726
- var React45 = __toESM(require("react"));
8280
+ var React46 = __toESM(require("react"));
7727
8281
 
7728
8282
  // src/data/apps/edit-queue/remote.ts
7729
8283
  var EditQueueRemoteDataSourceImpl = class extends BaseRemote {
@@ -7832,17 +8386,17 @@ var editQueueRepository = new EditQueueRepositoryImpl(
7832
8386
 
7833
8387
  // src/studio/hooks/useEditQueue.ts
7834
8388
  function useEditQueue(appId) {
7835
- const [items, setItems] = React45.useState([]);
7836
- const [loading, setLoading] = React45.useState(false);
7837
- const [error, setError] = React45.useState(null);
7838
- const activeRequestIdRef = React45.useRef(0);
8389
+ const [items, setItems] = React46.useState([]);
8390
+ const [loading, setLoading] = React46.useState(false);
8391
+ const [error, setError] = React46.useState(null);
8392
+ const activeRequestIdRef = React46.useRef(0);
7839
8393
  const foregroundSignal = useForegroundSignal(Boolean(appId));
7840
- const upsertSorted = React45.useCallback((prev, nextItem) => {
8394
+ const upsertSorted = React46.useCallback((prev, nextItem) => {
7841
8395
  const next = prev.some((x) => x.id === nextItem.id) ? prev.map((x) => x.id === nextItem.id ? nextItem : x) : [...prev, nextItem];
7842
8396
  next.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
7843
8397
  return next;
7844
8398
  }, []);
7845
- const refetch = React45.useCallback(async () => {
8399
+ const refetch = React46.useCallback(async () => {
7846
8400
  if (!appId) {
7847
8401
  setItems([]);
7848
8402
  return;
@@ -7862,10 +8416,10 @@ function useEditQueue(appId) {
7862
8416
  if (activeRequestIdRef.current === requestId) setLoading(false);
7863
8417
  }
7864
8418
  }, [appId]);
7865
- React45.useEffect(() => {
8419
+ React46.useEffect(() => {
7866
8420
  void refetch();
7867
8421
  }, [refetch]);
7868
- React45.useEffect(() => {
8422
+ React46.useEffect(() => {
7869
8423
  if (!appId) return;
7870
8424
  const unsubscribe = editQueueRepository.subscribeEditQueue(appId, {
7871
8425
  onInsert: (item) => setItems((prev) => upsertSorted(prev, item)),
@@ -7874,7 +8428,7 @@ function useEditQueue(appId) {
7874
8428
  });
7875
8429
  return unsubscribe;
7876
8430
  }, [appId, upsertSorted, foregroundSignal]);
7877
- React45.useEffect(() => {
8431
+ React46.useEffect(() => {
7878
8432
  if (!appId) return;
7879
8433
  if (foregroundSignal <= 0) return;
7880
8434
  void refetch();
@@ -7883,16 +8437,16 @@ function useEditQueue(appId) {
7883
8437
  }
7884
8438
 
7885
8439
  // src/studio/hooks/useEditQueueActions.ts
7886
- var React46 = __toESM(require("react"));
8440
+ var React47 = __toESM(require("react"));
7887
8441
  function useEditQueueActions(appId) {
7888
- const update = React46.useCallback(
8442
+ const update = React47.useCallback(
7889
8443
  async (queueItemId, payload) => {
7890
8444
  if (!appId) return;
7891
8445
  await editQueueRepository.update(appId, queueItemId, payload);
7892
8446
  },
7893
8447
  [appId]
7894
8448
  );
7895
- const cancel = React46.useCallback(
8449
+ const cancel = React47.useCallback(
7896
8450
  async (queueItemId) => {
7897
8451
  if (!appId) return;
7898
8452
  await editQueueRepository.cancel(appId, queueItemId);
@@ -7902,48 +8456,461 @@ function useEditQueueActions(appId) {
7902
8456
  return { update, cancel };
7903
8457
  }
7904
8458
 
8459
+ // src/studio/hooks/useAgentRunProgress.ts
8460
+ var React48 = __toESM(require("react"));
8461
+
8462
+ // src/data/agent-progress/repository.ts
8463
+ function mapRun(row) {
8464
+ return {
8465
+ id: row.id,
8466
+ appId: row.app_id,
8467
+ threadId: row.thread_id,
8468
+ queueItemId: row.queue_item_id,
8469
+ status: row.status,
8470
+ currentPhase: row.current_phase,
8471
+ lastSeq: Number(row.last_seq || 0),
8472
+ summary: row.summary || {},
8473
+ errorCode: row.error_code,
8474
+ errorMessage: row.error_message,
8475
+ startedAt: row.started_at,
8476
+ finishedAt: row.finished_at,
8477
+ createdAt: row.created_at,
8478
+ updatedAt: row.updated_at
8479
+ };
8480
+ }
8481
+ function mapEvent(row) {
8482
+ return {
8483
+ id: row.id,
8484
+ runId: row.run_id,
8485
+ appId: row.app_id,
8486
+ threadId: row.thread_id,
8487
+ queueItemId: row.queue_item_id,
8488
+ seq: Number(row.seq || 0),
8489
+ eventType: row.event_type,
8490
+ phase: row.phase,
8491
+ toolName: row.tool_name,
8492
+ path: row.path,
8493
+ payload: row.payload || {},
8494
+ createdAt: row.created_at
8495
+ };
8496
+ }
8497
+ var AgentProgressRepositoryImpl = class {
8498
+ async getLatestRun(threadId) {
8499
+ if (!threadId) return null;
8500
+ const supabase = getSupabaseClient();
8501
+ const { data, error } = await supabase.from("agent_run").select("*").eq("thread_id", threadId).order("started_at", { ascending: false }).limit(1).maybeSingle();
8502
+ if (error) throw new Error(error.message || "Failed to fetch latest agent run");
8503
+ if (!data) return null;
8504
+ return mapRun(data);
8505
+ }
8506
+ async listEvents(runId, afterSeq) {
8507
+ if (!runId) return [];
8508
+ const supabase = getSupabaseClient();
8509
+ let query = supabase.from("agent_run_event").select("*").eq("run_id", runId).order("seq", { ascending: true });
8510
+ if (typeof afterSeq === "number" && Number.isFinite(afterSeq) && afterSeq > 0) {
8511
+ query = query.gt("seq", afterSeq);
8512
+ }
8513
+ const { data, error } = await query;
8514
+ if (error) throw new Error(error.message || "Failed to fetch agent run events");
8515
+ const rows = Array.isArray(data) ? data : [];
8516
+ return rows.map(mapEvent);
8517
+ }
8518
+ subscribeThreadRuns(threadId, handlers) {
8519
+ return subscribeManagedChannel(`agent-progress:runs:thread:${threadId}`, (channel) => {
8520
+ channel.on(
8521
+ "postgres_changes",
8522
+ { event: "INSERT", schema: "public", table: "agent_run", filter: `thread_id=eq.${threadId}` },
8523
+ (payload) => {
8524
+ var _a;
8525
+ const row = payload.new;
8526
+ (_a = handlers.onInsert) == null ? void 0 : _a.call(handlers, mapRun(row));
8527
+ }
8528
+ ).on(
8529
+ "postgres_changes",
8530
+ { event: "UPDATE", schema: "public", table: "agent_run", filter: `thread_id=eq.${threadId}` },
8531
+ (payload) => {
8532
+ var _a;
8533
+ const row = payload.new;
8534
+ (_a = handlers.onUpdate) == null ? void 0 : _a.call(handlers, mapRun(row));
8535
+ }
8536
+ );
8537
+ });
8538
+ }
8539
+ subscribeRunEvents(runId, handlers) {
8540
+ return subscribeManagedChannel(`agent-progress:events:run:${runId}`, (channel) => {
8541
+ channel.on(
8542
+ "postgres_changes",
8543
+ { event: "INSERT", schema: "public", table: "agent_run_event", filter: `run_id=eq.${runId}` },
8544
+ (payload) => {
8545
+ var _a;
8546
+ const row = payload.new;
8547
+ (_a = handlers.onInsert) == null ? void 0 : _a.call(handlers, mapEvent(row));
8548
+ }
8549
+ ).on(
8550
+ "postgres_changes",
8551
+ { event: "UPDATE", schema: "public", table: "agent_run_event", filter: `run_id=eq.${runId}` },
8552
+ (payload) => {
8553
+ var _a;
8554
+ const row = payload.new;
8555
+ (_a = handlers.onUpdate) == null ? void 0 : _a.call(handlers, mapEvent(row));
8556
+ }
8557
+ );
8558
+ });
8559
+ }
8560
+ };
8561
+ var agentProgressRepository = new AgentProgressRepositoryImpl();
8562
+
8563
+ // src/studio/hooks/useAgentRunProgress.ts
8564
+ function upsertBySeq(prev, next) {
8565
+ const map = /* @__PURE__ */ new Map();
8566
+ for (const item of prev) map.set(item.seq, item);
8567
+ map.set(next.seq, next);
8568
+ return Array.from(map.values()).sort((a, b) => a.seq - b.seq);
8569
+ }
8570
+ function mergeMany(prev, incoming) {
8571
+ if (incoming.length === 0) return prev;
8572
+ const map = /* @__PURE__ */ new Map();
8573
+ for (const item of prev) map.set(item.seq, item);
8574
+ for (const item of incoming) map.set(item.seq, item);
8575
+ return Array.from(map.values()).sort((a, b) => a.seq - b.seq);
8576
+ }
8577
+ function toMs(v) {
8578
+ if (!v) return 0;
8579
+ const n = Date.parse(v);
8580
+ return Number.isFinite(n) ? n : 0;
8581
+ }
8582
+ function shouldSwitchRun(current, candidate) {
8583
+ if (!current) return true;
8584
+ if (candidate.id === current.id) return true;
8585
+ return toMs(candidate.startedAt) >= toMs(current.startedAt);
8586
+ }
8587
+ function toInt(value) {
8588
+ const n = Number(value);
8589
+ return Number.isFinite(n) ? n : 0;
8590
+ }
8591
+ function toBundleStage(value) {
8592
+ if (value === "queued") return "queued";
8593
+ if (value === "building") return "building";
8594
+ if (value === "fixing") return "fixing";
8595
+ if (value === "retrying") return "retrying";
8596
+ if (value === "finalizing") return "finalizing";
8597
+ if (value === "ready") return "ready";
8598
+ if (value === "failed") return "failed";
8599
+ return null;
8600
+ }
8601
+ function toBundlePlatform(value) {
8602
+ if (value === "ios" || value === "android") return value;
8603
+ return "both";
8604
+ }
8605
+ function clamp01(value) {
8606
+ if (value <= 0) return 0;
8607
+ if (value >= 1) return 1;
8608
+ return value;
8609
+ }
8610
+ function defaultBundleLabel(stage) {
8611
+ if (stage === "queued") return "Queued for build";
8612
+ if (stage === "building") return "Building bundle";
8613
+ if (stage === "fixing") return "Applying auto-fix";
8614
+ if (stage === "retrying") return "Retrying bundle";
8615
+ if (stage === "finalizing") return "Finalizing artifacts";
8616
+ if (stage === "ready") return "Bundle ready";
8617
+ if (stage === "failed") return "Bundle failed";
8618
+ return "Building bundle";
8619
+ }
8620
+ function fallbackBundleProgress(stage, startedAtMs, nowMs) {
8621
+ if (stage === "ready") return 1;
8622
+ if (stage === "failed") return 0.96;
8623
+ const elapsed = Math.max(0, nowMs - startedAtMs);
8624
+ const expectedMs = 6e4;
8625
+ if (elapsed <= expectedMs) {
8626
+ const t = clamp01(elapsed / expectedMs);
8627
+ return 0.05 + 0.85 * (1 - Math.pow(1 - t, 2));
8628
+ }
8629
+ const over = elapsed - expectedMs;
8630
+ return Math.min(0.9 + 0.07 * (1 - Math.exp(-over / 25e3)), 0.97);
8631
+ }
8632
+ function deriveView(run, events, nowMs) {
8633
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
8634
+ const files = [];
8635
+ const fileSeen = /* @__PURE__ */ new Set();
8636
+ let todoSummary = null;
8637
+ let latestMessage = null;
8638
+ let phase = (run == null ? void 0 : run.currentPhase) ?? null;
8639
+ let bundleStage = null;
8640
+ let bundleLabel = null;
8641
+ let bundleError = null;
8642
+ let bundleProgressHint = null;
8643
+ let bundlePlatform = "both";
8644
+ let bundleStartedAtMs = null;
8645
+ let lastBundleSig = null;
8646
+ for (const ev of events) {
8647
+ if (ev.eventType === "phase_changed") {
8648
+ if (typeof ((_a = ev.payload) == null ? void 0 : _a.message) === "string") latestMessage = ev.payload.message;
8649
+ if (ev.phase) phase = ev.phase;
8650
+ }
8651
+ if (ev.eventType === "file_changed") {
8652
+ if (ev.path && !fileSeen.has(ev.path)) {
8653
+ fileSeen.add(ev.path);
8654
+ files.push(ev.path);
8655
+ }
8656
+ }
8657
+ if (ev.eventType === "todo_updated") {
8658
+ todoSummary = {
8659
+ total: toInt((_b = ev.payload) == null ? void 0 : _b.total),
8660
+ pending: toInt((_c = ev.payload) == null ? void 0 : _c.pending),
8661
+ inProgress: toInt((_d = ev.payload) == null ? void 0 : _d.inProgress),
8662
+ completed: toInt((_e = ev.payload) == null ? void 0 : _e.completed),
8663
+ currentTask: typeof ((_f = ev.payload) == null ? void 0 : _f.currentTask) === "string" ? ev.payload.currentTask : null
8664
+ };
8665
+ }
8666
+ const stageType = typeof ((_g = ev.payload) == null ? void 0 : _g.stage) === "string" ? ev.payload.stage : null;
8667
+ if (stageType !== "bundle") continue;
8668
+ const nextStage = toBundleStage((_h = ev.payload) == null ? void 0 : _h.bundlePhase);
8669
+ if (!nextStage) continue;
8670
+ const nextPlatform = toBundlePlatform((_i = ev.payload) == null ? void 0 : _i.platform);
8671
+ const message = typeof ((_j = ev.payload) == null ? void 0 : _j.message) === "string" ? ev.payload.message : null;
8672
+ const phaseLabel = message || (typeof ((_k = ev.payload) == null ? void 0 : _k.message) === "string" ? ev.payload.message : null);
8673
+ const hintRaw = Number((_l = ev.payload) == null ? void 0 : _l.progressHint);
8674
+ const progressHint = Number.isFinite(hintRaw) ? clamp01(hintRaw) : null;
8675
+ const errorText = typeof ((_m = ev.payload) == null ? void 0 : _m.error) === "string" ? ev.payload.error : null;
8676
+ const sig = `${ev.seq}:${nextStage}:${nextPlatform}:${progressHint ?? "none"}:${phaseLabel ?? "none"}:${errorText ?? "none"}`;
8677
+ if (sig === lastBundleSig) continue;
8678
+ lastBundleSig = sig;
8679
+ bundleStage = nextStage;
8680
+ bundlePlatform = nextPlatform;
8681
+ if (phaseLabel) bundleLabel = phaseLabel;
8682
+ if (progressHint != null) bundleProgressHint = progressHint;
8683
+ if (errorText) bundleError = errorText;
8684
+ const evMs = toMs(ev.createdAt);
8685
+ if (!bundleStartedAtMs && evMs > 0) bundleStartedAtMs = evMs;
8686
+ }
8687
+ if (!latestMessage) {
8688
+ if (phase === "planning") latestMessage = "Planning changes...";
8689
+ else if (phase === "analyzing") latestMessage = "Analyzing relevant files...";
8690
+ else if (phase === "editing") latestMessage = "Applying code updates...";
8691
+ else if (phase === "validating") latestMessage = "Validating updates...";
8692
+ else if (phase === "finalizing") latestMessage = "Finalizing response...";
8693
+ else if (phase) latestMessage = `Working (${phase})...`;
8694
+ }
8695
+ const runFinished = (run == null ? void 0 : run.status) === "succeeded" || (run == null ? void 0 : run.status) === "failed" || (run == null ? void 0 : run.status) === "cancelled";
8696
+ let bundle = null;
8697
+ if (bundleStage && !runFinished) {
8698
+ const baseTime = bundleStartedAtMs ?? toMs(run == null ? void 0 : run.startedAt) ?? nowMs;
8699
+ const fallback = fallbackBundleProgress(bundleStage, baseTime || nowMs, nowMs);
8700
+ const value = bundleProgressHint != null ? Math.max(fallback, bundleProgressHint) : fallback;
8701
+ const status = bundleStage === "failed" ? "failed" : bundleStage === "ready" ? "succeeded" : "loading";
8702
+ bundle = {
8703
+ active: status === "loading",
8704
+ status,
8705
+ phaseLabel: bundleLabel || defaultBundleLabel(bundleStage),
8706
+ progressValue: clamp01(value),
8707
+ errorMessage: bundleError,
8708
+ platform: bundlePlatform
8709
+ };
8710
+ }
8711
+ return {
8712
+ runId: (run == null ? void 0 : run.id) ?? null,
8713
+ status: (run == null ? void 0 : run.status) ?? null,
8714
+ phase,
8715
+ latestMessage,
8716
+ changedFilesCount: fileSeen.size,
8717
+ recentFiles: files.slice(-5),
8718
+ todoSummary,
8719
+ bundle,
8720
+ events
8721
+ };
8722
+ }
8723
+ function useAgentRunProgress(threadId, opts) {
8724
+ var _a;
8725
+ const enabled = Boolean((opts == null ? void 0 : opts.enabled) ?? true);
8726
+ const [run, setRun] = React48.useState(null);
8727
+ const [events, setEvents] = React48.useState([]);
8728
+ const [loading, setLoading] = React48.useState(false);
8729
+ const [error, setError] = React48.useState(null);
8730
+ const activeRequestIdRef = React48.useRef(0);
8731
+ const lastSeqRef = React48.useRef(0);
8732
+ const runRef = React48.useRef(null);
8733
+ const foregroundSignal = useForegroundSignal(Boolean(threadId) && enabled);
8734
+ const [bundleTick, setBundleTick] = React48.useState(0);
8735
+ React48.useEffect(() => {
8736
+ lastSeqRef.current = 0;
8737
+ runRef.current = null;
8738
+ }, [threadId]);
8739
+ React48.useEffect(() => {
8740
+ runRef.current = run;
8741
+ }, [run]);
8742
+ const refetch = React48.useCallback(async () => {
8743
+ if (!threadId || !enabled) {
8744
+ setRun(null);
8745
+ setEvents([]);
8746
+ setLoading(false);
8747
+ setError(null);
8748
+ return;
8749
+ }
8750
+ const requestId = ++activeRequestIdRef.current;
8751
+ setLoading(true);
8752
+ setError(null);
8753
+ try {
8754
+ const latestRun = await agentProgressRepository.getLatestRun(threadId);
8755
+ if (activeRequestIdRef.current !== requestId) return;
8756
+ if (!latestRun) {
8757
+ setRun(null);
8758
+ setEvents([]);
8759
+ lastSeqRef.current = 0;
8760
+ return;
8761
+ }
8762
+ const initialEvents = await agentProgressRepository.listEvents(latestRun.id);
8763
+ if (activeRequestIdRef.current !== requestId) return;
8764
+ const sorted = [...initialEvents].sort((a, b) => a.seq - b.seq);
8765
+ setRun(latestRun);
8766
+ setEvents(sorted);
8767
+ lastSeqRef.current = sorted.length > 0 ? sorted[sorted.length - 1].seq : 0;
8768
+ } catch (e) {
8769
+ if (activeRequestIdRef.current !== requestId) return;
8770
+ setError(e instanceof Error ? e : new Error(String(e)));
8771
+ setRun(null);
8772
+ setEvents([]);
8773
+ lastSeqRef.current = 0;
8774
+ } finally {
8775
+ if (activeRequestIdRef.current === requestId) setLoading(false);
8776
+ }
8777
+ }, [enabled, threadId]);
8778
+ React48.useEffect(() => {
8779
+ void refetch();
8780
+ }, [refetch]);
8781
+ React48.useEffect(() => {
8782
+ if (!threadId || !enabled) return;
8783
+ if (foregroundSignal <= 0) return;
8784
+ void refetch();
8785
+ }, [enabled, foregroundSignal, refetch, threadId]);
8786
+ React48.useEffect(() => {
8787
+ if (!threadId || !enabled) return;
8788
+ const unsubRuns = agentProgressRepository.subscribeThreadRuns(threadId, {
8789
+ onInsert: (nextRun) => {
8790
+ const currentRun = runRef.current;
8791
+ if (!shouldSwitchRun(currentRun, nextRun)) return;
8792
+ setRun(nextRun);
8793
+ runRef.current = nextRun;
8794
+ if (!currentRun || currentRun.id !== nextRun.id) {
8795
+ lastSeqRef.current = 0;
8796
+ setEvents([]);
8797
+ void agentProgressRepository.listEvents(nextRun.id).then((initial) => {
8798
+ var _a2;
8799
+ if (((_a2 = runRef.current) == null ? void 0 : _a2.id) !== nextRun.id) return;
8800
+ setEvents((prev) => mergeMany(prev, initial));
8801
+ const maxSeq = initial.length > 0 ? initial[initial.length - 1].seq : 0;
8802
+ if (maxSeq > lastSeqRef.current) lastSeqRef.current = maxSeq;
8803
+ }).catch(() => {
8804
+ });
8805
+ }
8806
+ },
8807
+ onUpdate: (nextRun) => {
8808
+ const currentRun = runRef.current;
8809
+ if (!shouldSwitchRun(currentRun, nextRun)) return;
8810
+ setRun(nextRun);
8811
+ runRef.current = nextRun;
8812
+ }
8813
+ });
8814
+ return unsubRuns;
8815
+ }, [enabled, threadId, foregroundSignal]);
8816
+ React48.useEffect(() => {
8817
+ if (!enabled || !(run == null ? void 0 : run.id)) return;
8818
+ const runId = run.id;
8819
+ const processIncoming = (incoming) => {
8820
+ var _a2;
8821
+ if (((_a2 = runRef.current) == null ? void 0 : _a2.id) !== runId) return;
8822
+ setEvents((prev) => upsertBySeq(prev, incoming));
8823
+ if (incoming.seq > lastSeqRef.current) {
8824
+ const expectedNext = lastSeqRef.current + 1;
8825
+ const seenSeq = incoming.seq;
8826
+ const currentLast = lastSeqRef.current;
8827
+ lastSeqRef.current = seenSeq;
8828
+ if (seenSeq > expectedNext) {
8829
+ void agentProgressRepository.listEvents(runId, currentLast).then((missing) => {
8830
+ var _a3;
8831
+ if (((_a3 = runRef.current) == null ? void 0 : _a3.id) !== runId) return;
8832
+ setEvents((prev) => mergeMany(prev, missing));
8833
+ if (missing.length > 0) {
8834
+ const maxSeq = missing[missing.length - 1].seq;
8835
+ if (maxSeq > lastSeqRef.current) lastSeqRef.current = maxSeq;
8836
+ }
8837
+ }).catch(() => {
8838
+ });
8839
+ }
8840
+ }
8841
+ };
8842
+ const unsubscribe = agentProgressRepository.subscribeRunEvents(runId, {
8843
+ onInsert: processIncoming,
8844
+ onUpdate: processIncoming
8845
+ });
8846
+ return unsubscribe;
8847
+ }, [enabled, run == null ? void 0 : run.id, foregroundSignal]);
8848
+ const view = React48.useMemo(() => deriveView(run, events, Date.now()), [bundleTick, events, run]);
8849
+ React48.useEffect(() => {
8850
+ var _a2;
8851
+ if (!((_a2 = view.bundle) == null ? void 0 : _a2.active)) return;
8852
+ const interval = setInterval(() => {
8853
+ setBundleTick((v) => v + 1);
8854
+ }, 300);
8855
+ return () => clearInterval(interval);
8856
+ }, [(_a = view.bundle) == null ? void 0 : _a.active]);
8857
+ const hasLiveProgress = Boolean(run) && (run == null ? void 0 : run.status) === "running";
8858
+ return { run, view, loading, error, hasLiveProgress, refetch };
8859
+ }
8860
+
7905
8861
  // src/studio/ComergeStudio.tsx
7906
- var import_jsx_runtime60 = require("react/jsx-runtime");
8862
+ var import_jsx_runtime62 = require("react/jsx-runtime");
7907
8863
  function ComergeStudio({
7908
8864
  appId,
7909
8865
  clientKey: clientKey2,
7910
8866
  appKey = "MicroMain",
8867
+ analyticsEnabled,
7911
8868
  onNavigateHome,
7912
8869
  style,
7913
8870
  showBubble = true,
8871
+ enableAgentProgress = true,
7914
8872
  studioControlOptions,
7915
8873
  embeddedBaseBundles
7916
8874
  }) {
7917
- const [activeAppId, setActiveAppId] = React47.useState(appId);
7918
- const [runtimeAppId, setRuntimeAppId] = React47.useState(appId);
7919
- const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React47.useState(null);
7920
- const platform = React47.useMemo(() => import_react_native57.Platform.OS === "ios" ? "ios" : "android", []);
7921
- React47.useEffect(() => {
8875
+ const [activeAppId, setActiveAppId] = React49.useState(appId);
8876
+ const [runtimeAppId, setRuntimeAppId] = React49.useState(appId);
8877
+ const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React49.useState(null);
8878
+ const platform = React49.useMemo(() => import_react_native60.Platform.OS === "ios" ? "ios" : "android", []);
8879
+ React49.useEffect(() => {
7922
8880
  setActiveAppId(appId);
7923
8881
  setRuntimeAppId(appId);
7924
8882
  setPendingRuntimeTargetAppId(null);
7925
8883
  }, [appId]);
7926
- const captureTargetRef = React47.useRef(null);
7927
- return /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(StudioBootstrap, { clientKey: clientKey2, fallback: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(import_react_native57.View, { style: { flex: 1 } }), children: ({ userId }) => /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(import_bottom_sheet6.BottomSheetModalProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(LiquidGlassResetProvider, { resetTriggers: [appId, activeAppId, runtimeAppId], children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
7928
- ComergeStudioInner,
8884
+ const captureTargetRef = React49.useRef(null);
8885
+ return /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
8886
+ StudioBootstrap,
7929
8887
  {
7930
- userId,
7931
- activeAppId,
7932
- setActiveAppId,
7933
- runtimeAppId,
7934
- setRuntimeAppId,
7935
- pendingRuntimeTargetAppId,
7936
- setPendingRuntimeTargetAppId,
7937
- appKey,
7938
- platform,
7939
- onNavigateHome,
7940
- captureTargetRef,
7941
- style,
7942
- showBubble,
7943
- studioControlOptions,
7944
- embeddedBaseBundles
8888
+ clientKey: clientKey2,
8889
+ analyticsEnabled,
8890
+ fallback: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(import_react_native60.View, { style: { flex: 1 } }),
8891
+ children: ({ userId }) => /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(import_bottom_sheet6.BottomSheetModalProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(LiquidGlassResetProvider, { resetTriggers: [appId, activeAppId, runtimeAppId], children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
8892
+ ComergeStudioInner,
8893
+ {
8894
+ userId,
8895
+ activeAppId,
8896
+ setActiveAppId,
8897
+ runtimeAppId,
8898
+ setRuntimeAppId,
8899
+ pendingRuntimeTargetAppId,
8900
+ setPendingRuntimeTargetAppId,
8901
+ appKey,
8902
+ platform,
8903
+ onNavigateHome,
8904
+ captureTargetRef,
8905
+ style,
8906
+ showBubble,
8907
+ enableAgentProgress,
8908
+ studioControlOptions,
8909
+ embeddedBaseBundles
8910
+ }
8911
+ ) }) })
7945
8912
  }
7946
- ) }) }) });
8913
+ );
7947
8914
  }
7948
8915
  function ComergeStudioInner({
7949
8916
  userId,
@@ -7959,17 +8926,19 @@ function ComergeStudioInner({
7959
8926
  captureTargetRef,
7960
8927
  style,
7961
8928
  showBubble,
8929
+ enableAgentProgress,
7962
8930
  studioControlOptions,
7963
8931
  embeddedBaseBundles
7964
8932
  }) {
8933
+ var _a;
7965
8934
  const { app, loading: appLoading } = useApp(activeAppId);
7966
8935
  const { app: runtimeAppFromHook } = useApp(runtimeAppId, { enabled: runtimeAppId !== activeAppId });
7967
8936
  const runtimeApp = runtimeAppId === activeAppId ? app : runtimeAppFromHook;
7968
- const sawEditingOnPendingTargetRef = React47.useRef(false);
7969
- React47.useEffect(() => {
8937
+ const sawEditingOnPendingTargetRef = React49.useRef(false);
8938
+ React49.useEffect(() => {
7970
8939
  sawEditingOnPendingTargetRef.current = false;
7971
8940
  }, [pendingRuntimeTargetAppId]);
7972
- React47.useEffect(() => {
8941
+ React49.useEffect(() => {
7973
8942
  if (!pendingRuntimeTargetAppId) return;
7974
8943
  if (activeAppId !== pendingRuntimeTargetAppId) return;
7975
8944
  if ((app == null ? void 0 : app.status) === "editing") {
@@ -7987,13 +8956,13 @@ function ComergeStudioInner({
7987
8956
  canRequestLatest: (runtimeApp == null ? void 0 : runtimeApp.status) === "ready",
7988
8957
  embeddedBaseBundles
7989
8958
  });
7990
- const sawEditingOnActiveAppRef = React47.useRef(false);
7991
- const [showPostEditPreparing, setShowPostEditPreparing] = React47.useState(false);
7992
- React47.useEffect(() => {
8959
+ const sawEditingOnActiveAppRef = React49.useRef(false);
8960
+ const [showPostEditPreparing, setShowPostEditPreparing] = React49.useState(false);
8961
+ React49.useEffect(() => {
7993
8962
  sawEditingOnActiveAppRef.current = false;
7994
8963
  setShowPostEditPreparing(false);
7995
8964
  }, [activeAppId]);
7996
- React47.useEffect(() => {
8965
+ React49.useEffect(() => {
7997
8966
  if (!(app == null ? void 0 : app.id)) return;
7998
8967
  if (app.status === "editing") {
7999
8968
  sawEditingOnActiveAppRef.current = true;
@@ -8005,7 +8974,7 @@ function ComergeStudioInner({
8005
8974
  sawEditingOnActiveAppRef.current = false;
8006
8975
  }
8007
8976
  }, [app == null ? void 0 : app.id, app == null ? void 0 : app.status]);
8008
- React47.useEffect(() => {
8977
+ React49.useEffect(() => {
8009
8978
  if (!showPostEditPreparing) return;
8010
8979
  const stillProcessingBaseBundle = bundle.loading && bundle.loadingMode === "base" && !bundle.isTesting;
8011
8980
  if (!stillProcessingBaseBundle) {
@@ -8015,20 +8984,21 @@ function ComergeStudioInner({
8015
8984
  const threadId = (app == null ? void 0 : app.threadId) ?? "";
8016
8985
  const thread = useThreadMessages(threadId);
8017
8986
  const editQueue = useEditQueue(activeAppId);
8987
+ const agentProgress = useAgentRunProgress(threadId, { enabled: enableAgentProgress });
8018
8988
  const editQueueActions = useEditQueueActions(activeAppId);
8019
- const [lastEditQueueInfo, setLastEditQueueInfo] = React47.useState(null);
8020
- const lastEditQueueInfoRef = React47.useRef(null);
8021
- const [suppressQueueUntilResponse, setSuppressQueueUntilResponse] = React47.useState(false);
8989
+ const [lastEditQueueInfo, setLastEditQueueInfo] = React49.useState(null);
8990
+ const lastEditQueueInfoRef = React49.useRef(null);
8991
+ const [suppressQueueUntilResponse, setSuppressQueueUntilResponse] = React49.useState(false);
8022
8992
  const mergeRequests = useMergeRequests({ appId: activeAppId });
8023
- const hasOpenOutgoingMr = React47.useMemo(() => {
8993
+ const hasOpenOutgoingMr = React49.useMemo(() => {
8024
8994
  return mergeRequests.lists.outgoing.some((mr) => mr.status === "open");
8025
8995
  }, [mergeRequests.lists.outgoing]);
8026
- const incomingReviewMrs = React47.useMemo(() => {
8996
+ const incomingReviewMrs = React49.useMemo(() => {
8027
8997
  if (!userId) return mergeRequests.lists.incoming;
8028
8998
  return mergeRequests.lists.incoming.filter((mr) => mr.createdBy !== userId);
8029
8999
  }, [mergeRequests.lists.incoming, userId]);
8030
9000
  const uploader = useAttachmentUpload();
8031
- const updateLastEditQueueInfo = React47.useCallback(
9001
+ const updateLastEditQueueInfo = React49.useCallback(
8032
9002
  (info) => {
8033
9003
  lastEditQueueInfoRef.current = info;
8034
9004
  setLastEditQueueInfo(info);
@@ -8062,32 +9032,35 @@ function ComergeStudioInner({
8062
9032
  }
8063
9033
  },
8064
9034
  onEditFinished: () => {
8065
- var _a;
8066
- if (((_a = lastEditQueueInfoRef.current) == null ? void 0 : _a.queuePosition) !== 1) {
9035
+ var _a2;
9036
+ if (((_a2 = lastEditQueueInfoRef.current) == null ? void 0 : _a2.queuePosition) !== 1) {
8067
9037
  setSuppressQueueUntilResponse(false);
8068
9038
  }
8069
9039
  }
8070
9040
  });
8071
9041
  const chatSendDisabled = false;
8072
- const [processingMrId, setProcessingMrId] = React47.useState(null);
8073
- const [testingMrId, setTestingMrId] = React47.useState(null);
8074
- const [syncingUpstream, setSyncingUpstream] = React47.useState(false);
8075
- const [upstreamSyncStatus, setUpstreamSyncStatus] = React47.useState(null);
9042
+ const [processingMrId, setProcessingMrId] = React49.useState(null);
9043
+ const [testingMrId, setTestingMrId] = React49.useState(null);
9044
+ const [syncingUpstream, setSyncingUpstream] = React49.useState(false);
9045
+ const [upstreamSyncStatus, setUpstreamSyncStatus] = React49.useState(null);
8076
9046
  const isMrTestBuildInProgress = bundle.loading && bundle.loadingMode === "test";
8077
9047
  const isBaseBundleDownloading = bundle.loading && bundle.loadingMode === "base" && !bundle.isTesting;
8078
- const chatShowTypingIndicator = React47.useMemo(() => {
8079
- var _a;
9048
+ const runtimePreparingText = bundle.bundleStatus === "pending" ? "Bundling app\u2026 this may take a few minutes" : "Preparing app\u2026";
9049
+ const chatShowTypingIndicator = React49.useMemo(() => {
9050
+ var _a2;
9051
+ if (agentProgress.hasLiveProgress) return false;
8080
9052
  if (!thread.raw || thread.raw.length === 0) return false;
8081
9053
  const last = thread.raw[thread.raw.length - 1];
8082
- const payloadType = typeof ((_a = last.payload) == null ? void 0 : _a.type) === "string" ? String(last.payload.type) : void 0;
9054
+ const payloadType = typeof ((_a2 = last.payload) == null ? void 0 : _a2.type) === "string" ? String(last.payload.type) : void 0;
8083
9055
  return payloadType !== "outcome";
8084
- }, [thread.raw]);
8085
- React47.useEffect(() => {
9056
+ }, [agentProgress.hasLiveProgress, thread.raw]);
9057
+ const showChatProgress = agentProgress.hasLiveProgress || Boolean((_a = agentProgress.view.bundle) == null ? void 0 : _a.active);
9058
+ React49.useEffect(() => {
8086
9059
  updateLastEditQueueInfo(null);
8087
9060
  setSuppressQueueUntilResponse(false);
8088
9061
  setUpstreamSyncStatus(null);
8089
9062
  }, [activeAppId, updateLastEditQueueInfo]);
8090
- const handleSyncUpstream = React47.useCallback(async () => {
9063
+ const handleSyncUpstream = React49.useCallback(async () => {
8091
9064
  if (!(app == null ? void 0 : app.id)) {
8092
9065
  throw new Error("Missing app");
8093
9066
  }
@@ -8100,7 +9073,7 @@ function ComergeStudioInner({
8100
9073
  setSyncingUpstream(false);
8101
9074
  }
8102
9075
  }, [activeAppId, app == null ? void 0 : app.id]);
8103
- React47.useEffect(() => {
9076
+ React49.useEffect(() => {
8104
9077
  if (!(lastEditQueueInfo == null ? void 0 : lastEditQueueInfo.queueItemId)) return;
8105
9078
  const stillPresent = editQueue.items.some((item) => item.id === lastEditQueueInfo.queueItemId);
8106
9079
  if (!stillPresent) {
@@ -8108,31 +9081,32 @@ function ComergeStudioInner({
8108
9081
  setSuppressQueueUntilResponse(false);
8109
9082
  }
8110
9083
  }, [editQueue.items, lastEditQueueInfo == null ? void 0 : lastEditQueueInfo.queueItemId]);
8111
- const chatQueueItems = React47.useMemo(() => {
8112
- var _a;
9084
+ const chatQueueItems = React49.useMemo(() => {
9085
+ var _a2;
8113
9086
  if (suppressQueueUntilResponse && editQueue.items.length <= 1) {
8114
9087
  return [];
8115
9088
  }
8116
9089
  if (!lastEditQueueInfo || lastEditQueueInfo.queuePosition !== 1 || !lastEditQueueInfo.queueItemId) {
8117
9090
  return editQueue.items;
8118
9091
  }
8119
- if (editQueue.items.length === 1 && ((_a = editQueue.items[0]) == null ? void 0 : _a.id) === lastEditQueueInfo.queueItemId) {
9092
+ if (editQueue.items.length === 1 && ((_a2 = editQueue.items[0]) == null ? void 0 : _a2.id) === lastEditQueueInfo.queueItemId) {
8120
9093
  return [];
8121
9094
  }
8122
9095
  return editQueue.items;
8123
9096
  }, [editQueue.items, lastEditQueueInfo, suppressQueueUntilResponse]);
8124
- return /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(import_react_native57.View, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(import_react_native57.View, { ref: captureTargetRef, style: { flex: 1 }, collapsable: false, children: [
8125
- /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
9097
+ return /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(import_react_native60.View, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)(import_react_native60.View, { ref: captureTargetRef, style: { flex: 1 }, collapsable: false, children: [
9098
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
8126
9099
  RuntimeRenderer,
8127
9100
  {
8128
9101
  appKey,
8129
9102
  bundlePath: bundle.bundlePath,
9103
+ preparingText: runtimePreparingText,
8130
9104
  forcePreparing: showPostEditPreparing,
8131
9105
  renderToken: bundle.renderToken,
8132
9106
  allowInitialPreparing: !embeddedBaseBundles
8133
9107
  }
8134
9108
  ),
8135
- /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
9109
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
8136
9110
  StudioOverlay,
8137
9111
  {
8138
9112
  captureTargetRef,
@@ -8191,6 +9165,7 @@ function ComergeStudioInner({
8191
9165
  onSendChat: (text, attachments) => actions.sendEdit({ prompt: text, attachments }),
8192
9166
  chatQueueItems,
8193
9167
  onRemoveQueueItem: (id) => editQueueActions.cancel(id),
9168
+ chatProgress: showChatProgress ? agentProgress.view : null,
8194
9169
  onNavigateHome,
8195
9170
  showBubble,
8196
9171
  studioControlOptions