@comergehq/studio 0.1.26 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,7 @@ import type { App } from '../../data/apps/types';
4
4
  import { appsRepository } from '../../data/apps/repository';
5
5
  import { agentRepository } from '../../data/agent/repository';
6
6
  import type { AttachmentMeta } from '../../data/attachment/types';
7
+ import { trackEditApp, trackRemixApp } from '../analytics/track';
7
8
 
8
9
  function sleep(ms: number): Promise<void> {
9
10
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -104,14 +105,22 @@ export function useStudioActions({
104
105
 
105
106
  setSending(true);
106
107
  setError(null);
108
+ let forkSucceeded = false;
107
109
  try {
108
110
  let targetApp = app;
111
+ const sourceAppId = app.id;
109
112
 
110
113
  if (shouldForkOnEdit) {
111
114
  setForking(true);
112
- const sourceAppId = app.id;
113
115
  const forked = await appsRepository.fork(app.id, {});
114
116
  targetApp = forked;
117
+ await trackRemixApp({
118
+ appId: forked.id,
119
+ sourceAppId,
120
+ threadId: forked.threadId ?? undefined,
121
+ success: true,
122
+ });
123
+ forkSucceeded = true;
115
124
  // For fork+edit, keep rendering the original app until the edit completes on the fork.
116
125
  onForkedApp?.(forked.id, { keepRenderingAppId: sourceAppId });
117
126
  }
@@ -143,9 +152,33 @@ export function useStudioActions({
143
152
  queueItemId: editResult.queueItemId ?? null,
144
153
  queuePosition: editResult.queuePosition ?? null,
145
154
  });
155
+ await trackEditApp({
156
+ appId: targetApp.id,
157
+ threadId,
158
+ promptLength: prompt.trim().length,
159
+ success: true,
160
+ });
146
161
  } catch (e) {
147
162
  const err = e instanceof Error ? e : new Error(String(e));
148
163
  setError(err);
164
+ if (shouldForkOnEdit && !forkSucceeded && app?.id) {
165
+ await trackRemixApp({
166
+ appId: app.id,
167
+ sourceAppId: app.id,
168
+ threadId: app.threadId ?? undefined,
169
+ success: false,
170
+ error: err,
171
+ });
172
+ }
173
+ if (app?.id && app.threadId) {
174
+ await trackEditApp({
175
+ appId: app.id,
176
+ threadId: app.threadId,
177
+ promptLength: prompt.trim().length,
178
+ success: false,
179
+ error: err,
180
+ });
181
+ }
149
182
  throw err;
150
183
  } finally {
151
184
  setForking(false);
@@ -12,6 +12,10 @@ import { Text } from '../../components/primitives/Text';
12
12
  import type { ChatMessage } from '../../components/models/types';
13
13
  import type { EditQueueItem } from '../../data/apps/edit-queue/types';
14
14
  import { ChatQueue } from '../../components/chat/ChatQueue';
15
+ import { AgentProgressCard } from '../../components/chat/AgentProgressCard';
16
+ import { BundleProgressCard } from '../../components/chat/BundleProgressCard';
17
+ import type { AgentRunProgressView } from '../hooks/useAgentRunProgress';
18
+ import { useTheme } from '../../theme';
15
19
 
16
20
  export type ChatPanelProps = {
17
21
  title?: string;
@@ -34,6 +38,7 @@ export type ChatPanelProps = {
34
38
  isRetryingMessage?: (messageId: string) => boolean;
35
39
  queueItems?: EditQueueItem[];
36
40
  onRemoveQueueItem?: (id: string) => void;
41
+ progress?: AgentRunProgressView | null;
37
42
  };
38
43
 
39
44
  export function ChatPanel({
@@ -57,7 +62,9 @@ export function ChatPanel({
57
62
  isRetryingMessage,
58
63
  queueItems = [],
59
64
  onRemoveQueueItem,
65
+ progress = null,
60
66
  }: ChatPanelProps) {
67
+ const theme = useTheme();
61
68
  const listRef = React.useRef<ChatMessageListRef | null>(null);
62
69
  const [nearBottom, setNearBottom] = React.useState(true);
63
70
 
@@ -131,8 +138,12 @@ export function ChatPanel({
131
138
  );
132
139
  }
133
140
 
134
- const queueTop = queueItems.length > 0 ? (
135
- <ChatQueue items={queueItems} onRemove={onRemoveQueueItem} />
141
+ const bundleProgress = progress?.bundle ?? null;
142
+ const queueTop = progress || queueItems.length > 0 ? (
143
+ <View style={{ gap: theme.spacing.sm }}>
144
+ {progress ? (bundleProgress ? <BundleProgressCard progress={bundleProgress} /> : <AgentProgressCard progress={progress} />) : null}
145
+ {queueItems.length > 0 ? <ChatQueue items={queueItems} onRemove={onRemoveQueueItem} /> : null}
146
+ </View>
136
147
  ) : null;
137
148
 
138
149
  return (
@@ -12,6 +12,7 @@ import { PreviewMetaSection } from './preview-panel/PreviewMetaSection';
12
12
  import { PreviewCustomizeSection } from './preview-panel/PreviewCustomizeSection';
13
13
  import { PreviewCollaborateSection } from './preview-panel/PreviewCollaborateSection';
14
14
  import { usePreviewPanelData } from './preview-panel/usePreviewPanelData';
15
+ import { trackShareApp } from '../analytics/track';
15
16
 
16
17
  export type PreviewPanelProps = {
17
18
  app: App | null;
@@ -84,8 +85,17 @@ export function PreviewPanel({
84
85
  url: shareUrl,
85
86
  };
86
87
  await Share.share(payload);
88
+ await trackShareApp({
89
+ appId: app.id,
90
+ success: true,
91
+ });
87
92
  } catch (error) {
88
93
  log.warn('PreviewPanel share failed', error);
94
+ await trackShareApp({
95
+ appId: app.id,
96
+ success: false,
97
+ error,
98
+ });
89
99
  }
90
100
  }, [app]);
91
101
 
@@ -8,6 +8,10 @@ import { Text } from '../../components/primitives/Text';
8
8
  export type RuntimeRendererProps = {
9
9
  appKey: string;
10
10
  bundlePath: string | null;
11
+ /**
12
+ * Loading text shown while runtime cannot render a bundle yet.
13
+ */
14
+ preparingText?: string;
11
15
  /**
12
16
  * When true, show the "Preparing app…" UI even if a previous bundle is available.
13
17
  * Used to avoid briefly rendering an outdated bundle during post-edit base refresh.
@@ -28,6 +32,7 @@ export type RuntimeRendererProps = {
28
32
  export function RuntimeRenderer({
29
33
  appKey,
30
34
  bundlePath,
35
+ preparingText,
31
36
  forcePreparing,
32
37
  renderToken,
33
38
  style,
@@ -48,7 +53,7 @@ export function RuntimeRenderer({
48
53
 
49
54
  return (
50
55
  <View style={[{ flex: 1, justifyContent: 'center', alignItems: 'center', padding: 24 }, style]}>
51
- <Text variant="bodyMuted">Preparing app…</Text>
56
+ <Text variant="bodyMuted">{preparingText ?? 'Preparing app…'}</Text>
52
57
  </View>
53
58
  );
54
59
  }
@@ -64,6 +64,7 @@ export type StudioOverlayProps = {
64
64
  onSendChat: (text: string, attachments?: string[]) => void | Promise<void>;
65
65
  chatQueueItems?: import('../../data/apps/edit-queue/types').EditQueueItem[];
66
66
  onRemoveQueueItem?: (id: string) => void;
67
+ chatProgress?: import('../hooks/useAgentRunProgress').AgentRunProgressView | null;
67
68
 
68
69
  // Navigation callbacks
69
70
  onNavigateHome?: () => void;
@@ -105,6 +106,7 @@ export function StudioOverlay({
105
106
  onSendChat,
106
107
  chatQueueItems,
107
108
  onRemoveQueueItem,
109
+ chatProgress,
108
110
  onNavigateHome,
109
111
  showBubble,
110
112
  studioControlOptions,
@@ -287,6 +289,7 @@ export function StudioOverlay({
287
289
  isRetryingMessage={optimistic.isRetrying}
288
290
  queueItems={queueItemsForChat}
289
291
  onRemoveQueueItem={onRemoveQueueItem}
292
+ progress={chatProgress}
290
293
  />
291
294
  }
292
295
  />
@@ -104,6 +104,7 @@ export function usePreviewPanelData(params: {
104
104
  initialComments: commentCountOverride ?? insights.comments,
105
105
  initialIsLiked: Boolean(app?.isLiked),
106
106
  onOpenComments,
107
+ interactionSource: 'preview_panel',
107
108
  });
108
109
 
109
110
  const canSubmitMergeRequest = React.useMemo(() => {