@comergehq/studio 0.1.16 → 0.1.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comergehq/studio",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "Comerge studio",
5
5
  "main": "src/index.ts",
6
6
  "module": "dist/index.mjs",
@@ -12,6 +12,7 @@ import {
12
12
  Send,
13
13
  X,
14
14
  Check,
15
+ Share2,
15
16
  } from 'lucide-react-native';
16
17
 
17
18
  import { useTheme } from '../../theme';
@@ -62,5 +63,6 @@ export const IconSend = makeIcon(Send);
62
63
  export const IconPlay = makeIcon(Play);
63
64
  export const IconArrowDown = makeIcon(ArrowDown);
64
65
  export const IconApprove = makeIcon(Check);
66
+ export const IconShare = makeIcon(Share2);
65
67
 
66
68
 
@@ -58,7 +58,6 @@ export function useStudioActions({
58
58
  setSending(true);
59
59
  setError(null);
60
60
  try {
61
- onEditStart?.();
62
61
  let targetApp = app;
63
62
 
64
63
  if (shouldForkOnEdit) {
@@ -73,6 +72,7 @@ export function useStudioActions({
73
72
 
74
73
  const threadId = targetApp.threadId;
75
74
  if (!threadId) throw new Error('No thread available for this app.');
75
+ onEditStart?.();
76
76
 
77
77
  let attachmentMetas: AttachmentMeta[] | undefined;
78
78
  if (attachments && attachments.length > 0 && uploadAttachments) {
@@ -83,9 +83,8 @@ export function useThreadMessages(threadId: string): UseThreadMessagesResult {
83
83
  const foregroundSignal = useForegroundSignal(Boolean(threadId));
84
84
 
85
85
  const upsertSorted = React.useCallback((prev: Message[], m: Message) => {
86
- const include = !isQueuedHiddenMessage(m);
87
86
  const next = prev.filter((x) => x.id !== m.id);
88
- if (include) next.push(m);
87
+ next.push(m);
89
88
  next.sort(compareMessages);
90
89
  return next;
91
90
  }, []);
@@ -101,7 +100,7 @@ export function useThreadMessages(threadId: string): UseThreadMessagesResult {
101
100
  try {
102
101
  const list = await messagesRepository.list(threadId);
103
102
  if (activeRequestIdRef.current !== requestId) return;
104
- setRaw([...list].filter((m) => !isQueuedHiddenMessage(m)).sort(compareMessages));
103
+ setRaw([...list].sort(compareMessages));
105
104
  } catch (e) {
106
105
  if (activeRequestIdRef.current !== requestId) return;
107
106
  setError(e instanceof Error ? e : new Error(String(e)));
@@ -131,7 +130,11 @@ export function useThreadMessages(threadId: string): UseThreadMessagesResult {
131
130
  void refetch();
132
131
  }, [foregroundSignal, refetch, threadId]);
133
132
 
134
- const messages = React.useMemo(() => raw.map(mapMessageToChatMessage), [raw]);
133
+ const messages = React.useMemo(() => {
134
+ const visible = raw.filter((m) => !isQueuedHiddenMessage(m));
135
+ const resolved = visible.length > 0 ? visible : raw;
136
+ return resolved.map(mapMessageToChatMessage);
137
+ }, [raw]);
135
138
 
136
139
  return { raw, messages, loading, error, refetch };
137
140
  }
@@ -1,8 +1,9 @@
1
1
  import * as React from 'react';
2
- import { ActivityIndicator, View } from 'react-native';
2
+ import { ActivityIndicator, Platform, Share, View } from 'react-native';
3
3
 
4
4
  import type { App } from '../../data/apps/types';
5
5
  import type { MergeRequest } from '../../data/merge-requests/types';
6
+ import { log } from '../../core/logger';
6
7
  import { PreviewPage } from '../../components/preview/PreviewPage';
7
8
  import { Text } from '../../components/primitives/Text';
8
9
  import { PreviewPanelHeader } from './preview-panel/PreviewPanelHeader';
@@ -59,6 +60,29 @@ export function PreviewPanel({
59
60
  onOpenComments,
60
61
  commentCountOverride,
61
62
  }: PreviewPanelProps) {
63
+ const handleShare = React.useCallback(async () => {
64
+ if (!app || !app.isPublic) return;
65
+ const shareUrl = `https://comerge.ai/app/${app.id}`;
66
+ const message = app.name ? `${app.name} on Comerge\n${shareUrl}` : `Check out this app on Comerge\n${shareUrl}`;
67
+ try {
68
+ const title = app.name ?? 'Comerge app';
69
+ const payload =
70
+ Platform.OS === 'ios'
71
+ ? {
72
+ title,
73
+ message,
74
+ }
75
+ : {
76
+ title,
77
+ message,
78
+ url: shareUrl,
79
+ };
80
+ await Share.share(payload);
81
+ } catch (error) {
82
+ log.warn('PreviewPanel share failed', error);
83
+ }
84
+ }, [app]);
85
+
62
86
  const { imageUrl, imageLoaded, setImageLoaded, creator, insights, stats, showProcessing, canSubmitMergeRequest } = usePreviewPanelData({
63
87
  app,
64
88
  isOwner,
@@ -67,7 +91,16 @@ export function PreviewPanel({
67
91
  commentCountOverride,
68
92
  });
69
93
 
70
- const header = <PreviewPanelHeader isOwner={isOwner} onClose={onClose} onNavigateHome={onNavigateHome} onGoToChat={onGoToChat} />;
94
+ const header = (
95
+ <PreviewPanelHeader
96
+ isOwner={isOwner}
97
+ isPublic={Boolean(app?.isPublic)}
98
+ onClose={onClose}
99
+ onNavigateHome={onNavigateHome}
100
+ onGoToChat={onGoToChat}
101
+ onShare={handleShare}
102
+ />
103
+ );
71
104
 
72
105
  if (loading || !app) {
73
106
  return (
@@ -114,7 +114,9 @@ export function StudioOverlay({
114
114
  const [commentsCount, setCommentsCount] = React.useState<number | null>(null);
115
115
 
116
116
  const threadId = app?.threadId ?? null;
117
- const disableOptimistic = Boolean(chatQueueItems && chatQueueItems.length > 0) || app?.status === 'editing';
117
+ const isForking = chatForking || app?.status === 'forking';
118
+ const queueItemsForChat = isForking ? [] : chatQueueItems;
119
+ const disableOptimistic = Boolean(queueItemsForChat && queueItemsForChat.length > 0) || app?.status === 'editing';
118
120
  const optimistic = useOptimisticChatMessages({
119
121
  threadId,
120
122
  shouldForkOnEdit,
@@ -271,7 +273,7 @@ export function StudioOverlay({
271
273
  onNavigateHome={onNavigateHome}
272
274
  onStartDraw={startDraw}
273
275
  onSend={optimistic.onSend}
274
- queueItems={chatQueueItems}
276
+ queueItems={queueItemsForChat}
275
277
  onRemoveQueueItem={onRemoveQueueItem}
276
278
  />
277
279
  }
@@ -3,16 +3,25 @@ import { View } from 'react-native';
3
3
 
4
4
  import { StudioSheetHeader } from '../../../components/studio-sheet/StudioSheetHeader';
5
5
  import { StudioSheetHeaderIconButton } from '../../../components/studio-sheet/StudioSheetHeaderIconButton';
6
- import { IconChat, IconClose, IconHome } from '../../../components/icons/StudioIcons';
6
+ import { IconChat, IconClose, IconHome, IconShare } from '../../../components/icons/StudioIcons';
7
7
 
8
8
  export type PreviewPanelHeaderProps = {
9
9
  isOwner: boolean;
10
+ isPublic: boolean;
10
11
  onClose: () => void;
11
12
  onNavigateHome?: () => void;
12
13
  onGoToChat: () => void;
14
+ onShare?: () => void;
13
15
  };
14
16
 
15
- export function PreviewPanelHeader({ isOwner, onClose, onNavigateHome, onGoToChat }: PreviewPanelHeaderProps) {
17
+ export function PreviewPanelHeader({
18
+ isOwner,
19
+ isPublic,
20
+ onClose,
21
+ onNavigateHome,
22
+ onGoToChat,
23
+ onShare,
24
+ }: PreviewPanelHeaderProps) {
16
25
  return (
17
26
  <StudioSheetHeader
18
27
  left={
@@ -36,6 +45,17 @@ export function PreviewPanelHeader({ isOwner, onClose, onNavigateHome, onGoToCha
36
45
  <IconChat size={20} colorToken="onPrimary" />
37
46
  </StudioSheetHeaderIconButton>
38
47
  ) : null}
48
+ {isPublic && onShare ? (
49
+ <StudioSheetHeaderIconButton
50
+ onPress={onShare}
51
+ accessibilityLabel="Share"
52
+ intent="primary"
53
+ appearance="glass"
54
+ style={{ marginRight: 8 }}
55
+ >
56
+ <IconShare size={20} colorToken="onPrimary" />
57
+ </StudioSheetHeaderIconButton>
58
+ ) : null}
39
59
  <StudioSheetHeaderIconButton onPress={onClose} accessibilityLabel="Close" appearance="glass" intent="primary">
40
60
  <IconClose size={20} colorToken="onPrimary" />
41
61
  </StudioSheetHeaderIconButton>