Skip to content
Merged
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
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Environment variables for mux development

# Optional bearer token for mux server HTTP/WS auth
# When set, clients must include:
# - HTTP: Authorization: Bearer $MUX_SERVER_AUTH_TOKEN
# - WebSocket: ws://host:port/ws?token=$MUX_SERVER_AUTH_TOKEN (recommended for Expo)
# - or Sec-WebSocket-Protocol header with the token value
MUX_SERVER_AUTH_TOKEN=

# API Keys for AI providers
# Required for integration tests when TEST_INTEGRATION=1
ANTHROPIC_API_KEY=sk-ant-...
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,4 @@ test_hot_reload.sh
# mdBook auto-generated assets
docs/theme/pagetoc.css
docs/theme/pagetoc.js
mobile/.expo/
12 changes: 11 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ include fmt.mk

.PHONY: all build dev start clean help
.PHONY: build-renderer version build-icons build-static
.PHONY: lint lint-fix typecheck static-check
.PHONY: lint lint-fix typecheck typecheck-react-native static-check
.PHONY: test test-unit test-integration test-watch test-coverage test-e2e smoke-test
.PHONY: dist dist-mac dist-win dist-linux
.PHONY: vscode-ext vscode-ext-install
Expand Down Expand Up @@ -92,6 +92,12 @@ node_modules/.installed: package.json bun.lock
@bun install
@touch node_modules/.installed

# Mobile dependencies - separate from main project
mobile/node_modules/.installed: mobile/package.json mobile/bun.lock
@echo "Installing mobile dependencies..."
@cd mobile && bun install
@touch mobile/node_modules/.installed

# Legacy target for backwards compatibility
ensure-deps: node_modules/.installed

Expand Down Expand Up @@ -228,6 +234,10 @@ typecheck: node_modules/.installed src/version.ts
"$(TSGO) --noEmit -p tsconfig.main.json"
endif

typecheck-react-native: mobile/node_modules/.installed ## Run TypeScript type checking for React Native app
@echo "Type checking React Native app..."
@cd mobile && bunx tsc --noEmit

check-deadcode: node_modules/.installed ## Check for potential dead code (manual only, not in static-check)
@echo "Checking for potential dead code with ts-prune..."
@echo "(Note: Some unused exports are legitimate - types, public APIs, entry points, etc.)"
Expand Down
2 changes: 1 addition & 1 deletion fmt.mk
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
.PHONY: fmt fmt-check fmt-prettier fmt-prettier-check fmt-shell fmt-shell-check fmt-nix fmt-nix-check fmt-python fmt-python-check

# Centralized patterns - single source of truth
PRETTIER_PATTERNS := 'src/**/*.{ts,tsx,json}' 'tests/**/*.ts' 'docs/**/*.md' 'package.json' 'tsconfig*.json' 'README.md'
PRETTIER_PATTERNS := 'src/**/*.{ts,tsx,json}' 'mobile/**/*.{ts,tsx,json}' 'tests/**/*.ts' 'docs/**/*.md' 'package.json' 'tsconfig*.json' 'README.md'
SHELL_SCRIPTS := scripts
PYTHON_DIRS := benchmarks

Expand Down
9 changes: 9 additions & 0 deletions mobile/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.expo
ios/
android/

# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
# The following patterns were generated by expo-cli

expo-env.d.ts
# @end expo-cli
100 changes: 100 additions & 0 deletions mobile/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# mux Mobile App

Expo React Native app for mux - connects to mux server over HTTP/WebSocket.

## Requirements

- **Expo SDK 54** with **React Native 0.81**
- Node.js 20.19.4+
- For iOS: Xcode 16+ (for iOS 26 SDK)
- For Android: Android API 36+

## Development

### Quick Start (Expo Go)

**Note**: Expo Go on SDK 54 has limitations with native modules. For full functionality, use a development build (see below).

```bash
cd mobile
bun install
bun start
```

Scan the QR code in Expo Go (must be SDK 54).

### Development Build (Recommended)

For full native module support:

```bash
cd mobile
bun install

# iOS
bunx expo run:ios

# Android
bunx expo run:android
```

This creates a custom development build with all necessary native modules baked in.

## Configuration

Edit `app.json` to set your server URL and auth token:

```json
{
"expo": {
"extra": {
"mux": {
"baseUrl": "http://<your-tailscale-ip>:3000",
"authToken": "your_token_here"
}
}
}
}
```

## Server Setup

Start the mux server with auth (optional):

```bash
# In the main mux repo
MUX_SERVER_AUTH_TOKEN=your_token make dev-server BACKEND_HOST=0.0.0.0 BACKEND_PORT=3000
```

