diff --git a/.github/pr-screenshots/2401/chat-2-photos-d.png b/.github/pr-screenshots/2401/chat-2-photos-d.png
new file mode 100644
index 0000000000..044df2f0e1
Binary files /dev/null and b/.github/pr-screenshots/2401/chat-2-photos-d.png differ
diff --git a/.github/pr-screenshots/2401/chat-2-photos-l.png b/.github/pr-screenshots/2401/chat-2-photos-l.png
new file mode 100644
index 0000000000..591b255d39
Binary files /dev/null and b/.github/pr-screenshots/2401/chat-2-photos-l.png differ
diff --git a/.github/pr-screenshots/2401/chat-link-d.png b/.github/pr-screenshots/2401/chat-link-d.png
new file mode 100644
index 0000000000..5f2c9611d1
Binary files /dev/null and b/.github/pr-screenshots/2401/chat-link-d.png differ
diff --git a/.github/pr-screenshots/2401/chat-link-l.png b/.github/pr-screenshots/2401/chat-link-l.png
new file mode 100644
index 0000000000..7af624f6cf
Binary files /dev/null and b/.github/pr-screenshots/2401/chat-link-l.png differ
diff --git a/.github/pr-screenshots/2401/cold-chat-draft-l.png b/.github/pr-screenshots/2401/cold-chat-draft-l.png
new file mode 100644
index 0000000000..39a73bce45
Binary files /dev/null and b/.github/pr-screenshots/2401/cold-chat-draft-l.png differ
diff --git a/.github/pr-screenshots/2401/cold-close-chats-l.png b/.github/pr-screenshots/2401/cold-close-chats-l.png
new file mode 100644
index 0000000000..ead0f5d099
Binary files /dev/null and b/.github/pr-screenshots/2401/cold-close-chats-l.png differ
diff --git a/.github/pr-screenshots/2401/cold-send-to-l.png b/.github/pr-screenshots/2401/cold-send-to-l.png
new file mode 100644
index 0000000000..19a08ecc98
Binary files /dev/null and b/.github/pr-screenshots/2401/cold-send-to-l.png differ
diff --git a/.github/pr-screenshots/2401/sheet-2-photos-d.png b/.github/pr-screenshots/2401/sheet-2-photos-d.png
new file mode 100644
index 0000000000..362428fc8a
Binary files /dev/null and b/.github/pr-screenshots/2401/sheet-2-photos-d.png differ
diff --git a/.github/pr-screenshots/2401/sheet-2-photos-l.png b/.github/pr-screenshots/2401/sheet-2-photos-l.png
new file mode 100644
index 0000000000..d56fb37547
Binary files /dev/null and b/.github/pr-screenshots/2401/sheet-2-photos-l.png differ
diff --git a/.github/pr-screenshots/2401/sheet-2-photos-zh-d.png b/.github/pr-screenshots/2401/sheet-2-photos-zh-d.png
new file mode 100644
index 0000000000..84dac1aee7
Binary files /dev/null and b/.github/pr-screenshots/2401/sheet-2-photos-zh-d.png differ
diff --git a/.github/pr-screenshots/2401/sheet-2-photos-zh-l.png b/.github/pr-screenshots/2401/sheet-2-photos-zh-l.png
new file mode 100644
index 0000000000..8e3cbba880
Binary files /dev/null and b/.github/pr-screenshots/2401/sheet-2-photos-zh-l.png differ
diff --git a/.github/pr-screenshots/2401/sheet-link-d.png b/.github/pr-screenshots/2401/sheet-link-d.png
new file mode 100644
index 0000000000..ca14cfa1bf
Binary files /dev/null and b/.github/pr-screenshots/2401/sheet-link-d.png differ
diff --git a/.github/pr-screenshots/2401/sheet-link-l.png b/.github/pr-screenshots/2401/sheet-link-l.png
new file mode 100644
index 0000000000..7b30269598
Binary files /dev/null and b/.github/pr-screenshots/2401/sheet-link-l.png differ
diff --git a/.github/pr-screenshots/2401/unpaired-d.png b/.github/pr-screenshots/2401/unpaired-d.png
new file mode 100644
index 0000000000..8baa39a1ec
Binary files /dev/null and b/.github/pr-screenshots/2401/unpaired-d.png differ
diff --git a/.github/pr-screenshots/2401/unpaired-l.png b/.github/pr-screenshots/2401/unpaired-l.png
new file mode 100644
index 0000000000..9ea4f59b14
Binary files /dev/null and b/.github/pr-screenshots/2401/unpaired-l.png differ
diff --git a/.github/pr-screenshots/2401/unpaired-pair-screen-l.png b/.github/pr-screenshots/2401/unpaired-pair-screen-l.png
new file mode 100644
index 0000000000..9a5cf63c58
Binary files /dev/null and b/.github/pr-screenshots/2401/unpaired-pair-screen-l.png differ
diff --git a/apps/mobile/DESIGN.md b/apps/mobile/DESIGN.md
index 0fa5e9898a..5b4b577b9f 100644
--- a/apps/mobile/DESIGN.md
+++ b/apps/mobile/DESIGN.md
@@ -150,6 +150,7 @@ The shell is set by [ADR-0211](../../docs/adr/0211-the-mobile-app-has-two-tabs-w
- Swipe for pin and mute; long-press opens a native menu.
- On iOS a long-press on a Host conversation row peeks at the chat, like Messages: the row lifts into the system context-menu preview (expo-router `Link.Preview`) showing the conversation itself, and tapping the preview opens it. A plain tap opens the conversation as before.
- A long-press on the app icon lists the three chats opened most recently (Home Screen quick actions on iOS, app shortcuts on Android), newest first, and tapping one opens that chat on its Host. The list updates whenever a chat opens and is kept with the device's saved Host list.
+- **Share to a Bot.** DeepSeekBot is a target in the system share sheet (an iOS Share Extension, Android `SEND` / `SEND_MULTIPLE`). Sharing text, a link, photos, videos or files opens **Send to**: a preview of what arrived above the paired Host's chats, in the Chats order. Picking a chat opens it with the text added below any unsent draft and the files waiting as attachment tiles; nothing is sent until the Human taps Send. Cloud channels are not targets yet, and with no paired Host the screen says to pair first.
**Count chip**
diff --git a/apps/mobile/app.config.js b/apps/mobile/app.config.js
index 55ec4f8833..051cd39e23 100644
--- a/apps/mobile/app.config.js
+++ b/apps/mobile/app.config.js
@@ -1,6 +1,7 @@
const { execFileSync } = require('node:child_process');
const { existsSync } = require('node:fs');
const path = require('node:path');
+const withSharingPlugin = require('expo-sharing/app.plugin').default;
const mainCheckout = () => {
try {
@@ -21,10 +22,33 @@ const googleServicesFile = () => {
return existsSync(local) ? local : undefined;
};
+const sharing = {
+ ios: {
+ enabled: true,
+ activationRule: {
+ supportsText: true,
+ supportsWebUrlWithMaxCount: 1,
+ supportsImageWithMaxCount: 10,
+ supportsMovieWithMaxCount: 10,
+ supportsFileWithMaxCount: 10,
+ },
+ },
+ android: {
+ enabled: true,
+ singleShareMimeTypes: ['text/*', 'image/*', 'video/*', 'audio/*', 'application/*'],
+ multipleShareMimeTypes: ['image/*', 'video/*', 'audio/*', 'application/*'],
+ },
+};
+
+const withSharing = (resolved) => {
+ const scheme = Array.isArray(resolved.scheme) ? resolved.scheme[0] : resolved.scheme;
+ return { ...withSharingPlugin({ ...resolved, scheme }, sharing), scheme: resolved.scheme };
+};
+
module.exports = ({ config }) => {
const store = process.env.DEEPSEEKBOT_APP_VARIANT === 'store';
const firebase = googleServicesFile();
- return {
+ return withSharing({
...config,
owner: 'deepseekbots-team',
updates: { ...config.updates, enabled: false },
@@ -40,5 +64,5 @@ module.exports = ({ config }) => {
...(firebase ? { googleServicesFile: firebase } : {}),
},
...(store ? { name: 'DeepSeekBot', scheme: 'deepseekbot' } : {}),
- };
+ });
};
diff --git a/apps/mobile/app.json b/apps/mobile/app.json
index 09884b32e5..560beb21cd 100644
--- a/apps/mobile/app.json
+++ b/apps/mobile/app.json
@@ -76,7 +76,6 @@
],
"expo-font",
"expo-image",
- "expo-sharing",
"expo-status-bar",
"expo-web-browser"
],
diff --git a/apps/mobile/app/+native-intent.tsx b/apps/mobile/app/+native-intent.tsx
index dc4377278f..ebf6121124 100644
--- a/apps/mobile/app/+native-intent.tsx
+++ b/apps/mobile/app/+native-intent.tsx
@@ -2,7 +2,9 @@ import * as SecureStore from 'expo-secure-store';
import { cloudAvailable } from '../src/cloud/available';
import { cloudNativeIntent } from '../src/cloud/native-intent';
import { parseInvitationLink } from '../src/cloud/invitation-link';
+import { isSharePath } from '../src/ui/share/share-path';
export async function redirectSystemPath({ path }: { path: string; initial: boolean }) {
+ if (isSharePath(path)) return '/share';
const destination = cloudNativeIntent(path);
if (!cloudAvailable && destination.startsWith('/cloud-')) return '/';
if (destination.startsWith('/cloud-join?link=')) {
diff --git a/apps/mobile/app/_layout.tsx b/apps/mobile/app/_layout.tsx
index cb7d4b47f9..7b853b3108 100644
--- a/apps/mobile/app/_layout.tsx
+++ b/apps/mobile/app/_layout.tsx
@@ -65,6 +65,10 @@ export default function RootLayout() {
name="pair"
options={{ title: t.pairTitle, ...sheetScreenOptions(design, true) }}
/>
+
{
if (limit <= 0) return [];
if (source === 'file') {
diff --git a/apps/mobile/src/ui/chat/conversation-screen.tsx b/apps/mobile/src/ui/chat/conversation-screen.tsx
index 69dfed5d8d..67c4a0c4da 100644
--- a/apps/mobile/src/ui/chat/conversation-screen.tsx
+++ b/apps/mobile/src/ui/chat/conversation-screen.tsx
@@ -1,5 +1,6 @@
import { useSourceDraft } from '../../host/native-source-drafts';
import { hostDraftKey } from '../../host/source-drafts';
+import { takeSharedFiles } from '../share/incoming-share';
import { Stack, useIsFocused, useRouter } from 'expo-router';
import { memo, useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
import {
@@ -345,13 +346,12 @@ function ChatConversation({
const playback = useStreamPlayback(conversation, display.loaded && display.mode === 'streaming');
const snapshot = useConversationTimelineSnapshot(conversation, playback);
const shownDrafts = display.loaded && display.mode === 'streaming' ? snapshot.drafts : NO_DRAFTS;
- const [draft, setDraft] = useSourceDraft(
- hostDraftKey(
- saved?.hostId ?? '',
- place.kind,
- (place.kind === 'dm' ? place.slug : place.channelId) ?? '',
- ),
+ const draftKey = hostDraftKey(
+ saved?.hostId ?? '',
+ place.kind,
+ (place.kind === 'dm' ? place.slug : place.channelId) ?? '',
);
+ const [draft, setDraft] = useSourceDraft(draftKey);
const [mentions, setMentions] = useState([]);
const [caret, setCaret] = useState(0);
const [selection, setSelection] = useState<{ start: number; end: number }>();
@@ -474,14 +474,23 @@ function ChatConversation({
}
: undefined,
);
+ const sharedMount = useCallback(
+ (node: unknown) => {
+ if (node === null) return;
+ const shared = takeSharedFiles(draftKey);
+ if (shared.length > 0) setFiles((current) => [...current, ...shared].slice(0, MAX_PICKED));
+ },
+ [draftKey],
+ );
const mountRef = dictation.mountRef;
const screenRef = useCallback(
(node: unknown) => {
mountRef(node);
readMount(node);
recentMount(node);
+ sharedMount(node);
},
- [mountRef, readMount, recentMount],
+ [mountRef, readMount, recentMount, sharedMount],
);
const asking = snapshot.messages.findLast(
diff --git a/apps/mobile/src/ui/share/incoming-share.ts b/apps/mobile/src/ui/share/incoming-share.ts
new file mode 100644
index 0000000000..e8232e4068
--- /dev/null
+++ b/apps/mobile/src/ui/share/incoming-share.ts
@@ -0,0 +1,45 @@
+import type { ResolvedSharePayload } from 'expo-sharing';
+
+import { fromSharedFile, MAX_PICKED, type PickedFile } from '../../host/pick-files';
+import { sourceDrafts } from '../../host/native-source-drafts';
+
+export type IncomingShare = { text: string; files: PickedFile[] };
+
+export function incomingShare(payloads: readonly ResolvedSharePayload[]): IncomingShare {
+ const text: string[] = [];
+ const files: PickedFile[] = [];
+ for (const payload of payloads) {
+ const uri = payload.contentUri;
+ const attachment =
+ uri !== null &&
+ payload.shareType !== 'text' &&
+ payload.shareType !== 'url' &&
+ payload.contentType !== 'website' &&
+ payload.contentType !== 'text';
+ if (attachment) {
+ if (files.length < MAX_PICKED)
+ files.push(fromSharedFile(uri, payload.originalName, payload.contentMimeType ?? null));
+ } else if (payload.value.trim() !== '') text.push(payload.value.trim());
+ }
+ return { text: text.join('\n'), files };
+}
+
+export function appendShared(current: string, text: string): string {
+ if (text === '') return current;
+ return current.trim() === '' ? text : `${current.replace(/\s+$/u, '')}\n${text}`;
+}
+
+const held = new Map();
+
+export function takeSharedFiles(key: string): PickedFile[] {
+ const files = held.get(key) ?? [];
+ held.delete(key);
+ return files;
+}
+
+export async function deliverShare(key: string, share: IncomingShare): Promise {
+ await sourceDrafts.ready;
+ if (share.text !== '') sourceDrafts.set(key, appendShared(sourceDrafts.get(key), share.text));
+ if (share.files.length > 0)
+ held.set(key, [...(held.get(key) ?? []), ...share.files].slice(0, MAX_PICKED));
+}
diff --git a/apps/mobile/src/ui/share/share-path.ts b/apps/mobile/src/ui/share/share-path.ts
new file mode 100644
index 0000000000..cb68933c25
--- /dev/null
+++ b/apps/mobile/src/ui/share/share-path.ts
@@ -0,0 +1,3 @@
+export function isSharePath(path: string): boolean {
+ return /^(?:[a-z][a-z0-9+.-]*:\/\/)?\/?expo-sharing(?:[/?#]|$)/iu.test(path);
+}
diff --git a/apps/mobile/src/ui/share/share-screen.tsx b/apps/mobile/src/ui/share/share-screen.tsx
new file mode 100644
index 0000000000..7bbdfc53e9
--- /dev/null
+++ b/apps/mobile/src/ui/share/share-screen.tsx
@@ -0,0 +1,113 @@
+import { useIncomingShare } from 'expo-sharing';
+import { useRouter } from 'expo-router';
+import { useMemo, useState } from 'react';
+import { ActivityIndicator, FlatList, View } from 'react-native';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+
+import { useHostSession, useHostSnapshot, useQuietChannels } from '../../host/host-session';
+import { hostDraftKey } from '../../host/source-drafts';
+import { ConversationRow } from '../chat/conversation-row';
+import { conversations, type Conversation } from '../chat/conversations';
+import { useDesignTheme } from '../design/theme';
+import { radius, space } from '../design/tokens';
+import { Button } from '../primitives/button';
+import { DrawerHeader } from '../primitives/drawer-header';
+import { Text } from '../primitives/text';
+import { conversationHref } from '../shell/open-conversation';
+import { t } from '../strings';
+import { deliverShare, incomingShare } from './incoming-share';
+
+function draftKey(hostId: string, row: Conversation): string {
+ return row.kind === 'bot'
+ ? hostDraftKey(hostId, 'dm', row.bot.slug)
+ : hostDraftKey(hostId, 'channel', row.channel.id);
+}
+
+export default function ShareScreen() {
+ const router = useRouter();
+ const theme = useDesignTheme();
+ const insets = useSafeAreaInsets();
+ const { saved, client } = useHostSession();
+ const snapshot = useHostSnapshot(client);
+ const quiet = useQuietChannels(saved?.hostId);
+ const incoming = useIncomingShare();
+ const share = useMemo(
+ () => incomingShare(incoming.resolvedSharedPayloads),
+ [incoming.resolvedSharedPayloads],
+ );
+ const rows = useMemo(
+ () => conversations(snapshot.roster, [], {}, quiet),
+ [snapshot.roster, quiet],
+ );
+ const [sending, setSending] = useState(false);
+ const empty = !incoming.isResolving && share.text === '' && share.files.length === 0;
+ const send = async (row: Conversation) => {
+ if (saved === undefined || saved === null || sending || empty) return;
+ setSending(true);
+ await deliverShare(draftKey(saved.hostId, row), share);
+ incoming.clearSharedPayloads();
+ router.replace(conversationHref(row, saved.hostId));
+ };
+ const close = () => {
+ incoming.clearSharedPayloads();
+ if (router.canGoBack()) router.back();
+ else router.replace('/');
+ };
+ return (
+ row.key}
+ ListHeaderComponent={
+
+
+
+ {incoming.isResolving ? (
+
+ ) : empty ? (
+ {t.share.nothing}
+ ) : (
+ <>
+ {share.text !== '' && {share.text}}
+ {share.files.length > 0 && (
+
+ {t.share.files(share.files.length)}
+
+ )}
+ >
+ )}
+
+
+ }
+ ListEmptyComponent={
+ saved === null ? (
+
+ {t.share.pairFirst}
+
+ ) : !empty && snapshot.roster === undefined ? (
+
+ ) : undefined
+ }
+ renderItem={({ item }) => (
+ void send(item)} />
+ )}
+ />
+ );
+}
diff --git a/apps/mobile/src/ui/strings.ts b/apps/mobile/src/ui/strings.ts
index b524a83e9d..fd06973205 100644
--- a/apps/mobile/src/ui/strings.ts
+++ b/apps/mobile/src/ui/strings.ts
@@ -526,6 +526,12 @@ const en = {
pullToCompose: 'Pull up to type',
clipboardImage: 'Image on the clipboard',
pasteImage: 'Paste image',
+ share: {
+ title: 'Send to',
+ nothing: 'Nothing was shared.',
+ pairFirst: 'Pair a computer first to send this to a Bot.',
+ files: (count: number) => (count === 1 ? '1 attachment' : `${count} attachments`),
+ },
releaseToCompose: 'Release to type',
unseenLatest: (count: number) =>
`${count} new ${count === 1 ? 'message' : 'messages'} · Jump to latest`,
@@ -1593,6 +1599,12 @@ const zh: typeof en = {
pullToCompose: '上拉输入消息',
clipboardImage: '剪贴板里有一张图片',
pasteImage: '粘贴图片',
+ share: {
+ title: '发送到',
+ nothing: '没有收到分享的内容。',
+ pairFirst: '先配对一台电脑,才能发送给 Bot。',
+ files: (count: number) => `${count} 个附件`,
+ },
releaseToCompose: '松手输入消息',
unseenLatest: (count: number) => `${count} 条新消息 · 跳到最新`,
beginning: '这是你们私信的开始。',
diff --git a/apps/mobile/test/incoming-share.test.ts b/apps/mobile/test/incoming-share.test.ts
new file mode 100644
index 0000000000..a56124d416
--- /dev/null
+++ b/apps/mobile/test/incoming-share.test.ts
@@ -0,0 +1,120 @@
+import type { ResolvedSharePayload } from 'expo-sharing';
+import { describe, expect, it, vi } from 'vitest';
+
+const drafts = vi.hoisted(() => {
+ const values = new Map();
+ return {
+ values,
+ sourceDrafts: {
+ ready: Promise.resolve(),
+ get: (key: string) => values.get(key) ?? '',
+ set: (key: string, value: string) => void values.set(key, value),
+ },
+ };
+});
+
+vi.mock('expo-document-picker', () => ({}));
+vi.mock('expo-image-picker', () => ({}));
+vi.mock('../src/host/native-source-drafts', () => ({ sourceDrafts: drafts.sourceDrafts }));
+
+const { appendShared, deliverShare, incomingShare, takeSharedFiles } =
+ await import('../src/ui/share/incoming-share');
+const { isSharePath } = await import('../src/ui/share/share-path');
+
+function payload(overrides: Partial): ResolvedSharePayload {
+ return {
+ value: '',
+ shareType: 'text',
+ mimeType: 'text/plain',
+ contentUri: null,
+ contentType: 'text',
+ contentMimeType: null,
+ originalName: null,
+ contentSize: null,
+ ...overrides,
+ } as ResolvedSharePayload;
+}
+
+describe('share to Bot', () => {
+ it('turns shared text and links into draft text and files into attachments', () => {
+ const share = incomingShare([
+ payload({ value: ' Read this ' }),
+ payload({
+ value: 'https://example.com/post',
+ shareType: 'url',
+ contentUri: 'https://example.com/post',
+ contentType: 'website',
+ }),
+ payload({
+ value: 'file:///shared/photo.jpg',
+ shareType: 'image',
+ contentUri: 'file:///shared/photo.jpg',
+ contentType: 'image',
+ contentMimeType: 'image/jpeg',
+ originalName: 'photo.jpg',
+ }),
+ payload({
+ value: 'file:///shared/notes.pdf',
+ shareType: 'file',
+ contentUri: 'file:///shared/notes.pdf',
+ contentType: 'file',
+ contentMimeType: 'application/pdf',
+ }),
+ ]);
+ expect(share.text).toBe('Read this\nhttps://example.com/post');
+ expect(share.files.map(({ kind, name, uri }) => ({ kind, name, uri }))).toEqual([
+ { kind: 'image', name: 'photo.jpg', uri: 'file:///shared/photo.jpg' },
+ {
+ kind: 'file',
+ name: expect.stringMatching(/^shared-\d+\.pdf$/u),
+ uri: 'file:///shared/notes.pdf',
+ },
+ ]);
+ });
+
+ it('caps attachments at the composer limit', () => {
+ const many = Array.from({ length: 12 }, (_, index) =>
+ payload({
+ value: `file:///shared/${index}.png`,
+ shareType: 'image',
+ contentUri: `file:///shared/${index}.png`,
+ contentType: 'image',
+ contentMimeType: 'image/png',
+ }),
+ );
+ expect(incomingShare(many).files).toHaveLength(10);
+ });
+
+ it('adds shared text below an unsent draft', () => {
+ expect(appendShared('', 'link')).toBe('link');
+ expect(appendShared('my note \n', 'link')).toBe('my note\nlink');
+ expect(appendShared('my note', '')).toBe('my note');
+ });
+
+ it('hands the text to the chat draft and the files to the next chat screen once', async () => {
+ drafts.values.set('dm', 'hello');
+ const share = incomingShare([
+ payload({ value: 'shared' }),
+ payload({
+ value: 'file:///a.png',
+ shareType: 'image',
+ contentUri: 'file:///a.png',
+ contentType: 'image',
+ contentMimeType: 'image/png',
+ }),
+ ]);
+ await deliverShare('dm', share);
+ expect(drafts.values.get('dm')).toBe('hello\nshared');
+ expect(takeSharedFiles('dm').map((file) => file.uri)).toEqual(['file:///a.png']);
+ expect(takeSharedFiles('dm')).toEqual([]);
+ });
+
+ it('recognizes the share deep link in every form the system hands over', () => {
+ expect(isSharePath('deepseekbot-dev://expo-sharing')).toBe(true);
+ expect(isSharePath('deepseekbot://expo-sharing?x=1')).toBe(true);
+ expect(isSharePath('/expo-sharing')).toBe(true);
+ expect(isSharePath('expo-sharing')).toBe(true);
+ expect(isSharePath('/dm/expo-sharing')).toBe(false);
+ expect(isSharePath('deepseekbot://expo-sharing-other')).toBe(false);
+ });
+});
diff --git a/changes/2401-share-to-bot.md b/changes/2401-share-to-bot.md
new file mode 100644
index 0000000000..d3788586ab
--- /dev/null
+++ b/changes/2401-share-to-bot.md
@@ -0,0 +1,11 @@
+---
+section: Added
+---
+
+## en
+
+- Share to a Bot from any app: pick DeepSeekBot in the share sheet, choose a chat, and the shared text, link, photos or files wait in its composer for you to send ([#2401](https://github.com/BotHarness/DeepSeekBot/pull/2401)).
+
+## zh
+
+- 可以从任意 App 分享给 Bot:在分享面板里选 DeepSeekBot,再选一个聊天,分享的文字、链接、照片或文件会放进输入框,等你确认后发送([#2401](https://github.com/BotHarness/DeepSeekBot/pull/2401))。
diff --git a/tsconfig.json b/tsconfig.json
index 7d464ebd0d..97d154f0a8 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -48,6 +48,7 @@
"apps/mobile/test/activity-feed.test.ts",
"apps/mobile/test/open-conversation.test.ts",
"apps/mobile/test/quick-action-routing.test.ts",
+ "apps/mobile/test/incoming-share.test.ts",
"apps/mobile/test/grant-request-card.test.ts",
"apps/mobile/test/attention-actions.test.ts",
"apps/mobile/test/reanimated-shim.ts",