@comergehq/studio 0.1.20 → 0.1.22
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 +308 -100
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +309 -101
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/chat/ChatMessageBubble.tsx +5 -2
- package/src/core/services/supabase/realtimeManager.ts +112 -0
- package/src/data/apps/edit-queue/repository.ts +35 -40
- package/src/data/apps/remote.ts +9 -0
- package/src/data/apps/repository.ts +35 -33
- package/src/data/apps/types.ts +15 -0
- package/src/data/messages/repository.ts +28 -33
- package/src/studio/ComergeStudio.tsx +22 -0
- package/src/studio/ui/PreviewPanel.tsx +22 -2
- package/src/studio/ui/StudioOverlay.tsx +9 -0
- package/src/studio/ui/preview-panel/PreviewCollaborateSection.tsx +95 -3
- package/src/studio/ui/preview-panel/usePreviewPanelData.ts +8 -0
package/dist/index.js
CHANGED
|
@@ -549,6 +549,12 @@ var AppsRemoteDataSourceImpl = class extends BaseRemote {
|
|
|
549
549
|
);
|
|
550
550
|
return data;
|
|
551
551
|
}
|
|
552
|
+
async syncUpstream(appId) {
|
|
553
|
+
const { data } = await api.post(
|
|
554
|
+
`/v1/apps/${encodeURIComponent(appId)}/sync-upstream`
|
|
555
|
+
);
|
|
556
|
+
return data;
|
|
557
|
+
}
|
|
552
558
|
};
|
|
553
559
|
var appsRemoteDataSource = new AppsRemoteDataSourceImpl();
|
|
554
560
|
|
|
@@ -561,6 +567,95 @@ var BaseRepository = class {
|
|
|
561
567
|
}
|
|
562
568
|
};
|
|
563
569
|
|
|
570
|
+
// src/core/services/supabase/realtimeManager.ts
|
|
571
|
+
var INITIAL_BACKOFF_MS = 1e3;
|
|
572
|
+
var MAX_BACKOFF_MS = 3e4;
|
|
573
|
+
var realtimeLog = log.extend("realtime");
|
|
574
|
+
var entries = /* @__PURE__ */ new Map();
|
|
575
|
+
var subscriberIdCounter = 0;
|
|
576
|
+
function clearTimer(entry) {
|
|
577
|
+
if (!entry.timer) return;
|
|
578
|
+
clearTimeout(entry.timer);
|
|
579
|
+
entry.timer = null;
|
|
580
|
+
}
|
|
581
|
+
function buildChannel(entry) {
|
|
582
|
+
const supabase = getSupabaseClient();
|
|
583
|
+
const channel = supabase.channel(entry.key);
|
|
584
|
+
entry.subscribers.forEach((configure) => {
|
|
585
|
+
configure(channel);
|
|
586
|
+
});
|
|
587
|
+
return channel;
|
|
588
|
+
}
|
|
589
|
+
function scheduleResubscribe(entry, reason) {
|
|
590
|
+
if (entry.timer) return;
|
|
591
|
+
const delay = entry.backoffMs;
|
|
592
|
+
entry.backoffMs = Math.min(entry.backoffMs * 2, MAX_BACKOFF_MS);
|
|
593
|
+
realtimeLog.warn(`[realtime] channel ${entry.key} ${reason}; resubscribe in ${delay}ms`);
|
|
594
|
+
entry.timer = setTimeout(() => {
|
|
595
|
+
entry.timer = null;
|
|
596
|
+
if (!entries.has(entry.key)) return;
|
|
597
|
+
if (entry.subscribers.size === 0) return;
|
|
598
|
+
subscribeChannel(entry);
|
|
599
|
+
}, delay);
|
|
600
|
+
}
|
|
601
|
+
function handleStatus(entry, status) {
|
|
602
|
+
if (status === "SUBSCRIBED") {
|
|
603
|
+
entry.backoffMs = INITIAL_BACKOFF_MS;
|
|
604
|
+
clearTimer(entry);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
if (status === "CLOSED" || status === "TIMED_OUT" || status === "CHANNEL_ERROR") {
|
|
608
|
+
scheduleResubscribe(entry, status);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
function subscribeChannel(entry) {
|
|
612
|
+
try {
|
|
613
|
+
const supabase = getSupabaseClient();
|
|
614
|
+
if (entry.channel) supabase.removeChannel(entry.channel);
|
|
615
|
+
const channel = buildChannel(entry);
|
|
616
|
+
entry.channel = channel;
|
|
617
|
+
channel.subscribe((status) => handleStatus(entry, status));
|
|
618
|
+
} catch (error) {
|
|
619
|
+
realtimeLog.warn("[realtime] subscribe failed", error);
|
|
620
|
+
scheduleResubscribe(entry, "SUBSCRIBE_FAILED");
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function subscribeManagedChannel(key, configure) {
|
|
624
|
+
let entry = entries.get(key);
|
|
625
|
+
if (!entry) {
|
|
626
|
+
entry = {
|
|
627
|
+
key,
|
|
628
|
+
channel: null,
|
|
629
|
+
subscribers: /* @__PURE__ */ new Map(),
|
|
630
|
+
backoffMs: INITIAL_BACKOFF_MS,
|
|
631
|
+
timer: null
|
|
632
|
+
};
|
|
633
|
+
entries.set(key, entry);
|
|
634
|
+
}
|
|
635
|
+
const subscriberId = ++subscriberIdCounter;
|
|
636
|
+
entry.subscribers.set(subscriberId, configure);
|
|
637
|
+
if (!entry.channel) {
|
|
638
|
+
subscribeChannel(entry);
|
|
639
|
+
} else {
|
|
640
|
+
configure(entry.channel);
|
|
641
|
+
}
|
|
642
|
+
return () => {
|
|
643
|
+
const current = entries.get(key);
|
|
644
|
+
if (!current) return;
|
|
645
|
+
current.subscribers.delete(subscriberId);
|
|
646
|
+
if (current.subscribers.size === 0) {
|
|
647
|
+
clearTimer(current);
|
|
648
|
+
try {
|
|
649
|
+
if (current.channel) getSupabaseClient().removeChannel(current.channel);
|
|
650
|
+
} finally {
|
|
651
|
+
entries.delete(key);
|
|
652
|
+
}
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
subscribeChannel(current);
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
564
659
|
// src/data/apps/repository.ts
|
|
565
660
|
function mapDbAppRow(row) {
|
|
566
661
|
return {
|
|
@@ -627,6 +722,10 @@ var AppsRepositoryImpl = class extends BaseRepository {
|
|
|
627
722
|
const res = await this.remote.importFromGithub(payload);
|
|
628
723
|
return this.unwrapOrThrow(res);
|
|
629
724
|
}
|
|
725
|
+
async syncUpstream(appId) {
|
|
726
|
+
const res = await this.remote.syncUpstream(appId);
|
|
727
|
+
return this.unwrapOrThrow(res);
|
|
728
|
+
}
|
|
630
729
|
subscribeCreatedApps(userId, handlers) {
|
|
631
730
|
if (!userId) return () => {
|
|
632
731
|
};
|
|
@@ -638,35 +737,33 @@ var AppsRepositoryImpl = class extends BaseRepository {
|
|
|
638
737
|
return this.subscribeToAppChannel(`apps:id:${appId}`, `id=eq.${appId}`, handlers);
|
|
639
738
|
}
|
|
640
739
|
subscribeToAppChannel(channelKey, filter, handlers) {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
supabase.removeChannel(channel);
|
|
669
|
-
};
|
|
740
|
+
return subscribeManagedChannel(channelKey, (channel) => {
|
|
741
|
+
channel.on(
|
|
742
|
+
"postgres_changes",
|
|
743
|
+
{ event: "INSERT", schema: "public", table: "app", filter },
|
|
744
|
+
(payload) => {
|
|
745
|
+
var _a;
|
|
746
|
+
console.log("[subscribeToAppChannel] onInsert", payload);
|
|
747
|
+
(_a = handlers.onInsert) == null ? void 0 : _a.call(handlers, mapDbAppRow(payload.new));
|
|
748
|
+
}
|
|
749
|
+
).on(
|
|
750
|
+
"postgres_changes",
|
|
751
|
+
{ event: "UPDATE", schema: "public", table: "app", filter },
|
|
752
|
+
(payload) => {
|
|
753
|
+
var _a;
|
|
754
|
+
console.log("[subscribeToAppChannel] onUpdate", payload);
|
|
755
|
+
(_a = handlers.onUpdate) == null ? void 0 : _a.call(handlers, mapDbAppRow(payload.new));
|
|
756
|
+
}
|
|
757
|
+
).on(
|
|
758
|
+
"postgres_changes",
|
|
759
|
+
{ event: "DELETE", schema: "public", table: "app", filter },
|
|
760
|
+
(payload) => {
|
|
761
|
+
var _a;
|
|
762
|
+
console.log("[subscribeToAppChannel] onDelete", payload);
|
|
763
|
+
(_a = handlers.onDelete) == null ? void 0 : _a.call(handlers, mapDbAppRow(payload.old));
|
|
764
|
+
}
|
|
765
|
+
);
|
|
766
|
+
});
|
|
670
767
|
}
|
|
671
768
|
};
|
|
672
769
|
var appsRepository = new AppsRepositoryImpl(appsRemoteDataSource);
|
|
@@ -797,35 +894,33 @@ var MessagesRepositoryImpl = class extends BaseRepository {
|
|
|
797
894
|
return this.unwrapOrThrow(res);
|
|
798
895
|
}
|
|
799
896
|
subscribeThread(threadId, handlers) {
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
supabase.removeChannel(channel);
|
|
828
|
-
};
|
|
897
|
+
return subscribeManagedChannel(`messages:thread:${threadId}`, (channel) => {
|
|
898
|
+
channel.on(
|
|
899
|
+
"postgres_changes",
|
|
900
|
+
{ event: "INSERT", schema: "public", table: "message", filter: `thread_id=eq.${threadId}` },
|
|
901
|
+
(payload) => {
|
|
902
|
+
var _a;
|
|
903
|
+
const row = payload.new;
|
|
904
|
+
(_a = handlers.onInsert) == null ? void 0 : _a.call(handlers, mapDbRowToMessage(row));
|
|
905
|
+
}
|
|
906
|
+
).on(
|
|
907
|
+
"postgres_changes",
|
|
908
|
+
{ event: "UPDATE", schema: "public", table: "message", filter: `thread_id=eq.${threadId}` },
|
|
909
|
+
(payload) => {
|
|
910
|
+
var _a;
|
|
911
|
+
const row = payload.new;
|
|
912
|
+
(_a = handlers.onUpdate) == null ? void 0 : _a.call(handlers, mapDbRowToMessage(row));
|
|
913
|
+
}
|
|
914
|
+
).on(
|
|
915
|
+
"postgres_changes",
|
|
916
|
+
{ event: "DELETE", schema: "public", table: "message", filter: `thread_id=eq.${threadId}` },
|
|
917
|
+
(payload) => {
|
|
918
|
+
var _a;
|
|
919
|
+
const row = payload.old;
|
|
920
|
+
(_a = handlers.onDelete) == null ? void 0 : _a.call(handlers, mapDbRowToMessage(row));
|
|
921
|
+
}
|
|
922
|
+
);
|
|
923
|
+
});
|
|
829
924
|
}
|
|
830
925
|
};
|
|
831
926
|
var messagesRepository = new MessagesRepositoryImpl(messagesRemoteDataSource);
|
|
@@ -5390,6 +5485,9 @@ function ReviewMergeRequestCarousel({
|
|
|
5390
5485
|
var import_jsx_runtime44 = require("react/jsx-runtime");
|
|
5391
5486
|
function PreviewCollaborateSection({
|
|
5392
5487
|
canSubmitMergeRequest,
|
|
5488
|
+
canSyncUpstream,
|
|
5489
|
+
syncingUpstream,
|
|
5490
|
+
upstreamSyncStatus,
|
|
5393
5491
|
incomingMergeRequests,
|
|
5394
5492
|
outgoingMergeRequests,
|
|
5395
5493
|
creatorStatsById,
|
|
@@ -5398,15 +5496,18 @@ function PreviewCollaborateSection({
|
|
|
5398
5496
|
testingMrId,
|
|
5399
5497
|
toMergeRequestSummary,
|
|
5400
5498
|
onSubmitMergeRequest,
|
|
5499
|
+
onSyncUpstream,
|
|
5401
5500
|
onRequestApprove,
|
|
5402
5501
|
onReject,
|
|
5403
5502
|
onTestMr
|
|
5404
5503
|
}) {
|
|
5405
5504
|
const theme = useTheme();
|
|
5406
5505
|
const [submittingMr, setSubmittingMr] = React32.useState(false);
|
|
5407
|
-
const
|
|
5506
|
+
const [syncingLocal, setSyncingLocal] = React32.useState(false);
|
|
5507
|
+
const hasSection = canSubmitMergeRequest || canSyncUpstream || incomingMergeRequests.length > 0 || outgoingMergeRequests.length > 0;
|
|
5408
5508
|
if (!hasSection) return null;
|
|
5409
|
-
const
|
|
5509
|
+
const isSyncing = Boolean(syncingUpstream || syncingLocal);
|
|
5510
|
+
const showActionsSubtitle = canSubmitMergeRequest && onSubmitMergeRequest || canSyncUpstream && onSyncUpstream || onTestMr && incomingMergeRequests.length > 0;
|
|
5410
5511
|
return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
|
|
5411
5512
|
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(SectionTitle, { marginTop: theme.spacing.xl, children: "Collaborate" }),
|
|
5412
5513
|
showActionsSubtitle ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
@@ -5475,6 +5576,64 @@ function PreviewCollaborateSection({
|
|
|
5475
5576
|
right: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react_native9.Send, { size: 16, color: "#03DAC6" })
|
|
5476
5577
|
}
|
|
5477
5578
|
) : null,
|
|
5579
|
+
canSyncUpstream && onSyncUpstream ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
5580
|
+
PressableCardRow,
|
|
5581
|
+
{
|
|
5582
|
+
accessibilityLabel: "Sync from original",
|
|
5583
|
+
disabled: isSyncing,
|
|
5584
|
+
onPress: () => {
|
|
5585
|
+
import_react_native42.Alert.alert(
|
|
5586
|
+
"Sync from Original",
|
|
5587
|
+
"This will pull the latest upstream changes into your remix.",
|
|
5588
|
+
[
|
|
5589
|
+
{ text: "Cancel", style: "cancel" },
|
|
5590
|
+
{
|
|
5591
|
+
text: "Sync",
|
|
5592
|
+
style: "destructive",
|
|
5593
|
+
onPress: () => {
|
|
5594
|
+
setSyncingLocal(true);
|
|
5595
|
+
Promise.resolve(onSyncUpstream()).then((result) => {
|
|
5596
|
+
if ((result == null ? void 0 : result.status) === "up-to-date") {
|
|
5597
|
+
import_react_native42.Alert.alert("Up to date", "Your remix already includes the latest upstream changes.");
|
|
5598
|
+
} else {
|
|
5599
|
+
import_react_native42.Alert.alert("Sync started", "Upstream changes are being merged into your remix.");
|
|
5600
|
+
}
|
|
5601
|
+
}).catch(() => {
|
|
5602
|
+
import_react_native42.Alert.alert("Sync failed", "We could not start the sync. Please try again.");
|
|
5603
|
+
}).finally(() => setSyncingLocal(false));
|
|
5604
|
+
}
|
|
5605
|
+
}
|
|
5606
|
+
]
|
|
5607
|
+
);
|
|
5608
|
+
},
|
|
5609
|
+
style: {
|
|
5610
|
+
padding: theme.spacing.lg,
|
|
5611
|
+
borderRadius: theme.radii.lg,
|
|
5612
|
+
backgroundColor: withAlpha(theme.colors.surfaceRaised, 0.5),
|
|
5613
|
+
borderWidth: 1,
|
|
5614
|
+
borderColor: withAlpha(theme.colors.primary, 0.25),
|
|
5615
|
+
marginBottom: theme.spacing.sm
|
|
5616
|
+
},
|
|
5617
|
+
left: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
5618
|
+
import_react_native42.View,
|
|
5619
|
+
{
|
|
5620
|
+
style: {
|
|
5621
|
+
width: 40,
|
|
5622
|
+
height: 40,
|
|
5623
|
+
borderRadius: 999,
|
|
5624
|
+
alignItems: "center",
|
|
5625
|
+
justifyContent: "center",
|
|
5626
|
+
backgroundColor: withAlpha(theme.colors.primary, 0.12),
|
|
5627
|
+
marginRight: theme.spacing.lg
|
|
5628
|
+
},
|
|
5629
|
+
children: isSyncing ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_react_native42.ActivityIndicator, { color: theme.colors.primary, size: "small" }) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react_native9.RefreshCw, { size: 18, color: theme.colors.primary })
|
|
5630
|
+
}
|
|
5631
|
+
),
|
|
5632
|
+
title: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(Text, { style: { color: theme.colors.text, fontSize: 16, lineHeight: 20, fontWeight: theme.typography.fontWeight.semibold }, children: "Sync from Original" }),
|
|
5633
|
+
subtitle: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(Text, { style: { color: theme.colors.textMuted, fontSize: 12, lineHeight: 16, marginTop: 2 }, children: isSyncing ? "Syncing upstream changes..." : upstreamSyncStatus === "up-to-date" ? "You are already up to date with the original app" : "Pull the latest upstream changes into this remix" }),
|
|
5634
|
+
right: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react_native9.RefreshCw, { size: 16, color: theme.colors.primary })
|
|
5635
|
+
}
|
|
5636
|
+
) : null,
|
|
5478
5637
|
onTestMr && incomingMergeRequests.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
5479
5638
|
ReviewMergeRequestCarousel,
|
|
5480
5639
|
{
|
|
@@ -5782,6 +5941,12 @@ function usePreviewPanelData(params) {
|
|
|
5782
5941
|
if (app.headCommitId && app.forkedFromCommitId && app.headCommitId !== app.forkedFromCommitId) return true;
|
|
5783
5942
|
return false;
|
|
5784
5943
|
}, [app, isOwner, outgoingMergeRequests]);
|
|
5944
|
+
const canSyncUpstream = React34.useMemo(() => {
|
|
5945
|
+
if (!isOwner) return false;
|
|
5946
|
+
if (!app) return false;
|
|
5947
|
+
if (!app.forkedFromAppId) return false;
|
|
5948
|
+
return app.status === "ready";
|
|
5949
|
+
}, [app, isOwner]);
|
|
5785
5950
|
const showProcessing = app ? app.status !== "ready" : false;
|
|
5786
5951
|
return {
|
|
5787
5952
|
imageUrl,
|
|
@@ -5791,7 +5956,8 @@ function usePreviewPanelData(params) {
|
|
|
5791
5956
|
insights,
|
|
5792
5957
|
stats,
|
|
5793
5958
|
showProcessing,
|
|
5794
|
-
canSubmitMergeRequest
|
|
5959
|
+
canSubmitMergeRequest,
|
|
5960
|
+
canSyncUpstream
|
|
5795
5961
|
};
|
|
5796
5962
|
}
|
|
5797
5963
|
|
|
@@ -5814,6 +5980,9 @@ function PreviewPanel({
|
|
|
5814
5980
|
onGoToChat,
|
|
5815
5981
|
onStartDraw,
|
|
5816
5982
|
onSubmitMergeRequest,
|
|
5983
|
+
onSyncUpstream,
|
|
5984
|
+
syncingUpstream,
|
|
5985
|
+
upstreamSyncStatus,
|
|
5817
5986
|
onRequestApprove,
|
|
5818
5987
|
onReject,
|
|
5819
5988
|
onTestMr,
|
|
@@ -5841,7 +6010,17 @@ ${shareUrl}`;
|
|
|
5841
6010
|
log.warn("PreviewPanel share failed", error);
|
|
5842
6011
|
}
|
|
5843
6012
|
}, [app]);
|
|
5844
|
-
const {
|
|
6013
|
+
const {
|
|
6014
|
+
imageUrl,
|
|
6015
|
+
imageLoaded,
|
|
6016
|
+
setImageLoaded,
|
|
6017
|
+
creator,
|
|
6018
|
+
insights,
|
|
6019
|
+
stats,
|
|
6020
|
+
showProcessing,
|
|
6021
|
+
canSubmitMergeRequest,
|
|
6022
|
+
canSyncUpstream
|
|
6023
|
+
} = usePreviewPanelData({
|
|
5845
6024
|
app,
|
|
5846
6025
|
isOwner,
|
|
5847
6026
|
outgoingMergeRequests,
|
|
@@ -5901,6 +6080,9 @@ ${shareUrl}`;
|
|
|
5901
6080
|
PreviewCollaborateSection,
|
|
5902
6081
|
{
|
|
5903
6082
|
canSubmitMergeRequest,
|
|
6083
|
+
canSyncUpstream,
|
|
6084
|
+
syncingUpstream,
|
|
6085
|
+
upstreamSyncStatus,
|
|
5904
6086
|
incomingMergeRequests,
|
|
5905
6087
|
outgoingMergeRequests,
|
|
5906
6088
|
creatorStatsById,
|
|
@@ -5909,6 +6091,7 @@ ${shareUrl}`;
|
|
|
5909
6091
|
testingMrId,
|
|
5910
6092
|
toMergeRequestSummary,
|
|
5911
6093
|
onSubmitMergeRequest,
|
|
6094
|
+
onSyncUpstream,
|
|
5912
6095
|
onRequestApprove,
|
|
5913
6096
|
onReject,
|
|
5914
6097
|
onTestMr
|
|
@@ -5943,6 +6126,8 @@ function ChatMessageBubble({ message, renderContent, style }) {
|
|
|
5943
6126
|
const isMergeApproved = metaEvent === "merge_request.approved";
|
|
5944
6127
|
const isMergeRejected = metaEvent === "merge_request.rejected";
|
|
5945
6128
|
const isMergeCompleted = metaEvent === "merge.completed";
|
|
6129
|
+
const isSyncStarted = metaEvent === "sync.started";
|
|
6130
|
+
const isSyncCompleted = metaEvent === "sync.completed";
|
|
5946
6131
|
const isHuman = message.author === "human" || isMergeApproved || isMergeRejected;
|
|
5947
6132
|
const align = { alignSelf: isHuman ? "flex-end" : "flex-start" };
|
|
5948
6133
|
const bubbleVariant = isHuman ? "surface" : "surfaceRaised";
|
|
@@ -5964,8 +6149,8 @@ function ChatMessageBubble({ message, renderContent, style }) {
|
|
|
5964
6149
|
cornerStyle
|
|
5965
6150
|
],
|
|
5966
6151
|
children: /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(import_react_native44.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
|
|
5967
|
-
isMergeCompleted ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react_native10.CheckCheck, { size: 16, color: theme.colors.success, style: { marginRight: theme.spacing.sm } }) : null,
|
|
5968
|
-
isMergeApproved ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react_native10.GitMerge, { size: 16, color: theme.colors.text, style: { marginRight: theme.spacing.sm } }) : null,
|
|
6152
|
+
isMergeCompleted || isSyncCompleted ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react_native10.CheckCheck, { size: 16, color: theme.colors.success, style: { marginRight: theme.spacing.sm } }) : null,
|
|
6153
|
+
isMergeApproved || isSyncStarted ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react_native10.GitMerge, { size: 16, color: theme.colors.text, style: { marginRight: theme.spacing.sm } }) : null,
|
|
5969
6154
|
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_react_native44.View, { style: { flexShrink: 1, minWidth: 0 }, children: renderContent ? renderContent(message) : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(MarkdownText, { markdown: message.content, variant: "chat", bodyColor }) })
|
|
5970
6155
|
] })
|
|
5971
6156
|
}
|
|
@@ -6928,6 +7113,9 @@ function StudioOverlay({
|
|
|
6928
7113
|
testingMrId,
|
|
6929
7114
|
toMergeRequestSummary,
|
|
6930
7115
|
onSubmitMergeRequest,
|
|
7116
|
+
onSyncUpstream,
|
|
7117
|
+
syncingUpstream,
|
|
7118
|
+
upstreamSyncStatus,
|
|
6931
7119
|
onApprove,
|
|
6932
7120
|
onReject,
|
|
6933
7121
|
onTestMr,
|
|
@@ -7072,6 +7260,9 @@ function StudioOverlay({
|
|
|
7072
7260
|
onGoToChat: goToChat,
|
|
7073
7261
|
onStartDraw: isOwner ? startDraw : void 0,
|
|
7074
7262
|
onSubmitMergeRequest,
|
|
7263
|
+
onSyncUpstream,
|
|
7264
|
+
syncingUpstream,
|
|
7265
|
+
upstreamSyncStatus,
|
|
7075
7266
|
onRequestApprove: (mr) => setConfirmMrId(mr.id),
|
|
7076
7267
|
onReject,
|
|
7077
7268
|
onTestMr: handleTestMr,
|
|
@@ -7217,42 +7408,40 @@ var EditQueueRepositoryImpl = class extends BaseRepository {
|
|
|
7217
7408
|
return this.unwrapOrThrow(res);
|
|
7218
7409
|
}
|
|
7219
7410
|
subscribeEditQueue(appId, handlers) {
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
supabase.removeChannel(channel);
|
|
7255
|
-
};
|
|
7411
|
+
return subscribeManagedChannel(`edit-queue:app:${appId}`, (channel) => {
|
|
7412
|
+
channel.on(
|
|
7413
|
+
"postgres_changes",
|
|
7414
|
+
{ event: "INSERT", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
|
|
7415
|
+
(payload) => {
|
|
7416
|
+
var _a;
|
|
7417
|
+
const row = payload.new;
|
|
7418
|
+
if (row.kind !== "edit") return;
|
|
7419
|
+
const item = mapQueueItem(row);
|
|
7420
|
+
if (!ACTIVE_STATUSES.includes(item.status)) return;
|
|
7421
|
+
(_a = handlers.onInsert) == null ? void 0 : _a.call(handlers, item);
|
|
7422
|
+
}
|
|
7423
|
+
).on(
|
|
7424
|
+
"postgres_changes",
|
|
7425
|
+
{ event: "UPDATE", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
|
|
7426
|
+
(payload) => {
|
|
7427
|
+
var _a, _b;
|
|
7428
|
+
const row = payload.new;
|
|
7429
|
+
if (row.kind !== "edit") return;
|
|
7430
|
+
const item = mapQueueItem(row);
|
|
7431
|
+
if (ACTIVE_STATUSES.includes(item.status)) (_a = handlers.onUpdate) == null ? void 0 : _a.call(handlers, item);
|
|
7432
|
+
else (_b = handlers.onDelete) == null ? void 0 : _b.call(handlers, item);
|
|
7433
|
+
}
|
|
7434
|
+
).on(
|
|
7435
|
+
"postgres_changes",
|
|
7436
|
+
{ event: "DELETE", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
|
|
7437
|
+
(payload) => {
|
|
7438
|
+
var _a;
|
|
7439
|
+
const row = payload.old;
|
|
7440
|
+
if (row.kind !== "edit") return;
|
|
7441
|
+
(_a = handlers.onDelete) == null ? void 0 : _a.call(handlers, mapQueueItem(row));
|
|
7442
|
+
}
|
|
7443
|
+
);
|
|
7444
|
+
});
|
|
7256
7445
|
}
|
|
7257
7446
|
};
|
|
7258
7447
|
var editQueueRepository = new EditQueueRepositoryImpl(
|
|
@@ -7500,6 +7689,8 @@ function ComergeStudioInner({
|
|
|
7500
7689
|
const chatSendDisabled = false;
|
|
7501
7690
|
const [processingMrId, setProcessingMrId] = React47.useState(null);
|
|
7502
7691
|
const [testingMrId, setTestingMrId] = React47.useState(null);
|
|
7692
|
+
const [syncingUpstream, setSyncingUpstream] = React47.useState(false);
|
|
7693
|
+
const [upstreamSyncStatus, setUpstreamSyncStatus] = React47.useState(null);
|
|
7503
7694
|
const chatShowTypingIndicator = React47.useMemo(() => {
|
|
7504
7695
|
var _a;
|
|
7505
7696
|
if (!thread.raw || thread.raw.length === 0) return false;
|
|
@@ -7510,7 +7701,21 @@ function ComergeStudioInner({
|
|
|
7510
7701
|
React47.useEffect(() => {
|
|
7511
7702
|
updateLastEditQueueInfo(null);
|
|
7512
7703
|
setSuppressQueueUntilResponse(false);
|
|
7704
|
+
setUpstreamSyncStatus(null);
|
|
7513
7705
|
}, [activeAppId, updateLastEditQueueInfo]);
|
|
7706
|
+
const handleSyncUpstream = React47.useCallback(async () => {
|
|
7707
|
+
if (!(app == null ? void 0 : app.id)) {
|
|
7708
|
+
throw new Error("Missing app");
|
|
7709
|
+
}
|
|
7710
|
+
setSyncingUpstream(true);
|
|
7711
|
+
try {
|
|
7712
|
+
const result = await appsRepository.syncUpstream(activeAppId);
|
|
7713
|
+
setUpstreamSyncStatus(result.status);
|
|
7714
|
+
return result;
|
|
7715
|
+
} finally {
|
|
7716
|
+
setSyncingUpstream(false);
|
|
7717
|
+
}
|
|
7718
|
+
}, [activeAppId, app == null ? void 0 : app.id]);
|
|
7514
7719
|
React47.useEffect(() => {
|
|
7515
7720
|
if (!(lastEditQueueInfo == null ? void 0 : lastEditQueueInfo.queueItemId)) return;
|
|
7516
7721
|
const stillPresent = editQueue.items.some((item) => item.id === lastEditQueueInfo.queueItemId);
|
|
@@ -7566,6 +7771,9 @@ function ComergeStudioInner({
|
|
|
7566
7771
|
onSubmitMergeRequest: (app == null ? void 0 : app.forkedFromAppId) && actions.isOwner && !hasOpenOutgoingMr ? async () => {
|
|
7567
7772
|
await mergeRequests.actions.openMergeRequest(activeAppId);
|
|
7568
7773
|
} : void 0,
|
|
7774
|
+
onSyncUpstream: actions.isOwner && (app == null ? void 0 : app.forkedFromAppId) ? handleSyncUpstream : void 0,
|
|
7775
|
+
syncingUpstream,
|
|
7776
|
+
upstreamSyncStatus,
|
|
7569
7777
|
onApprove: async (mr) => {
|
|
7570
7778
|
if (processingMrId) return;
|
|
7571
7779
|
setProcessingMrId(mr.id);
|