The mobile app will:

- Call APIs via POST `/ipc/<channel>` with `Authorization: Bearer <token>`
- Subscribe to workspace events via WebSocket `/ws?token=<token>`

## Features

- Real-time chat interface with streaming responses
- **Message editing**: Long press user messages to edit (truncates history after edited message)
- Provider configuration (Anthropic, OpenAI, etc.)
- Project and workspace management
- Secure credential storage

## Architecture

- **expo-router** for file-based routing
- **@tanstack/react-query** for server state
- **WebSocket** for live chat streaming
- Thin fetch/WS client in `src/api/client.ts`

## Troubleshooting

**"TurboModuleRegistry" errors in Expo Go**: This happens because Expo Go SDK 54 doesn't include all native modules. Build a development build instead:

```bash
bunx expo prebuild --clean
bunx expo run:ios # or run:android
```

**Version mismatch**: Ensure Expo Go is SDK 54 (check App Store/Play Store for latest).

**Connection refused**: Make sure the mux server is running and accessible from your device (use your machine's Tailscale IP or local network IP, not `localhost`).
30 changes: 30 additions & 0 deletions mobile/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"expo": {
"name": "mux-mobile",
"slug": "mux-mobile",
"version": "0.0.1",
"scheme": "mux",
"orientation": "portrait",
"platforms": ["ios", "android"],
"newArchEnabled": true,
"jsEngine": "hermes",
"experiments": {
"typedRoutes": true
},
"extra": {
"mux": {},
"router": {},
"eas": {
"projectId": "6e2245c1-ec09-44ef-9b08-a6ba165d8496"
}
},
"plugins": ["expo-router", "expo-secure-store"],
"ios": {
"bundleIdentifier": "com.coder.mux-mobile"
},
"owner": "coder-technologies",
"android": {
"package": "com.coder.muxmobile"
}
}
}
85 changes: 85 additions & 0 deletions mobile/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import type { JSX } from "react";
import { Stack } from "expo-router";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useMemo } from "react";
import { SafeAreaProvider } from "react-native-safe-area-context";
import { StatusBar } from "expo-status-bar";
import { View } from "react-native";
import { ThemeProvider, useTheme } from "../src/theme";
import { WorkspaceChatProvider } from "../src/contexts/WorkspaceChatContext";
import { AppConfigProvider } from "../src/contexts/AppConfigContext";

function AppFrame(): JSX.Element {
const theme = useTheme();

return (
<>
<StatusBar style={theme.statusBarStyle} animated />
<View style={{ flex: 1, backgroundColor: theme.colors.background }}>
<Stack
screenOptions={{
headerStyle: { backgroundColor: theme.colors.surfaceSunken },
headerTintColor: theme.colors.foregroundPrimary,
headerTitleStyle: {
fontWeight: theme.typography.weights.semibold as any,
fontSize: theme.typography.sizes.titleSmall,
color: theme.colors.foregroundPrimary,
},
headerShadowVisible: false,
contentStyle: { backgroundColor: theme.colors.background },
}}
>
<Stack.Screen
name="index"
options={{
title: "Workspaces",
}}
/>
<Stack.Screen
name="workspace/[id]"
options={{
title: "", // Title set dynamically by WorkspaceScreen
headerBackTitle: "", // Just show <, no text
}}
/>
<Stack.Screen
name="settings"
options={{
title: "Settings",
headerBackTitle: "", // Just show <, no text
}}
/>
</Stack>
</View>
</>
);
}

export default function RootLayout(): JSX.Element {
const client = useMemo(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 30_000,
refetchOnWindowFocus: false,
},
},
}),
[]
);

return (
<QueryClientProvider client={client}>
<SafeAreaProvider>
<ThemeProvider>
<AppConfigProvider>
<WorkspaceChatProvider>
<AppFrame />
</WorkspaceChatProvider>
</AppConfigProvider>
</ThemeProvider>
</SafeAreaProvider>
</QueryClientProvider>
);
}
28 changes: 28 additions & 0 deletions mobile/app/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { JSX } from "react";
import { Stack, useRouter } from "expo-router";
import { Pressable } from "react-native";
import { Ionicons } from "@expo/vector-icons";
import ProjectsScreen from "../src/screens/ProjectsScreen";

export default function ProjectsRoute(): JSX.Element {
const router = useRouter();

return (
<>
<Stack.Screen
options={{
headerRight: () => (
<Pressable
onPress={() => router.push("/settings")}
style={{ paddingHorizontal: 12 }}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>
<Ionicons name="settings-outline" size={24} color="#fff" />
</Pressable>
),
}}
/>
<ProjectsScreen />
</>
);
}
Loading