diff --git a/bun.lock b/bun.lock index ec228acc5f6..4c85db1406e 100644 --- a/bun.lock +++ b/bun.lock @@ -296,6 +296,7 @@ }, "devDependencies": { "@types/react": "~19.1.10", + "expo-build-properties": "1.0.10", "typescript": "~5.9.2", }, }, @@ -2953,6 +2954,8 @@ "expo-asset": ["expo-asset@12.0.12", "", { "dependencies": { "@expo/image-utils": "^0.8.8", "expo-constants": "~18.0.12" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-CsXFCQbx2fElSMn0lyTdRIyKlSXOal6ilLJd+yeZ6xaC7I9AICQgscY5nj0QcwgA+KYYCCEQEBndMsmj7drOWQ=="], + "expo-build-properties": ["expo-build-properties@1.0.10", "", { "dependencies": { "ajv": "^8.11.0", "semver": "^7.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-mFCZbrbrv0AP5RB151tAoRzwRJelqM7bCJzCkxpu+owOyH+p/rFC/q7H5q8B9EpVWj8etaIuszR+gKwohpmu1Q=="], + "expo-clipboard": ["expo-clipboard@8.0.8", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-VKoBkHIpZZDJTB0jRO4/PZskHdMNOEz3P/41tmM6fDuODMpqhvyWK053X0ebspkxiawJX9lX33JXHBCvVsTTOA=="], "expo-constants": ["expo-constants@18.0.13", "", { "dependencies": { "@expo/config": "~12.0.13", "@expo/env": "~2.0.8" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-FnZn12E1dRYKDHlAdIyNFhBurKTS3F9CrfrBDJI5m3D7U17KBHMQ6JEfYlSj7LG7t+Ulr+IKaj58L1k5gBwTcQ=="], diff --git a/packages/mobile/app.json b/packages/mobile/app.json index 80d7abef6cb..7dfdb566bdb 100644 --- a/packages/mobile/app.json +++ b/packages/mobile/app.json @@ -24,13 +24,31 @@ "web": { "favicon": "./assets/favicon.png" }, - "plugins": ["expo-router", "expo-secure-store", "expo-local-authentication"], + "plugins": [ + "expo-router", + "expo-secure-store", + "expo-local-authentication", + [ + "expo-build-properties", + { + "android": { + "usesCleartextTraffic": true + } + } + ] + ], "experiments": { "typedRoutes": true, "reactCompiler": true }, "ios": { - "bundleIdentifier": "ai.opencode.mobile" + "bundleIdentifier": "ai.opencode.mobile", + "infoPlist": { + "NSAppTransportSecurity": { + "NSAllowsArbitraryLoads": true, + "NSAllowsLocalNetworking": true + } + } } } } diff --git a/packages/mobile/app/session/[id].tsx b/packages/mobile/app/session/[id].tsx index 578c64222f4..ee3d0d8a2d6 100644 --- a/packages/mobile/app/session/[id].tsx +++ b/packages/mobile/app/session/[id].tsx @@ -33,7 +33,6 @@ import { type Attachment, } from "../../src/components/chat" import { AtPopover, type FileItem } from "../../src/components/chat/AtPopover" -import { FileContextPills, type FileContextItem } from "../../src/components/chat/FileContextPills" import { useSessions } from "../../src/stores/sessions" import { useEvents, refreshPending } from "../../src/stores/events" import { useConnections } from "../../src/stores/connections" @@ -166,7 +165,6 @@ export default function SessionScreen() { const [atQuery, setAtQuery] = useState("") const [atResults, setAtResults] = useState([]) const [atLoading, setAtLoading] = useState(false) - const [fileContext, setFileContext] = useState([]) const atControllerRef = useRef(null) const allCommands = useMemo(() => { @@ -302,27 +300,16 @@ export default function SessionScreen() { // @ file selection handler const handleAtSelect = useCallback( (file: FileItem) => { - // Remove the @query from input - const newInput = input.replace(/@(\S*)$/, "") + // Replace the @query with @filepath + space + const newInput = input.replace(/@(\S*)$/, `@${file.display} `) setInput(newInput) - // Add to file context if not already there - setFileContext((prev) => { - const exists = prev.some((f) => f.path === file.path) - if (exists) return prev - return [...prev, { path: file.path, display: file.display }] - }) - // Close popover setAtActive(false) }, [input], ) - const removeFileContext = useCallback((index: number) => { - setFileContext((prev) => prev.filter((_, i) => i !== index)) - }, []) - // --- Image picking --- // Convert any image (including HEIC/HEIF from iOS) to guaranteed JPEG bytes @@ -410,16 +397,22 @@ export default function SessionScreen() { // --- Send --- const handleSend = async () => { - if (!input.trim() && attachments.length === 0 && fileContext.length === 0) return + if (!input.trim() && attachments.length === 0) return const authenticated = await authenticateForMessage() if (!authenticated) return const text = input.trim() const files = [...attachments] - const context = [...fileContext] + + // Parse @filepath mentions from input text + const atMatches = text.match(/@(\S+)/g) || [] + const context = atMatches.map((match) => ({ + path: match.slice(1), // Remove @ prefix + display: match.slice(1), + })) + setInput("") setAttachments([]) - setFileContext([]) // Server slash commands (no attachments for commands) if (text.startsWith("/") && files.length === 0 && context.length === 0) { @@ -697,9 +690,6 @@ export default function SessionScreen() { {/* Attachment preview */} - {/* File context pills */} - - {/* Input */} void -} - -export function FileContextPills({ files, onRemove }: Props) { - const { colors } = useTheme() - - if (files.length === 0) return null - - return ( - - - {files.map((file, idx) => { - // Extract filename from path - const filename = file.display.split("/").pop() || file.display - - // Detect if this is a directory (ends with /) - const isDirectory = file.display.endsWith("/") - const icon = isDirectory ? "folder-outline" : "document-outline" - - return ( - onRemove(idx)}> - {filename} - - ) - })} - - - ) -} - -const styles = StyleSheet.create({ - container: { - paddingHorizontal: 12, - paddingVertical: 8, - borderTopWidth: 1, - }, - scroll: { - gap: 8, - }, -})