@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.js CHANGED
@@ -36,7 +36,7 @@ __export(index_exports, {
36
36
  module.exports = __toCommonJS(index_exports);
37
37
 
38
38
  // src/studio/ComergeStudio.tsx
39
- var React42 = __toESM(require("react"));
39
+ var React43 = __toESM(require("react"));
40
40
  var import_react_native55 = require("react-native");
41
41
  var import_bottom_sheet6 = require("@gorhom/bottom-sheet");
42
42
 
@@ -218,11 +218,11 @@ var React = __toESM(require("react"));
218
218
  var import_axios = __toESM(require("axios"));
219
219
 
220
220
  // src/core/services/http/baseUrl.ts
221
- var BASE_URL = "https://comerge.ai";
221
+ var BASE_URL = "https://api.comerge.ai";
222
222
 
223
223
  // src/core/services/http/public.ts
224
224
  var CLIENT_KEY_HEADER = "x-comerge-api-key";
225
- var clientApiKey = null;
225
+ var clientKey = null;
226
226
  var publicApi = import_axios.default.create({
227
227
  baseURL: BASE_URL,
228
228
  timeout: 3e4,
@@ -231,19 +231,19 @@ var publicApi = import_axios.default.create({
231
231
  "Content-Type": "application/json"
232
232
  }
233
233
  });
234
- function setClientApiKey(apiKey) {
234
+ function setClientKey(clientKeyInput) {
235
235
  var _a;
236
- const trimmed = ((_a = apiKey == null ? void 0 : apiKey.trim) == null ? void 0 : _a.call(apiKey)) ?? "";
236
+ const trimmed = ((_a = clientKeyInput == null ? void 0 : clientKeyInput.trim) == null ? void 0 : _a.call(clientKeyInput)) ?? "";
237
237
  if (!trimmed) {
238
- throw new Error("comerge-studio: apiKey is required");
238
+ throw new Error("comerge-studio: clientKey is required");
239
239
  }
240
- clientApiKey = trimmed;
240
+ clientKey = trimmed;
241
241
  publicApi.defaults.headers.common[CLIENT_KEY_HEADER] = trimmed;
242
242
  }
243
243
  publicApi.interceptors.request.use((config) => {
244
- if (!clientApiKey) return config;
244
+ if (!clientKey) return config;
245
245
  config.headers = config.headers ?? {};
246
- config.headers[CLIENT_KEY_HEADER] = clientApiKey;
246
+ config.headers[CLIENT_KEY_HEADER] = clientKey;
247
247
  return config;
248
248
  });
249
249
 
@@ -308,42 +308,9 @@ async function ensureAnonymousSession() {
308
308
  return { user: data.user, isNew: true };
309
309
  }
310
310
 
311
- // src/data/base-repository.ts
312
- var BaseRepository = class {
313
- unwrapOrThrow(res) {
314
- if (res.success && res.responseObject) return res.responseObject;
315
- const msg = res.message || "Request failed";
316
- throw new Error(msg);
317
- }
318
- };
319
-
320
- // src/data/base-remote.ts
321
- var BaseRemote = class {
322
- };
323
-
324
- // src/data/public/studio-config/remote.ts
325
- var StudioConfigRemoteDataSourceImpl = class extends BaseRemote {
326
- async get() {
327
- const { data } = await publicApi.get("/v1/public/studio-config");
328
- return data;
329
- }
330
- };
331
- var studioConfigRemoteDataSource = new StudioConfigRemoteDataSourceImpl();
332
-
333
- // src/data/public/studio-config/repository.ts
334
- var StudioConfigRepositoryImpl = class extends BaseRepository {
335
- constructor(remote) {
336
- super();
337
- this.remote = remote;
338
- }
339
- async get() {
340
- const res = await this.remote.get();
341
- return this.unwrapOrThrow(res);
342
- }
343
- };
344
- var studioConfigRepository = new StudioConfigRepositoryImpl(studioConfigRemoteDataSource);
345
-
346
311
  // src/studio/bootstrap/useStudioBootstrap.ts
312
+ var SUPABASE_URL = "https://xtfxwbckjpfmqubnsusu.supabase.co";
313
+ var SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh0Znh3YmNranBmbXF1Ym5zdXN1Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjA2MDEyMzAsImV4cCI6MjA3NjE3NzIzMH0.dzWGAWrK4CvrmHVHzf8w7JlUZohdap0ZPnLZnABMV8s";
347
314
  function useStudioBootstrap(options) {
348
315
  const [state, setState] = React.useState({
349
316
  ready: false,
@@ -354,11 +321,10 @@ function useStudioBootstrap(options) {
354
321
  let cancelled = false;
355
322
  (async () => {
356
323
  try {
357
- setClientApiKey(options.apiKey);
324
+ setClientKey(options.clientKey);
358
325
  const requireAuth = isSupabaseClientInjected();
359
326
  if (!requireAuth) {
360
- const cfg = await studioConfigRepository.get();
361
- setSupabaseConfig(cfg);
327
+ setSupabaseConfig({ url: SUPABASE_URL, anonKey: SUPABASE_ANON_KEY });
362
328
  }
363
329
  const { user } = requireAuth ? await ensureAuthenticatedSession() : await ensureAnonymousSession();
364
330
  if (cancelled) return;
@@ -372,14 +338,14 @@ function useStudioBootstrap(options) {
372
338
  return () => {
373
339
  cancelled = true;
374
340
  };
375
- }, [options.apiKey]);
341
+ }, [options.clientKey]);
376
342
  return state;
377
343
  }
378
344
 
379
345
  // src/studio/bootstrap/StudioBootstrap.tsx
380
346
  var import_jsx_runtime2 = require("react/jsx-runtime");
381
- function StudioBootstrap({ children, fallback, renderError, apiKey }) {
382
- const { ready, error, userId } = useStudioBootstrap({ apiKey });
347
+ function StudioBootstrap({ children, fallback, renderError, clientKey: clientKey2 }) {
348
+ const { ready, error, userId } = useStudioBootstrap({ clientKey: clientKey2 });
383
349
  if (error) {
384
350
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { style: { flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, children: renderError ? renderError(error) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { variant: "bodyMuted", children: error.message }) });
385
351
  }
@@ -527,6 +493,10 @@ var createApiClient = (baseURL) => {
527
493
  };
528
494
  var api = createApiClient(BASE_URL);
529
495
 
496
+ // src/data/base-remote.ts
497
+ var BaseRemote = class {
498
+ };
499
+
530
500
  // src/data/apps/remote.ts
531
501
  var AppsRemoteDataSourceImpl = class extends BaseRemote {
532
502
  async list(projectId) {
@@ -582,6 +552,15 @@ var AppsRemoteDataSourceImpl = class extends BaseRemote {
582
552
  };
583
553
  var appsRemoteDataSource = new AppsRemoteDataSourceImpl();
584
554
 
555
+ // src/data/base-repository.ts
556
+ var BaseRepository = class {
557
+ unwrapOrThrow(res) {
558
+ if (res.success && res.responseObject) return res.responseObject;
559
+ const msg = res.message || "Request failed";
560
+ throw new Error(msg);
561
+ }
562
+ };
563
+
585
564
  // src/data/apps/repository.ts
586
565
  function mapDbAppRow(row) {
587
566
  return {
@@ -934,6 +913,8 @@ function useThreadMessages(threadId) {
934
913
  // src/studio/hooks/useBundleManager.ts
935
914
  var React5 = __toESM(require("react"));
936
915
  var FileSystem = __toESM(require("expo-file-system/legacy"));
916
+ var import_expo_asset = require("expo-asset");
917
+ var import_react_native_zip_archive = require("react-native-zip-archive");
937
918
 
938
919
  // src/data/apps/bundles/remote.ts
939
920
  var BundlesRemoteDataSourceImpl = class extends BaseRemote {
@@ -957,6 +938,13 @@ var BundlesRemoteDataSourceImpl = class extends BaseRemote {
957
938
  );
958
939
  return data;
959
940
  }
941
+ async getSignedAssetsDownloadUrl(appId, bundleId, options) {
942
+ const { data } = await api.get(
943
+ `/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}/assets/download`,
944
+ { params: { redirect: (options == null ? void 0 : options.redirect) ?? false, kind: options == null ? void 0 : options.kind } }
945
+ );
946
+ return data;
947
+ }
960
948
  };
961
949
  var bundlesRemoteDataSource = new BundlesRemoteDataSourceImpl();
962
950
 
@@ -978,6 +966,10 @@ var BundlesRepositoryImpl = class extends BaseRepository {
978
966
  const res = await this.remote.getSignedDownloadUrl(appId, bundleId, options);
979
967
  return this.unwrapOrThrow(res);
980
968
  }
969
+ async getSignedAssetsDownloadUrl(appId, bundleId, options) {
970
+ const res = await this.remote.getSignedAssetsDownloadUrl(appId, bundleId, options);
971
+ return this.unwrapOrThrow(res);
972
+ }
981
973
  };
982
974
  var bundlesRepository = new BundlesRepositoryImpl(bundlesRemoteDataSource);
983
975
 
@@ -1029,20 +1021,40 @@ async function ensureDir(path) {
1029
1021
  if (info.exists) return;
1030
1022
  await FileSystem.makeDirectoryAsync(path, { intermediates: true });
1031
1023
  }
1024
+ async function ensureBundleDir(key) {
1025
+ await ensureDir(bundlesCacheDir());
1026
+ await ensureDir(bundleDir(key));
1027
+ }
1032
1028
  function baseBundleKey(appId, platform) {
1033
1029
  return `base:${appId}:${platform}`;
1034
1030
  }
1035
1031
  function testBundleKey(appId, commitId, platform, bundleId) {
1036
1032
  return `test:${appId}:${commitId ?? "head"}:${platform}:${bundleId}`;
1037
1033
  }
1038
- function toBundleFileUri(key) {
1034
+ function legacyBundleFileUri(key) {
1039
1035
  const dir = bundlesCacheDir();
1040
1036
  return `${dir}${safeName(key)}.jsbundle`;
1041
1037
  }
1042
- function toBundleMetaFileUri(key) {
1038
+ function legacyBundleMetaFileUri(key) {
1043
1039
  const dir = bundlesCacheDir();
1044
1040
  return `${dir}${safeName(key)}.meta.json`;
1045
1041
  }
1042
+ function bundleDir(key) {
1043
+ const dir = bundlesCacheDir();
1044
+ return `${dir}${safeName(key)}/`;
1045
+ }
1046
+ function toBundleFileUri(key, platform) {
1047
+ return `${bundleDir(key)}index.${platform}.jsbundle`;
1048
+ }
1049
+ function toBundleMetaFileUri(key) {
1050
+ return `${bundleDir(key)}bundle.meta.json`;
1051
+ }
1052
+ function toAssetsMetaFileUri(key) {
1053
+ return `${bundleDir(key)}assets.meta.json`;
1054
+ }
1055
+ function toAssetsDir(key) {
1056
+ return `${bundleDir(key)}assets/`;
1057
+ }
1046
1058
  async function readJsonFile(fileUri) {
1047
1059
  try {
1048
1060
  const info = await FileSystem.getInfoAsync(fileUri);
@@ -1069,6 +1081,14 @@ async function getExistingNonEmptyFileUri(fileUri) {
1069
1081
  return null;
1070
1082
  }
1071
1083
  }
1084
+ async function getExistingBundleFileUri(key, platform) {
1085
+ const nextPath = toBundleFileUri(key, platform);
1086
+ const next = await getExistingNonEmptyFileUri(nextPath);
1087
+ if (next) return next;
1088
+ const legacyPath = legacyBundleFileUri(key);
1089
+ const legacy = await getExistingNonEmptyFileUri(legacyPath);
1090
+ return legacy;
1091
+ }
1072
1092
  async function downloadIfMissing(url, fileUri) {
1073
1093
  const existing = await getExistingNonEmptyFileUri(fileUri);
1074
1094
  if (existing) return existing;
@@ -1092,8 +1112,71 @@ async function deleteFileIfExists(fileUri) {
1092
1112
  } catch {
1093
1113
  }
1094
1114
  }
1095
- async function safeReplaceFileFromUrl(url, targetUri, tmpKey) {
1096
- const tmpUri = toBundleFileUri(`tmp:${tmpKey}:${Date.now()}`);
1115
+ async function hydrateBaseFromEmbeddedAsset(appId, platform, embedded) {
1116
+ if (!(embedded == null ? void 0 : embedded.module)) return null;
1117
+ const key = baseBundleKey(appId, platform);
1118
+ const existing = await getExistingBundleFileUri(key, platform);
1119
+ if (existing) {
1120
+ return { bundlePath: existing, meta: embedded.meta ?? null };
1121
+ }
1122
+ await ensureBundleDir(key);
1123
+ const targetUri = toBundleFileUri(key, platform);
1124
+ const asset = import_expo_asset.Asset.fromModule(embedded.module);
1125
+ await asset.downloadAsync();
1126
+ const sourceUri = asset.localUri ?? asset.uri;
1127
+ if (!sourceUri) return null;
1128
+ const info = await FileSystem.getInfoAsync(sourceUri);
1129
+ if (!info.exists) return null;
1130
+ await deleteFileIfExists(targetUri);
1131
+ await FileSystem.copyAsync({ from: sourceUri, to: targetUri });
1132
+ const finalUri = await getExistingNonEmptyFileUri(targetUri);
1133
+ if (!finalUri) return null;
1134
+ return { bundlePath: finalUri, meta: embedded.meta ?? null };
1135
+ }
1136
+ async function hydrateAssetsFromEmbeddedAsset(appId, platform, key, embedded) {
1137
+ var _a;
1138
+ const moduleId = embedded == null ? void 0 : embedded.assetsModule;
1139
+ if (!moduleId) return false;
1140
+ const assetsMeta = (embedded == null ? void 0 : embedded.assetsMeta) ?? null;
1141
+ const assetsDir = toAssetsDir(key);
1142
+ const metaUri = toAssetsMetaFileUri(key);
1143
+ const existingMeta = await readJsonFile(metaUri);
1144
+ const assetsDirInfo = await FileSystem.getInfoAsync(assetsDir);
1145
+ const assetsDirExists = assetsDirInfo.exists && assetsDirInfo.isDirectory;
1146
+ 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);
1147
+ const embeddedMetaMatches = (_a = existingMeta == null ? void 0 : existingMeta.storageKey) == null ? void 0 : _a.startsWith("embedded:");
1148
+ if (assetsDirExists && checksumMatches && embeddedMetaMatches) {
1149
+ return true;
1150
+ }
1151
+ await ensureBundleDir(key);
1152
+ await ensureDir(assetsDir);
1153
+ const asset = import_expo_asset.Asset.fromModule(moduleId);
1154
+ await asset.downloadAsync();
1155
+ const sourceUri = asset.localUri ?? asset.uri;
1156
+ if (!sourceUri) return false;
1157
+ const info = await FileSystem.getInfoAsync(sourceUri);
1158
+ if (!info.exists) return false;
1159
+ const zipUri = `${bundleDir(key)}assets.zip`;
1160
+ await deleteFileIfExists(zipUri);
1161
+ await FileSystem.copyAsync({ from: sourceUri, to: zipUri });
1162
+ try {
1163
+ await FileSystem.deleteAsync(assetsDir, { idempotent: true }).catch(() => {
1164
+ });
1165
+ } catch {
1166
+ }
1167
+ await ensureDir(assetsDir);
1168
+ await unzipArchive(zipUri, assetsDir);
1169
+ await writeJsonFile(metaUri, {
1170
+ checksumSha256: (assetsMeta == null ? void 0 : assetsMeta.checksumSha256) ?? null,
1171
+ storageKey: `embedded:${(assetsMeta == null ? void 0 : assetsMeta.checksumSha256) ?? "unknown"}`,
1172
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1173
+ });
1174
+ return true;
1175
+ }
1176
+ async function safeReplaceFileFromUrl(url, targetUri, tmpKey, platform) {
1177
+ const tmpKeySafe = `tmp:${tmpKey}:${Date.now()}`;
1178
+ const tmpUri = toBundleFileUri(tmpKeySafe, platform);
1179
+ await ensureDir(bundleDir(tmpKeySafe));
1097
1180
  try {
1098
1181
  await withRetry(
1099
1182
  async () => {
@@ -1113,6 +1196,82 @@ async function safeReplaceFileFromUrl(url, targetUri, tmpKey) {
1113
1196
  await deleteFileIfExists(tmpUri);
1114
1197
  }
1115
1198
  }
1199
+ async function safeReplaceFileFromUrlToPath(url, targetUri, tmpKey) {
1200
+ const tmpDir = `${bundlesCacheDir()}tmp/`;
1201
+ await ensureDir(tmpDir);
1202
+ const tmpUri = `${tmpDir}${safeName(tmpKey)}.tmp`;
1203
+ try {
1204
+ await withRetry(
1205
+ async () => {
1206
+ await deleteFileIfExists(tmpUri);
1207
+ await FileSystem.downloadAsync(url, tmpUri);
1208
+ const tmpOk = await getExistingNonEmptyFileUri(tmpUri);
1209
+ if (!tmpOk) throw new Error("Downloaded file is empty.");
1210
+ },
1211
+ { attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
1212
+ );
1213
+ await deleteFileIfExists(targetUri);
1214
+ await FileSystem.moveAsync({ from: tmpUri, to: targetUri });
1215
+ const finalOk = await getExistingNonEmptyFileUri(targetUri);
1216
+ if (!finalOk) throw new Error("File replacement failed.");
1217
+ return targetUri;
1218
+ } finally {
1219
+ await deleteFileIfExists(tmpUri);
1220
+ }
1221
+ }
1222
+ function getMetroAssets(bundle) {
1223
+ const assets = bundle.assets ?? [];
1224
+ return assets.find((asset) => asset.kind === "metro-assets") ?? null;
1225
+ }
1226
+ async function ensureAssetsForBundle(appId, bundle, key, platform) {
1227
+ var _a;
1228
+ const asset = getMetroAssets(bundle);
1229
+ if (!(asset == null ? void 0 : asset.storageKey)) return;
1230
+ await ensureBundleDir(key);
1231
+ const assetsDir = toAssetsDir(key);
1232
+ await ensureDir(assetsDir);
1233
+ const metaUri = toAssetsMetaFileUri(key);
1234
+ const existingMeta = await readJsonFile(metaUri);
1235
+ const assetsDirInfo = await FileSystem.getInfoAsync(assetsDir);
1236
+ const assetsDirExists = assetsDirInfo.exists && assetsDirInfo.isDirectory;
1237
+ 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) {
1238
+ return;
1239
+ }
1240
+ const signed = await withRetry(
1241
+ async () => {
1242
+ return await bundlesRepository.getSignedAssetsDownloadUrl(appId, bundle.id, {
1243
+ redirect: false,
1244
+ kind: asset.kind
1245
+ });
1246
+ },
1247
+ { attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
1248
+ );
1249
+ const zipUri = `${bundleDir(key)}assets.zip`;
1250
+ await safeReplaceFileFromUrlToPath(signed.url, zipUri, `${appId}:${bundle.id}:${platform}:${asset.kind}`);
1251
+ try {
1252
+ await FileSystem.deleteAsync(assetsDir, { idempotent: true }).catch(() => {
1253
+ });
1254
+ } catch {
1255
+ }
1256
+ await ensureDir(assetsDir);
1257
+ await unzipArchive(zipUri, assetsDir);
1258
+ await writeJsonFile(metaUri, {
1259
+ checksumSha256: asset.checksumSha256 ?? null,
1260
+ storageKey: asset.storageKey,
1261
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1262
+ });
1263
+ }
1264
+ async function unzipArchive(sourceUri, destDir) {
1265
+ try {
1266
+ await (0, import_react_native_zip_archive.unzip)(sourceUri, destDir);
1267
+ } catch (e) {
1268
+ throw new Error(
1269
+ `Failed to extract assets archive. Ensure 'react-native-zip-archive' is installed in the host app. ${String(
1270
+ (e == null ? void 0 : e.message) ?? e
1271
+ )}`
1272
+ );
1273
+ }
1274
+ }
1116
1275
  async function pollBundle(appId, bundleId, opts) {
1117
1276
  const start = Date.now();
1118
1277
  while (true) {
@@ -1148,23 +1307,39 @@ async function resolveBundlePath(src, platform, mode) {
1148
1307
  if (finalBundle.status === "failed") {
1149
1308
  throw new Error("Bundle build failed.");
1150
1309
  }
1310
+ let bundleWithAssets = finalBundle;
1311
+ if (finalBundle.status === "succeeded" && (!finalBundle.assets || finalBundle.assets.length === 0)) {
1312
+ try {
1313
+ bundleWithAssets = await bundlesRepository.getById(appId, finalBundle.id);
1314
+ } catch {
1315
+ bundleWithAssets = finalBundle;
1316
+ }
1317
+ }
1151
1318
  const signed = await withRetry(
1152
1319
  async () => {
1153
1320
  return await bundlesRepository.getSignedDownloadUrl(appId, finalBundle.id, { redirect: false });
1154
1321
  },
1155
1322
  { attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
1156
1323
  );
1324
+ const key = mode === "base" ? baseBundleKey(appId, platform) : testBundleKey(appId, commitId, platform, finalBundle.id);
1325
+ await ensureBundleDir(key);
1157
1326
  const bundlePath = mode === "base" ? await safeReplaceFileFromUrl(
1158
1327
  signed.url,
1159
- toBundleFileUri(baseBundleKey(appId, platform)),
1160
- `${appId}:${commitId ?? "head"}:${platform}:${finalBundle.id}`
1161
- ) : await downloadIfMissing(signed.url, toBundleFileUri(testBundleKey(appId, commitId, platform, finalBundle.id)));
1162
- return { bundlePath, label: "Ready", bundle: finalBundle };
1328
+ toBundleFileUri(key, platform),
1329
+ `${appId}:${commitId ?? "head"}:${platform}:${finalBundle.id}`,
1330
+ platform
1331
+ ) : await downloadIfMissing(signed.url, toBundleFileUri(key, platform));
1332
+ try {
1333
+ await ensureAssetsForBundle(appId, bundleWithAssets, key, platform);
1334
+ } catch {
1335
+ }
1336
+ return { bundlePath, label: "Ready", bundle: bundleWithAssets };
1163
1337
  }
1164
1338
  function useBundleManager({
1165
1339
  base,
1166
1340
  platform,
1167
- canRequestLatest = true
1341
+ canRequestLatest = true,
1342
+ embeddedBaseBundles
1168
1343
  }) {
1169
1344
  const [bundlePath, setBundlePath] = React5.useState(null);
1170
1345
  const [renderToken, setRenderToken] = React5.useState(0);
@@ -1175,6 +1350,8 @@ function useBundleManager({
1175
1350
  const [isTesting, setIsTesting] = React5.useState(false);
1176
1351
  const baseRef = React5.useRef(base);
1177
1352
  baseRef.current = base;
1353
+ const embeddedBaseBundlesRef = React5.useRef(embeddedBaseBundles);
1354
+ embeddedBaseBundlesRef.current = embeddedBaseBundles;
1178
1355
  const baseOpIdRef = React5.useRef(0);
1179
1356
  const testOpIdRef = React5.useRef(0);
1180
1357
  const activeLoadModeRef = React5.useRef(null);
@@ -1197,16 +1374,39 @@ function useBundleManager({
1197
1374
  const hasCompletedFirstNetworkBaseLoadRef = React5.useRef(false);
1198
1375
  const hydrateBaseFromDisk = React5.useCallback(
1199
1376
  async (appId, reason) => {
1377
+ var _a, _b, _c;
1200
1378
  try {
1201
1379
  const dir = bundlesCacheDir();
1202
1380
  await ensureDir(dir);
1203
1381
  const key = baseBundleKey(appId, platform);
1204
- const uri = toBundleFileUri(key);
1205
- const existing = await getExistingNonEmptyFileUri(uri);
1382
+ let existing = await getExistingBundleFileUri(key, platform);
1383
+ let embeddedMeta = null;
1384
+ if (!existing) {
1385
+ const embedded = (_a = embeddedBaseBundlesRef.current) == null ? void 0 : _a[platform];
1386
+ const hydrated = await hydrateBaseFromEmbeddedAsset(appId, platform, embedded);
1387
+ if (hydrated == null ? void 0 : hydrated.bundlePath) {
1388
+ existing = hydrated.bundlePath;
1389
+ embeddedMeta = hydrated.meta ?? null;
1390
+ if (embeddedMeta) {
1391
+ await ensureBundleDir(key);
1392
+ await writeJsonFile(toBundleMetaFileUri(key), embeddedMeta);
1393
+ await writeJsonFile(legacyBundleMetaFileUri(key), embeddedMeta);
1394
+ }
1395
+ }
1396
+ }
1206
1397
  if (existing) {
1207
1398
  lastBaseBundlePathRef.current = existing;
1208
1399
  setBundlePath(existing);
1209
- const meta = await readJsonFile(toBundleMetaFileUri(key));
1400
+ const meta = embeddedMeta ?? await readJsonFile(toBundleMetaFileUri(key)) ?? await readJsonFile(legacyBundleMetaFileUri(key));
1401
+ const embedded = (_b = embeddedBaseBundlesRef.current) == null ? void 0 : _b[platform];
1402
+ const embeddedFingerprint = ((_c = embedded == null ? void 0 : embedded.meta) == null ? void 0 : _c.fingerprint) ?? null;
1403
+ const actualFingerprint = (meta == null ? void 0 : meta.fingerprint) ?? (embeddedMeta == null ? void 0 : embeddedMeta.fingerprint) ?? null;
1404
+ if ((embedded == null ? void 0 : embedded.assetsModule) && embeddedFingerprint && actualFingerprint === embeddedFingerprint) {
1405
+ try {
1406
+ await hydrateAssetsFromEmbeddedAsset(appId, platform, key, embedded);
1407
+ } catch {
1408
+ }
1409
+ }
1210
1410
  if (meta == null ? void 0 : meta.fingerprint) {
1211
1411
  lastBaseFingerprintRef.current = meta.fingerprint;
1212
1412
  }
@@ -1271,7 +1471,16 @@ function useBundleManager({
1271
1471
  lastBaseFingerprintRef.current = fingerprint;
1272
1472
  hasCompletedFirstNetworkBaseLoadRef.current = true;
1273
1473
  initialHydratedBaseFromDiskRef.current = false;
1274
- void writeJsonFile(toBundleMetaFileUri(baseBundleKey(src.appId, platform)), {
1474
+ const metaKey = baseBundleKey(src.appId, platform);
1475
+ await ensureBundleDir(metaKey);
1476
+ void writeJsonFile(toBundleMetaFileUri(metaKey), {
1477
+ fingerprint,
1478
+ bundleId: bundle.id,
1479
+ checksumSha256: bundle.checksumSha256 ?? null,
1480
+ size: bundle.size ?? null,
1481
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1482
+ });
1483
+ void writeJsonFile(legacyBundleMetaFileUri(metaKey), {
1275
1484
  fingerprint,
1276
1485
  bundleId: bundle.id,
1277
1486
  checksumSha256: bundle.checksumSha256 ?? null,
@@ -1793,11 +2002,28 @@ function hasNoOutcomeAfterLastHuman(messages) {
1793
2002
  }
1794
2003
 
1795
2004
  // src/studio/ui/RuntimeRenderer.tsx
2005
+ var React9 = __toESM(require("react"));
1796
2006
  var import_react_native6 = require("react-native");
1797
2007
  var import_runtime = require("@comergehq/runtime");
1798
2008
  var import_jsx_runtime3 = require("react/jsx-runtime");
1799
- function RuntimeRenderer({ appKey, bundlePath, forcePreparing, renderToken, style }) {
2009
+ function RuntimeRenderer({
2010
+ appKey,
2011
+ bundlePath,
2012
+ forcePreparing,
2013
+ renderToken,
2014
+ style,
2015
+ allowInitialPreparing = true
2016
+ }) {
2017
+ const [hasRenderedOnce, setHasRenderedOnce] = React9.useState(false);
2018
+ React9.useEffect(() => {
2019
+ if (bundlePath) {
2020
+ setHasRenderedOnce(true);
2021
+ }
2022
+ }, [bundlePath]);
1800
2023
  if (!bundlePath || forcePreparing) {
2024
+ if (!hasRenderedOnce && !forcePreparing && !allowInitialPreparing) {
2025
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native6.View, { style: [{ flex: 1 }, style] });
2026
+ }
1801
2027
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native6.View, { style: [{ flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { variant: "bodyMuted", children: "Preparing app\u2026" }) });
1802
2028
  }
1803
2029
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native6.View, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -1812,11 +2038,11 @@ function RuntimeRenderer({ appKey, bundlePath, forcePreparing, renderToken, styl
1812
2038
  }
1813
2039
 
1814
2040
  // src/studio/ui/StudioOverlay.tsx
1815
- var React41 = __toESM(require("react"));
2041
+ var React42 = __toESM(require("react"));
1816
2042
  var import_react_native54 = require("react-native");
1817
2043
 
1818
2044
  // src/components/studio-sheet/StudioBottomSheet.tsx
1819
- var React11 = __toESM(require("react"));
2045
+ var React12 = __toESM(require("react"));
1820
2046
  var import_react_native9 = require("react-native");
1821
2047
  var import_bottom_sheet = __toESM(require("@gorhom/bottom-sheet"));
1822
2048
  var import_react_native_safe_area_context = require("react-native-safe-area-context");
@@ -1826,20 +2052,20 @@ var import_react_native8 = require("react-native");
1826
2052
  var import_liquid_glass2 = require("@callstack/liquid-glass");
1827
2053
 
1828
2054
  // src/components/utils/ResettableLiquidGlassView.tsx
1829
- var React10 = __toESM(require("react"));
2055
+ var React11 = __toESM(require("react"));
1830
2056
  var import_liquid_glass = require("@callstack/liquid-glass");
1831
2057
 
1832
2058
  // src/components/utils/liquidGlassReset.tsx
1833
- var React9 = __toESM(require("react"));
2059
+ var React10 = __toESM(require("react"));
1834
2060
  var import_react_native7 = require("react-native");
1835
2061
  var import_jsx_runtime4 = require("react/jsx-runtime");
1836
- var LiquidGlassResetContext = React9.createContext(0);
2062
+ var LiquidGlassResetContext = React10.createContext(0);
1837
2063
  function LiquidGlassResetProvider({
1838
2064
  children,
1839
2065
  resetTriggers = []
1840
2066
  }) {
1841
- const [token, setToken] = React9.useState(0);
1842
- React9.useEffect(() => {
2067
+ const [token, setToken] = React10.useState(0);
2068
+ React10.useEffect(() => {
1843
2069
  if (import_react_native7.Platform.OS !== "ios") return;
1844
2070
  const onChange = (state) => {
1845
2071
  if (state === "active") setToken((t) => t + 1);
@@ -1847,21 +2073,21 @@ function LiquidGlassResetProvider({
1847
2073
  const sub = import_react_native7.AppState.addEventListener("change", onChange);
1848
2074
  return () => sub.remove();
1849
2075
  }, []);
1850
- React9.useEffect(() => {
2076
+ React10.useEffect(() => {
1851
2077
  setToken((t) => t + 1);
1852
2078
  }, resetTriggers);
1853
2079
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(LiquidGlassResetContext.Provider, { value: token, children });
1854
2080
  }
1855
2081
  function useLiquidGlassResetToken() {
1856
- return React9.useContext(LiquidGlassResetContext);
2082
+ return React10.useContext(LiquidGlassResetContext);
1857
2083
  }
1858
2084
 
1859
2085
  // src/components/utils/ResettableLiquidGlassView.tsx
1860
2086
  var import_jsx_runtime5 = require("react/jsx-runtime");
1861
2087
  function ResettableLiquidGlassView({ children, ...props }) {
1862
2088
  const token = useLiquidGlassResetToken();
1863
- const [layoutBootKey, setLayoutBootKey] = React10.useState(0);
1864
- const sawNonZeroLayoutRef = React10.useRef(false);
2089
+ const [layoutBootKey, setLayoutBootKey] = React11.useState(0);
2090
+ const sawNonZeroLayoutRef = React11.useRef(false);
1865
2091
  const onLayout = (e) => {
1866
2092
  var _a;
1867
2093
  (_a = props.onLayout) == null ? void 0 : _a.call(props, e);
@@ -1935,11 +2161,11 @@ function StudioBottomSheet({
1935
2161
  }) {
1936
2162
  const theme = useTheme();
1937
2163
  const insets = (0, import_react_native_safe_area_context.useSafeAreaInsets)();
1938
- const internalSheetRef = React11.useRef(null);
2164
+ const internalSheetRef = React12.useRef(null);
1939
2165
  const resolvedSheetRef = sheetRef ?? internalSheetRef;
1940
- const currentIndexRef = React11.useRef(open ? snapPoints.length - 1 : -1);
1941
- const lastAppStateRef = React11.useRef(import_react_native9.AppState.currentState);
1942
- React11.useEffect(() => {
2166
+ const currentIndexRef = React12.useRef(open ? snapPoints.length - 1 : -1);
2167
+ const lastAppStateRef = React12.useRef(import_react_native9.AppState.currentState);
2168
+ React12.useEffect(() => {
1943
2169
  const sub = import_react_native9.AppState.addEventListener("change", (state) => {
1944
2170
  const prev = lastAppStateRef.current;
1945
2171
  lastAppStateRef.current = state;
@@ -1959,7 +2185,7 @@ function StudioBottomSheet({
1959
2185
  });
1960
2186
  return () => sub.remove();
1961
2187
  }, [open, resolvedSheetRef]);
1962
- React11.useEffect(() => {
2188
+ React12.useEffect(() => {
1963
2189
  const sheet = resolvedSheetRef.current;
1964
2190
  if (!sheet) return;
1965
2191
  if (open) {
@@ -1968,7 +2194,7 @@ function StudioBottomSheet({
1968
2194
  sheet.close();
1969
2195
  }
1970
2196
  }, [open, resolvedSheetRef, snapPoints.length]);
1971
- const handleChange = React11.useCallback(
2197
+ const handleChange = React12.useCallback(
1972
2198
  (index) => {
1973
2199
  currentIndexRef.current = index;
1974
2200
  onOpenChange == null ? void 0 : onOpenChange(index >= 0);
@@ -1997,12 +2223,12 @@ function StudioBottomSheet({
1997
2223
  }
1998
2224
 
1999
2225
  // src/components/studio-sheet/StudioSheetPager.tsx
2000
- var React12 = __toESM(require("react"));
2226
+ var React13 = __toESM(require("react"));
2001
2227
  var import_react_native10 = require("react-native");
2002
2228
  var import_jsx_runtime8 = require("react/jsx-runtime");
2003
2229
  function StudioSheetPager({ activePage, width, preview, chat, style }) {
2004
- const anim = React12.useRef(new import_react_native10.Animated.Value(activePage === "chat" ? 1 : 0)).current;
2005
- React12.useEffect(() => {
2230
+ const anim = React13.useRef(new import_react_native10.Animated.Value(activePage === "chat" ? 1 : 0)).current;
2231
+ React13.useEffect(() => {
2006
2232
  import_react_native10.Animated.spring(anim, {
2007
2233
  toValue: activePage === "chat" ? 1 : 0,
2008
2234
  useNativeDriver: true,
@@ -2050,14 +2276,14 @@ function StudioSheetPager({ activePage, width, preview, chat, style }) {
2050
2276
  ] });
2051
2277
  }
2052
2278
 
2053
- // src/components/floating-draggable-button/FloatingDraggableButton.tsx
2279
+ // src/components/bubble/Bubble.tsx
2054
2280
  var import_react = require("react");
2055
2281
  var import_react_native11 = require("react-native");
2056
2282
  var Haptics = __toESM(require("expo-haptics"));
2057
2283
  var import_react_native_reanimated = __toESM(require("react-native-reanimated"));
2058
2284
  var import_liquid_glass3 = require("@callstack/liquid-glass");
2059
2285
 
2060
- // src/components/floating-draggable-button/constants.ts
2286
+ // src/components/bubble/constants.ts
2061
2287
  var DEFAULT_SIZE = 48;
2062
2288
  var DEFAULT_EDGE_PADDING = 10;
2063
2289
  var DEFAULT_OFFSET = {
@@ -2068,7 +2294,7 @@ var ENTER_SCALE_FROM = 0.3;
2068
2294
  var ENTER_ROTATION_FROM_DEG = -180;
2069
2295
  var PULSE_DURATION_MS = 900;
2070
2296
 
2071
- // src/components/floating-draggable-button/FloatingDraggableButton.tsx
2297
+ // src/components/bubble/Bubble.tsx
2072
2298
  var import_jsx_runtime9 = require("react/jsx-runtime");
2073
2299
  var HIDDEN_OFFSET_X = 20;
2074
2300
  var SPRING_POSITION = { damping: 12, stiffness: 100, mass: 0.8 };
@@ -2090,7 +2316,7 @@ function getHiddenTranslateY(height) {
2090
2316
  function getFinalTranslateY(height, size, bottomOffset) {
2091
2317
  return height - size - bottomOffset;
2092
2318
  }
2093
- function FloatingDraggableButton({
2319
+ function Bubble({
2094
2320
  onPress,
2095
2321
  size = DEFAULT_SIZE,
2096
2322
  disabled = false,
@@ -2315,7 +2541,7 @@ var styles = import_react_native11.StyleSheet.create({
2315
2541
  });
2316
2542
 
2317
2543
  // src/components/overlays/EdgeGlowFrame.tsx
2318
- var React13 = __toESM(require("react"));
2544
+ var React14 = __toESM(require("react"));
2319
2545
  var import_react_native12 = require("react-native");
2320
2546
  var import_expo_linear_gradient = require("expo-linear-gradient");
2321
2547
 
@@ -2358,8 +2584,8 @@ function EdgeGlowFrame({
2358
2584
  }) {
2359
2585
  const theme = useTheme();
2360
2586
  const alpha = Math.max(0, Math.min(1, intensity));
2361
- const anim = React13.useRef(new import_react_native12.Animated.Value(visible ? 1 : 0)).current;
2362
- React13.useEffect(() => {
2587
+ const anim = React14.useRef(new import_react_native12.Animated.Value(visible ? 1 : 0)).current;
2588
+ React14.useEffect(() => {
2363
2589
  import_react_native12.Animated.timing(anim, {
2364
2590
  toValue: visible ? 1 : 0,
2365
2591
  duration: 300,
@@ -2410,12 +2636,12 @@ function EdgeGlowFrame({
2410
2636
  }
2411
2637
 
2412
2638
  // src/components/draw/DrawModeOverlay.tsx
2413
- var React16 = __toESM(require("react"));
2639
+ var React17 = __toESM(require("react"));
2414
2640
  var import_react_native16 = require("react-native");
2415
2641
  var import_react_native_view_shot = require("react-native-view-shot");
2416
2642
 
2417
2643
  // src/components/draw/DrawSurface.tsx
2418
- var React14 = __toESM(require("react"));
2644
+ var React15 = __toESM(require("react"));
2419
2645
  var import_react_native13 = require("react-native");
2420
2646
  var import_react_native_svg = __toESM(require("react-native-svg"));
2421
2647
 
@@ -2447,25 +2673,25 @@ function DrawSurface({
2447
2673
  style,
2448
2674
  minDistance = 1
2449
2675
  }) {
2450
- const [renderTick, setRenderTick] = React14.useState(0);
2451
- const currentPointsRef = React14.useRef([]);
2452
- const rafRef = React14.useRef(null);
2453
- const triggerRender = React14.useCallback(() => {
2676
+ const [renderTick, setRenderTick] = React15.useState(0);
2677
+ const currentPointsRef = React15.useRef([]);
2678
+ const rafRef = React15.useRef(null);
2679
+ const triggerRender = React15.useCallback(() => {
2454
2680
  if (rafRef.current !== null) return;
2455
2681
  rafRef.current = requestAnimationFrame(() => {
2456
2682
  rafRef.current = null;
2457
2683
  setRenderTick((n) => n + 1);
2458
2684
  });
2459
2685
  }, []);
2460
- React14.useEffect(() => () => {
2686
+ React15.useEffect(() => () => {
2461
2687
  if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
2462
2688
  }, []);
2463
- const onStart = React14.useCallback((e) => {
2689
+ const onStart = React15.useCallback((e) => {
2464
2690
  const { locationX, locationY } = e.nativeEvent;
2465
2691
  currentPointsRef.current = [{ x: locationX, y: locationY }];
2466
2692
  triggerRender();
2467
2693
  }, [triggerRender]);
2468
- const onMove = React14.useCallback((e, _g) => {
2694
+ const onMove = React15.useCallback((e, _g) => {
2469
2695
  const { locationX, locationY } = e.nativeEvent;
2470
2696
  const pts = currentPointsRef.current;
2471
2697
  if (pts.length > 0) {
@@ -2478,7 +2704,7 @@ function DrawSurface({
2478
2704
  currentPointsRef.current = [...pts, { x: locationX, y: locationY }];
2479
2705
  triggerRender();
2480
2706
  }, [minDistance, triggerRender]);
2481
- const onEnd = React14.useCallback(() => {
2707
+ const onEnd = React15.useCallback(() => {
2482
2708
  const points = currentPointsRef.current;
2483
2709
  if (points.length > 0) {
2484
2710
  onAddStroke({ points, color, width: strokeWidth });
@@ -2486,7 +2712,7 @@ function DrawSurface({
2486
2712
  currentPointsRef.current = [];
2487
2713
  triggerRender();
2488
2714
  }, [color, onAddStroke, strokeWidth, triggerRender]);
2489
- const panResponder = React14.useMemo(
2715
+ const panResponder = React15.useMemo(
2490
2716
  () => import_react_native13.PanResponder.create({
2491
2717
  onStartShouldSetPanResponder: () => true,
2492
2718
  onMoveShouldSetPanResponder: () => true,
@@ -2536,7 +2762,7 @@ var styles2 = import_react_native13.StyleSheet.create({
2536
2762
  });
2537
2763
 
2538
2764
  // src/components/draw/DrawToolbar.tsx
2539
- var React15 = __toESM(require("react"));
2765
+ var React16 = __toESM(require("react"));
2540
2766
  var import_react_native15 = require("react-native");
2541
2767
  var import_react_native_safe_area_context2 = require("react-native-safe-area-context");
2542
2768
  var import_lucide_react_native = require("lucide-react-native");
@@ -2626,11 +2852,11 @@ function DrawToolbar({
2626
2852
  }) {
2627
2853
  const insets = (0, import_react_native_safe_area_context2.useSafeAreaInsets)();
2628
2854
  const { width: screenWidth, height: screenHeight } = (0, import_react_native15.useWindowDimensions)();
2629
- const [expanded, setExpanded] = React15.useState(false);
2630
- const pos = React15.useRef(new import_react_native15.Animated.ValueXY({ x: screenWidth / 2 - 110, y: -140 })).current;
2631
- const start = React15.useRef({ x: 0, y: 0 });
2632
- const currentPos = React15.useRef({ x: 0, y: 0 });
2633
- React15.useEffect(() => {
2855
+ const [expanded, setExpanded] = React16.useState(false);
2856
+ const pos = React16.useRef(new import_react_native15.Animated.ValueXY({ x: screenWidth / 2 - 110, y: -140 })).current;
2857
+ const start = React16.useRef({ x: 0, y: 0 });
2858
+ const currentPos = React16.useRef({ x: 0, y: 0 });
2859
+ React16.useEffect(() => {
2634
2860
  if (hidden) return;
2635
2861
  import_react_native15.Animated.spring(pos.y, {
2636
2862
  toValue: insets.top + 60,
@@ -2640,7 +2866,7 @@ function DrawToolbar({
2640
2866
  mass: 0.8
2641
2867
  }).start();
2642
2868
  }, [hidden, insets.top, pos.y]);
2643
- React15.useEffect(() => {
2869
+ React16.useEffect(() => {
2644
2870
  const id = pos.addListener((v) => {
2645
2871
  currentPos.current = { x: v.x ?? 0, y: v.y ?? 0 };
2646
2872
  });
@@ -2648,7 +2874,7 @@ function DrawToolbar({
2648
2874
  pos.removeListener(id);
2649
2875
  };
2650
2876
  }, [pos]);
2651
- const clamp2 = React15.useCallback(
2877
+ const clamp2 = React16.useCallback(
2652
2878
  (x, y) => {
2653
2879
  const minX = 10;
2654
2880
  const maxX = Math.max(10, screenWidth - 230);
@@ -2658,7 +2884,7 @@ function DrawToolbar({
2658
2884
  },
2659
2885
  [insets.top, screenHeight, screenWidth]
2660
2886
  );
2661
- const panResponder = React15.useMemo(
2887
+ const panResponder = React16.useMemo(
2662
2888
  () => import_react_native15.PanResponder.create({
2663
2889
  onStartShouldSetPanResponder: () => false,
2664
2890
  onMoveShouldSetPanResponder: (_e, g) => Math.abs(g.dx) > 5 || Math.abs(g.dy) > 5,
@@ -2686,7 +2912,7 @@ function DrawToolbar({
2686
2912
  children
2687
2913
  }) {
2688
2914
  const isDisabled = Boolean(disabled) || Boolean(capturingDisabled);
2689
- const [pressed, setPressed] = React15.useState(false);
2915
+ const [pressed, setPressed] = React16.useState(false);
2690
2916
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2691
2917
  import_react_native15.View,
2692
2918
  {
@@ -2824,7 +3050,7 @@ function DrawModeOverlay({
2824
3050
  renderDragHandle
2825
3051
  }) {
2826
3052
  const theme = useTheme();
2827
- const defaultPalette = React16.useMemo(
3053
+ const defaultPalette = React17.useMemo(
2828
3054
  () => [
2829
3055
  "#EF4444",
2830
3056
  // Red
@@ -2842,11 +3068,11 @@ function DrawModeOverlay({
2842
3068
  []
2843
3069
  );
2844
3070
  const colors = palette && palette.length > 0 ? palette : defaultPalette;
2845
- const [selectedColor, setSelectedColor] = React16.useState(colors[0] ?? "#EF4444");
2846
- const [strokes, setStrokes] = React16.useState([]);
2847
- const [capturing, setCapturing] = React16.useState(false);
2848
- const [hideUi, setHideUi] = React16.useState(false);
2849
- React16.useEffect(() => {
3071
+ const [selectedColor, setSelectedColor] = React17.useState(colors[0] ?? "#EF4444");
3072
+ const [strokes, setStrokes] = React17.useState([]);
3073
+ const [capturing, setCapturing] = React17.useState(false);
3074
+ const [hideUi, setHideUi] = React17.useState(false);
3075
+ React17.useEffect(() => {
2850
3076
  if (!visible) return;
2851
3077
  setStrokes([]);
2852
3078
  setSelectedColor(colors[0] ?? "#EF4444");
@@ -2854,14 +3080,14 @@ function DrawModeOverlay({
2854
3080
  setHideUi(false);
2855
3081
  }, [colors, visible]);
2856
3082
  const canUndo = strokes.length > 0;
2857
- const handleUndo = React16.useCallback(() => {
3083
+ const handleUndo = React17.useCallback(() => {
2858
3084
  setStrokes((prev) => prev.slice(0, -1));
2859
3085
  }, []);
2860
- const handleCancel = React16.useCallback(() => {
3086
+ const handleCancel = React17.useCallback(() => {
2861
3087
  setStrokes([]);
2862
3088
  onCancel();
2863
3089
  }, [onCancel]);
2864
- const handleDone = React16.useCallback(async () => {
3090
+ const handleDone = React17.useCallback(async () => {
2865
3091
  if (!captureTargetRef.current || capturing) return;
2866
3092
  try {
2867
3093
  setCapturing(true);
@@ -2921,7 +3147,7 @@ var styles3 = import_react_native16.StyleSheet.create({
2921
3147
  });
2922
3148
 
2923
3149
  // src/components/comments/AppCommentsSheet.tsx
2924
- var React23 = __toESM(require("react"));
3150
+ var React24 = __toESM(require("react"));
2925
3151
  var import_react_native22 = require("react-native");
2926
3152
  var import_bottom_sheet3 = require("@gorhom/bottom-sheet");
2927
3153
  var import_react_native_safe_area_context3 = require("react-native-safe-area-context");
@@ -2929,17 +3155,17 @@ var import_liquid_glass5 = require("@callstack/liquid-glass");
2929
3155
  var import_lucide_react_native4 = require("lucide-react-native");
2930
3156
 
2931
3157
  // src/components/chat/ChatComposer.tsx
2932
- var React18 = __toESM(require("react"));
3158
+ var React19 = __toESM(require("react"));
2933
3159
  var import_react_native18 = require("react-native");
2934
3160
  var import_liquid_glass4 = require("@callstack/liquid-glass");
2935
3161
  var import_lucide_react_native3 = require("lucide-react-native");
2936
3162
 
2937
3163
  // src/components/chat/MultilineTextInput.tsx
2938
- var React17 = __toESM(require("react"));
3164
+ var React18 = __toESM(require("react"));
2939
3165
  var import_react_native17 = require("react-native");
2940
3166
  var import_bottom_sheet2 = require("@gorhom/bottom-sheet");
2941
3167
  var import_jsx_runtime15 = require("react/jsx-runtime");
2942
- var MultilineTextInput = React17.forwardRef(function MultilineTextInput2({ useBottomSheetTextInput = false, placeholder, placeholderTextColor, style, ...props }, ref) {
3168
+ var MultilineTextInput = React18.forwardRef(function MultilineTextInput2({ useBottomSheetTextInput = false, placeholder, placeholderTextColor, style, ...props }, ref) {
2943
3169
  const theme = useTheme();
2944
3170
  const baseStyle = {
2945
3171
  minHeight: 44,
@@ -3011,7 +3237,7 @@ function AspectRatioThumbnail({
3011
3237
  onRemove,
3012
3238
  renderRemoveIcon
3013
3239
  }) {
3014
- const [aspectRatio, setAspectRatio] = React18.useState(1);
3240
+ const [aspectRatio, setAspectRatio] = React19.useState(1);
3015
3241
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_react_native18.View, { style: { height: THUMBNAIL_HEIGHT, aspectRatio, position: "relative" }, children: [
3016
3242
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native18.View, { style: { flex: 1, borderRadius: 8, overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3017
3243
  import_react_native18.Image,
@@ -3068,19 +3294,19 @@ function ChatComposer({
3068
3294
  style
3069
3295
  }) {
3070
3296
  const theme = useTheme();
3071
- const [internal, setInternal] = React18.useState("");
3297
+ const [internal, setInternal] = React19.useState("");
3072
3298
  const text = value ?? internal;
3073
3299
  const setText = onChangeValue ?? setInternal;
3074
3300
  const hasAttachments = attachments.length > 0;
3075
3301
  const hasText = text.trim().length > 0;
3076
3302
  const composerMinHeight = hasAttachments ? THUMBNAIL_HEIGHT + 44 + 24 : 44;
3077
3303
  const isButtonDisabled = sending || disabled || sendDisabled;
3078
- const maxInputHeight = React18.useMemo(() => import_react_native18.Dimensions.get("window").height * 0.5, []);
3079
- const shakeAnim = React18.useRef(new import_react_native18.Animated.Value(0)).current;
3080
- const [sendPressed, setSendPressed] = React18.useState(false);
3081
- const inputRef = React18.useRef(null);
3082
- const prevAutoFocusRef = React18.useRef(false);
3083
- React18.useEffect(() => {
3304
+ const maxInputHeight = React19.useMemo(() => import_react_native18.Dimensions.get("window").height * 0.5, []);
3305
+ const shakeAnim = React19.useRef(new import_react_native18.Animated.Value(0)).current;
3306
+ const [sendPressed, setSendPressed] = React19.useState(false);
3307
+ const inputRef = React19.useRef(null);
3308
+ const prevAutoFocusRef = React19.useRef(false);
3309
+ React19.useEffect(() => {
3084
3310
  const shouldFocus = autoFocus && !prevAutoFocusRef.current && !disabled && !sending;
3085
3311
  prevAutoFocusRef.current = autoFocus;
3086
3312
  if (!shouldFocus) return;
@@ -3090,7 +3316,7 @@ function ChatComposer({
3090
3316
  }, 75);
3091
3317
  return () => clearTimeout(t);
3092
3318
  }, [autoFocus, disabled, sending]);
3093
- const triggerShake = React18.useCallback(() => {
3319
+ const triggerShake = React19.useCallback(() => {
3094
3320
  shakeAnim.setValue(0);
3095
3321
  import_react_native18.Animated.sequence([
3096
3322
  import_react_native18.Animated.timing(shakeAnim, { toValue: 10, duration: 50, useNativeDriver: true }),
@@ -3100,7 +3326,7 @@ function ChatComposer({
3100
3326
  import_react_native18.Animated.timing(shakeAnim, { toValue: 0, duration: 50, useNativeDriver: true })
3101
3327
  ]).start();
3102
3328
  }, [shakeAnim]);
3103
- const handleSend = React18.useCallback(async () => {
3329
+ const handleSend = React19.useCallback(async () => {
3104
3330
  if (isButtonDisabled) return;
3105
3331
  if (!hasText) {
3106
3332
  triggerShake();
@@ -3233,7 +3459,7 @@ function ChatComposer({
3233
3459
  }
3234
3460
 
3235
3461
  // src/components/comments/CommentRow.tsx
3236
- var React19 = __toESM(require("react"));
3462
+ var React20 = __toESM(require("react"));
3237
3463
  var import_react_native20 = require("react-native");
3238
3464
 
3239
3465
  // src/components/primitives/Avatar.tsx
@@ -3305,9 +3531,9 @@ function formatTimeAgo(iso) {
3305
3531
  var import_jsx_runtime19 = require("react/jsx-runtime");
3306
3532
  function CommentRow({ comment, showDivider }) {
3307
3533
  const theme = useTheme();
3308
- const [authorName, setAuthorName] = React19.useState(null);
3309
- const [authorAvatar, setAuthorAvatar] = React19.useState(null);
3310
- React19.useEffect(() => {
3534
+ const [authorName, setAuthorName] = React20.useState(null);
3535
+ const [authorAvatar, setAuthorAvatar] = React20.useState(null);
3536
+ React20.useEffect(() => {
3311
3537
  let cancelled = false;
3312
3538
  (async () => {
3313
3539
  try {
@@ -3347,7 +3573,7 @@ function CommentRow({ comment, showDivider }) {
3347
3573
  }
3348
3574
 
3349
3575
  // src/components/comments/useAppComments.ts
3350
- var React20 = __toESM(require("react"));
3576
+ var React21 = __toESM(require("react"));
3351
3577
 
3352
3578
  // src/data/comments/remote.ts
3353
3579
  var AppCommentsRemoteDataSourceImpl = class extends BaseRemote {
@@ -3419,18 +3645,18 @@ var appCommentsRepository = new AppCommentsRepositoryImpl(appCommentsRemoteDataS
3419
3645
 
3420
3646
  // src/components/comments/useAppComments.ts
3421
3647
  function useAppComments(appId) {
3422
- const [comments, setComments] = React20.useState([]);
3423
- const [loading, setLoading] = React20.useState(false);
3424
- const [sending, setSending] = React20.useState(false);
3425
- const [error, setError] = React20.useState(null);
3426
- const sortByCreatedAtAsc = React20.useCallback((items) => {
3648
+ const [comments, setComments] = React21.useState([]);
3649
+ const [loading, setLoading] = React21.useState(false);
3650
+ const [sending, setSending] = React21.useState(false);
3651
+ const [error, setError] = React21.useState(null);
3652
+ const sortByCreatedAtAsc = React21.useCallback((items) => {
3427
3653
  return [...items].sort((a, b) => {
3428
3654
  const at = a.createdAt ? new Date(a.createdAt).getTime() : 0;
3429
3655
  const bt = b.createdAt ? new Date(b.createdAt).getTime() : 0;
3430
3656
  return at - bt;
3431
3657
  });
3432
3658
  }, []);
3433
- const refresh = React20.useCallback(async () => {
3659
+ const refresh = React21.useCallback(async () => {
3434
3660
  if (!appId) {
3435
3661
  setComments([]);
3436
3662
  return;
@@ -3447,10 +3673,10 @@ function useAppComments(appId) {
3447
3673
  setLoading(false);
3448
3674
  }
3449
3675
  }, [appId, sortByCreatedAtAsc]);
3450
- React20.useEffect(() => {
3676
+ React21.useEffect(() => {
3451
3677
  void refresh();
3452
3678
  }, [refresh]);
3453
- const create = React20.useCallback(
3679
+ const create = React21.useCallback(
3454
3680
  async (text) => {
3455
3681
  if (!appId) return;
3456
3682
  const trimmed = text.trim();
@@ -3473,11 +3699,11 @@ function useAppComments(appId) {
3473
3699
  }
3474
3700
 
3475
3701
  // src/components/comments/useAppDetails.ts
3476
- var React21 = __toESM(require("react"));
3702
+ var React22 = __toESM(require("react"));
3477
3703
  function useAppDetails(appId) {
3478
- const [app, setApp] = React21.useState(null);
3479
- const [loading, setLoading] = React21.useState(false);
3480
- React21.useEffect(() => {
3704
+ const [app, setApp] = React22.useState(null);
3705
+ const [loading, setLoading] = React22.useState(false);
3706
+ React22.useEffect(() => {
3481
3707
  if (!appId) {
3482
3708
  setApp(null);
3483
3709
  return;
@@ -3502,11 +3728,11 @@ function useAppDetails(appId) {
3502
3728
  }
3503
3729
 
3504
3730
  // src/components/comments/useIosKeyboardSnapFix.ts
3505
- var React22 = __toESM(require("react"));
3731
+ var React23 = __toESM(require("react"));
3506
3732
  var import_react_native21 = require("react-native");
3507
3733
  function useIosKeyboardSnapFix(sheetRef, options) {
3508
- const [keyboardVisible, setKeyboardVisible] = React22.useState(false);
3509
- React22.useEffect(() => {
3734
+ const [keyboardVisible, setKeyboardVisible] = React23.useState(false);
3735
+ React23.useEffect(() => {
3510
3736
  if (import_react_native21.Platform.OS !== "ios") return;
3511
3737
  const show = import_react_native21.Keyboard.addListener("keyboardWillShow", () => setKeyboardVisible(true));
3512
3738
  const hide = import_react_native21.Keyboard.addListener("keyboardWillHide", () => {
@@ -3534,16 +3760,16 @@ var import_jsx_runtime20 = require("react/jsx-runtime");
3534
3760
  function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
3535
3761
  const theme = useTheme();
3536
3762
  const insets = (0, import_react_native_safe_area_context3.useSafeAreaInsets)();
3537
- const sheetRef = React23.useRef(null);
3538
- const snapPoints = React23.useMemo(() => ["50%", "90%"], []);
3539
- const currentIndexRef = React23.useRef(1);
3763
+ const sheetRef = React24.useRef(null);
3764
+ const snapPoints = React24.useMemo(() => ["50%", "90%"], []);
3765
+ const currentIndexRef = React24.useRef(1);
3540
3766
  const { comments, loading, sending, error, create, refresh } = useAppComments(appId);
3541
3767
  const { app, loading: loadingApp } = useAppDetails(appId);
3542
3768
  const { keyboardVisible } = useIosKeyboardSnapFix(sheetRef, {
3543
3769
  getCurrentIndex: () => currentIndexRef.current,
3544
3770
  targetIndex: 1
3545
3771
  });
3546
- React23.useEffect(() => {
3772
+ React24.useEffect(() => {
3547
3773
  var _a, _b;
3548
3774
  if (appId) {
3549
3775
  (_a = sheetRef.current) == null ? void 0 : _a.present();
@@ -3552,22 +3778,22 @@ function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
3552
3778
  (_b = sheetRef.current) == null ? void 0 : _b.dismiss();
3553
3779
  }
3554
3780
  }, [appId, refresh]);
3555
- React23.useEffect(() => {
3781
+ React24.useEffect(() => {
3556
3782
  if (!appId) return;
3557
3783
  onCountChange == null ? void 0 : onCountChange(comments.length);
3558
3784
  }, [appId, comments.length, onCountChange]);
3559
- const renderBackdrop = React23.useCallback(
3785
+ const renderBackdrop = React24.useCallback(
3560
3786
  (props) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_bottom_sheet3.BottomSheetBackdrop, { ...props, disappearsOnIndex: -1, appearsOnIndex: 0, opacity: 0.5 }),
3561
3787
  []
3562
3788
  );
3563
- const handleChange = React23.useCallback(
3789
+ const handleChange = React24.useCallback(
3564
3790
  (index) => {
3565
3791
  currentIndexRef.current = index;
3566
3792
  if (index === -1) onClose();
3567
3793
  },
3568
3794
  [onClose]
3569
3795
  );
3570
- const handlePlay = React23.useCallback(async () => {
3796
+ const handlePlay = React24.useCallback(async () => {
3571
3797
  var _a;
3572
3798
  if (!appId) return;
3573
3799
  (_a = sheetRef.current) == null ? void 0 : _a.dismiss();
@@ -3777,7 +4003,7 @@ function StudioSheetHeader({ left, center, right, style }) {
3777
4003
  }
3778
4004
 
3779
4005
  // src/components/studio-sheet/StudioSheetHeaderIconButton.tsx
3780
- var React24 = __toESM(require("react"));
4006
+ var React25 = __toESM(require("react"));
3781
4007
  var import_react_native25 = require("react-native");
3782
4008
  var import_liquid_glass6 = require("@callstack/liquid-glass");
3783
4009
  var import_jsx_runtime23 = require("react/jsx-runtime");
@@ -3792,7 +4018,7 @@ function StudioSheetHeaderIconButton({
3792
4018
  }) {
3793
4019
  const theme = useTheme();
3794
4020
  const size = 44;
3795
- const [pressed, setPressed] = React24.useState(false);
4021
+ const [pressed, setPressed] = React25.useState(false);
3796
4022
  const solidBg = intent === "danger" ? theme.colors.danger : intent === "primary" ? theme.colors.primary : theme.colors.neutral;
3797
4023
  const glassFallbackBg = theme.scheme === "dark" ? "#18181B" : "#F6F6F6";
3798
4024
  const glassInnerBg = intent === "danger" ? theme.colors.danger : theme.colors.primary;
@@ -3982,14 +4208,14 @@ function PreviewHeroCard({
3982
4208
  }
3983
4209
 
3984
4210
  // src/components/preview/PreviewPlaceholder.tsx
3985
- var React25 = __toESM(require("react"));
4211
+ var React26 = __toESM(require("react"));
3986
4212
  var import_react_native29 = require("react-native");
3987
4213
  var import_expo_linear_gradient2 = require("expo-linear-gradient");
3988
4214
  var import_jsx_runtime28 = require("react/jsx-runtime");
3989
4215
  function PreviewPlaceholder({ visible, style }) {
3990
4216
  if (!visible) return null;
3991
- const opacityAnim = React25.useRef(new import_react_native29.Animated.Value(0)).current;
3992
- React25.useEffect(() => {
4217
+ const opacityAnim = React26.useRef(new import_react_native29.Animated.Value(0)).current;
4218
+ React26.useEffect(() => {
3993
4219
  if (!visible) return;
3994
4220
  const animation = import_react_native29.Animated.loop(
3995
4221
  import_react_native29.Animated.sequence([
@@ -4573,12 +4799,12 @@ function PreviewCustomizeSection({
4573
4799
  }
4574
4800
 
4575
4801
  // src/studio/ui/preview-panel/PreviewCollaborateSection.tsx
4576
- var React31 = __toESM(require("react"));
4802
+ var React32 = __toESM(require("react"));
4577
4803
  var import_react_native42 = require("react-native");
4578
4804
  var import_lucide_react_native9 = require("lucide-react-native");
4579
4805
 
4580
4806
  // src/components/merge-requests/MergeRequestStatusCard.tsx
4581
- var React27 = __toESM(require("react"));
4807
+ var React28 = __toESM(require("react"));
4582
4808
  var import_react_native38 = require("react-native");
4583
4809
  var import_lucide_react_native7 = require("lucide-react-native");
4584
4810
 
@@ -4660,11 +4886,11 @@ function toIsoString(input) {
4660
4886
  }
4661
4887
 
4662
4888
  // src/components/merge-requests/useControlledExpansion.ts
4663
- var React26 = __toESM(require("react"));
4889
+ var React27 = __toESM(require("react"));
4664
4890
  function useControlledExpansion(props) {
4665
- const [uncontrolled, setUncontrolled] = React26.useState(false);
4891
+ const [uncontrolled, setUncontrolled] = React27.useState(false);
4666
4892
  const expanded = props.expanded ?? uncontrolled;
4667
- const setExpanded = React26.useCallback(
4893
+ const setExpanded = React27.useCallback(
4668
4894
  (next) => {
4669
4895
  var _a;
4670
4896
  (_a = props.onExpandedChange) == null ? void 0 : _a.call(props, next);
@@ -4689,8 +4915,8 @@ function MergeRequestStatusCard({
4689
4915
  const isDark = theme.scheme === "dark";
4690
4916
  const textColor = isDark ? "#FFFFFF" : "#000000";
4691
4917
  const subTextColor = isDark ? "#A1A1AA" : "#71717A";
4692
- const status = React27.useMemo(() => getMergeRequestStatusDisplay(String(mergeRequest.status)), [mergeRequest.status]);
4693
- const { StatusIcon, iconColor, bgColor, statusText } = React27.useMemo(() => {
4918
+ const status = React28.useMemo(() => getMergeRequestStatusDisplay(String(mergeRequest.status)), [mergeRequest.status]);
4919
+ const { StatusIcon, iconColor, bgColor, statusText } = React28.useMemo(() => {
4694
4920
  switch (mergeRequest.status) {
4695
4921
  case "approved":
4696
4922
  case "merged":
@@ -4721,8 +4947,8 @@ function MergeRequestStatusCard({
4721
4947
  const createdIso = toIsoString(mergeRequest.createdAt ?? null);
4722
4948
  const headerTimeAgo = updatedIso ? formatTimeAgo(updatedIso) : "";
4723
4949
  const createdTimeAgo = createdIso ? formatTimeAgo(createdIso) : "";
4724
- const rotate = React27.useRef(new import_react_native38.Animated.Value(expanded ? 1 : 0)).current;
4725
- React27.useEffect(() => {
4950
+ const rotate = React28.useRef(new import_react_native38.Animated.Value(expanded ? 1 : 0)).current;
4951
+ React28.useEffect(() => {
4726
4952
  import_react_native38.Animated.timing(rotate, {
4727
4953
  toValue: expanded ? 1 : 0,
4728
4954
  duration: 200,
@@ -4813,16 +5039,16 @@ function MergeRequestStatusCard({
4813
5039
  }
4814
5040
 
4815
5041
  // src/components/merge-requests/ReviewMergeRequestCarousel.tsx
4816
- var React30 = __toESM(require("react"));
5042
+ var React31 = __toESM(require("react"));
4817
5043
  var import_react_native41 = require("react-native");
4818
5044
 
4819
5045
  // src/components/merge-requests/ReviewMergeRequestCard.tsx
4820
- var React29 = __toESM(require("react"));
5046
+ var React30 = __toESM(require("react"));
4821
5047
  var import_react_native40 = require("react-native");
4822
5048
  var import_lucide_react_native8 = require("lucide-react-native");
4823
5049
 
4824
5050
  // src/components/merge-requests/ReviewMergeRequestActionButton.tsx
4825
- var React28 = __toESM(require("react"));
5051
+ var React29 = __toESM(require("react"));
4826
5052
  var import_react_native39 = require("react-native");
4827
5053
  var import_jsx_runtime41 = require("react/jsx-runtime");
4828
5054
  function ReviewMergeRequestActionButton({
@@ -4833,7 +5059,7 @@ function ReviewMergeRequestActionButton({
4833
5059
  children,
4834
5060
  iconOnly
4835
5061
  }) {
4836
- const [pressed, setPressed] = React28.useState(false);
5062
+ const [pressed, setPressed] = React29.useState(false);
4837
5063
  const height = iconOnly ? 36 : 40;
4838
5064
  const width = iconOnly ? 36 : void 0;
4839
5065
  const paddingHorizontal = iconOnly ? 0 : 16;
@@ -4895,10 +5121,10 @@ function ReviewMergeRequestCard({
4895
5121
  onTest
4896
5122
  }) {
4897
5123
  const theme = useTheme();
4898
- const status = React29.useMemo(() => getMergeRequestStatusDisplay(mr.status), [mr.status]);
5124
+ const status = React30.useMemo(() => getMergeRequestStatusDisplay(mr.status), [mr.status]);
4899
5125
  const canAct = mr.status === "open";
4900
- const rotate = React29.useRef(new import_react_native40.Animated.Value(isExpanded ? 1 : 0)).current;
4901
- React29.useEffect(() => {
5126
+ const rotate = React30.useRef(new import_react_native40.Animated.Value(isExpanded ? 1 : 0)).current;
5127
+ React30.useEffect(() => {
4902
5128
  import_react_native40.Animated.timing(rotate, { toValue: isExpanded ? 1 : 0, duration: 200, useNativeDriver: true }).start();
4903
5129
  }, [isExpanded, rotate]);
4904
5130
  const position = total > 1 ? `${index + 1}/${total}` : "Merge request";
@@ -5030,11 +5256,11 @@ function ReviewMergeRequestCarousel({
5030
5256
  }) {
5031
5257
  const theme = useTheme();
5032
5258
  const { width } = (0, import_react_native41.useWindowDimensions)();
5033
- const [expanded, setExpanded] = React30.useState({});
5034
- const carouselScrollX = React30.useRef(new import_react_native41.Animated.Value(0)).current;
5259
+ const [expanded, setExpanded] = React31.useState({});
5260
+ const carouselScrollX = React31.useRef(new import_react_native41.Animated.Value(0)).current;
5035
5261
  const peekAmount = 24;
5036
5262
  const gap = 16;
5037
- const cardWidth = React30.useMemo(() => Math.max(1, width - theme.spacing.lg * 2 - peekAmount), [peekAmount, theme.spacing.lg, width]);
5263
+ const cardWidth = React31.useMemo(() => Math.max(1, width - theme.spacing.lg * 2 - peekAmount), [peekAmount, theme.spacing.lg, width]);
5038
5264
  const snapInterval = cardWidth + gap;
5039
5265
  const dotColor = theme.scheme === "dark" ? "#FFFFFF" : "#000000";
5040
5266
  if (mergeRequests.length === 0) return null;
@@ -5133,7 +5359,7 @@ function PreviewCollaborateSection({
5133
5359
  onTestMr
5134
5360
  }) {
5135
5361
  const theme = useTheme();
5136
- const [submittingMr, setSubmittingMr] = React31.useState(false);
5362
+ const [submittingMr, setSubmittingMr] = React32.useState(false);
5137
5363
  const hasSection = canSubmitMergeRequest || incomingMergeRequests.length > 0 || outgoingMergeRequests.length > 0;
5138
5364
  if (!hasSection) return null;
5139
5365
  const showActionsSubtitle = canSubmitMergeRequest && onSubmitMergeRequest || onTestMr && incomingMergeRequests.length > 0;
@@ -5241,7 +5467,7 @@ function PreviewCollaborateSection({
5241
5467
  }
5242
5468
 
5243
5469
  // src/studio/ui/preview-panel/usePreviewPanelData.ts
5244
- var React33 = __toESM(require("react"));
5470
+ var React34 = __toESM(require("react"));
5245
5471
 
5246
5472
  // src/data/apps/images/remote.ts
5247
5473
  var AppImagesRemoteDataSourceImpl = class extends BaseRemote {
@@ -5292,7 +5518,7 @@ var AppImagesRepositoryImpl = class extends BaseRepository {
5292
5518
  var appImagesRepository = new AppImagesRepositoryImpl(appImagesRemoteDataSource);
5293
5519
 
5294
5520
  // src/studio/hooks/useAppStats.ts
5295
- var React32 = __toESM(require("react"));
5521
+ var React33 = __toESM(require("react"));
5296
5522
  var Haptics2 = __toESM(require("expo-haptics"));
5297
5523
 
5298
5524
  // src/data/likes/remote.ts
@@ -5361,34 +5587,34 @@ function useAppStats({
5361
5587
  initialIsLiked = false,
5362
5588
  onOpenComments
5363
5589
  }) {
5364
- const [likeCount, setLikeCount] = React32.useState(initialLikes);
5365
- const [commentCount, setCommentCount] = React32.useState(initialComments);
5366
- const [forkCount, setForkCount] = React32.useState(initialForks);
5367
- const [isLiked, setIsLiked] = React32.useState(initialIsLiked);
5368
- const didMutateRef = React32.useRef(false);
5369
- const lastAppIdRef = React32.useRef("");
5370
- React32.useEffect(() => {
5590
+ const [likeCount, setLikeCount] = React33.useState(initialLikes);
5591
+ const [commentCount, setCommentCount] = React33.useState(initialComments);
5592
+ const [forkCount, setForkCount] = React33.useState(initialForks);
5593
+ const [isLiked, setIsLiked] = React33.useState(initialIsLiked);
5594
+ const didMutateRef = React33.useRef(false);
5595
+ const lastAppIdRef = React33.useRef("");
5596
+ React33.useEffect(() => {
5371
5597
  if (lastAppIdRef.current === appId) return;
5372
5598
  lastAppIdRef.current = appId;
5373
5599
  didMutateRef.current = false;
5374
5600
  }, [appId]);
5375
- React32.useEffect(() => {
5601
+ React33.useEffect(() => {
5376
5602
  if (didMutateRef.current) return;
5377
5603
  setLikeCount(initialLikes);
5378
5604
  }, [appId, initialLikes]);
5379
- React32.useEffect(() => {
5605
+ React33.useEffect(() => {
5380
5606
  if (didMutateRef.current) return;
5381
5607
  setCommentCount(initialComments);
5382
5608
  }, [appId, initialComments]);
5383
- React32.useEffect(() => {
5609
+ React33.useEffect(() => {
5384
5610
  if (didMutateRef.current) return;
5385
5611
  setForkCount(initialForks);
5386
5612
  }, [appId, initialForks]);
5387
- React32.useEffect(() => {
5613
+ React33.useEffect(() => {
5388
5614
  if (didMutateRef.current) return;
5389
5615
  setIsLiked(initialIsLiked);
5390
5616
  }, [appId, initialIsLiked]);
5391
- const handleLike = React32.useCallback(async () => {
5617
+ const handleLike = React33.useCallback(async () => {
5392
5618
  var _a, _b;
5393
5619
  if (!appId) return;
5394
5620
  didMutateRef.current = true;
@@ -5412,7 +5638,7 @@ function useAppStats({
5412
5638
  setLikeCount((prev) => Math.max(0, prev + (newIsLiked ? -1 : 1)));
5413
5639
  }
5414
5640
  }, [appId, isLiked, likeCount]);
5415
- const handleOpenComments = React32.useCallback(() => {
5641
+ const handleOpenComments = React33.useCallback(() => {
5416
5642
  if (!appId) return;
5417
5643
  try {
5418
5644
  void Haptics2.impactAsync(Haptics2.ImpactFeedbackStyle.Light);
@@ -5427,11 +5653,11 @@ function useAppStats({
5427
5653
  var LIKE_DEBUG_PREFIX = "[COMERGE_LIKE_DEBUG]";
5428
5654
  function usePreviewPanelData(params) {
5429
5655
  const { app, isOwner, outgoingMergeRequests, onOpenComments, commentCountOverride } = params;
5430
- const [imageUrl, setImageUrl] = React33.useState(null);
5431
- const [imageLoaded, setImageLoaded] = React33.useState(false);
5432
- const [insights, setInsights] = React33.useState({ likes: 0, comments: 0, forks: 0, downloads: 0 });
5433
- const [creator, setCreator] = React33.useState(null);
5434
- React33.useEffect(() => {
5656
+ const [imageUrl, setImageUrl] = React34.useState(null);
5657
+ const [imageLoaded, setImageLoaded] = React34.useState(false);
5658
+ const [insights, setInsights] = React34.useState({ likes: 0, comments: 0, forks: 0, downloads: 0 });
5659
+ const [creator, setCreator] = React34.useState(null);
5660
+ React34.useEffect(() => {
5435
5661
  if (!(app == null ? void 0 : app.id)) return;
5436
5662
  let cancelled = false;
5437
5663
  (async () => {
@@ -5446,7 +5672,7 @@ function usePreviewPanelData(params) {
5446
5672
  cancelled = true;
5447
5673
  };
5448
5674
  }, [app == null ? void 0 : app.id]);
5449
- React33.useEffect(() => {
5675
+ React34.useEffect(() => {
5450
5676
  if (!(app == null ? void 0 : app.createdBy)) return;
5451
5677
  let cancelled = false;
5452
5678
  (async () => {
@@ -5462,10 +5688,10 @@ function usePreviewPanelData(params) {
5462
5688
  cancelled = true;
5463
5689
  };
5464
5690
  }, [app == null ? void 0 : app.createdBy]);
5465
- React33.useEffect(() => {
5691
+ React34.useEffect(() => {
5466
5692
  setImageLoaded(false);
5467
5693
  }, [app == null ? void 0 : app.id]);
5468
- React33.useEffect(() => {
5694
+ React34.useEffect(() => {
5469
5695
  if (!(app == null ? void 0 : app.id)) return;
5470
5696
  let cancelled = false;
5471
5697
  (async () => {
@@ -5490,7 +5716,7 @@ function usePreviewPanelData(params) {
5490
5716
  cancelled = true;
5491
5717
  };
5492
5718
  }, [app == null ? void 0 : app.id]);
5493
- React33.useEffect(() => {
5719
+ React34.useEffect(() => {
5494
5720
  if (!(app == null ? void 0 : app.id)) return;
5495
5721
  log.debug(
5496
5722
  `${LIKE_DEBUG_PREFIX} usePreviewPanelData.appChanged appId=${app.id} app.isLiked=${String(app.isLiked)}`
@@ -5504,7 +5730,7 @@ function usePreviewPanelData(params) {
5504
5730
  initialIsLiked: Boolean(app == null ? void 0 : app.isLiked),
5505
5731
  onOpenComments
5506
5732
  });
5507
- const canSubmitMergeRequest = React33.useMemo(() => {
5733
+ const canSubmitMergeRequest = React34.useMemo(() => {
5508
5734
  if (!isOwner) return false;
5509
5735
  if (!app) return false;
5510
5736
  if (!app.forkedFromAppId) return false;
@@ -5617,16 +5843,16 @@ function PreviewPanel({
5617
5843
  }
5618
5844
 
5619
5845
  // src/studio/ui/ChatPanel.tsx
5620
- var React38 = __toESM(require("react"));
5846
+ var React39 = __toESM(require("react"));
5621
5847
  var import_react_native51 = require("react-native");
5622
5848
 
5623
5849
  // src/components/chat/ChatPage.tsx
5624
- var React36 = __toESM(require("react"));
5850
+ var React37 = __toESM(require("react"));
5625
5851
  var import_react_native47 = require("react-native");
5626
5852
  var import_react_native_safe_area_context4 = require("react-native-safe-area-context");
5627
5853
 
5628
5854
  // src/components/chat/ChatMessageList.tsx
5629
- var React35 = __toESM(require("react"));
5855
+ var React36 = __toESM(require("react"));
5630
5856
  var import_react_native46 = require("react-native");
5631
5857
  var import_bottom_sheet5 = require("@gorhom/bottom-sheet");
5632
5858
 
@@ -5672,17 +5898,17 @@ function ChatMessageBubble({ message, renderContent, style }) {
5672
5898
  }
5673
5899
 
5674
5900
  // src/components/chat/TypingIndicator.tsx
5675
- var React34 = __toESM(require("react"));
5901
+ var React35 = __toESM(require("react"));
5676
5902
  var import_react_native45 = require("react-native");
5677
5903
  var import_jsx_runtime47 = require("react/jsx-runtime");
5678
5904
  function TypingIndicator({ style }) {
5679
5905
  const theme = useTheme();
5680
5906
  const dotColor = theme.colors.textSubtle;
5681
- const anims = React34.useMemo(
5907
+ const anims = React35.useMemo(
5682
5908
  () => [new import_react_native45.Animated.Value(0.3), new import_react_native45.Animated.Value(0.3), new import_react_native45.Animated.Value(0.3)],
5683
5909
  []
5684
5910
  );
5685
- React34.useEffect(() => {
5911
+ React35.useEffect(() => {
5686
5912
  const loops = [];
5687
5913
  anims.forEach((a, idx) => {
5688
5914
  const seq = import_react_native45.Animated.sequence([
@@ -5716,7 +5942,7 @@ function TypingIndicator({ style }) {
5716
5942
 
5717
5943
  // src/components/chat/ChatMessageList.tsx
5718
5944
  var import_jsx_runtime48 = require("react/jsx-runtime");
5719
- var ChatMessageList = React35.forwardRef(
5945
+ var ChatMessageList = React36.forwardRef(
5720
5946
  ({
5721
5947
  messages,
5722
5948
  showTypingIndicator = false,
@@ -5727,20 +5953,20 @@ var ChatMessageList = React35.forwardRef(
5727
5953
  nearBottomThreshold = 200
5728
5954
  }, ref) => {
5729
5955
  const theme = useTheme();
5730
- const listRef = React35.useRef(null);
5731
- const nearBottomRef = React35.useRef(true);
5732
- const initialScrollDoneRef = React35.useRef(false);
5733
- const lastMessageIdRef = React35.useRef(null);
5734
- const data = React35.useMemo(() => {
5956
+ const listRef = React36.useRef(null);
5957
+ const nearBottomRef = React36.useRef(true);
5958
+ const initialScrollDoneRef = React36.useRef(false);
5959
+ const lastMessageIdRef = React36.useRef(null);
5960
+ const data = React36.useMemo(() => {
5735
5961
  return [...messages].reverse();
5736
5962
  }, [messages]);
5737
- const scrollToBottom = React35.useCallback((options) => {
5963
+ const scrollToBottom = React36.useCallback((options) => {
5738
5964
  var _a;
5739
5965
  const animated = (options == null ? void 0 : options.animated) ?? true;
5740
5966
  (_a = listRef.current) == null ? void 0 : _a.scrollToOffset({ offset: 0, animated });
5741
5967
  }, []);
5742
- React35.useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);
5743
- const handleScroll = React35.useCallback(
5968
+ React36.useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);
5969
+ const handleScroll = React36.useCallback(
5744
5970
  (e) => {
5745
5971
  const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent;
5746
5972
  const distanceFromBottom = Math.max(contentOffset.y - Math.max(bottomInset, 0), 0);
@@ -5752,7 +5978,7 @@ var ChatMessageList = React35.forwardRef(
5752
5978
  },
5753
5979
  [bottomInset, nearBottomThreshold, onNearBottomChange]
5754
5980
  );
5755
- React35.useEffect(() => {
5981
+ React36.useEffect(() => {
5756
5982
  if (!initialScrollDoneRef.current) return;
5757
5983
  const lastId = messages.length > 0 ? messages[messages.length - 1].id : null;
5758
5984
  const prevLastId = lastMessageIdRef.current;
@@ -5762,7 +5988,7 @@ var ChatMessageList = React35.forwardRef(
5762
5988
  const id = requestAnimationFrame(() => scrollToBottom({ animated: true }));
5763
5989
  return () => cancelAnimationFrame(id);
5764
5990
  }, [messages, scrollToBottom]);
5765
- React35.useEffect(() => {
5991
+ React36.useEffect(() => {
5766
5992
  if (showTypingIndicator && nearBottomRef.current) {
5767
5993
  const id = requestAnimationFrame(() => scrollToBottom({ animated: true }));
5768
5994
  return () => cancelAnimationFrame(id);
@@ -5824,9 +6050,9 @@ function ChatPage({
5824
6050
  }) {
5825
6051
  const theme = useTheme();
5826
6052
  const insets = (0, import_react_native_safe_area_context4.useSafeAreaInsets)();
5827
- const [composerHeight, setComposerHeight] = React36.useState(0);
5828
- const [keyboardVisible, setKeyboardVisible] = React36.useState(false);
5829
- React36.useEffect(() => {
6053
+ const [composerHeight, setComposerHeight] = React37.useState(0);
6054
+ const [keyboardVisible, setKeyboardVisible] = React37.useState(false);
6055
+ React37.useEffect(() => {
5830
6056
  if (import_react_native47.Platform.OS !== "ios") return;
5831
6057
  const show = import_react_native47.Keyboard.addListener("keyboardWillShow", () => setKeyboardVisible(true));
5832
6058
  const hide = import_react_native47.Keyboard.addListener("keyboardWillHide", () => setKeyboardVisible(false));
@@ -5838,12 +6064,12 @@ function ChatPage({
5838
6064
  const footerBottomPadding = import_react_native47.Platform.OS === "ios" ? keyboardVisible ? 0 : insets.bottom : insets.bottom + 10;
5839
6065
  const overlayBottom = composerHeight + footerBottomPadding + theme.spacing.lg;
5840
6066
  const bottomInset = composerHeight + footerBottomPadding + theme.spacing.xl;
5841
- const resolvedOverlay = React36.useMemo(() => {
6067
+ const resolvedOverlay = React37.useMemo(() => {
5842
6068
  var _a;
5843
6069
  if (!overlay) return null;
5844
- if (!React36.isValidElement(overlay)) return overlay;
6070
+ if (!React37.isValidElement(overlay)) return overlay;
5845
6071
  const prevStyle = (_a = overlay.props) == null ? void 0 : _a.style;
5846
- return React36.cloneElement(overlay, {
6072
+ return React37.cloneElement(overlay, {
5847
6073
  style: [prevStyle, { bottom: overlayBottom }]
5848
6074
  });
5849
6075
  }, [overlay, overlayBottom]);
@@ -5898,15 +6124,15 @@ function ChatPage({
5898
6124
  }
5899
6125
 
5900
6126
  // src/components/chat/ScrollToBottomButton.tsx
5901
- var React37 = __toESM(require("react"));
6127
+ var React38 = __toESM(require("react"));
5902
6128
  var import_react_native48 = require("react-native");
5903
6129
  var import_react_native_reanimated2 = __toESM(require("react-native-reanimated"));
5904
6130
  var import_jsx_runtime50 = require("react/jsx-runtime");
5905
6131
  function ScrollToBottomButton({ visible, onPress, children, style }) {
5906
6132
  const theme = useTheme();
5907
6133
  const progress = (0, import_react_native_reanimated2.useSharedValue)(visible ? 1 : 0);
5908
- const [pressed, setPressed] = React37.useState(false);
5909
- React37.useEffect(() => {
6134
+ const [pressed, setPressed] = React38.useState(false);
6135
+ React38.useEffect(() => {
5910
6136
  progress.value = (0, import_react_native_reanimated2.withTiming)(visible ? 1 : 0, { duration: 200, easing: import_react_native_reanimated2.Easing.out(import_react_native_reanimated2.Easing.ease) });
5911
6137
  }, [progress, visible]);
5912
6138
  const animStyle = (0, import_react_native_reanimated2.useAnimatedStyle)(() => ({
@@ -6061,9 +6287,9 @@ function ChatPanel({
6061
6287
  onStartDraw,
6062
6288
  onSend
6063
6289
  }) {
6064
- const listRef = React38.useRef(null);
6065
- const [nearBottom, setNearBottom] = React38.useState(true);
6066
- const handleSend = React38.useCallback(
6290
+ const listRef = React39.useRef(null);
6291
+ const [nearBottom, setNearBottom] = React39.useState(true);
6292
+ const handleSend = React39.useCallback(
6067
6293
  async (text, composerAttachments) => {
6068
6294
  const all = composerAttachments ?? attachments;
6069
6295
  await onSend(text, all.length > 0 ? all : void 0);
@@ -6077,7 +6303,7 @@ function ChatPanel({
6077
6303
  },
6078
6304
  [attachments, nearBottom, onClearAttachments, onSend]
6079
6305
  );
6080
- const handleScrollToBottom = React38.useCallback(() => {
6306
+ const handleScrollToBottom = React39.useCallback(() => {
6081
6307
  var _a;
6082
6308
  (_a = listRef.current) == null ? void 0 : _a.scrollToBottom({ animated: true });
6083
6309
  }, []);
@@ -6151,7 +6377,7 @@ function ChatPanel({
6151
6377
  }
6152
6378
 
6153
6379
  // src/components/dialogs/ConfirmMergeRequestDialog.tsx
6154
- var React39 = __toESM(require("react"));
6380
+ var React40 = __toESM(require("react"));
6155
6381
  var import_react_native53 = require("react-native");
6156
6382
 
6157
6383
  // src/components/primitives/Modal.tsx
@@ -6199,14 +6425,14 @@ function ConfirmMergeRequestDialog({
6199
6425
  onTestFirst
6200
6426
  }) {
6201
6427
  const theme = useTheme();
6202
- const close = React39.useCallback(() => onOpenChange(false), [onOpenChange]);
6428
+ const close = React40.useCallback(() => onOpenChange(false), [onOpenChange]);
6203
6429
  const canConfirm = Boolean(mergeRequest) && !approveDisabled;
6204
- const handleConfirm = React39.useCallback(() => {
6430
+ const handleConfirm = React40.useCallback(() => {
6205
6431
  if (!mergeRequest) return;
6206
6432
  onOpenChange(false);
6207
6433
  void onConfirm();
6208
6434
  }, [mergeRequest, onConfirm, onOpenChange]);
6209
- const handleTestFirst = React39.useCallback(() => {
6435
+ const handleTestFirst = React40.useCallback(() => {
6210
6436
  if (!mergeRequest) return;
6211
6437
  onOpenChange(false);
6212
6438
  void onTestFirst(mergeRequest);
@@ -6355,7 +6581,7 @@ function ConfirmMergeFlow({
6355
6581
  }
6356
6582
 
6357
6583
  // src/studio/hooks/useOptimisticChatMessages.ts
6358
- var React40 = __toESM(require("react"));
6584
+ var React41 = __toESM(require("react"));
6359
6585
  function makeOptimisticId() {
6360
6586
  return `optimistic:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 10)}`;
6361
6587
  }
@@ -6393,11 +6619,11 @@ function useOptimisticChatMessages({
6393
6619
  chatMessages,
6394
6620
  onSendChat
6395
6621
  }) {
6396
- const [optimisticChat, setOptimisticChat] = React40.useState([]);
6397
- React40.useEffect(() => {
6622
+ const [optimisticChat, setOptimisticChat] = React41.useState([]);
6623
+ React41.useEffect(() => {
6398
6624
  setOptimisticChat([]);
6399
6625
  }, [threadId]);
6400
- const messages = React40.useMemo(() => {
6626
+ const messages = React41.useMemo(() => {
6401
6627
  if (!optimisticChat || optimisticChat.length === 0) return chatMessages;
6402
6628
  const unresolved = optimisticChat.filter((o) => !isOptimisticResolvedByServer(chatMessages, o));
6403
6629
  if (unresolved.length === 0) return chatMessages;
@@ -6413,7 +6639,7 @@ function useOptimisticChatMessages({
6413
6639
  merged.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
6414
6640
  return merged;
6415
6641
  }, [chatMessages, optimisticChat]);
6416
- React40.useEffect(() => {
6642
+ React41.useEffect(() => {
6417
6643
  if (optimisticChat.length === 0) return;
6418
6644
  setOptimisticChat((prev) => {
6419
6645
  if (prev.length === 0) return prev;
@@ -6421,7 +6647,7 @@ function useOptimisticChatMessages({
6421
6647
  return next.length === prev.length ? prev : next;
6422
6648
  });
6423
6649
  }, [chatMessages, optimisticChat.length]);
6424
- const onSend = React40.useCallback(
6650
+ const onSend = React41.useCallback(
6425
6651
  async (text, attachments) => {
6426
6652
  if (shouldForkOnEdit) {
6427
6653
  await onSendChat(text, attachments);
@@ -6470,18 +6696,18 @@ function StudioOverlay({
6470
6696
  chatShowTypingIndicator,
6471
6697
  onSendChat,
6472
6698
  onNavigateHome,
6473
- showFloatingButton,
6699
+ showBubble,
6474
6700
  studioControlOptions
6475
6701
  }) {
6476
6702
  const theme = useTheme();
6477
6703
  const { width } = (0, import_react_native54.useWindowDimensions)();
6478
- const [sheetOpen, setSheetOpen] = React41.useState(false);
6479
- const sheetOpenRef = React41.useRef(sheetOpen);
6480
- const [activePage, setActivePage] = React41.useState("preview");
6481
- const [drawing, setDrawing] = React41.useState(false);
6482
- const [chatAttachments, setChatAttachments] = React41.useState([]);
6483
- const [commentsAppId, setCommentsAppId] = React41.useState(null);
6484
- const [commentsCount, setCommentsCount] = React41.useState(null);
6704
+ const [sheetOpen, setSheetOpen] = React42.useState(false);
6705
+ const sheetOpenRef = React42.useRef(sheetOpen);
6706
+ const [activePage, setActivePage] = React42.useState("preview");
6707
+ const [drawing, setDrawing] = React42.useState(false);
6708
+ const [chatAttachments, setChatAttachments] = React42.useState([]);
6709
+ const [commentsAppId, setCommentsAppId] = React42.useState(null);
6710
+ const [commentsCount, setCommentsCount] = React42.useState(null);
6485
6711
  const threadId = (app == null ? void 0 : app.threadId) ?? null;
6486
6712
  const optimistic = useOptimisticChatMessages({
6487
6713
  threadId,
@@ -6489,24 +6715,24 @@ function StudioOverlay({
6489
6715
  chatMessages,
6490
6716
  onSendChat
6491
6717
  });
6492
- const [confirmMrId, setConfirmMrId] = React41.useState(null);
6493
- const confirmMr = React41.useMemo(
6718
+ const [confirmMrId, setConfirmMrId] = React42.useState(null);
6719
+ const confirmMr = React42.useMemo(
6494
6720
  () => confirmMrId ? incomingMergeRequests.find((m) => m.id === confirmMrId) ?? null : null,
6495
6721
  [confirmMrId, incomingMergeRequests]
6496
6722
  );
6497
- const handleSheetOpenChange = React41.useCallback((open) => {
6723
+ const handleSheetOpenChange = React42.useCallback((open) => {
6498
6724
  setSheetOpen(open);
6499
6725
  if (!open) import_react_native54.Keyboard.dismiss();
6500
6726
  }, []);
6501
- const closeSheet = React41.useCallback(() => {
6727
+ const closeSheet = React42.useCallback(() => {
6502
6728
  handleSheetOpenChange(false);
6503
6729
  }, [handleSheetOpenChange]);
6504
- const openSheet = React41.useCallback(() => setSheetOpen(true), []);
6505
- const goToChat = React41.useCallback(() => {
6730
+ const openSheet = React42.useCallback(() => setSheetOpen(true), []);
6731
+ const goToChat = React42.useCallback(() => {
6506
6732
  setActivePage("chat");
6507
6733
  openSheet();
6508
6734
  }, [openSheet]);
6509
- const backToPreview = React41.useCallback(() => {
6735
+ const backToPreview = React42.useCallback(() => {
6510
6736
  if (import_react_native54.Platform.OS !== "ios") {
6511
6737
  import_react_native54.Keyboard.dismiss();
6512
6738
  setActivePage("preview");
@@ -6524,11 +6750,11 @@ function StudioOverlay({
6524
6750
  const t = setTimeout(finalize, 350);
6525
6751
  import_react_native54.Keyboard.dismiss();
6526
6752
  }, []);
6527
- const startDraw = React41.useCallback(() => {
6753
+ const startDraw = React42.useCallback(() => {
6528
6754
  setDrawing(true);
6529
6755
  closeSheet();
6530
6756
  }, [closeSheet]);
6531
- const handleDrawCapture = React41.useCallback(
6757
+ const handleDrawCapture = React42.useCallback(
6532
6758
  (dataUrl) => {
6533
6759
  setChatAttachments((prev) => [...prev, dataUrl]);
6534
6760
  setDrawing(false);
@@ -6537,7 +6763,7 @@ function StudioOverlay({
6537
6763
  },
6538
6764
  [openSheet]
6539
6765
  );
6540
- const toggleSheet = React41.useCallback(async () => {
6766
+ const toggleSheet = React42.useCallback(async () => {
6541
6767
  if (!sheetOpen) {
6542
6768
  const shouldExitTest = Boolean(testingMrId) || isTesting;
6543
6769
  if (shouldExitTest) {
@@ -6549,7 +6775,7 @@ function StudioOverlay({
6549
6775
  closeSheet();
6550
6776
  }
6551
6777
  }, [closeSheet, isTesting, onRestoreBase, sheetOpen, testingMrId]);
6552
- const handleTestMr = React41.useCallback(
6778
+ const handleTestMr = React42.useCallback(
6553
6779
  async (mr) => {
6554
6780
  if (!onTestMr) return;
6555
6781
  await onTestMr(mr);
@@ -6557,10 +6783,10 @@ function StudioOverlay({
6557
6783
  },
6558
6784
  [closeSheet, onTestMr]
6559
6785
  );
6560
- React41.useEffect(() => {
6786
+ React42.useEffect(() => {
6561
6787
  sheetOpenRef.current = sheetOpen;
6562
6788
  }, [sheetOpen]);
6563
- React41.useEffect(() => {
6789
+ React42.useEffect(() => {
6564
6790
  const poller = (0, import_studio_control.startStudioControlPolling)((action) => {
6565
6791
  if (action === "show" && !sheetOpenRef.current) openSheet();
6566
6792
  if (action === "hide" && sheetOpenRef.current) closeSheet();
@@ -6568,7 +6794,7 @@ function StudioOverlay({
6568
6794
  }, studioControlOptions);
6569
6795
  return () => poller.stop();
6570
6796
  }, [closeSheet, openSheet, studioControlOptions, toggleSheet]);
6571
- React41.useEffect(() => {
6797
+ React42.useEffect(() => {
6572
6798
  void (0, import_studio_control.publishComergeStudioUIState)(sheetOpen, studioControlOptions);
6573
6799
  }, [sheetOpen, studioControlOptions]);
6574
6800
  return /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(import_jsx_runtime57.Fragment, { children: [
@@ -6627,8 +6853,8 @@ function StudioOverlay({
6627
6853
  )
6628
6854
  }
6629
6855
  ) }),
6630
- showFloatingButton && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
6631
- FloatingDraggableButton,
6856
+ showBubble && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
6857
+ Bubble,
6632
6858
  {
6633
6859
  visible: !sheetOpen && !drawing,
6634
6860
  ariaLabel: sheetOpen ? "Hide studio" : "Show studio",
@@ -6676,24 +6902,25 @@ function StudioOverlay({
6676
6902
  var import_jsx_runtime58 = require("react/jsx-runtime");
6677
6903
  function ComergeStudio({
6678
6904
  appId,
6679
- apiKey,
6905
+ clientKey: clientKey2,
6680
6906
  appKey = "MicroMain",
6681
6907
  onNavigateHome,
6682
6908
  style,
6683
- showFloatingButton = true,
6684
- studioControlOptions
6909
+ showBubble = true,
6910
+ studioControlOptions,
6911
+ embeddedBaseBundles
6685
6912
  }) {
6686
- const [activeAppId, setActiveAppId] = React42.useState(appId);
6687
- const [runtimeAppId, setRuntimeAppId] = React42.useState(appId);
6688
- const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React42.useState(null);
6689
- const platform = React42.useMemo(() => import_react_native55.Platform.OS === "ios" ? "ios" : "android", []);
6690
- React42.useEffect(() => {
6913
+ const [activeAppId, setActiveAppId] = React43.useState(appId);
6914
+ const [runtimeAppId, setRuntimeAppId] = React43.useState(appId);
6915
+ const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React43.useState(null);
6916
+ const platform = React43.useMemo(() => import_react_native55.Platform.OS === "ios" ? "ios" : "android", []);
6917
+ React43.useEffect(() => {
6691
6918
  setActiveAppId(appId);
6692
6919
  setRuntimeAppId(appId);
6693
6920
  setPendingRuntimeTargetAppId(null);
6694
6921
  }, [appId]);
6695
- const captureTargetRef = React42.useRef(null);
6696
- return /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(StudioBootstrap, { apiKey, children: ({ userId }) => /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(import_bottom_sheet6.BottomSheetModalProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(LiquidGlassResetProvider, { resetTriggers: [appId, activeAppId, runtimeAppId], children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6922
+ const captureTargetRef = React43.useRef(null);
6923
+ return /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(StudioBootstrap, { clientKey: clientKey2, fallback: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(import_react_native55.View, { style: { flex: 1 } }), children: ({ userId }) => /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(import_bottom_sheet6.BottomSheetModalProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(LiquidGlassResetProvider, { resetTriggers: [appId, activeAppId, runtimeAppId], children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
6697
6924
  ComergeStudioInner,
6698
6925
  {
6699
6926
  userId,
@@ -6708,8 +6935,9 @@ function ComergeStudio({
6708
6935
  onNavigateHome,
6709
6936
  captureTargetRef,
6710
6937
  style,
6711
- showFloatingButton,
6712
- studioControlOptions
6938
+ showBubble,
6939
+ studioControlOptions,
6940
+ embeddedBaseBundles
6713
6941
  }
6714
6942
  ) }) }) });
6715
6943
  }
@@ -6726,17 +6954,18 @@ function ComergeStudioInner({
6726
6954
  onNavigateHome,
6727
6955
  captureTargetRef,
6728
6956
  style,
6729
- showFloatingButton,
6730
- studioControlOptions
6957
+ showBubble,
6958
+ studioControlOptions,
6959
+ embeddedBaseBundles
6731
6960
  }) {
6732
6961
  const { app, loading: appLoading } = useApp(activeAppId);
6733
6962
  const { app: runtimeAppFromHook } = useApp(runtimeAppId, { enabled: runtimeAppId !== activeAppId });
6734
6963
  const runtimeApp = runtimeAppId === activeAppId ? app : runtimeAppFromHook;
6735
- const sawEditingOnPendingTargetRef = React42.useRef(false);
6736
- React42.useEffect(() => {
6964
+ const sawEditingOnPendingTargetRef = React43.useRef(false);
6965
+ React43.useEffect(() => {
6737
6966
  sawEditingOnPendingTargetRef.current = false;
6738
6967
  }, [pendingRuntimeTargetAppId]);
6739
- React42.useEffect(() => {
6968
+ React43.useEffect(() => {
6740
6969
  if (!pendingRuntimeTargetAppId) return;
6741
6970
  if (activeAppId !== pendingRuntimeTargetAppId) return;
6742
6971
  if ((app == null ? void 0 : app.status) === "editing") {
@@ -6751,15 +6980,16 @@ function ComergeStudioInner({
6751
6980
  const bundle = useBundleManager({
6752
6981
  base: { appId: runtimeAppId, commitId: (runtimeApp == null ? void 0 : runtimeApp.headCommitId) ?? void 0 },
6753
6982
  platform,
6754
- canRequestLatest: (runtimeApp == null ? void 0 : runtimeApp.status) === "ready"
6983
+ canRequestLatest: (runtimeApp == null ? void 0 : runtimeApp.status) === "ready",
6984
+ embeddedBaseBundles
6755
6985
  });
6756
- const sawEditingOnActiveAppRef = React42.useRef(false);
6757
- const [showPostEditPreparing, setShowPostEditPreparing] = React42.useState(false);
6758
- React42.useEffect(() => {
6986
+ const sawEditingOnActiveAppRef = React43.useRef(false);
6987
+ const [showPostEditPreparing, setShowPostEditPreparing] = React43.useState(false);
6988
+ React43.useEffect(() => {
6759
6989
  sawEditingOnActiveAppRef.current = false;
6760
6990
  setShowPostEditPreparing(false);
6761
6991
  }, [activeAppId]);
6762
- React42.useEffect(() => {
6992
+ React43.useEffect(() => {
6763
6993
  if (!(app == null ? void 0 : app.id)) return;
6764
6994
  if (app.status === "editing") {
6765
6995
  sawEditingOnActiveAppRef.current = true;
@@ -6771,7 +7001,7 @@ function ComergeStudioInner({
6771
7001
  sawEditingOnActiveAppRef.current = false;
6772
7002
  }
6773
7003
  }, [app == null ? void 0 : app.id, app == null ? void 0 : app.status]);
6774
- React42.useEffect(() => {
7004
+ React43.useEffect(() => {
6775
7005
  if (!showPostEditPreparing) return;
6776
7006
  const stillProcessingBaseBundle = bundle.loading && bundle.loadingMode === "base" && !bundle.isTesting;
6777
7007
  if (!stillProcessingBaseBundle) {
@@ -6781,10 +7011,10 @@ function ComergeStudioInner({
6781
7011
  const threadId = (app == null ? void 0 : app.threadId) ?? "";
6782
7012
  const thread = useThreadMessages(threadId);
6783
7013
  const mergeRequests = useMergeRequests({ appId: activeAppId });
6784
- const hasOpenOutgoingMr = React42.useMemo(() => {
7014
+ const hasOpenOutgoingMr = React43.useMemo(() => {
6785
7015
  return mergeRequests.lists.outgoing.some((mr) => mr.status === "open");
6786
7016
  }, [mergeRequests.lists.outgoing]);
6787
- const incomingReviewMrs = React42.useMemo(() => {
7017
+ const incomingReviewMrs = React43.useMemo(() => {
6788
7018
  if (!userId) return mergeRequests.lists.incoming;
6789
7019
  return mergeRequests.lists.incoming.filter((mr) => mr.createdBy !== userId);
6790
7020
  }, [mergeRequests.lists.incoming, userId]);
@@ -6806,9 +7036,9 @@ function ComergeStudioInner({
6806
7036
  uploadAttachments: uploader.uploadBase64Images
6807
7037
  });
6808
7038
  const chatSendDisabled = hasNoOutcomeAfterLastHuman(thread.raw);
6809
- const [processingMrId, setProcessingMrId] = React42.useState(null);
6810
- const [testingMrId, setTestingMrId] = React42.useState(null);
6811
- const chatShowTypingIndicator = React42.useMemo(() => {
7039
+ const [processingMrId, setProcessingMrId] = React43.useState(null);
7040
+ const [testingMrId, setTestingMrId] = React43.useState(null);
7041
+ const chatShowTypingIndicator = React43.useMemo(() => {
6812
7042
  var _a;
6813
7043
  if (!thread.raw || thread.raw.length === 0) return false;
6814
7044
  const last = thread.raw[thread.raw.length - 1];
@@ -6822,7 +7052,8 @@ function ComergeStudioInner({
6822
7052
  appKey,
6823
7053
  bundlePath: bundle.bundlePath,
6824
7054
  forcePreparing: showPostEditPreparing,
6825
- renderToken: bundle.renderToken
7055
+ renderToken: bundle.renderToken,
7056
+ allowInitialPreparing: !embeddedBaseBundles
6826
7057
  }
6827
7058
  ),
6828
7059
  /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
@@ -6878,7 +7109,7 @@ function ComergeStudioInner({
6878
7109
  chatShowTypingIndicator,
6879
7110
  onSendChat: (text, attachments) => actions.sendEdit({ prompt: text, attachments }),
6880
7111
  onNavigateHome,
6881
- showFloatingButton,
7112
+ showBubble,
6882
7113
  studioControlOptions
6883
7114
  }
6884
7115
  )