Skip to content

Commit 60ab98c

Browse files
grichaclaude
andcommitted
Redesign mobile app with improved navigation and features
- Replace tab navigation with stack-only navigation - Add HomeScreen with workspace list and host access toggle - Add WorkspaceDetailScreen showing sessions grouped by date - Add SessionChatScreen with tool call display matching web UI - Add TerminalScreen using ghostty-web via WebView - Add WorkspaceSettingsScreen for workspace-specific actions - Support host machine access with amber-colored UI elements - Add agent selection (Claude Code/OpenCode/Codex) for new chats - Implement message pagination with "Load older messages" - Display tool calls with expandable input/result sections - Show workspace name and agent type in chat header 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent c497e36 commit 60ab98c

File tree

11 files changed

+2297
-1452
lines changed

11 files changed

+2297
-1452
lines changed

mobile/bun.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mobile/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
"react": "19.1.0",
3030
"react-native": "0.81.5",
3131
"react-native-safe-area-context": "^5.6.2",
32-
"react-native-screens": "^4.19.0"
32+
"react-native-screens": "^4.19.0",
33+
"react-native-webview": "13.15.0"
3334
},
3435
"devDependencies": {
3536
"@types/react": "~19.1.0",

mobile/src/lib/api.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ export interface InfoResponse {
2121
dockerVersion: string
2222
}
2323

24+
export interface HostInfo {
25+
enabled: boolean
26+
hostname: string
27+
username: string
28+
homeDir: string
29+
}
30+
31+
export const HOST_WORKSPACE_NAME = '@host'
32+
2433
export interface CreateWorkspaceRequest {
2534
name: string
2635
clone?: string
@@ -146,9 +155,13 @@ function createClient() {
146155
limit?: number
147156
offset?: number
148157
}) => Promise<{ sessions: (SessionInfo & { workspaceName: string })[]; total: number; hasMore: boolean }>
149-
get: (input: { workspaceName: string; sessionId: string; agentType?: AgentType }) => Promise<SessionDetail>
158+
get: (input: { workspaceName: string; sessionId: string; agentType?: AgentType; limit?: number; offset?: number }) => Promise<SessionDetail & { total: number; hasMore: boolean }>
150159
}
151160
info: () => Promise<InfoResponse>
161+
host: {
162+
info: () => Promise<HostInfo>
163+
updateAccess: (input: { enabled: boolean }) => Promise<HostInfo>
164+
}
152165
config: {
153166
credentials: {
154167
get: () => Promise<Credentials>
@@ -182,6 +195,17 @@ export interface SessionInfoWithWorkspace extends SessionInfo {
182195
workspaceName: string
183196
}
184197

198+
export function getTerminalUrl(workspaceName: string): string {
199+
const wsUrl = baseUrl.replace(/^http/, 'ws')
200+
return `${wsUrl}/rpc/terminal/${encodeURIComponent(workspaceName)}`
201+
}
202+
203+
export function getChatUrl(workspaceName: string, agentType: AgentType = 'claude-code'): string {
204+
const wsUrl = baseUrl.replace(/^http/, 'ws')
205+
const endpoint = agentType === 'opencode' ? 'opencode' : 'chat'
206+
return `${wsUrl}/rpc/${endpoint}/${encodeURIComponent(workspaceName)}`
207+
}
208+
185209
export const api = {
186210
listWorkspaces: () => client.workspaces.list(),
187211
getWorkspace: (name: string) => client.workspaces.get({ name }),
@@ -196,9 +220,11 @@ export const api = {
196220
client.sessions.list({ workspaceName, agentType, limit, offset }),
197221
listAllSessions: (agentType?: AgentType, limit?: number, offset?: number) =>
198222
client.sessions.listAll({ agentType, limit, offset }),
199-
getSession: (workspaceName: string, sessionId: string, agentType?: AgentType) =>
200-
client.sessions.get({ workspaceName, sessionId, agentType }),
223+
getSession: (workspaceName: string, sessionId: string, agentType?: AgentType, limit?: number, offset?: number) =>
224+
client.sessions.get({ workspaceName, sessionId, agentType, limit, offset }),
201225
getInfo: () => client.info(),
226+
getHostInfo: () => client.host.info(),
227+
updateHostAccess: (enabled: boolean) => client.host.updateAccess({ enabled }),
202228
getCredentials: () => client.config.credentials.get(),
203229
updateCredentials: (data: Credentials) => client.config.credentials.update(data),
204230
getScripts: () => client.config.scripts.get(),
Lines changed: 17 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,27 @@
1-
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
21
import { createNativeStackNavigator } from '@react-navigation/native-stack'
3-
import { View, Text, StyleSheet } from 'react-native'
4-
import { WorkspacesScreen } from '../screens/WorkspacesScreen'
2+
import { HomeScreen } from '../screens/HomeScreen'
53
import { WorkspaceDetailScreen } from '../screens/WorkspaceDetailScreen'
6-
import { SessionsScreen } from '../screens/SessionsScreen'
7-
import { SessionDetailScreen } from '../screens/SessionDetailScreen'
4+
import { SessionChatScreen } from '../screens/SessionChatScreen'
5+
import { TerminalScreen } from '../screens/TerminalScreen'
86
import { SettingsScreen } from '../screens/SettingsScreen'
7+
import { WorkspaceSettingsScreen } from '../screens/WorkspaceSettingsScreen'
98

10-
const Tab = createBottomTabNavigator()
11-
const WorkspacesStack = createNativeStackNavigator()
12-
const SessionsStack = createNativeStackNavigator()
13-
14-
function TabIcon({ name, focused }: { name: string; focused: boolean }) {
15-
const icons: Record<string, string> = {
16-
WorkspacesTab: '▣',
17-
SessionsTab: '◈',
18-
Settings: '⚙',
19-
}
20-
return (
21-
<View style={styles.iconContainer}>
22-
<Text style={[styles.icon, focused && styles.iconFocused]}>{icons[name] || '?'}</Text>
23-
</View>
24-
)
25-
}
26-
27-
function WorkspacesStackNavigator() {
28-
return (
29-
<WorkspacesStack.Navigator screenOptions={{ headerShown: false }}>
30-
<WorkspacesStack.Screen name="WorkspacesList" component={WorkspacesScreen} />
31-
<WorkspacesStack.Screen name="WorkspaceDetail" component={WorkspaceDetailScreen} />
32-
<WorkspacesStack.Screen name="SessionDetail" component={SessionDetailScreen} />
33-
</WorkspacesStack.Navigator>
34-
)
35-
}
36-
37-
function SessionsStackNavigator() {
38-
return (
39-
<SessionsStack.Navigator screenOptions={{ headerShown: false }}>
40-
<SessionsStack.Screen name="SessionsList" component={SessionsScreen} />
41-
<SessionsStack.Screen name="SessionDetail" component={SessionDetailScreen} />
42-
</SessionsStack.Navigator>
43-
)
44-
}
9+
const Stack = createNativeStackNavigator()
4510

4611
export function TabNavigator() {
4712
return (
48-
<Tab.Navigator
49-
screenOptions={({ route }) => ({
50-
tabBarIcon: ({ focused }) => <TabIcon name={route.name} focused={focused} />,
51-
tabBarActiveTintColor: '#0a84ff',
52-
tabBarInactiveTintColor: '#8e8e93',
53-
headerStyle: {
54-
backgroundColor: '#000',
55-
borderBottomWidth: 1,
56-
borderBottomColor: '#1c1c1e',
57-
},
58-
headerTintColor: '#fff',
59-
tabBarStyle: {
60-
backgroundColor: '#000',
61-
borderTopColor: '#1c1c1e',
62-
},
63-
})}
13+
<Stack.Navigator
14+
screenOptions={{
15+
headerShown: false,
16+
contentStyle: { backgroundColor: '#000' },
17+
}}
6418
>
65-
<Tab.Screen
66-
name="WorkspacesTab"
67-
component={WorkspacesStackNavigator}
68-
options={{ title: 'Workspaces', headerShown: false }}
69-
/>
70-
<Tab.Screen
71-
name="SessionsTab"
72-
component={SessionsStackNavigator}
73-
options={{ title: 'Sessions', headerShown: false }}
74-
/>
75-
<Tab.Screen name="Settings" component={SettingsScreen} />
76-
</Tab.Navigator>
19+
<Stack.Screen name="Home" component={HomeScreen} />
20+
<Stack.Screen name="Settings" component={SettingsScreen} />
21+
<Stack.Screen name="WorkspaceDetail" component={WorkspaceDetailScreen} />
22+
<Stack.Screen name="WorkspaceSettings" component={WorkspaceSettingsScreen} />
23+
<Stack.Screen name="SessionChat" component={SessionChatScreen} />
24+
<Stack.Screen name="Terminal" component={TerminalScreen} />
25+
</Stack.Navigator>
7726
)
7827
}
79-
80-
const styles = StyleSheet.create({
81-
iconContainer: {
82-
width: 28,
83-
height: 28,
84-
alignItems: 'center',
85-
justifyContent: 'center',
86-
},
87-
icon: {
88-
fontSize: 18,
89-
fontWeight: '600',
90-
color: '#8e8e93',
91-
},
92-
iconFocused: {
93-
color: '#0a84ff',
94-
},
95-
})

0 commit comments

Comments
 (0)