Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,127 changes: 1,098 additions & 29 deletions bun.lock

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"packages/*",
"packages/console/*",
"packages/sdk/js",
"packages/slack"
"packages/slack",
"packages/mobile"
],
"catalog": {
"@types/bun": "1.3.4",
Expand Down Expand Up @@ -92,7 +93,9 @@
],
"overrides": {
"@types/bun": "catalog:",
"@types/node": "catalog:"
"@types/node": "catalog:",
"@types/react": "~19.1.10",
"react-native": "0.81.5"
},
"patchedDependencies": {
"[email protected]": "patches/[email protected]",
Expand Down
41 changes: 41 additions & 0 deletions packages/mobile/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files

# dependencies
node_modules/

# Expo
.expo/
dist/
web-build/
expo-env.d.ts

# Native
.kotlin/
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision

# Metro
.metro-health-check*

# debug
npm-debug.*
yarn-debug.*
yarn-error.*

# macOS
.DS_Store
*.pem

# local env files
.env*.local

# typescript
*.tsbuildinfo

# generated native folders
/ios
/android
146 changes: 146 additions & 0 deletions packages/mobile/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# OpenPad

A mobile client for [OpenCode](https://opencode.ai) - the AI coding assistant. Built with Expo and React Native.

## Features

- Connect to any OpenCode server
- View and manage chat sessions
- Send messages and receive AI responses in real-time
- View tool usage (file reads, edits, bash commands, etc.)
- Expandable tool blocks with input/output details
- Image attachment support
- Dark/light theme support
- Live polling for real-time updates

## Screenshots

*Coming soon*

## Requirements

- Bun 1.3+
- iOS Simulator (Mac) or Android Emulator, or physical device with Expo Go

## Installation

This package is part of the shuvcode monorepo. From the repository root:

```bash
bun install
```

Start the development server:

```bash
bun run --cwd packages/mobile start
```

4. Run on your device:
- **iOS Simulator**: Press `i` in the terminal
- **Android Emulator**: Press `a` in the terminal
- **Physical Device**: Scan the QR code with Expo Go app

## Connecting to OpenCode

1. Make sure you have an OpenCode server running (default: `http://localhost:9034`)
2. If running on a physical device, use your machine's local IP address instead of `localhost`
3. Enter the server URL in the connect screen and tap "Connect"

## Project Structure

```
openpad/
├── App.tsx # Main app entry point with navigation
├── src/
│ ├── components/ # Reusable UI components
│ │ ├── GlassCard.tsx # Glass morphism card component
│ │ ├── Icon.tsx # Icon wrapper for lucide-react-native
│ │ └── Markdown.tsx # Markdown renderer for messages
│ ├── hooks/ # Custom React hooks
│ │ ├── useOpenCode.ts # OpenCode SDK hook (legacy)
│ │ └── useTheme.ts # Theme hook for dark/light mode
│ ├── providers/ # Context providers
│ │ └── OpenCodeProvider.tsx # OpenCode client & state management
│ ├── screens/ # App screens
│ │ ├── ChatScreen.tsx # Chat conversation view
│ │ ├── ConnectScreen.tsx # Server connection screen
│ │ ├── SessionsScreen.tsx # Sessions list
│ │ └── SettingsScreen.tsx # App settings
│ └── theme/ # Theme configuration
│ └── index.ts # Colors, typography, spacing
├── assets/ # App icons and images
└── package.json
```

## Tech Stack

- **Expo SDK 54** - React Native development platform
- **React Navigation** - Native stack navigation
- **@opencode-ai/sdk** - OpenCode API client
- **lucide-react-native** - Icon library
- **expo-blur** - Blur effects for iOS
- **react-native-markdown-display** - Markdown rendering

## Contributing

We welcome contributions! Here's how you can help:

### Getting Started

1. Fork the repository
2. Create a feature branch: `git checkout -b feature/my-feature`
3. Make your changes
4. Run TypeScript check: `bun run typecheck`
5. Commit your changes: `git commit -m "feat: Add my feature"`
6. Push to your fork: `git push origin feature/my-feature`
7. Open a Pull Request

### Commit Convention

We follow [Conventional Commits](https://www.conventionalcommits.org/):

- `feat:` - New features
- `fix:` - Bug fixes
- `docs:` - Documentation changes
- `style:` - Code style changes (formatting, etc.)
- `refactor:` - Code refactoring
- `test:` - Adding or updating tests
- `chore:` - Maintenance tasks

### Code Style

- Use TypeScript for all new code
- Follow existing patterns in the codebase
- Use functional components with hooks
- Keep components small and focused
- Use the theme system for colors and spacing

### Areas for Contribution

- UI/UX improvements
- New features (voice input, image upload, etc.)
- Performance optimizations
- Bug fixes
- Documentation improvements
- Tests

### Reporting Issues

Found a bug or have a feature request? [Open an issue](https://github.com/R44VC0RP/openpad/issues) with:

- Clear description of the problem or feature
- Steps to reproduce (for bugs)
- Expected vs actual behavior
- Screenshots if applicable
- Device/OS information

## License

MIT

## Acknowledgments

- [OpenCode](https://opencode.ai) - The AI coding assistant this app connects to
- [Expo](https://expo.dev) - React Native development platform
- [Lucide](https://lucide.dev) - Beautiful icons
37 changes: 37 additions & 0 deletions packages/mobile/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"expo": {
"name": "OpenPad",
"slug": "openpad",
"version": "1.0.0",
"scheme": "openpad",
"orientation": "default",
"icon": "./assets/icon.png",
"userInterfaceStyle": "automatic",
"newArchEnabled": true,
"splash": {
"image": "./assets/splash-icon.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.openpad.app"
},
"android": {
"package": "com.openpad.app",
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"edgeToEdgeEnabled": true,
"predictiveBackGestureEnabled": false
},
"web": {
"favicon": "./assets/favicon.png",
"bundler": "metro"
},
"plugins": [
"expo-router"
]
}
}
45 changes: 45 additions & 0 deletions packages/mobile/app/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Platform } from 'react-native';
import { NativeTabs, Icon, Label, VectorIcon } from 'expo-router/unstable-native-tabs';
import { useTheme } from '../../src/hooks/useTheme';
import { Ionicons } from '@expo/vector-icons';

export default function TabLayout() {
const { isDark } = useTheme();

const labelColor = Platform.OS === 'ios'
? { color: { dark: 'white', light: 'black' } as any }
: isDark ? 'white' : 'black';

const tintColor = Platform.OS === 'ios'
? { color: { dark: '#22d3ee', light: '#0891b2' } as any }
: isDark ? '#22d3ee' : '#0891b2';

return (
<NativeTabs
// iOS 26 Liquid Glass features
minimizeBehavior="onScrollDown"
disableTransparentOnScrollEdge={true}
// Styling for liquid glass color adaptation
labelStyle={{
color: labelColor as any,
}}
tintColor={tintColor as any}
>
<NativeTabs.Trigger name="sessions">
<Icon
sf={{ default: 'bubble.left', selected: 'bubble.left.fill' }}
src={<VectorIcon family={Ionicons} name="chatbubble-outline" /> as any}
/>
<Label>Sessions</Label>
</NativeTabs.Trigger>

<NativeTabs.Trigger name="settings">
<Icon
sf={{ default: 'gear', selected: 'gear' }}
src={<VectorIcon family={Ionicons} name="settings-outline" /> as any}
/>
<Label>Settings</Label>
</NativeTabs.Trigger>
</NativeTabs>
);
}
9 changes: 9 additions & 0 deletions packages/mobile/app/(tabs)/sessions/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Stack } from 'expo-router';

export default function SessionsLayout() {
return (
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="index" />
</Stack>
);
}
38 changes: 38 additions & 0 deletions packages/mobile/app/(tabs)/sessions/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useCallback } from 'react';
import { useRouter } from 'expo-router';
import { SessionsScreen } from '../../../src/screens/SessionsScreen';
import { useOpenCode, Session } from '../../../src/providers/OpenCodeProvider';

export default function Sessions() {
const router = useRouter();
const {
sessions,
sessionsLoading,
sessionsRefreshing,
refreshSessions,
createSession,
} = useOpenCode();

const handleCreateSession = useCallback(async () => {
const session = await createSession();
if (session) {
router.push(`/chat/${session.id}`);
}
}, [createSession, router]);

const handleSelectSession = useCallback((session: Session) => {
// Navigate to chat screen outside of tabs (hides tab bar)
router.push(`/chat/${session.id}`);
}, [router]);

return (
<SessionsScreen
sessions={sessions}
loading={sessionsLoading}
refreshing={sessionsRefreshing}
onRefresh={refreshSessions}
onSelectSession={handleSelectSession}
onCreateSession={handleCreateSession}
/>
);
}
21 changes: 21 additions & 0 deletions packages/mobile/app/(tabs)/settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useCallback } from 'react';
import { useRouter } from 'expo-router';
import { SettingsScreen } from '../../src/screens/SettingsScreen';
import { useOpenCode } from '../../src/providers/OpenCodeProvider';

export default function Settings() {
const router = useRouter();
const { serverUrl, disconnect } = useOpenCode();

const handleDisconnect = useCallback(() => {
disconnect();
router.replace('/connect');
}, [disconnect, router]);

return (
<SettingsScreen
serverUrl={serverUrl}
onDisconnect={handleDisconnect}
/>
);
}
Loading