@comergehq/studio 0.1.12 → 0.1.15

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.mjs CHANGED
@@ -6,7 +6,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
6
6
  });
7
7
 
8
8
  // src/studio/ComergeStudio.tsx
9
- import * as React42 from "react";
9
+ import * as React43 from "react";
10
10
  import { Platform as RNPlatform, View as View45 } from "react-native";
11
11
  import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
12
12
 
@@ -188,11 +188,11 @@ import * as React from "react";
188
188
  import axios from "axios";
189
189
 
190
190
  // src/core/services/http/baseUrl.ts
191
- var BASE_URL = "https://comerge.ai";
191
+ var BASE_URL = "https://api.comerge.ai";
192
192
 
193
193
  // src/core/services/http/public.ts
194
194
  var CLIENT_KEY_HEADER = "x-comerge-api-key";
195
- var clientApiKey = null;
195
+ var clientKey = null;
196
196
  var publicApi = axios.create({
197
197
  baseURL: BASE_URL,
198
198
  timeout: 3e4,
@@ -201,19 +201,19 @@ var publicApi = axios.create({
201
201
  "Content-Type": "application/json"
202
202
  }
203
203
  });
204
- function setClientApiKey(apiKey) {
204
+ function setClientKey(clientKeyInput) {
205
205
  var _a;
206
- const trimmed = ((_a = apiKey == null ? void 0 : apiKey.trim) == null ? void 0 : _a.call(apiKey)) ?? "";
206
+ const trimmed = ((_a = clientKeyInput == null ? void 0 : clientKeyInput.trim) == null ? void 0 : _a.call(clientKeyInput)) ?? "";
207
207
  if (!trimmed) {
208
- throw new Error("comerge-studio: apiKey is required");
208
+ throw new Error("comerge-studio: clientKey is required");
209
209
  }
210
- clientApiKey = trimmed;
210
+ clientKey = trimmed;
211
211
  publicApi.defaults.headers.common[CLIENT_KEY_HEADER] = trimmed;
212
212
  }
213
213
  publicApi.interceptors.request.use((config) => {
214
- if (!clientApiKey) return config;
214
+ if (!clientKey) return config;
215
215
  config.headers = config.headers ?? {};
216
- config.headers[CLIENT_KEY_HEADER] = clientApiKey;
216
+ config.headers[CLIENT_KEY_HEADER] = clientKey;
217
217
  return config;
218
218
  });
219
219
 
@@ -278,42 +278,9 @@ async function ensureAnonymousSession() {
278
278
  return { user: data.user, isNew: true };
279
279
  }
280
280
 
281
- // src/data/base-repository.ts
282
- var BaseRepository = class {
283
- unwrapOrThrow(res) {
284
- if (res.success && res.responseObject) return res.responseObject;
285
- const msg = res.message || "Request failed";
286
- throw new Error(msg);
287
- }
288
- };
289
-
290
- // src/data/base-remote.ts
291
- var BaseRemote = class {
292
- };
293
-
294
- // src/data/public/studio-config/remote.ts
295
- var StudioConfigRemoteDataSourceImpl = class extends BaseRemote {
296
- async get() {
297
- const { data } = await publicApi.get("/v1/public/studio-config");
298
- return data;
299
- }
300
- };
301
- var studioConfigRemoteDataSource = new StudioConfigRemoteDataSourceImpl();
302
-
303
- // src/data/public/studio-config/repository.ts
304
- var StudioConfigRepositoryImpl = class extends BaseRepository {
305
- constructor(remote) {
306
- super();
307
- this.remote = remote;
308
- }
309
- async get() {
310
- const res = await this.remote.get();
311
- return this.unwrapOrThrow(res);
312
- }
313
- };
314
- var studioConfigRepository = new StudioConfigRepositoryImpl(studioConfigRemoteDataSource);
315
-
316
281
  // src/studio/bootstrap/useStudioBootstrap.ts
282
+ var SUPABASE_URL = "https://xtfxwbckjpfmqubnsusu.supabase.co";
283
+ var SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh0Znh3YmNranBmbXF1Ym5zdXN1Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjA2MDEyMzAsImV4cCI6MjA3NjE3NzIzMH0.dzWGAWrK4CvrmHVHzf8w7JlUZohdap0ZPnLZnABMV8s";
317
284
  function useStudioBootstrap(options) {
318
285
  const [state, setState] = React.useState({
319
286
  ready: false,
@@ -324,11 +291,10 @@ function useStudioBootstrap(options) {
324
291
  let cancelled = false;
325
292
  (async () => {
326
293
  try {
327
- setClientApiKey(options.apiKey);
294
+ setClientKey(options.clientKey);
328
295
  const requireAuth = isSupabaseClientInjected();
329
296
  if (!requireAuth) {
330
- const cfg = await studioConfigRepository.get();
331
- setSupabaseConfig(cfg);
297
+ setSupabaseConfig({ url: SUPABASE_URL, anonKey: SUPABASE_ANON_KEY });
332
298
  }
333
299
  const { user } = requireAuth ? await ensureAuthenticatedSession() : await ensureAnonymousSession();
334
300
  if (cancelled) return;
@@ -342,14 +308,14 @@ function useStudioBootstrap(options) {
342
308
  return () => {
343
309
  cancelled = true;
344
310
  };
345
- }, [options.apiKey]);
311
+ }, [options.clientKey]);
346
312
  return state;
347
313
  }
348
314
 
349
315
  // src/studio/bootstrap/StudioBootstrap.tsx
350
316
  import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
351
- function StudioBootstrap({ children, fallback, renderError, apiKey }) {
352
- const { ready, error, userId } = useStudioBootstrap({ apiKey });
317
+ function StudioBootstrap({ children, fallback, renderError, clientKey: clientKey2 }) {
318
+ const { ready, error, userId } = useStudioBootstrap({ clientKey: clientKey2 });
353
319
  if (error) {
354
320
  return /* @__PURE__ */ jsx2(View, { style: { flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, children: renderError ? renderError(error) : /* @__PURE__ */ jsx2(Text, { variant: "bodyMuted", children: error.message }) });
355
321
  }
@@ -497,6 +463,10 @@ var createApiClient = (baseURL) => {
497
463
  };
498
464
  var api = createApiClient(BASE_URL);
499
465
 
466
+ // src/data/base-remote.ts
467
+ var BaseRemote = class {
468
+ };
469
+
500
470
  // src/data/apps/remote.ts
501
471
  var AppsRemoteDataSourceImpl = class extends BaseRemote {
502
472
  async list(projectId) {
@@ -552,6 +522,15 @@ var AppsRemoteDataSourceImpl = class extends BaseRemote {
552
522
  };
553
523
  var appsRemoteDataSource = new AppsRemoteDataSourceImpl();
554
524
 
525
+ // src/data/base-repository.ts
526
+ var BaseRepository = class {
527
+ unwrapOrThrow(res) {
528
+ if (res.success && res.responseObject) return res.responseObject;
529
+ const msg = res.message || "Request failed";
530
+ throw new Error(msg);
531
+ }
532
+ };
533
+
555
534
  // src/data/apps/repository.ts
556
535
  function mapDbAppRow(row) {
557
536
  return {
@@ -904,6 +883,8 @@ function useThreadMessages(threadId) {
904
883
  // src/studio/hooks/useBundleManager.ts
905
884
  import * as React5 from "react";
906
885
  import * as FileSystem from "expo-file-system/legacy";
886
+ import { Asset } from "expo-asset";
887
+ import { unzip } from "react-native-zip-archive";
907
888
 
908
889
  // src/data/apps/bundles/remote.ts
909
890
  var BundlesRemoteDataSourceImpl = class extends BaseRemote {
@@ -927,6 +908,13 @@ var BundlesRemoteDataSourceImpl = class extends BaseRemote {
927
908
  );
928
909
  return data;
929
910
  }
911
+ async getSignedAssetsDownloadUrl(appId, bundleId, options) {
912
+ const { data } = await api.get(
913
+ `/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}/assets/download`,
914
+ { params: { redirect: (options == null ? void 0 : options.redirect) ?? false, kind: options == null ? void 0 : options.kind } }
915
+ );
916
+ return data;
917
+ }
930
918
  };
931
919
  var bundlesRemoteDataSource = new BundlesRemoteDataSourceImpl();
932
920
 
@@ -948,6 +936,10 @@ var BundlesRepositoryImpl = class extends BaseRepository {
948
936
  const res = await this.remote.getSignedDownloadUrl(appId, bundleId, options);
949
937
  return this.unwrapOrThrow(res);
950
938
  }
939
+ async getSignedAssetsDownloadUrl(appId, bundleId, options) {
940
+ const res = await this.remote.getSignedAssetsDownloadUrl(appId, bundleId, options);
941
+ return this.unwrapOrThrow(res);
942
+ }
951
943
  };
952
944
  var bundlesRepository = new BundlesRepositoryImpl(bundlesRemoteDataSource);
953
945
 
@@ -999,20 +991,40 @@ async function ensureDir(path) {
999
991
  if (info.exists) return;
1000
992
  await FileSystem.makeDirectoryAsync(path, { intermediates: true });
1001
993
  }
994
+ async function ensureBundleDir(key) {
995
+ await ensureDir(bundlesCacheDir());
996
+ await ensureDir(bundleDir(key));
997
+ }
1002
998
  function baseBundleKey(appId, platform) {
1003
999
  return `base:${appId}:${platform}`;
1004
1000
  }
1005
1001
  function testBundleKey(appId, commitId, platform, bundleId) {
1006
1002
  return `test:${appId}:${commitId ?? "head"}:${platform}:${bundleId}`;
1007
1003
  }
1008
- function toBundleFileUri(key) {
1004
+ function legacyBundleFileUri(key) {
1009
1005
  const dir = bundlesCacheDir();
1010
1006
  return `${dir}${safeName(key)}.jsbundle`;
1011
1007
  }
1012
- function toBundleMetaFileUri(key) {
1008
+ function legacyBundleMetaFileUri(key) {
1013
1009
  const dir = bundlesCacheDir();
1014
1010
  return `${dir}${safeName(key)}.meta.json`;
1015
1011
  }
1012
+ function bundleDir(key) {
1013
+ const dir = bundlesCacheDir();
1014
+ return `${dir}${safeName(key)}/`;
1015
+ }
1016
+ function toBundleFileUri(key, platform) {
1017
+ return `${bundleDir(key)}index.${platform}.jsbundle`;
1018
+ }
1019
+ function toBundleMetaFileUri(key) {
1020
+ return `${bundleDir(key)}bundle.meta.json`;
1021
+ }
1022
+ function toAssetsMetaFileUri(key) {
1023
+ return `${bundleDir(key)}assets.meta.json`;
1024
+ }
1025
+ function toAssetsDir(key) {
1026
+ return `${bundleDir(key)}assets/`;
1027
+ }
1016
1028
  async function readJsonFile(fileUri) {
1017
1029
  try {
1018
1030
  const info = await FileSystem.getInfoAsync(fileUri);
@@ -1039,6 +1051,14 @@ async function getExistingNonEmptyFileUri(fileUri) {
1039
1051
  return null;
1040
1052
  }
1041
1053
  }
1054
+ async function getExistingBundleFileUri(key, platform) {
1055
+ const nextPath = toBundleFileUri(key, platform);
1056
+ const next = await getExistingNonEmptyFileUri(nextPath);
1057
+ if (next) return next;
1058
+ const legacyPath = legacyBundleFileUri(key);
1059
+ const legacy = await getExistingNonEmptyFileUri(legacyPath);
1060
+ return legacy;
1061
+ }
1042
1062
  async function downloadIfMissing(url, fileUri) {
1043
1063
  const existing = await getExistingNonEmptyFileUri(fileUri);
1044
1064
  if (existing) return existing;
@@ -1062,8 +1082,71 @@ async function deleteFileIfExists(fileUri) {
1062
1082
  } catch {
1063
1083
  }
1064
1084
  }
1065
- async function safeReplaceFileFromUrl(url, targetUri, tmpKey) {
1066
- const tmpUri = toBundleFileUri(`tmp:${tmpKey}:${Date.now()}`);
1085
+ async function hydrateBaseFromEmbeddedAsset(appId, platform, embedded) {
1086
+ if (!(embedded == null ? void 0 : embedded.module)) return null;
1087
+ const key = baseBundleKey(appId, platform);
1088
+ const existing = await getExistingBundleFileUri(key, platform);
1089
+ if (existing) {
1090
+ return { bundlePath: existing, meta: embedded.meta ?? null };
1091
+ }
1092
+ await ensureBundleDir(key);
1093
+ const targetUri = toBundleFileUri(key, platform);
1094
+ const asset = Asset.fromModule(embedded.module);
1095
+ await asset.downloadAsync();
1096
+ const sourceUri = asset.localUri ?? asset.uri;
1097
+ if (!sourceUri) return null;
1098
+ const info = await FileSystem.getInfoAsync(sourceUri);
1099
+ if (!info.exists) return null;
1100
+ await deleteFileIfExists(targetUri);
1101
+ await FileSystem.copyAsync({ from: sourceUri, to: targetUri });
1102
+ const finalUri = await getExistingNonEmptyFileUri(targetUri);
1103
+ if (!finalUri) return null;
1104
+ return { bundlePath: finalUri, meta: embedded.meta ?? null };
1105
+ }
1106
+ async function hydrateAssetsFromEmbeddedAsset(appId, platform, key, embedded) {
1107
+ var _a;
1108
+ const moduleId = embedded == null ? void 0 : embedded.assetsModule;
1109
+ if (!moduleId) return false;
1110
+ const assetsMeta = (embedded == null ? void 0 : embedded.assetsMeta) ?? null;
1111
+ const assetsDir = toAssetsDir(key);
1112
+ const metaUri = toAssetsMetaFileUri(key);
1113
+ const existingMeta = await readJsonFile(metaUri);
1114
+ const assetsDirInfo = await FileSystem.getInfoAsync(assetsDir);
1115
+ const assetsDirExists = assetsDirInfo.exists && assetsDirInfo.isDirectory;
1116
+ const checksumMatches = Boolean(existingMeta == null ? void 0 : existingMeta.checksumSha256) && Boolean(assetsMeta == null ? void 0 : assetsMeta.checksumSha256) && (existingMeta == null ? void 0 : existingMeta.checksumSha256) === (assetsMeta == null ? void 0 : assetsMeta.checksumSha256);
1117
+ const embeddedMetaMatches = (_a = existingMeta == null ? void 0 : existingMeta.storageKey) == null ? void 0 : _a.startsWith("embedded:");
1118
+ if (assetsDirExists && checksumMatches && embeddedMetaMatches) {
1119
+ return true;
1120
+ }
1121
+ await ensureBundleDir(key);
1122
+ await ensureDir(assetsDir);
1123
+ const asset = Asset.fromModule(moduleId);
1124
+ await asset.downloadAsync();
1125
+ const sourceUri = asset.localUri ?? asset.uri;
1126
+ if (!sourceUri) return false;
1127
+ const info = await FileSystem.getInfoAsync(sourceUri);
1128
+ if (!info.exists) return false;
1129
+ const zipUri = `${bundleDir(key)}assets.zip`;
1130
+ await deleteFileIfExists(zipUri);
1131
+ await FileSystem.copyAsync({ from: sourceUri, to: zipUri });
1132
+ try {
1133
+ await FileSystem.deleteAsync(assetsDir, { idempotent: true }).catch(() => {
1134
+ });
1135
+ } catch {
1136
+ }
1137
+ await ensureDir(assetsDir);
1138
+ await unzipArchive(zipUri, assetsDir);
1139
+ await writeJsonFile(metaUri, {
1140
+ checksumSha256: (assetsMeta == null ? void 0 : assetsMeta.checksumSha256) ?? null,
1141
+ storageKey: `embedded:${(assetsMeta == null ? void 0 : assetsMeta.checksumSha256) ?? "unknown"}`,
1142
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1143
+ });
1144
+ return true;
1145
+ }
1146
+ async function safeReplaceFileFromUrl(url, targetUri, tmpKey, platform) {
1147
+ const tmpKeySafe = `tmp:${tmpKey}:${Date.now()}`;
1148
+ const tmpUri = toBundleFileUri(tmpKeySafe, platform);
1149
+ await ensureDir(bundleDir(tmpKeySafe));
1067
1150
  try {
1068
1151
  await withRetry(
1069
1152
  async () => {
@@ -1083,6 +1166,82 @@ async function safeReplaceFileFromUrl(url, targetUri, tmpKey) {
1083
1166
  await deleteFileIfExists(tmpUri);
1084
1167
  }
1085
1168
  }
1169
+ async function safeReplaceFileFromUrlToPath(url, targetUri, tmpKey) {
1170
+ const tmpDir = `${bundlesCacheDir()}tmp/`;
1171
+ await ensureDir(tmpDir);
1172
+ const tmpUri = `${tmpDir}${safeName(tmpKey)}.tmp`;
1173
+ try {
1174
+ await withRetry(
1175
+ async () => {
1176
+ await deleteFileIfExists(tmpUri);
1177
+ await FileSystem.downloadAsync(url, tmpUri);
1178
+ const tmpOk = await getExistingNonEmptyFileUri(tmpUri);
1179
+ if (!tmpOk) throw new Error("Downloaded file is empty.");
1180
+ },
1181
+ { attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
1182
+ );
1183
+ await deleteFileIfExists(targetUri);
1184
+ await FileSystem.moveAsync({ from: tmpUri, to: targetUri });
1185
+ const finalOk = await getExistingNonEmptyFileUri(targetUri);
1186
+ if (!finalOk) throw new Error("File replacement failed.");
1187
+ return targetUri;
1188
+ } finally {
1189
+ await deleteFileIfExists(tmpUri);
1190
+ }
1191
+ }
1192
+ function getMetroAssets(bundle) {
1193
+ const assets = bundle.assets ?? [];
1194
+ return assets.find((asset) => asset.kind === "metro-assets") ?? null;
1195
+ }
1196
+ async function ensureAssetsForBundle(appId, bundle, key, platform) {
1197
+ var _a;
1198
+ const asset = getMetroAssets(bundle);
1199
+ if (!(asset == null ? void 0 : asset.storageKey)) return;
1200
+ await ensureBundleDir(key);
1201
+ const assetsDir = toAssetsDir(key);
1202
+ await ensureDir(assetsDir);
1203
+ const metaUri = toAssetsMetaFileUri(key);
1204
+ const existingMeta = await readJsonFile(metaUri);
1205
+ const assetsDirInfo = await FileSystem.getInfoAsync(assetsDir);
1206
+ const assetsDirExists = assetsDirInfo.exists && assetsDirInfo.isDirectory;
1207
+ if ((existingMeta == null ? void 0 : existingMeta.checksumSha256) && asset.checksumSha256 && existingMeta.checksumSha256 === asset.checksumSha256 && (existingMeta.storageKey === asset.storageKey || ((_a = existingMeta.storageKey) == null ? void 0 : _a.startsWith("embedded:"))) && assetsDirExists) {
1208
+ return;
1209
+ }
1210
+ const signed = await withRetry(
1211
+ async () => {
1212
+ return await bundlesRepository.getSignedAssetsDownloadUrl(appId, bundle.id, {
1213
+ redirect: false,
1214
+ kind: asset.kind
1215
+ });
1216
+ },
1217
+ { attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
1218
+ );
1219
+ const zipUri = `${bundleDir(key)}assets.zip`;
1220
+ await safeReplaceFileFromUrlToPath(signed.url, zipUri, `${appId}:${bundle.id}:${platform}:${asset.kind}`);
1221
+ try {
1222
+ await FileSystem.deleteAsync(assetsDir, { idempotent: true }).catch(() => {
1223
+ });
1224
+ } catch {
1225
+ }
1226
+ await ensureDir(assetsDir);
1227
+ await unzipArchive(zipUri, assetsDir);
1228
+ await writeJsonFile(metaUri, {
1229
+ checksumSha256: asset.checksumSha256 ?? null,
1230
+ storageKey: asset.storageKey,
1231
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1232
+ });
1233
+ }
1234
+ async function unzipArchive(sourceUri, destDir) {
1235
+ try {
1236
+ await unzip(sourceUri, destDir);
1237
+ } catch (e) {
1238
+ throw new Error(
1239
+ `Failed to extract assets archive. Ensure 'react-native-zip-archive' is installed in the host app. ${String(
1240
+ (e == null ? void 0 : e.message) ?? e
1241
+ )}`
1242
+ );
1243
+ }
1244
+ }
1086
1245
  async function pollBundle(appId, bundleId, opts) {
1087
1246
  const start = Date.now();
1088
1247
  while (true) {
@@ -1118,23 +1277,39 @@ async function resolveBundlePath(src, platform, mode) {
1118
1277
  if (finalBundle.status === "failed") {
1119
1278
  throw new Error("Bundle build failed.");
1120
1279
  }
1280
+ let bundleWithAssets = finalBundle;
1281
+ if (finalBundle.status === "succeeded" && (!finalBundle.assets || finalBundle.assets.length === 0)) {
1282
+ try {
1283
+ bundleWithAssets = await bundlesRepository.getById(appId, finalBundle.id);
1284
+ } catch {
1285
+ bundleWithAssets = finalBundle;
1286
+ }
1287
+ }
1121
1288
  const signed = await withRetry(
1122
1289
  async () => {
1123
1290
  return await bundlesRepository.getSignedDownloadUrl(appId, finalBundle.id, { redirect: false });
1124
1291
  },
1125
1292
  { attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
1126
1293
  );
1294
+ const key = mode === "base" ? baseBundleKey(appId, platform) : testBundleKey(appId, commitId, platform, finalBundle.id);
1295
+ await ensureBundleDir(key);
1127
1296
  const bundlePath = mode === "base" ? await safeReplaceFileFromUrl(
1128
1297
  signed.url,
1129
- toBundleFileUri(baseBundleKey(appId, platform)),
1130
- `${appId}:${commitId ?? "head"}:${platform}:${finalBundle.id}`
1131
- ) : await downloadIfMissing(signed.url, toBundleFileUri(testBundleKey(appId, commitId, platform, finalBundle.id)));
1132
- return { bundlePath, label: "Ready", bundle: finalBundle };
1298
+ toBundleFileUri(key, platform),
1299
+ `${appId}:${commitId ?? "head"}:${platform}:${finalBundle.id}`,
1300
+ platform
1301
+ ) : await downloadIfMissing(signed.url, toBundleFileUri(key, platform));
1302
+ try {
1303
+ await ensureAssetsForBundle(appId, bundleWithAssets, key, platform);
1304
+ } catch {
1305
+ }
1306
+ return { bundlePath, label: "Ready", bundle: bundleWithAssets };
1133
1307
  }
1134
1308
  function useBundleManager({
1135
1309
  base,
1136
1310
  platform,
1137
- canRequestLatest = true
1311
+ canRequestLatest = true,
1312
+ embeddedBaseBundles
1138
1313
  }) {
1139
1314
  const [bundlePath, setBundlePath] = React5.useState(null);
1140
1315
  const [renderToken, setRenderToken] = React5.useState(0);
@@ -1145,6 +1320,8 @@ function useBundleManager({
1145
1320
  const [isTesting, setIsTesting] = React5.useState(false);
1146
1321
  const baseRef = React5.useRef(base);
1147
1322
  baseRef.current = base;
1323
+ const embeddedBaseBundlesRef = React5.useRef(embeddedBaseBundles);
1324
+ embeddedBaseBundlesRef.current = embeddedBaseBundles;
1148
1325
  const baseOpIdRef = React5.useRef(0);
1149
1326
  const testOpIdRef = React5.useRef(0);
1150
1327
  const activeLoadModeRef = React5.useRef(null);
@@ -1167,16 +1344,39 @@ function useBundleManager({
1167
1344
  const hasCompletedFirstNetworkBaseLoadRef = React5.useRef(false);
1168
1345
  const hydrateBaseFromDisk = React5.useCallback(
1169
1346
  async (appId, reason) => {
1347
+ var _a, _b, _c;
1170
1348
  try {
1171
1349
  const dir = bundlesCacheDir();
1172
1350
  await ensureDir(dir);
1173
1351
  const key = baseBundleKey(appId, platform);
1174
- const uri = toBundleFileUri(key);
1175
- const existing = await getExistingNonEmptyFileUri(uri);
1352
+ let existing = await getExistingBundleFileUri(key, platform);
1353
+ let embeddedMeta = null;
1354
+ if (!existing) {
1355
+ const embedded = (_a = embeddedBaseBundlesRef.current) == null ? void 0 : _a[platform];
1356
+ const hydrated = await hydrateBaseFromEmbeddedAsset(appId, platform, embedded);
1357
+ if (hydrated == null ? void 0 : hydrated.bundlePath) {
1358
+ existing = hydrated.bundlePath;
1359
+ embeddedMeta = hydrated.meta ?? null;
1360
+ if (embeddedMeta) {
1361
+ await ensureBundleDir(key);
1362
+ await writeJsonFile(toBundleMetaFileUri(key), embeddedMeta);
1363
+ await writeJsonFile(legacyBundleMetaFileUri(key), embeddedMeta);
1364
+ }
1365
+ }
1366
+ }
1176
1367
  if (existing) {
1177
1368
  lastBaseBundlePathRef.current = existing;
1178
1369
  setBundlePath(existing);
1179
- const meta = await readJsonFile(toBundleMetaFileUri(key));
1370
+ const meta = embeddedMeta ?? await readJsonFile(toBundleMetaFileUri(key)) ?? await readJsonFile(legacyBundleMetaFileUri(key));
1371
+ const embedded = (_b = embeddedBaseBundlesRef.current) == null ? void 0 : _b[platform];
1372
+ const embeddedFingerprint = ((_c = embedded == null ? void 0 : embedded.meta) == null ? void 0 : _c.fingerprint) ?? null;
1373
+ const actualFingerprint = (meta == null ? void 0 : meta.fingerprint) ?? (embeddedMeta == null ? void 0 : embeddedMeta.fingerprint) ?? null;
1374
+ if ((embedded == null ? void 0 : embedded.assetsModule) && embeddedFingerprint && actualFingerprint === embeddedFingerprint) {
1375
+ try {
1376
+ await hydrateAssetsFromEmbeddedAsset(appId, platform, key, embedded);
1377
+ } catch {
1378
+ }
1379
+ }
1180
1380
  if (meta == null ? void 0 : meta.fingerprint) {
1181
1381
  lastBaseFingerprintRef.current = meta.fingerprint;
1182
1382
  }
@@ -1241,7 +1441,16 @@ function useBundleManager({
1241
1441
  lastBaseFingerprintRef.current = fingerprint;
1242
1442
  hasCompletedFirstNetworkBaseLoadRef.current = true;
1243
1443
  initialHydratedBaseFromDiskRef.current = false;
1244
- void writeJsonFile(toBundleMetaFileUri(baseBundleKey(src.appId, platform)), {
1444
+ const metaKey = baseBundleKey(src.appId, platform);
1445
+ await ensureBundleDir(metaKey);
1446
+ void writeJsonFile(toBundleMetaFileUri(metaKey), {
1447
+ fingerprint,
1448
+ bundleId: bundle.id,
1449
+ checksumSha256: bundle.checksumSha256 ?? null,
1450
+ size: bundle.size ?? null,
1451
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1452
+ });
1453
+ void writeJsonFile(legacyBundleMetaFileUri(metaKey), {
1245
1454
  fingerprint,
1246
1455
  bundleId: bundle.id,
1247
1456
  checksumSha256: bundle.checksumSha256 ?? null,
@@ -1763,11 +1972,28 @@ function hasNoOutcomeAfterLastHuman(messages) {
1763
1972
  }
1764
1973
 
1765
1974
  // src/studio/ui/RuntimeRenderer.tsx
1975
+ import * as React9 from "react";
1766
1976
  import { View as View2 } from "react-native";
1767
1977
  import { ComergeRuntimeRenderer } from "@comergehq/runtime";
1768
1978
  import { jsx as jsx3 } from "react/jsx-runtime";
1769
- function RuntimeRenderer({ appKey, bundlePath, forcePreparing, renderToken, style }) {
1979
+ function RuntimeRenderer({
1980
+ appKey,
1981
+ bundlePath,
1982
+ forcePreparing,
1983
+ renderToken,
1984
+ style,
1985
+ allowInitialPreparing = true
1986
+ }) {
1987
+ const [hasRenderedOnce, setHasRenderedOnce] = React9.useState(false);
1988
+ React9.useEffect(() => {
1989
+ if (bundlePath) {
1990
+ setHasRenderedOnce(true);
1991
+ }
1992
+ }, [bundlePath]);
1770
1993
  if (!bundlePath || forcePreparing) {
1994
+ if (!hasRenderedOnce && !forcePreparing && !allowInitialPreparing) {
1995
+ return /* @__PURE__ */ jsx3(View2, { style: [{ flex: 1 }, style] });
1996
+ }
1771
1997
  return /* @__PURE__ */ jsx3(View2, { style: [{ flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, style], children: /* @__PURE__ */ jsx3(Text, { variant: "bodyMuted", children: "Preparing app\u2026" }) });
1772
1998
  }
1773
1999
  return /* @__PURE__ */ jsx3(View2, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ jsx3(
@@ -1782,11 +2008,11 @@ function RuntimeRenderer({ appKey, bundlePath, forcePreparing, renderToken, styl
1782
2008
  }
1783
2009
 
1784
2010
  // src/studio/ui/StudioOverlay.tsx
1785
- import * as React41 from "react";
2011
+ import * as React42 from "react";
1786
2012
  import { Keyboard as Keyboard5, Platform as Platform10, View as View44, useWindowDimensions as useWindowDimensions4 } from "react-native";
1787
2013
 
1788
2014
  // src/components/studio-sheet/StudioBottomSheet.tsx
1789
- import * as React11 from "react";
2015
+ import * as React12 from "react";
1790
2016
  import { AppState as AppState3, Keyboard, View as View4 } from "react-native";
1791
2017
  import BottomSheet from "@gorhom/bottom-sheet";
1792
2018
  import { useSafeAreaInsets } from "react-native-safe-area-context";
@@ -1796,20 +2022,20 @@ import { Platform as Platform3, View as View3 } from "react-native";
1796
2022
  import { isLiquidGlassSupported } from "@callstack/liquid-glass";
1797
2023
 
1798
2024
  // src/components/utils/ResettableLiquidGlassView.tsx
1799
- import * as React10 from "react";
2025
+ import * as React11 from "react";
1800
2026
  import { LiquidGlassView } from "@callstack/liquid-glass";
1801
2027
 
1802
2028
  // src/components/utils/liquidGlassReset.tsx
1803
- import * as React9 from "react";
2029
+ import * as React10 from "react";
1804
2030
  import { AppState as AppState2, Platform as Platform2 } from "react-native";
1805
2031
  import { jsx as jsx4 } from "react/jsx-runtime";
1806
- var LiquidGlassResetContext = React9.createContext(0);
2032
+ var LiquidGlassResetContext = React10.createContext(0);
1807
2033
  function LiquidGlassResetProvider({
1808
2034
  children,
1809
2035
  resetTriggers = []
1810
2036
  }) {
1811
- const [token, setToken] = React9.useState(0);
1812
- React9.useEffect(() => {
2037
+ const [token, setToken] = React10.useState(0);
2038
+ React10.useEffect(() => {
1813
2039
  if (Platform2.OS !== "ios") return;
1814
2040
  const onChange = (state) => {
1815
2041
  if (state === "active") setToken((t) => t + 1);
@@ -1817,21 +2043,21 @@ function LiquidGlassResetProvider({
1817
2043
  const sub = AppState2.addEventListener("change", onChange);
1818
2044
  return () => sub.remove();
1819
2045
  }, []);
1820
- React9.useEffect(() => {
2046
+ React10.useEffect(() => {
1821
2047
  setToken((t) => t + 1);
1822
2048
  }, resetTriggers);
1823
2049
  return /* @__PURE__ */ jsx4(LiquidGlassResetContext.Provider, { value: token, children });
1824
2050
  }
1825
2051
  function useLiquidGlassResetToken() {
1826
- return React9.useContext(LiquidGlassResetContext);
2052
+ return React10.useContext(LiquidGlassResetContext);
1827
2053
  }
1828
2054
 
1829
2055
  // src/components/utils/ResettableLiquidGlassView.tsx
1830
2056
  import { jsx as jsx5 } from "react/jsx-runtime";
1831
2057
  function ResettableLiquidGlassView({ children, ...props }) {
1832
2058
  const token = useLiquidGlassResetToken();
1833
- const [layoutBootKey, setLayoutBootKey] = React10.useState(0);
1834
- const sawNonZeroLayoutRef = React10.useRef(false);
2059
+ const [layoutBootKey, setLayoutBootKey] = React11.useState(0);
2060
+ const sawNonZeroLayoutRef = React11.useRef(false);
1835
2061
  const onLayout = (e) => {
1836
2062
  var _a;
1837
2063
  (_a = props.onLayout) == null ? void 0 : _a.call(props, e);
@@ -1905,11 +2131,11 @@ function StudioBottomSheet({
1905
2131
  }) {
1906
2132
  const theme = useTheme();
1907
2133
  const insets = useSafeAreaInsets();
1908
- const internalSheetRef = React11.useRef(null);
2134
+ const internalSheetRef = React12.useRef(null);
1909
2135
  const resolvedSheetRef = sheetRef ?? internalSheetRef;
1910
- const currentIndexRef = React11.useRef(open ? snapPoints.length - 1 : -1);
1911
- const lastAppStateRef = React11.useRef(AppState3.currentState);
1912
- React11.useEffect(() => {
2136
+ const currentIndexRef = React12.useRef(open ? snapPoints.length - 1 : -1);
2137
+ const lastAppStateRef = React12.useRef(AppState3.currentState);
2138
+ React12.useEffect(() => {
1913
2139
  const sub = AppState3.addEventListener("change", (state) => {
1914
2140
  const prev = lastAppStateRef.current;
1915
2141
  lastAppStateRef.current = state;
@@ -1929,7 +2155,7 @@ function StudioBottomSheet({
1929
2155
  });
1930
2156
  return () => sub.remove();
1931
2157
  }, [open, resolvedSheetRef]);
1932
- React11.useEffect(() => {
2158
+ React12.useEffect(() => {
1933
2159
  const sheet = resolvedSheetRef.current;
1934
2160
  if (!sheet) return;
1935
2161
  if (open) {
@@ -1938,7 +2164,7 @@ function StudioBottomSheet({
1938
2164
  sheet.close();
1939
2165
  }
1940
2166
  }, [open, resolvedSheetRef, snapPoints.length]);
1941
- const handleChange = React11.useCallback(
2167
+ const handleChange = React12.useCallback(
1942
2168
  (index) => {
1943
2169
  currentIndexRef.current = index;
1944
2170
  onOpenChange == null ? void 0 : onOpenChange(index >= 0);
@@ -1967,12 +2193,12 @@ function StudioBottomSheet({
1967
2193
  }
1968
2194
 
1969
2195
  // src/components/studio-sheet/StudioSheetPager.tsx
1970
- import * as React12 from "react";
2196
+ import * as React13 from "react";
1971
2197
  import { Animated } from "react-native";
1972
2198
  import { jsx as jsx8, jsxs as jsxs2 } from "react/jsx-runtime";
1973
2199
  function StudioSheetPager({ activePage, width, preview, chat, style }) {
1974
- const anim = React12.useRef(new Animated.Value(activePage === "chat" ? 1 : 0)).current;
1975
- React12.useEffect(() => {
2200
+ const anim = React13.useRef(new Animated.Value(activePage === "chat" ? 1 : 0)).current;
2201
+ React13.useEffect(() => {
1976
2202
  Animated.spring(anim, {
1977
2203
  toValue: activePage === "chat" ? 1 : 0,
1978
2204
  useNativeDriver: true,
@@ -2020,8 +2246,8 @@ function StudioSheetPager({ activePage, width, preview, chat, style }) {
2020
2246
  ] });
2021
2247
  }
2022
2248
 
2023
- // src/components/floating-draggable-button/FloatingDraggableButton.tsx
2024
- import { useCallback as useCallback8, useEffect as useEffect10, useMemo as useMemo3, useRef as useRef7 } from "react";
2249
+ // src/components/bubble/Bubble.tsx
2250
+ import { useCallback as useCallback8, useEffect as useEffect11, useMemo as useMemo3, useRef as useRef7 } from "react";
2025
2251
  import {
2026
2252
  PanResponder,
2027
2253
  Pressable,
@@ -2044,7 +2270,7 @@ import Animated2, {
2044
2270
  } from "react-native-reanimated";
2045
2271
  import { isLiquidGlassSupported as isLiquidGlassSupported2 } from "@callstack/liquid-glass";
2046
2272
 
2047
- // src/components/floating-draggable-button/constants.ts
2273
+ // src/components/bubble/constants.ts
2048
2274
  var DEFAULT_SIZE = 48;
2049
2275
  var DEFAULT_EDGE_PADDING = 10;
2050
2276
  var DEFAULT_OFFSET = {
@@ -2055,7 +2281,7 @@ var ENTER_SCALE_FROM = 0.3;
2055
2281
  var ENTER_ROTATION_FROM_DEG = -180;
2056
2282
  var PULSE_DURATION_MS = 900;
2057
2283
 
2058
- // src/components/floating-draggable-button/FloatingDraggableButton.tsx
2284
+ // src/components/bubble/Bubble.tsx
2059
2285
  import { jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
2060
2286
  var HIDDEN_OFFSET_X = 20;
2061
2287
  var SPRING_POSITION = { damping: 12, stiffness: 100, mass: 0.8 };
@@ -2077,7 +2303,7 @@ function getHiddenTranslateY(height) {
2077
2303
  function getFinalTranslateY(height, size, bottomOffset) {
2078
2304
  return height - size - bottomOffset;
2079
2305
  }
2080
- function FloatingDraggableButton({
2306
+ function Bubble({
2081
2307
  onPress,
2082
2308
  size = DEFAULT_SIZE,
2083
2309
  disabled = false,
@@ -2098,7 +2324,7 @@ function FloatingDraggableButton({
2098
2324
  const { width, height } = useWindowDimensions();
2099
2325
  const isDanger = variant === "danger";
2100
2326
  const onPressRef = useRef7(onPress);
2101
- useEffect10(() => {
2327
+ useEffect11(() => {
2102
2328
  onPressRef.current = onPress;
2103
2329
  }, [onPress]);
2104
2330
  const fallbackBgColor = useMemo3(() => {
@@ -2139,7 +2365,7 @@ function FloatingDraggableButton({
2139
2365
  }
2140
2366
  });
2141
2367
  }, [animateToHidden]);
2142
- useEffect10(() => {
2368
+ useEffect11(() => {
2143
2369
  if (isLoading) {
2144
2370
  borderPulse.value = withRepeat(
2145
2371
  withSequence(
@@ -2164,7 +2390,7 @@ function FloatingDraggableButton({
2164
2390
  );
2165
2391
  rotation.value = withSpring(0, SPRING_ROTATION_IN);
2166
2392
  }, [height, offset.bottom, offset.left, rotation, scale, size, translateX, translateY]);
2167
- useEffect10(() => {
2393
+ useEffect11(() => {
2168
2394
  const timer = setTimeout(() => {
2169
2395
  if (visible) {
2170
2396
  animateIn();
@@ -2172,7 +2398,7 @@ function FloatingDraggableButton({
2172
2398
  }, 100);
2173
2399
  return () => clearTimeout(timer);
2174
2400
  }, []);
2175
- useEffect10(() => {
2401
+ useEffect11(() => {
2176
2402
  if (visible && isAnimatingOut.current) {
2177
2403
  animateIn();
2178
2404
  } else if (!visible && !isAnimatingOut.current) {
@@ -2180,7 +2406,7 @@ function FloatingDraggableButton({
2180
2406
  isAnimatingOut.current = true;
2181
2407
  }
2182
2408
  }, [visible, animateIn, animateToHidden]);
2183
- useEffect10(() => {
2409
+ useEffect11(() => {
2184
2410
  if (forceShowTrigger > 0 && visible) {
2185
2411
  isAnimatingOut.current = false;
2186
2412
  animateIn();
@@ -2302,7 +2528,7 @@ var styles = StyleSheet.create({
2302
2528
  });
2303
2529
 
2304
2530
  // src/components/overlays/EdgeGlowFrame.tsx
2305
- import * as React13 from "react";
2531
+ import * as React14 from "react";
2306
2532
  import { Animated as Animated3, View as View6 } from "react-native";
2307
2533
  import { LinearGradient } from "expo-linear-gradient";
2308
2534
 
@@ -2345,8 +2571,8 @@ function EdgeGlowFrame({
2345
2571
  }) {
2346
2572
  const theme = useTheme();
2347
2573
  const alpha = Math.max(0, Math.min(1, intensity));
2348
- const anim = React13.useRef(new Animated3.Value(visible ? 1 : 0)).current;
2349
- React13.useEffect(() => {
2574
+ const anim = React14.useRef(new Animated3.Value(visible ? 1 : 0)).current;
2575
+ React14.useEffect(() => {
2350
2576
  Animated3.timing(anim, {
2351
2577
  toValue: visible ? 1 : 0,
2352
2578
  duration: 300,
@@ -2397,12 +2623,12 @@ function EdgeGlowFrame({
2397
2623
  }
2398
2624
 
2399
2625
  // src/components/draw/DrawModeOverlay.tsx
2400
- import * as React16 from "react";
2626
+ import * as React17 from "react";
2401
2627
  import { StyleSheet as StyleSheet3, View as View10 } from "react-native";
2402
2628
  import { captureRef } from "react-native-view-shot";
2403
2629
 
2404
2630
  // src/components/draw/DrawSurface.tsx
2405
- import * as React14 from "react";
2631
+ import * as React15 from "react";
2406
2632
  import { PanResponder as PanResponder2, StyleSheet as StyleSheet2, View as View7 } from "react-native";
2407
2633
  import Svg, { Path } from "react-native-svg";
2408
2634
 
@@ -2434,25 +2660,25 @@ function DrawSurface({
2434
2660
  style,
2435
2661
  minDistance = 1
2436
2662
  }) {
2437
- const [renderTick, setRenderTick] = React14.useState(0);
2438
- const currentPointsRef = React14.useRef([]);
2439
- const rafRef = React14.useRef(null);
2440
- const triggerRender = React14.useCallback(() => {
2663
+ const [renderTick, setRenderTick] = React15.useState(0);
2664
+ const currentPointsRef = React15.useRef([]);
2665
+ const rafRef = React15.useRef(null);
2666
+ const triggerRender = React15.useCallback(() => {
2441
2667
  if (rafRef.current !== null) return;
2442
2668
  rafRef.current = requestAnimationFrame(() => {
2443
2669
  rafRef.current = null;
2444
2670
  setRenderTick((n) => n + 1);
2445
2671
  });
2446
2672
  }, []);
2447
- React14.useEffect(() => () => {
2673
+ React15.useEffect(() => () => {
2448
2674
  if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
2449
2675
  }, []);
2450
- const onStart = React14.useCallback((e) => {
2676
+ const onStart = React15.useCallback((e) => {
2451
2677
  const { locationX, locationY } = e.nativeEvent;
2452
2678
  currentPointsRef.current = [{ x: locationX, y: locationY }];
2453
2679
  triggerRender();
2454
2680
  }, [triggerRender]);
2455
- const onMove = React14.useCallback((e, _g) => {
2681
+ const onMove = React15.useCallback((e, _g) => {
2456
2682
  const { locationX, locationY } = e.nativeEvent;
2457
2683
  const pts = currentPointsRef.current;
2458
2684
  if (pts.length > 0) {
@@ -2465,7 +2691,7 @@ function DrawSurface({
2465
2691
  currentPointsRef.current = [...pts, { x: locationX, y: locationY }];
2466
2692
  triggerRender();
2467
2693
  }, [minDistance, triggerRender]);
2468
- const onEnd = React14.useCallback(() => {
2694
+ const onEnd = React15.useCallback(() => {
2469
2695
  const points = currentPointsRef.current;
2470
2696
  if (points.length > 0) {
2471
2697
  onAddStroke({ points, color, width: strokeWidth });
@@ -2473,7 +2699,7 @@ function DrawSurface({
2473
2699
  currentPointsRef.current = [];
2474
2700
  triggerRender();
2475
2701
  }, [color, onAddStroke, strokeWidth, triggerRender]);
2476
- const panResponder = React14.useMemo(
2702
+ const panResponder = React15.useMemo(
2477
2703
  () => PanResponder2.create({
2478
2704
  onStartShouldSetPanResponder: () => true,
2479
2705
  onMoveShouldSetPanResponder: () => true,
@@ -2523,7 +2749,7 @@ var styles2 = StyleSheet2.create({
2523
2749
  });
2524
2750
 
2525
2751
  // src/components/draw/DrawToolbar.tsx
2526
- import * as React15 from "react";
2752
+ import * as React16 from "react";
2527
2753
  import {
2528
2754
  ActivityIndicator,
2529
2755
  Animated as Animated4,
@@ -2620,11 +2846,11 @@ function DrawToolbar({
2620
2846
  }) {
2621
2847
  const insets = useSafeAreaInsets2();
2622
2848
  const { width: screenWidth, height: screenHeight } = useWindowDimensions2();
2623
- const [expanded, setExpanded] = React15.useState(false);
2624
- const pos = React15.useRef(new Animated4.ValueXY({ x: screenWidth / 2 - 110, y: -140 })).current;
2625
- const start = React15.useRef({ x: 0, y: 0 });
2626
- const currentPos = React15.useRef({ x: 0, y: 0 });
2627
- React15.useEffect(() => {
2849
+ const [expanded, setExpanded] = React16.useState(false);
2850
+ const pos = React16.useRef(new Animated4.ValueXY({ x: screenWidth / 2 - 110, y: -140 })).current;
2851
+ const start = React16.useRef({ x: 0, y: 0 });
2852
+ const currentPos = React16.useRef({ x: 0, y: 0 });
2853
+ React16.useEffect(() => {
2628
2854
  if (hidden) return;
2629
2855
  Animated4.spring(pos.y, {
2630
2856
  toValue: insets.top + 60,
@@ -2634,7 +2860,7 @@ function DrawToolbar({
2634
2860
  mass: 0.8
2635
2861
  }).start();
2636
2862
  }, [hidden, insets.top, pos.y]);
2637
- React15.useEffect(() => {
2863
+ React16.useEffect(() => {
2638
2864
  const id = pos.addListener((v) => {
2639
2865
  currentPos.current = { x: v.x ?? 0, y: v.y ?? 0 };
2640
2866
  });
@@ -2642,7 +2868,7 @@ function DrawToolbar({
2642
2868
  pos.removeListener(id);
2643
2869
  };
2644
2870
  }, [pos]);
2645
- const clamp2 = React15.useCallback(
2871
+ const clamp2 = React16.useCallback(
2646
2872
  (x, y) => {
2647
2873
  const minX = 10;
2648
2874
  const maxX = Math.max(10, screenWidth - 230);
@@ -2652,7 +2878,7 @@ function DrawToolbar({
2652
2878
  },
2653
2879
  [insets.top, screenHeight, screenWidth]
2654
2880
  );
2655
- const panResponder = React15.useMemo(
2881
+ const panResponder = React16.useMemo(
2656
2882
  () => PanResponder3.create({
2657
2883
  onStartShouldSetPanResponder: () => false,
2658
2884
  onMoveShouldSetPanResponder: (_e, g) => Math.abs(g.dx) > 5 || Math.abs(g.dy) > 5,
@@ -2680,7 +2906,7 @@ function DrawToolbar({
2680
2906
  children
2681
2907
  }) {
2682
2908
  const isDisabled = Boolean(disabled) || Boolean(capturingDisabled);
2683
- const [pressed, setPressed] = React15.useState(false);
2909
+ const [pressed, setPressed] = React16.useState(false);
2684
2910
  return /* @__PURE__ */ jsx13(
2685
2911
  View9,
2686
2912
  {
@@ -2818,7 +3044,7 @@ function DrawModeOverlay({
2818
3044
  renderDragHandle
2819
3045
  }) {
2820
3046
  const theme = useTheme();
2821
- const defaultPalette = React16.useMemo(
3047
+ const defaultPalette = React17.useMemo(
2822
3048
  () => [
2823
3049
  "#EF4444",
2824
3050
  // Red
@@ -2836,11 +3062,11 @@ function DrawModeOverlay({
2836
3062
  []
2837
3063
  );
2838
3064
  const colors = palette && palette.length > 0 ? palette : defaultPalette;
2839
- const [selectedColor, setSelectedColor] = React16.useState(colors[0] ?? "#EF4444");
2840
- const [strokes, setStrokes] = React16.useState([]);
2841
- const [capturing, setCapturing] = React16.useState(false);
2842
- const [hideUi, setHideUi] = React16.useState(false);
2843
- React16.useEffect(() => {
3065
+ const [selectedColor, setSelectedColor] = React17.useState(colors[0] ?? "#EF4444");
3066
+ const [strokes, setStrokes] = React17.useState([]);
3067
+ const [capturing, setCapturing] = React17.useState(false);
3068
+ const [hideUi, setHideUi] = React17.useState(false);
3069
+ React17.useEffect(() => {
2844
3070
  if (!visible) return;
2845
3071
  setStrokes([]);
2846
3072
  setSelectedColor(colors[0] ?? "#EF4444");
@@ -2848,14 +3074,14 @@ function DrawModeOverlay({
2848
3074
  setHideUi(false);
2849
3075
  }, [colors, visible]);
2850
3076
  const canUndo = strokes.length > 0;
2851
- const handleUndo = React16.useCallback(() => {
3077
+ const handleUndo = React17.useCallback(() => {
2852
3078
  setStrokes((prev) => prev.slice(0, -1));
2853
3079
  }, []);
2854
- const handleCancel = React16.useCallback(() => {
3080
+ const handleCancel = React17.useCallback(() => {
2855
3081
  setStrokes([]);
2856
3082
  onCancel();
2857
3083
  }, [onCancel]);
2858
- const handleDone = React16.useCallback(async () => {
3084
+ const handleDone = React17.useCallback(async () => {
2859
3085
  if (!captureTargetRef.current || capturing) return;
2860
3086
  try {
2861
3087
  setCapturing(true);
@@ -2915,7 +3141,7 @@ var styles3 = StyleSheet3.create({
2915
3141
  });
2916
3142
 
2917
3143
  // src/components/comments/AppCommentsSheet.tsx
2918
- import * as React23 from "react";
3144
+ import * as React24 from "react";
2919
3145
  import { ActivityIndicator as ActivityIndicator3, Keyboard as Keyboard3, Platform as Platform6, Pressable as Pressable5, View as View14 } from "react-native";
2920
3146
  import {
2921
3147
  BottomSheetBackdrop,
@@ -2927,7 +3153,7 @@ import { isLiquidGlassSupported as isLiquidGlassSupported4 } from "@callstack/li
2927
3153
  import { Play as Play2 } from "lucide-react-native";
2928
3154
 
2929
3155
  // src/components/chat/ChatComposer.tsx
2930
- import * as React18 from "react";
3156
+ import * as React19 from "react";
2931
3157
  import {
2932
3158
  ActivityIndicator as ActivityIndicator2,
2933
3159
  Animated as Animated5,
@@ -2941,11 +3167,11 @@ import { isLiquidGlassSupported as isLiquidGlassSupported3 } from "@callstack/li
2941
3167
  import { Plus } from "lucide-react-native";
2942
3168
 
2943
3169
  // src/components/chat/MultilineTextInput.tsx
2944
- import * as React17 from "react";
3170
+ import * as React18 from "react";
2945
3171
  import { TextInput } from "react-native";
2946
3172
  import { BottomSheetTextInput } from "@gorhom/bottom-sheet";
2947
3173
  import { jsx as jsx15 } from "react/jsx-runtime";
2948
- var MultilineTextInput = React17.forwardRef(function MultilineTextInput2({ useBottomSheetTextInput = false, placeholder, placeholderTextColor, style, ...props }, ref) {
3174
+ var MultilineTextInput = React18.forwardRef(function MultilineTextInput2({ useBottomSheetTextInput = false, placeholder, placeholderTextColor, style, ...props }, ref) {
2949
3175
  const theme = useTheme();
2950
3176
  const baseStyle = {
2951
3177
  minHeight: 44,
@@ -3029,7 +3255,7 @@ function AspectRatioThumbnail({
3029
3255
  onRemove,
3030
3256
  renderRemoveIcon
3031
3257
  }) {
3032
- const [aspectRatio, setAspectRatio] = React18.useState(1);
3258
+ const [aspectRatio, setAspectRatio] = React19.useState(1);
3033
3259
  return /* @__PURE__ */ jsxs8(View11, { style: { height: THUMBNAIL_HEIGHT, aspectRatio, position: "relative" }, children: [
3034
3260
  /* @__PURE__ */ jsx17(View11, { style: { flex: 1, borderRadius: 8, overflow: "hidden" }, children: /* @__PURE__ */ jsx17(
3035
3261
  Image,
@@ -3086,19 +3312,19 @@ function ChatComposer({
3086
3312
  style
3087
3313
  }) {
3088
3314
  const theme = useTheme();
3089
- const [internal, setInternal] = React18.useState("");
3315
+ const [internal, setInternal] = React19.useState("");
3090
3316
  const text = value ?? internal;
3091
3317
  const setText = onChangeValue ?? setInternal;
3092
3318
  const hasAttachments = attachments.length > 0;
3093
3319
  const hasText = text.trim().length > 0;
3094
3320
  const composerMinHeight = hasAttachments ? THUMBNAIL_HEIGHT + 44 + 24 : 44;
3095
3321
  const isButtonDisabled = sending || disabled || sendDisabled;
3096
- const maxInputHeight = React18.useMemo(() => Dimensions.get("window").height * 0.5, []);
3097
- const shakeAnim = React18.useRef(new Animated5.Value(0)).current;
3098
- const [sendPressed, setSendPressed] = React18.useState(false);
3099
- const inputRef = React18.useRef(null);
3100
- const prevAutoFocusRef = React18.useRef(false);
3101
- React18.useEffect(() => {
3322
+ const maxInputHeight = React19.useMemo(() => Dimensions.get("window").height * 0.5, []);
3323
+ const shakeAnim = React19.useRef(new Animated5.Value(0)).current;
3324
+ const [sendPressed, setSendPressed] = React19.useState(false);
3325
+ const inputRef = React19.useRef(null);
3326
+ const prevAutoFocusRef = React19.useRef(false);
3327
+ React19.useEffect(() => {
3102
3328
  const shouldFocus = autoFocus && !prevAutoFocusRef.current && !disabled && !sending;
3103
3329
  prevAutoFocusRef.current = autoFocus;
3104
3330
  if (!shouldFocus) return;
@@ -3108,7 +3334,7 @@ function ChatComposer({
3108
3334
  }, 75);
3109
3335
  return () => clearTimeout(t);
3110
3336
  }, [autoFocus, disabled, sending]);
3111
- const triggerShake = React18.useCallback(() => {
3337
+ const triggerShake = React19.useCallback(() => {
3112
3338
  shakeAnim.setValue(0);
3113
3339
  Animated5.sequence([
3114
3340
  Animated5.timing(shakeAnim, { toValue: 10, duration: 50, useNativeDriver: true }),
@@ -3118,7 +3344,7 @@ function ChatComposer({
3118
3344
  Animated5.timing(shakeAnim, { toValue: 0, duration: 50, useNativeDriver: true })
3119
3345
  ]).start();
3120
3346
  }, [shakeAnim]);
3121
- const handleSend = React18.useCallback(async () => {
3347
+ const handleSend = React19.useCallback(async () => {
3122
3348
  if (isButtonDisabled) return;
3123
3349
  if (!hasText) {
3124
3350
  triggerShake();
@@ -3251,7 +3477,7 @@ function ChatComposer({
3251
3477
  }
3252
3478
 
3253
3479
  // src/components/comments/CommentRow.tsx
3254
- import * as React19 from "react";
3480
+ import * as React20 from "react";
3255
3481
  import { View as View13 } from "react-native";
3256
3482
 
3257
3483
  // src/components/primitives/Avatar.tsx
@@ -3323,9 +3549,9 @@ function formatTimeAgo(iso) {
3323
3549
  import { jsx as jsx19, jsxs as jsxs9 } from "react/jsx-runtime";
3324
3550
  function CommentRow({ comment, showDivider }) {
3325
3551
  const theme = useTheme();
3326
- const [authorName, setAuthorName] = React19.useState(null);
3327
- const [authorAvatar, setAuthorAvatar] = React19.useState(null);
3328
- React19.useEffect(() => {
3552
+ const [authorName, setAuthorName] = React20.useState(null);
3553
+ const [authorAvatar, setAuthorAvatar] = React20.useState(null);
3554
+ React20.useEffect(() => {
3329
3555
  let cancelled = false;
3330
3556
  (async () => {
3331
3557
  try {
@@ -3365,7 +3591,7 @@ function CommentRow({ comment, showDivider }) {
3365
3591
  }
3366
3592
 
3367
3593
  // src/components/comments/useAppComments.ts
3368
- import * as React20 from "react";
3594
+ import * as React21 from "react";
3369
3595
 
3370
3596
  // src/data/comments/remote.ts
3371
3597
  var AppCommentsRemoteDataSourceImpl = class extends BaseRemote {
@@ -3437,18 +3663,18 @@ var appCommentsRepository = new AppCommentsRepositoryImpl(appCommentsRemoteDataS
3437
3663
 
3438
3664
  // src/components/comments/useAppComments.ts
3439
3665
  function useAppComments(appId) {
3440
- const [comments, setComments] = React20.useState([]);
3441
- const [loading, setLoading] = React20.useState(false);
3442
- const [sending, setSending] = React20.useState(false);
3443
- const [error, setError] = React20.useState(null);
3444
- const sortByCreatedAtAsc = React20.useCallback((items) => {
3666
+ const [comments, setComments] = React21.useState([]);
3667
+ const [loading, setLoading] = React21.useState(false);
3668
+ const [sending, setSending] = React21.useState(false);
3669
+ const [error, setError] = React21.useState(null);
3670
+ const sortByCreatedAtAsc = React21.useCallback((items) => {
3445
3671
  return [...items].sort((a, b) => {
3446
3672
  const at = a.createdAt ? new Date(a.createdAt).getTime() : 0;
3447
3673
  const bt = b.createdAt ? new Date(b.createdAt).getTime() : 0;
3448
3674
  return at - bt;
3449
3675
  });
3450
3676
  }, []);
3451
- const refresh = React20.useCallback(async () => {
3677
+ const refresh = React21.useCallback(async () => {
3452
3678
  if (!appId) {
3453
3679
  setComments([]);
3454
3680
  return;
@@ -3465,10 +3691,10 @@ function useAppComments(appId) {
3465
3691
  setLoading(false);
3466
3692
  }
3467
3693
  }, [appId, sortByCreatedAtAsc]);
3468
- React20.useEffect(() => {
3694
+ React21.useEffect(() => {
3469
3695
  void refresh();
3470
3696
  }, [refresh]);
3471
- const create = React20.useCallback(
3697
+ const create = React21.useCallback(
3472
3698
  async (text) => {
3473
3699
  if (!appId) return;
3474
3700
  const trimmed = text.trim();
@@ -3491,11 +3717,11 @@ function useAppComments(appId) {
3491
3717
  }
3492
3718
 
3493
3719
  // src/components/comments/useAppDetails.ts
3494
- import * as React21 from "react";
3720
+ import * as React22 from "react";
3495
3721
  function useAppDetails(appId) {
3496
- const [app, setApp] = React21.useState(null);
3497
- const [loading, setLoading] = React21.useState(false);
3498
- React21.useEffect(() => {
3722
+ const [app, setApp] = React22.useState(null);
3723
+ const [loading, setLoading] = React22.useState(false);
3724
+ React22.useEffect(() => {
3499
3725
  if (!appId) {
3500
3726
  setApp(null);
3501
3727
  return;
@@ -3520,11 +3746,11 @@ function useAppDetails(appId) {
3520
3746
  }
3521
3747
 
3522
3748
  // src/components/comments/useIosKeyboardSnapFix.ts
3523
- import * as React22 from "react";
3749
+ import * as React23 from "react";
3524
3750
  import { Keyboard as Keyboard2, Platform as Platform5 } from "react-native";
3525
3751
  function useIosKeyboardSnapFix(sheetRef, options) {
3526
- const [keyboardVisible, setKeyboardVisible] = React22.useState(false);
3527
- React22.useEffect(() => {
3752
+ const [keyboardVisible, setKeyboardVisible] = React23.useState(false);
3753
+ React23.useEffect(() => {
3528
3754
  if (Platform5.OS !== "ios") return;
3529
3755
  const show = Keyboard2.addListener("keyboardWillShow", () => setKeyboardVisible(true));
3530
3756
  const hide = Keyboard2.addListener("keyboardWillHide", () => {
@@ -3552,16 +3778,16 @@ import { jsx as jsx20, jsxs as jsxs10 } from "react/jsx-runtime";
3552
3778
  function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
3553
3779
  const theme = useTheme();
3554
3780
  const insets = useSafeAreaInsets3();
3555
- const sheetRef = React23.useRef(null);
3556
- const snapPoints = React23.useMemo(() => ["50%", "90%"], []);
3557
- const currentIndexRef = React23.useRef(1);
3781
+ const sheetRef = React24.useRef(null);
3782
+ const snapPoints = React24.useMemo(() => ["50%", "90%"], []);
3783
+ const currentIndexRef = React24.useRef(1);
3558
3784
  const { comments, loading, sending, error, create, refresh } = useAppComments(appId);
3559
3785
  const { app, loading: loadingApp } = useAppDetails(appId);
3560
3786
  const { keyboardVisible } = useIosKeyboardSnapFix(sheetRef, {
3561
3787
  getCurrentIndex: () => currentIndexRef.current,
3562
3788
  targetIndex: 1
3563
3789
  });
3564
- React23.useEffect(() => {
3790
+ React24.useEffect(() => {
3565
3791
  var _a, _b;
3566
3792
  if (appId) {
3567
3793
  (_a = sheetRef.current) == null ? void 0 : _a.present();
@@ -3570,22 +3796,22 @@ function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
3570
3796
  (_b = sheetRef.current) == null ? void 0 : _b.dismiss();
3571
3797
  }
3572
3798
  }, [appId, refresh]);
3573
- React23.useEffect(() => {
3799
+ React24.useEffect(() => {
3574
3800
  if (!appId) return;
3575
3801
  onCountChange == null ? void 0 : onCountChange(comments.length);
3576
3802
  }, [appId, comments.length, onCountChange]);
3577
- const renderBackdrop = React23.useCallback(
3803
+ const renderBackdrop = React24.useCallback(
3578
3804
  (props) => /* @__PURE__ */ jsx20(BottomSheetBackdrop, { ...props, disappearsOnIndex: -1, appearsOnIndex: 0, opacity: 0.5 }),
3579
3805
  []
3580
3806
  );
3581
- const handleChange = React23.useCallback(
3807
+ const handleChange = React24.useCallback(
3582
3808
  (index) => {
3583
3809
  currentIndexRef.current = index;
3584
3810
  if (index === -1) onClose();
3585
3811
  },
3586
3812
  [onClose]
3587
3813
  );
3588
- const handlePlay = React23.useCallback(async () => {
3814
+ const handlePlay = React24.useCallback(async () => {
3589
3815
  var _a;
3590
3816
  if (!appId) return;
3591
3817
  (_a = sheetRef.current) == null ? void 0 : _a.dismiss();
@@ -3795,7 +4021,7 @@ function StudioSheetHeader({ left, center, right, style }) {
3795
4021
  }
3796
4022
 
3797
4023
  // src/components/studio-sheet/StudioSheetHeaderIconButton.tsx
3798
- import * as React24 from "react";
4024
+ import * as React25 from "react";
3799
4025
  import { Pressable as Pressable6, View as View17 } from "react-native";
3800
4026
  import { isLiquidGlassSupported as isLiquidGlassSupported5 } from "@callstack/liquid-glass";
3801
4027
  import { jsx as jsx23 } from "react/jsx-runtime";
@@ -3810,7 +4036,7 @@ function StudioSheetHeaderIconButton({
3810
4036
  }) {
3811
4037
  const theme = useTheme();
3812
4038
  const size = 44;
3813
- const [pressed, setPressed] = React24.useState(false);
4039
+ const [pressed, setPressed] = React25.useState(false);
3814
4040
  const solidBg = intent === "danger" ? theme.colors.danger : intent === "primary" ? theme.colors.primary : theme.colors.neutral;
3815
4041
  const glassFallbackBg = theme.scheme === "dark" ? "#18181B" : "#F6F6F6";
3816
4042
  const glassInnerBg = intent === "danger" ? theme.colors.danger : theme.colors.primary;
@@ -4000,14 +4226,14 @@ function PreviewHeroCard({
4000
4226
  }
4001
4227
 
4002
4228
  // src/components/preview/PreviewPlaceholder.tsx
4003
- import * as React25 from "react";
4229
+ import * as React26 from "react";
4004
4230
  import { Animated as Animated6 } from "react-native";
4005
4231
  import { LinearGradient as LinearGradient2 } from "expo-linear-gradient";
4006
4232
  import { Fragment as Fragment3, jsx as jsx28, jsxs as jsxs15 } from "react/jsx-runtime";
4007
4233
  function PreviewPlaceholder({ visible, style }) {
4008
4234
  if (!visible) return null;
4009
- const opacityAnim = React25.useRef(new Animated6.Value(0)).current;
4010
- React25.useEffect(() => {
4235
+ const opacityAnim = React26.useRef(new Animated6.Value(0)).current;
4236
+ React26.useEffect(() => {
4011
4237
  if (!visible) return;
4012
4238
  const animation = Animated6.loop(
4013
4239
  Animated6.sequence([
@@ -4599,12 +4825,12 @@ function PreviewCustomizeSection({
4599
4825
  }
4600
4826
 
4601
4827
  // src/studio/ui/preview-panel/PreviewCollaborateSection.tsx
4602
- import * as React31 from "react";
4828
+ import * as React32 from "react";
4603
4829
  import { ActivityIndicator as ActivityIndicator6, Alert, View as View32 } from "react-native";
4604
4830
  import { Send as Send2 } from "lucide-react-native";
4605
4831
 
4606
4832
  // src/components/merge-requests/MergeRequestStatusCard.tsx
4607
- import * as React27 from "react";
4833
+ import * as React28 from "react";
4608
4834
  import { Animated as Animated7, Pressable as Pressable9, View as View28 } from "react-native";
4609
4835
  import { Ban, Check as Check3, CheckCheck, ChevronDown as ChevronDown2 } from "lucide-react-native";
4610
4836
 
@@ -4686,11 +4912,11 @@ function toIsoString(input) {
4686
4912
  }
4687
4913
 
4688
4914
  // src/components/merge-requests/useControlledExpansion.ts
4689
- import * as React26 from "react";
4915
+ import * as React27 from "react";
4690
4916
  function useControlledExpansion(props) {
4691
- const [uncontrolled, setUncontrolled] = React26.useState(false);
4917
+ const [uncontrolled, setUncontrolled] = React27.useState(false);
4692
4918
  const expanded = props.expanded ?? uncontrolled;
4693
- const setExpanded = React26.useCallback(
4919
+ const setExpanded = React27.useCallback(
4694
4920
  (next) => {
4695
4921
  var _a;
4696
4922
  (_a = props.onExpandedChange) == null ? void 0 : _a.call(props, next);
@@ -4715,8 +4941,8 @@ function MergeRequestStatusCard({
4715
4941
  const isDark = theme.scheme === "dark";
4716
4942
  const textColor = isDark ? "#FFFFFF" : "#000000";
4717
4943
  const subTextColor = isDark ? "#A1A1AA" : "#71717A";
4718
- const status = React27.useMemo(() => getMergeRequestStatusDisplay(String(mergeRequest.status)), [mergeRequest.status]);
4719
- const { StatusIcon, iconColor, bgColor, statusText } = React27.useMemo(() => {
4944
+ const status = React28.useMemo(() => getMergeRequestStatusDisplay(String(mergeRequest.status)), [mergeRequest.status]);
4945
+ const { StatusIcon, iconColor, bgColor, statusText } = React28.useMemo(() => {
4720
4946
  switch (mergeRequest.status) {
4721
4947
  case "approved":
4722
4948
  case "merged":
@@ -4747,8 +4973,8 @@ function MergeRequestStatusCard({
4747
4973
  const createdIso = toIsoString(mergeRequest.createdAt ?? null);
4748
4974
  const headerTimeAgo = updatedIso ? formatTimeAgo(updatedIso) : "";
4749
4975
  const createdTimeAgo = createdIso ? formatTimeAgo(createdIso) : "";
4750
- const rotate = React27.useRef(new Animated7.Value(expanded ? 1 : 0)).current;
4751
- React27.useEffect(() => {
4976
+ const rotate = React28.useRef(new Animated7.Value(expanded ? 1 : 0)).current;
4977
+ React28.useEffect(() => {
4752
4978
  Animated7.timing(rotate, {
4753
4979
  toValue: expanded ? 1 : 0,
4754
4980
  duration: 200,
@@ -4839,16 +5065,16 @@ function MergeRequestStatusCard({
4839
5065
  }
4840
5066
 
4841
5067
  // src/components/merge-requests/ReviewMergeRequestCarousel.tsx
4842
- import * as React30 from "react";
5068
+ import * as React31 from "react";
4843
5069
  import { Animated as Animated9, FlatList, View as View31, useWindowDimensions as useWindowDimensions3 } from "react-native";
4844
5070
 
4845
5071
  // src/components/merge-requests/ReviewMergeRequestCard.tsx
4846
- import * as React29 from "react";
5072
+ import * as React30 from "react";
4847
5073
  import { ActivityIndicator as ActivityIndicator5, Animated as Animated8, Pressable as Pressable11, View as View30 } from "react-native";
4848
5074
  import { Check as Check4, ChevronDown as ChevronDown3, Play as Play3, X as X3 } from "lucide-react-native";
4849
5075
 
4850
5076
  // src/components/merge-requests/ReviewMergeRequestActionButton.tsx
4851
- import * as React28 from "react";
5077
+ import * as React29 from "react";
4852
5078
  import { Pressable as Pressable10, View as View29 } from "react-native";
4853
5079
  import { jsx as jsx41 } from "react/jsx-runtime";
4854
5080
  function ReviewMergeRequestActionButton({
@@ -4859,7 +5085,7 @@ function ReviewMergeRequestActionButton({
4859
5085
  children,
4860
5086
  iconOnly
4861
5087
  }) {
4862
- const [pressed, setPressed] = React28.useState(false);
5088
+ const [pressed, setPressed] = React29.useState(false);
4863
5089
  const height = iconOnly ? 36 : 40;
4864
5090
  const width = iconOnly ? 36 : void 0;
4865
5091
  const paddingHorizontal = iconOnly ? 0 : 16;
@@ -4921,10 +5147,10 @@ function ReviewMergeRequestCard({
4921
5147
  onTest
4922
5148
  }) {
4923
5149
  const theme = useTheme();
4924
- const status = React29.useMemo(() => getMergeRequestStatusDisplay(mr.status), [mr.status]);
5150
+ const status = React30.useMemo(() => getMergeRequestStatusDisplay(mr.status), [mr.status]);
4925
5151
  const canAct = mr.status === "open";
4926
- const rotate = React29.useRef(new Animated8.Value(isExpanded ? 1 : 0)).current;
4927
- React29.useEffect(() => {
5152
+ const rotate = React30.useRef(new Animated8.Value(isExpanded ? 1 : 0)).current;
5153
+ React30.useEffect(() => {
4928
5154
  Animated8.timing(rotate, { toValue: isExpanded ? 1 : 0, duration: 200, useNativeDriver: true }).start();
4929
5155
  }, [isExpanded, rotate]);
4930
5156
  const position = total > 1 ? `${index + 1}/${total}` : "Merge request";
@@ -5056,11 +5282,11 @@ function ReviewMergeRequestCarousel({
5056
5282
  }) {
5057
5283
  const theme = useTheme();
5058
5284
  const { width } = useWindowDimensions3();
5059
- const [expanded, setExpanded] = React30.useState({});
5060
- const carouselScrollX = React30.useRef(new Animated9.Value(0)).current;
5285
+ const [expanded, setExpanded] = React31.useState({});
5286
+ const carouselScrollX = React31.useRef(new Animated9.Value(0)).current;
5061
5287
  const peekAmount = 24;
5062
5288
  const gap = 16;
5063
- const cardWidth = React30.useMemo(() => Math.max(1, width - theme.spacing.lg * 2 - peekAmount), [peekAmount, theme.spacing.lg, width]);
5289
+ const cardWidth = React31.useMemo(() => Math.max(1, width - theme.spacing.lg * 2 - peekAmount), [peekAmount, theme.spacing.lg, width]);
5064
5290
  const snapInterval = cardWidth + gap;
5065
5291
  const dotColor = theme.scheme === "dark" ? "#FFFFFF" : "#000000";
5066
5292
  if (mergeRequests.length === 0) return null;
@@ -5159,7 +5385,7 @@ function PreviewCollaborateSection({
5159
5385
  onTestMr
5160
5386
  }) {
5161
5387
  const theme = useTheme();
5162
- const [submittingMr, setSubmittingMr] = React31.useState(false);
5388
+ const [submittingMr, setSubmittingMr] = React32.useState(false);
5163
5389
  const hasSection = canSubmitMergeRequest || incomingMergeRequests.length > 0 || outgoingMergeRequests.length > 0;
5164
5390
  if (!hasSection) return null;
5165
5391
  const showActionsSubtitle = canSubmitMergeRequest && onSubmitMergeRequest || onTestMr && incomingMergeRequests.length > 0;
@@ -5267,7 +5493,7 @@ function PreviewCollaborateSection({
5267
5493
  }
5268
5494
 
5269
5495
  // src/studio/ui/preview-panel/usePreviewPanelData.ts
5270
- import * as React33 from "react";
5496
+ import * as React34 from "react";
5271
5497
 
5272
5498
  // src/data/apps/images/remote.ts
5273
5499
  var AppImagesRemoteDataSourceImpl = class extends BaseRemote {
@@ -5318,7 +5544,7 @@ var AppImagesRepositoryImpl = class extends BaseRepository {
5318
5544
  var appImagesRepository = new AppImagesRepositoryImpl(appImagesRemoteDataSource);
5319
5545
 
5320
5546
  // src/studio/hooks/useAppStats.ts
5321
- import * as React32 from "react";
5547
+ import * as React33 from "react";
5322
5548
  import * as Haptics2 from "expo-haptics";
5323
5549
 
5324
5550
  // src/data/likes/remote.ts
@@ -5387,34 +5613,34 @@ function useAppStats({
5387
5613
  initialIsLiked = false,
5388
5614
  onOpenComments
5389
5615
  }) {
5390
- const [likeCount, setLikeCount] = React32.useState(initialLikes);
5391
- const [commentCount, setCommentCount] = React32.useState(initialComments);
5392
- const [forkCount, setForkCount] = React32.useState(initialForks);
5393
- const [isLiked, setIsLiked] = React32.useState(initialIsLiked);
5394
- const didMutateRef = React32.useRef(false);
5395
- const lastAppIdRef = React32.useRef("");
5396
- React32.useEffect(() => {
5616
+ const [likeCount, setLikeCount] = React33.useState(initialLikes);
5617
+ const [commentCount, setCommentCount] = React33.useState(initialComments);
5618
+ const [forkCount, setForkCount] = React33.useState(initialForks);
5619
+ const [isLiked, setIsLiked] = React33.useState(initialIsLiked);
5620
+ const didMutateRef = React33.useRef(false);
5621
+ const lastAppIdRef = React33.useRef("");
5622
+ React33.useEffect(() => {
5397
5623
  if (lastAppIdRef.current === appId) return;
5398
5624
  lastAppIdRef.current = appId;
5399
5625
  didMutateRef.current = false;
5400
5626
  }, [appId]);
5401
- React32.useEffect(() => {
5627
+ React33.useEffect(() => {
5402
5628
  if (didMutateRef.current) return;
5403
5629
  setLikeCount(initialLikes);
5404
5630
  }, [appId, initialLikes]);
5405
- React32.useEffect(() => {
5631
+ React33.useEffect(() => {
5406
5632
  if (didMutateRef.current) return;
5407
5633
  setCommentCount(initialComments);
5408
5634
  }, [appId, initialComments]);
5409
- React32.useEffect(() => {
5635
+ React33.useEffect(() => {
5410
5636
  if (didMutateRef.current) return;
5411
5637
  setForkCount(initialForks);
5412
5638
  }, [appId, initialForks]);
5413
- React32.useEffect(() => {
5639
+ React33.useEffect(() => {
5414
5640
  if (didMutateRef.current) return;
5415
5641
  setIsLiked(initialIsLiked);
5416
5642
  }, [appId, initialIsLiked]);
5417
- const handleLike = React32.useCallback(async () => {
5643
+ const handleLike = React33.useCallback(async () => {
5418
5644
  var _a, _b;
5419
5645
  if (!appId) return;
5420
5646
  didMutateRef.current = true;
@@ -5438,7 +5664,7 @@ function useAppStats({
5438
5664
  setLikeCount((prev) => Math.max(0, prev + (newIsLiked ? -1 : 1)));
5439
5665
  }
5440
5666
  }, [appId, isLiked, likeCount]);
5441
- const handleOpenComments = React32.useCallback(() => {
5667
+ const handleOpenComments = React33.useCallback(() => {
5442
5668
  if (!appId) return;
5443
5669
  try {
5444
5670
  void Haptics2.impactAsync(Haptics2.ImpactFeedbackStyle.Light);
@@ -5453,11 +5679,11 @@ function useAppStats({
5453
5679
  var LIKE_DEBUG_PREFIX = "[COMERGE_LIKE_DEBUG]";
5454
5680
  function usePreviewPanelData(params) {
5455
5681
  const { app, isOwner, outgoingMergeRequests, onOpenComments, commentCountOverride } = params;
5456
- const [imageUrl, setImageUrl] = React33.useState(null);
5457
- const [imageLoaded, setImageLoaded] = React33.useState(false);
5458
- const [insights, setInsights] = React33.useState({ likes: 0, comments: 0, forks: 0, downloads: 0 });
5459
- const [creator, setCreator] = React33.useState(null);
5460
- React33.useEffect(() => {
5682
+ const [imageUrl, setImageUrl] = React34.useState(null);
5683
+ const [imageLoaded, setImageLoaded] = React34.useState(false);
5684
+ const [insights, setInsights] = React34.useState({ likes: 0, comments: 0, forks: 0, downloads: 0 });
5685
+ const [creator, setCreator] = React34.useState(null);
5686
+ React34.useEffect(() => {
5461
5687
  if (!(app == null ? void 0 : app.id)) return;
5462
5688
  let cancelled = false;
5463
5689
  (async () => {
@@ -5472,7 +5698,7 @@ function usePreviewPanelData(params) {
5472
5698
  cancelled = true;
5473
5699
  };
5474
5700
  }, [app == null ? void 0 : app.id]);
5475
- React33.useEffect(() => {
5701
+ React34.useEffect(() => {
5476
5702
  if (!(app == null ? void 0 : app.createdBy)) return;
5477
5703
  let cancelled = false;
5478
5704
  (async () => {
@@ -5488,10 +5714,10 @@ function usePreviewPanelData(params) {
5488
5714
  cancelled = true;
5489
5715
  };
5490
5716
  }, [app == null ? void 0 : app.createdBy]);
5491
- React33.useEffect(() => {
5717
+ React34.useEffect(() => {
5492
5718
  setImageLoaded(false);
5493
5719
  }, [app == null ? void 0 : app.id]);
5494
- React33.useEffect(() => {
5720
+ React34.useEffect(() => {
5495
5721
  if (!(app == null ? void 0 : app.id)) return;
5496
5722
  let cancelled = false;
5497
5723
  (async () => {
@@ -5516,7 +5742,7 @@ function usePreviewPanelData(params) {
5516
5742
  cancelled = true;
5517
5743
  };
5518
5744
  }, [app == null ? void 0 : app.id]);
5519
- React33.useEffect(() => {
5745
+ React34.useEffect(() => {
5520
5746
  if (!(app == null ? void 0 : app.id)) return;
5521
5747
  log.debug(
5522
5748
  `${LIKE_DEBUG_PREFIX} usePreviewPanelData.appChanged appId=${app.id} app.isLiked=${String(app.isLiked)}`
@@ -5530,7 +5756,7 @@ function usePreviewPanelData(params) {
5530
5756
  initialIsLiked: Boolean(app == null ? void 0 : app.isLiked),
5531
5757
  onOpenComments
5532
5758
  });
5533
- const canSubmitMergeRequest = React33.useMemo(() => {
5759
+ const canSubmitMergeRequest = React34.useMemo(() => {
5534
5760
  if (!isOwner) return false;
5535
5761
  if (!app) return false;
5536
5762
  if (!app.forkedFromAppId) return false;
@@ -5643,16 +5869,16 @@ function PreviewPanel({
5643
5869
  }
5644
5870
 
5645
5871
  // src/studio/ui/ChatPanel.tsx
5646
- import * as React38 from "react";
5872
+ import * as React39 from "react";
5647
5873
  import { ActivityIndicator as ActivityIndicator8, View as View41 } from "react-native";
5648
5874
 
5649
5875
  // src/components/chat/ChatPage.tsx
5650
- import * as React36 from "react";
5876
+ import * as React37 from "react";
5651
5877
  import { Keyboard as Keyboard4, Platform as Platform9, View as View37 } from "react-native";
5652
5878
  import { useSafeAreaInsets as useSafeAreaInsets4 } from "react-native-safe-area-context";
5653
5879
 
5654
5880
  // src/components/chat/ChatMessageList.tsx
5655
- import * as React35 from "react";
5881
+ import * as React36 from "react";
5656
5882
  import { View as View36 } from "react-native";
5657
5883
  import { BottomSheetFlatList } from "@gorhom/bottom-sheet";
5658
5884
 
@@ -5698,17 +5924,17 @@ function ChatMessageBubble({ message, renderContent, style }) {
5698
5924
  }
5699
5925
 
5700
5926
  // src/components/chat/TypingIndicator.tsx
5701
- import * as React34 from "react";
5927
+ import * as React35 from "react";
5702
5928
  import { Animated as Animated10, View as View35 } from "react-native";
5703
5929
  import { jsx as jsx47 } from "react/jsx-runtime";
5704
5930
  function TypingIndicator({ style }) {
5705
5931
  const theme = useTheme();
5706
5932
  const dotColor = theme.colors.textSubtle;
5707
- const anims = React34.useMemo(
5933
+ const anims = React35.useMemo(
5708
5934
  () => [new Animated10.Value(0.3), new Animated10.Value(0.3), new Animated10.Value(0.3)],
5709
5935
  []
5710
5936
  );
5711
- React34.useEffect(() => {
5937
+ React35.useEffect(() => {
5712
5938
  const loops = [];
5713
5939
  anims.forEach((a, idx) => {
5714
5940
  const seq = Animated10.sequence([
@@ -5742,7 +5968,7 @@ function TypingIndicator({ style }) {
5742
5968
 
5743
5969
  // src/components/chat/ChatMessageList.tsx
5744
5970
  import { jsx as jsx48, jsxs as jsxs28 } from "react/jsx-runtime";
5745
- var ChatMessageList = React35.forwardRef(
5971
+ var ChatMessageList = React36.forwardRef(
5746
5972
  ({
5747
5973
  messages,
5748
5974
  showTypingIndicator = false,
@@ -5753,20 +5979,20 @@ var ChatMessageList = React35.forwardRef(
5753
5979
  nearBottomThreshold = 200
5754
5980
  }, ref) => {
5755
5981
  const theme = useTheme();
5756
- const listRef = React35.useRef(null);
5757
- const nearBottomRef = React35.useRef(true);
5758
- const initialScrollDoneRef = React35.useRef(false);
5759
- const lastMessageIdRef = React35.useRef(null);
5760
- const data = React35.useMemo(() => {
5982
+ const listRef = React36.useRef(null);
5983
+ const nearBottomRef = React36.useRef(true);
5984
+ const initialScrollDoneRef = React36.useRef(false);
5985
+ const lastMessageIdRef = React36.useRef(null);
5986
+ const data = React36.useMemo(() => {
5761
5987
  return [...messages].reverse();
5762
5988
  }, [messages]);
5763
- const scrollToBottom = React35.useCallback((options) => {
5989
+ const scrollToBottom = React36.useCallback((options) => {
5764
5990
  var _a;
5765
5991
  const animated = (options == null ? void 0 : options.animated) ?? true;
5766
5992
  (_a = listRef.current) == null ? void 0 : _a.scrollToOffset({ offset: 0, animated });
5767
5993
  }, []);
5768
- React35.useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);
5769
- const handleScroll = React35.useCallback(
5994
+ React36.useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);
5995
+ const handleScroll = React36.useCallback(
5770
5996
  (e) => {
5771
5997
  const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent;
5772
5998
  const distanceFromBottom = Math.max(contentOffset.y - Math.max(bottomInset, 0), 0);
@@ -5778,7 +6004,7 @@ var ChatMessageList = React35.forwardRef(
5778
6004
  },
5779
6005
  [bottomInset, nearBottomThreshold, onNearBottomChange]
5780
6006
  );
5781
- React35.useEffect(() => {
6007
+ React36.useEffect(() => {
5782
6008
  if (!initialScrollDoneRef.current) return;
5783
6009
  const lastId = messages.length > 0 ? messages[messages.length - 1].id : null;
5784
6010
  const prevLastId = lastMessageIdRef.current;
@@ -5788,7 +6014,7 @@ var ChatMessageList = React35.forwardRef(
5788
6014
  const id = requestAnimationFrame(() => scrollToBottom({ animated: true }));
5789
6015
  return () => cancelAnimationFrame(id);
5790
6016
  }, [messages, scrollToBottom]);
5791
- React35.useEffect(() => {
6017
+ React36.useEffect(() => {
5792
6018
  if (showTypingIndicator && nearBottomRef.current) {
5793
6019
  const id = requestAnimationFrame(() => scrollToBottom({ animated: true }));
5794
6020
  return () => cancelAnimationFrame(id);
@@ -5850,9 +6076,9 @@ function ChatPage({
5850
6076
  }) {
5851
6077
  const theme = useTheme();
5852
6078
  const insets = useSafeAreaInsets4();
5853
- const [composerHeight, setComposerHeight] = React36.useState(0);
5854
- const [keyboardVisible, setKeyboardVisible] = React36.useState(false);
5855
- React36.useEffect(() => {
6079
+ const [composerHeight, setComposerHeight] = React37.useState(0);
6080
+ const [keyboardVisible, setKeyboardVisible] = React37.useState(false);
6081
+ React37.useEffect(() => {
5856
6082
  if (Platform9.OS !== "ios") return;
5857
6083
  const show = Keyboard4.addListener("keyboardWillShow", () => setKeyboardVisible(true));
5858
6084
  const hide = Keyboard4.addListener("keyboardWillHide", () => setKeyboardVisible(false));
@@ -5864,12 +6090,12 @@ function ChatPage({
5864
6090
  const footerBottomPadding = Platform9.OS === "ios" ? keyboardVisible ? 0 : insets.bottom : insets.bottom + 10;
5865
6091
  const overlayBottom = composerHeight + footerBottomPadding + theme.spacing.lg;
5866
6092
  const bottomInset = composerHeight + footerBottomPadding + theme.spacing.xl;
5867
- const resolvedOverlay = React36.useMemo(() => {
6093
+ const resolvedOverlay = React37.useMemo(() => {
5868
6094
  var _a;
5869
6095
  if (!overlay) return null;
5870
- if (!React36.isValidElement(overlay)) return overlay;
6096
+ if (!React37.isValidElement(overlay)) return overlay;
5871
6097
  const prevStyle = (_a = overlay.props) == null ? void 0 : _a.style;
5872
- return React36.cloneElement(overlay, {
6098
+ return React37.cloneElement(overlay, {
5873
6099
  style: [prevStyle, { bottom: overlayBottom }]
5874
6100
  });
5875
6101
  }, [overlay, overlayBottom]);
@@ -5924,15 +6150,15 @@ function ChatPage({
5924
6150
  }
5925
6151
 
5926
6152
  // src/components/chat/ScrollToBottomButton.tsx
5927
- import * as React37 from "react";
6153
+ import * as React38 from "react";
5928
6154
  import { Pressable as Pressable12, View as View38 } from "react-native";
5929
6155
  import Animated11, { Easing as Easing2, useAnimatedStyle as useAnimatedStyle2, useSharedValue as useSharedValue2, withTiming as withTiming2 } from "react-native-reanimated";
5930
6156
  import { jsx as jsx50 } from "react/jsx-runtime";
5931
6157
  function ScrollToBottomButton({ visible, onPress, children, style }) {
5932
6158
  const theme = useTheme();
5933
6159
  const progress = useSharedValue2(visible ? 1 : 0);
5934
- const [pressed, setPressed] = React37.useState(false);
5935
- React37.useEffect(() => {
6160
+ const [pressed, setPressed] = React38.useState(false);
6161
+ React38.useEffect(() => {
5936
6162
  progress.value = withTiming2(visible ? 1 : 0, { duration: 200, easing: Easing2.out(Easing2.ease) });
5937
6163
  }, [progress, visible]);
5938
6164
  const animStyle = useAnimatedStyle2(() => ({
@@ -6087,9 +6313,9 @@ function ChatPanel({
6087
6313
  onStartDraw,
6088
6314
  onSend
6089
6315
  }) {
6090
- const listRef = React38.useRef(null);
6091
- const [nearBottom, setNearBottom] = React38.useState(true);
6092
- const handleSend = React38.useCallback(
6316
+ const listRef = React39.useRef(null);
6317
+ const [nearBottom, setNearBottom] = React39.useState(true);
6318
+ const handleSend = React39.useCallback(
6093
6319
  async (text, composerAttachments) => {
6094
6320
  const all = composerAttachments ?? attachments;
6095
6321
  await onSend(text, all.length > 0 ? all : void 0);
@@ -6103,7 +6329,7 @@ function ChatPanel({
6103
6329
  },
6104
6330
  [attachments, nearBottom, onClearAttachments, onSend]
6105
6331
  );
6106
- const handleScrollToBottom = React38.useCallback(() => {
6332
+ const handleScrollToBottom = React39.useCallback(() => {
6107
6333
  var _a;
6108
6334
  (_a = listRef.current) == null ? void 0 : _a.scrollToBottom({ animated: true });
6109
6335
  }, []);
@@ -6177,7 +6403,7 @@ function ChatPanel({
6177
6403
  }
6178
6404
 
6179
6405
  // src/components/dialogs/ConfirmMergeRequestDialog.tsx
6180
- import * as React39 from "react";
6406
+ import * as React40 from "react";
6181
6407
  import { Pressable as Pressable14, View as View43 } from "react-native";
6182
6408
 
6183
6409
  // src/components/primitives/Modal.tsx
@@ -6229,14 +6455,14 @@ function ConfirmMergeRequestDialog({
6229
6455
  onTestFirst
6230
6456
  }) {
6231
6457
  const theme = useTheme();
6232
- const close = React39.useCallback(() => onOpenChange(false), [onOpenChange]);
6458
+ const close = React40.useCallback(() => onOpenChange(false), [onOpenChange]);
6233
6459
  const canConfirm = Boolean(mergeRequest) && !approveDisabled;
6234
- const handleConfirm = React39.useCallback(() => {
6460
+ const handleConfirm = React40.useCallback(() => {
6235
6461
  if (!mergeRequest) return;
6236
6462
  onOpenChange(false);
6237
6463
  void onConfirm();
6238
6464
  }, [mergeRequest, onConfirm, onOpenChange]);
6239
- const handleTestFirst = React39.useCallback(() => {
6465
+ const handleTestFirst = React40.useCallback(() => {
6240
6466
  if (!mergeRequest) return;
6241
6467
  onOpenChange(false);
6242
6468
  void onTestFirst(mergeRequest);
@@ -6385,7 +6611,7 @@ function ConfirmMergeFlow({
6385
6611
  }
6386
6612
 
6387
6613
  // src/studio/hooks/useOptimisticChatMessages.ts
6388
- import * as React40 from "react";
6614
+ import * as React41 from "react";
6389
6615
  function makeOptimisticId() {
6390
6616
  return `optimistic:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 10)}`;
6391
6617
  }
@@ -6423,11 +6649,11 @@ function useOptimisticChatMessages({
6423
6649
  chatMessages,
6424
6650
  onSendChat
6425
6651
  }) {
6426
- const [optimisticChat, setOptimisticChat] = React40.useState([]);
6427
- React40.useEffect(() => {
6652
+ const [optimisticChat, setOptimisticChat] = React41.useState([]);
6653
+ React41.useEffect(() => {
6428
6654
  setOptimisticChat([]);
6429
6655
  }, [threadId]);
6430
- const messages = React40.useMemo(() => {
6656
+ const messages = React41.useMemo(() => {
6431
6657
  if (!optimisticChat || optimisticChat.length === 0) return chatMessages;
6432
6658
  const unresolved = optimisticChat.filter((o) => !isOptimisticResolvedByServer(chatMessages, o));
6433
6659
  if (unresolved.length === 0) return chatMessages;
@@ -6443,7 +6669,7 @@ function useOptimisticChatMessages({
6443
6669
  merged.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
6444
6670
  return merged;
6445
6671
  }, [chatMessages, optimisticChat]);
6446
- React40.useEffect(() => {
6672
+ React41.useEffect(() => {
6447
6673
  if (optimisticChat.length === 0) return;
6448
6674
  setOptimisticChat((prev) => {
6449
6675
  if (prev.length === 0) return prev;
@@ -6451,7 +6677,7 @@ function useOptimisticChatMessages({
6451
6677
  return next.length === prev.length ? prev : next;
6452
6678
  });
6453
6679
  }, [chatMessages, optimisticChat.length]);
6454
- const onSend = React40.useCallback(
6680
+ const onSend = React41.useCallback(
6455
6681
  async (text, attachments) => {
6456
6682
  if (shouldForkOnEdit) {
6457
6683
  await onSendChat(text, attachments);
@@ -6503,18 +6729,18 @@ function StudioOverlay({
6503
6729
  chatShowTypingIndicator,
6504
6730
  onSendChat,
6505
6731
  onNavigateHome,
6506
- showFloatingButton,
6732
+ showBubble,
6507
6733
  studioControlOptions
6508
6734
  }) {
6509
6735
  const theme = useTheme();
6510
6736
  const { width } = useWindowDimensions4();
6511
- const [sheetOpen, setSheetOpen] = React41.useState(false);
6512
- const sheetOpenRef = React41.useRef(sheetOpen);
6513
- const [activePage, setActivePage] = React41.useState("preview");
6514
- const [drawing, setDrawing] = React41.useState(false);
6515
- const [chatAttachments, setChatAttachments] = React41.useState([]);
6516
- const [commentsAppId, setCommentsAppId] = React41.useState(null);
6517
- const [commentsCount, setCommentsCount] = React41.useState(null);
6737
+ const [sheetOpen, setSheetOpen] = React42.useState(false);
6738
+ const sheetOpenRef = React42.useRef(sheetOpen);
6739
+ const [activePage, setActivePage] = React42.useState("preview");
6740
+ const [drawing, setDrawing] = React42.useState(false);
6741
+ const [chatAttachments, setChatAttachments] = React42.useState([]);
6742
+ const [commentsAppId, setCommentsAppId] = React42.useState(null);
6743
+ const [commentsCount, setCommentsCount] = React42.useState(null);
6518
6744
  const threadId = (app == null ? void 0 : app.threadId) ?? null;
6519
6745
  const optimistic = useOptimisticChatMessages({
6520
6746
  threadId,
@@ -6522,24 +6748,24 @@ function StudioOverlay({
6522
6748
  chatMessages,
6523
6749
  onSendChat
6524
6750
  });
6525
- const [confirmMrId, setConfirmMrId] = React41.useState(null);
6526
- const confirmMr = React41.useMemo(
6751
+ const [confirmMrId, setConfirmMrId] = React42.useState(null);
6752
+ const confirmMr = React42.useMemo(
6527
6753
  () => confirmMrId ? incomingMergeRequests.find((m) => m.id === confirmMrId) ?? null : null,
6528
6754
  [confirmMrId, incomingMergeRequests]
6529
6755
  );
6530
- const handleSheetOpenChange = React41.useCallback((open) => {
6756
+ const handleSheetOpenChange = React42.useCallback((open) => {
6531
6757
  setSheetOpen(open);
6532
6758
  if (!open) Keyboard5.dismiss();
6533
6759
  }, []);
6534
- const closeSheet = React41.useCallback(() => {
6760
+ const closeSheet = React42.useCallback(() => {
6535
6761
  handleSheetOpenChange(false);
6536
6762
  }, [handleSheetOpenChange]);
6537
- const openSheet = React41.useCallback(() => setSheetOpen(true), []);
6538
- const goToChat = React41.useCallback(() => {
6763
+ const openSheet = React42.useCallback(() => setSheetOpen(true), []);
6764
+ const goToChat = React42.useCallback(() => {
6539
6765
  setActivePage("chat");
6540
6766
  openSheet();
6541
6767
  }, [openSheet]);
6542
- const backToPreview = React41.useCallback(() => {
6768
+ const backToPreview = React42.useCallback(() => {
6543
6769
  if (Platform10.OS !== "ios") {
6544
6770
  Keyboard5.dismiss();
6545
6771
  setActivePage("preview");
@@ -6557,11 +6783,11 @@ function StudioOverlay({
6557
6783
  const t = setTimeout(finalize, 350);
6558
6784
  Keyboard5.dismiss();
6559
6785
  }, []);
6560
- const startDraw = React41.useCallback(() => {
6786
+ const startDraw = React42.useCallback(() => {
6561
6787
  setDrawing(true);
6562
6788
  closeSheet();
6563
6789
  }, [closeSheet]);
6564
- const handleDrawCapture = React41.useCallback(
6790
+ const handleDrawCapture = React42.useCallback(
6565
6791
  (dataUrl) => {
6566
6792
  setChatAttachments((prev) => [...prev, dataUrl]);
6567
6793
  setDrawing(false);
@@ -6570,7 +6796,7 @@ function StudioOverlay({
6570
6796
  },
6571
6797
  [openSheet]
6572
6798
  );
6573
- const toggleSheet = React41.useCallback(async () => {
6799
+ const toggleSheet = React42.useCallback(async () => {
6574
6800
  if (!sheetOpen) {
6575
6801
  const shouldExitTest = Boolean(testingMrId) || isTesting;
6576
6802
  if (shouldExitTest) {
@@ -6582,7 +6808,7 @@ function StudioOverlay({
6582
6808
  closeSheet();
6583
6809
  }
6584
6810
  }, [closeSheet, isTesting, onRestoreBase, sheetOpen, testingMrId]);
6585
- const handleTestMr = React41.useCallback(
6811
+ const handleTestMr = React42.useCallback(
6586
6812
  async (mr) => {
6587
6813
  if (!onTestMr) return;
6588
6814
  await onTestMr(mr);
@@ -6590,10 +6816,10 @@ function StudioOverlay({
6590
6816
  },
6591
6817
  [closeSheet, onTestMr]
6592
6818
  );
6593
- React41.useEffect(() => {
6819
+ React42.useEffect(() => {
6594
6820
  sheetOpenRef.current = sheetOpen;
6595
6821
  }, [sheetOpen]);
6596
- React41.useEffect(() => {
6822
+ React42.useEffect(() => {
6597
6823
  const poller = startStudioControlPolling((action) => {
6598
6824
  if (action === "show" && !sheetOpenRef.current) openSheet();
6599
6825
  if (action === "hide" && sheetOpenRef.current) closeSheet();
@@ -6601,7 +6827,7 @@ function StudioOverlay({
6601
6827
  }, studioControlOptions);
6602
6828
  return () => poller.stop();
6603
6829
  }, [closeSheet, openSheet, studioControlOptions, toggleSheet]);
6604
- React41.useEffect(() => {
6830
+ React42.useEffect(() => {
6605
6831
  void publishComergeStudioUIState(sheetOpen, studioControlOptions);
6606
6832
  }, [sheetOpen, studioControlOptions]);
6607
6833
  return /* @__PURE__ */ jsxs34(Fragment6, { children: [
@@ -6660,8 +6886,8 @@ function StudioOverlay({
6660
6886
  )
6661
6887
  }
6662
6888
  ) }),
6663
- showFloatingButton && /* @__PURE__ */ jsx57(
6664
- FloatingDraggableButton,
6889
+ showBubble && /* @__PURE__ */ jsx57(
6890
+ Bubble,
6665
6891
  {
6666
6892
  visible: !sheetOpen && !drawing,
6667
6893
  ariaLabel: sheetOpen ? "Hide studio" : "Show studio",
@@ -6709,24 +6935,25 @@ function StudioOverlay({
6709
6935
  import { jsx as jsx58, jsxs as jsxs35 } from "react/jsx-runtime";
6710
6936
  function ComergeStudio({
6711
6937
  appId,
6712
- apiKey,
6938
+ clientKey: clientKey2,
6713
6939
  appKey = "MicroMain",
6714
6940
  onNavigateHome,
6715
6941
  style,
6716
- showFloatingButton = true,
6717
- studioControlOptions
6942
+ showBubble = true,
6943
+ studioControlOptions,
6944
+ embeddedBaseBundles
6718
6945
  }) {
6719
- const [activeAppId, setActiveAppId] = React42.useState(appId);
6720
- const [runtimeAppId, setRuntimeAppId] = React42.useState(appId);
6721
- const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React42.useState(null);
6722
- const platform = React42.useMemo(() => RNPlatform.OS === "ios" ? "ios" : "android", []);
6723
- React42.useEffect(() => {
6946
+ const [activeAppId, setActiveAppId] = React43.useState(appId);
6947
+ const [runtimeAppId, setRuntimeAppId] = React43.useState(appId);
6948
+ const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React43.useState(null);
6949
+ const platform = React43.useMemo(() => RNPlatform.OS === "ios" ? "ios" : "android", []);
6950
+ React43.useEffect(() => {
6724
6951
  setActiveAppId(appId);
6725
6952
  setRuntimeAppId(appId);
6726
6953
  setPendingRuntimeTargetAppId(null);
6727
6954
  }, [appId]);
6728
- const captureTargetRef = React42.useRef(null);
6729
- return /* @__PURE__ */ jsx58(StudioBootstrap, { apiKey, children: ({ userId }) => /* @__PURE__ */ jsx58(BottomSheetModalProvider, { children: /* @__PURE__ */ jsx58(LiquidGlassResetProvider, { resetTriggers: [appId, activeAppId, runtimeAppId], children: /* @__PURE__ */ jsx58(
6955
+ const captureTargetRef = React43.useRef(null);
6956
+ return /* @__PURE__ */ jsx58(StudioBootstrap, { clientKey: clientKey2, fallback: /* @__PURE__ */ jsx58(View45, { style: { flex: 1 } }), children: ({ userId }) => /* @__PURE__ */ jsx58(BottomSheetModalProvider, { children: /* @__PURE__ */ jsx58(LiquidGlassResetProvider, { resetTriggers: [appId, activeAppId, runtimeAppId], children: /* @__PURE__ */ jsx58(
6730
6957
  ComergeStudioInner,
6731
6958
  {
6732
6959
  userId,
@@ -6741,8 +6968,9 @@ function ComergeStudio({
6741
6968
  onNavigateHome,
6742
6969
  captureTargetRef,
6743
6970
  style,
6744
- showFloatingButton,
6745
- studioControlOptions
6971
+ showBubble,
6972
+ studioControlOptions,
6973
+ embeddedBaseBundles
6746
6974
  }
6747
6975
  ) }) }) });
6748
6976
  }
@@ -6759,17 +6987,18 @@ function ComergeStudioInner({
6759
6987
  onNavigateHome,
6760
6988
  captureTargetRef,
6761
6989
  style,
6762
- showFloatingButton,
6763
- studioControlOptions
6990
+ showBubble,
6991
+ studioControlOptions,
6992
+ embeddedBaseBundles
6764
6993
  }) {
6765
6994
  const { app, loading: appLoading } = useApp(activeAppId);
6766
6995
  const { app: runtimeAppFromHook } = useApp(runtimeAppId, { enabled: runtimeAppId !== activeAppId });
6767
6996
  const runtimeApp = runtimeAppId === activeAppId ? app : runtimeAppFromHook;
6768
- const sawEditingOnPendingTargetRef = React42.useRef(false);
6769
- React42.useEffect(() => {
6997
+ const sawEditingOnPendingTargetRef = React43.useRef(false);
6998
+ React43.useEffect(() => {
6770
6999
  sawEditingOnPendingTargetRef.current = false;
6771
7000
  }, [pendingRuntimeTargetAppId]);
6772
- React42.useEffect(() => {
7001
+ React43.useEffect(() => {
6773
7002
  if (!pendingRuntimeTargetAppId) return;
6774
7003
  if (activeAppId !== pendingRuntimeTargetAppId) return;
6775
7004
  if ((app == null ? void 0 : app.status) === "editing") {
@@ -6784,15 +7013,16 @@ function ComergeStudioInner({
6784
7013
  const bundle = useBundleManager({
6785
7014
  base: { appId: runtimeAppId, commitId: (runtimeApp == null ? void 0 : runtimeApp.headCommitId) ?? void 0 },
6786
7015
  platform,
6787
- canRequestLatest: (runtimeApp == null ? void 0 : runtimeApp.status) === "ready"
7016
+ canRequestLatest: (runtimeApp == null ? void 0 : runtimeApp.status) === "ready",
7017
+ embeddedBaseBundles
6788
7018
  });
6789
- const sawEditingOnActiveAppRef = React42.useRef(false);
6790
- const [showPostEditPreparing, setShowPostEditPreparing] = React42.useState(false);
6791
- React42.useEffect(() => {
7019
+ const sawEditingOnActiveAppRef = React43.useRef(false);
7020
+ const [showPostEditPreparing, setShowPostEditPreparing] = React43.useState(false);
7021
+ React43.useEffect(() => {
6792
7022
  sawEditingOnActiveAppRef.current = false;
6793
7023
  setShowPostEditPreparing(false);
6794
7024
  }, [activeAppId]);
6795
- React42.useEffect(() => {
7025
+ React43.useEffect(() => {
6796
7026
  if (!(app == null ? void 0 : app.id)) return;
6797
7027
  if (app.status === "editing") {
6798
7028
  sawEditingOnActiveAppRef.current = true;
@@ -6804,7 +7034,7 @@ function ComergeStudioInner({
6804
7034
  sawEditingOnActiveAppRef.current = false;
6805
7035
  }
6806
7036
  }, [app == null ? void 0 : app.id, app == null ? void 0 : app.status]);
6807
- React42.useEffect(() => {
7037
+ React43.useEffect(() => {
6808
7038
  if (!showPostEditPreparing) return;
6809
7039
  const stillProcessingBaseBundle = bundle.loading && bundle.loadingMode === "base" && !bundle.isTesting;
6810
7040
  if (!stillProcessingBaseBundle) {
@@ -6814,10 +7044,10 @@ function ComergeStudioInner({
6814
7044
  const threadId = (app == null ? void 0 : app.threadId) ?? "";
6815
7045
  const thread = useThreadMessages(threadId);
6816
7046
  const mergeRequests = useMergeRequests({ appId: activeAppId });
6817
- const hasOpenOutgoingMr = React42.useMemo(() => {
7047
+ const hasOpenOutgoingMr = React43.useMemo(() => {
6818
7048
  return mergeRequests.lists.outgoing.some((mr) => mr.status === "open");
6819
7049
  }, [mergeRequests.lists.outgoing]);
6820
- const incomingReviewMrs = React42.useMemo(() => {
7050
+ const incomingReviewMrs = React43.useMemo(() => {
6821
7051
  if (!userId) return mergeRequests.lists.incoming;
6822
7052
  return mergeRequests.lists.incoming.filter((mr) => mr.createdBy !== userId);
6823
7053
  }, [mergeRequests.lists.incoming, userId]);
@@ -6839,9 +7069,9 @@ function ComergeStudioInner({
6839
7069
  uploadAttachments: uploader.uploadBase64Images
6840
7070
  });
6841
7071
  const chatSendDisabled = hasNoOutcomeAfterLastHuman(thread.raw);
6842
- const [processingMrId, setProcessingMrId] = React42.useState(null);
6843
- const [testingMrId, setTestingMrId] = React42.useState(null);
6844
- const chatShowTypingIndicator = React42.useMemo(() => {
7072
+ const [processingMrId, setProcessingMrId] = React43.useState(null);
7073
+ const [testingMrId, setTestingMrId] = React43.useState(null);
7074
+ const chatShowTypingIndicator = React43.useMemo(() => {
6845
7075
  var _a;
6846
7076
  if (!thread.raw || thread.raw.length === 0) return false;
6847
7077
  const last = thread.raw[thread.raw.length - 1];
@@ -6855,7 +7085,8 @@ function ComergeStudioInner({
6855
7085
  appKey,
6856
7086
  bundlePath: bundle.bundlePath,
6857
7087
  forcePreparing: showPostEditPreparing,
6858
- renderToken: bundle.renderToken
7088
+ renderToken: bundle.renderToken,
7089
+ allowInitialPreparing: !embeddedBaseBundles
6859
7090
  }
6860
7091
  ),
6861
7092
  /* @__PURE__ */ jsx58(
@@ -6911,7 +7142,7 @@ function ComergeStudioInner({
6911
7142
  chatShowTypingIndicator,
6912
7143
  onSendChat: (text, attachments) => actions.sendEdit({ prompt: text, attachments }),
6913
7144
  onNavigateHome,
6914
- showFloatingButton,
7145
+ showBubble,
6915
7146
  studioControlOptions
6916
7147
  }
6917
7148
  )