-
Notifications
You must be signed in to change notification settings - Fork 27
🤖 feat: add React Native mobile client and server bridge #657
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
180cb55 to
9a74ebd
Compare
|
@codex review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
898d37b to
08d24d4
Compare
|
@codex review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
5d20e02 to
e7d1233
Compare
|
@codex review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
|
Codex Review: Didn't find any major issues. 🎉 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
68bfc69 to
da3e3c2
Compare
🤖 feat: Add React Native mobile app with Expo
- Create apps/mobile directory with Expo Router setup
- Implement Projects screen with workspace list
- Implement Workspace screen with chat interface
- Add WebSocket-based real-time chat synchronization
- Support Plan/Exec mode toggles and Reasoning level control
- Add theming system with colors, typography, spacing
- Implement message rendering with proper type handling
- Add server-mode support to main process for mobile connectivity
- Include auth token support via query parameters
_Generated with cmux_
Change-Id: I6597d24921e21a6d6670807a237b8b9b603d929c
Signed-off-by: Test <test@example.com>
🤖 refactor: Move mode & reasoning controls to Settings in mobile app
- Create useWorkspaceDefaults hook for persistent mode and reasoning settings
- Add Workspace Defaults section to Settings screen with:
- Default Execution Mode toggle (Plan/Exec)
- Default Reasoning Level slider (Off/Low/Medium/High)
- Remove mode toggles and ReasoningControl from WorkspaceScreen header
- Add settings icon to workspace header for easy access
- Initialize workspace with defaults from settings
Benefits:
- Frees ~140dp of vertical space for chat messages
- Follows mobile UX pattern of infrequent config in Settings
- Messages now start at 100dp from top instead of 240dp
- Settings persist across all workspaces via SecureStore
Net change: +116 LoC (hook), +180 LoC (settings), -70 LoC (workspace)
_Generated with cmux_
Change-Id: I475804c7ad564829b2658b08718b7b7aea5a5265
Signed-off-by: Test <test@example.com>
🤖 fix: Sanitize workspace IDs for SecureStore key compatibility
- Add sanitizeWorkspaceId() to replace invalid characters with underscores
- SecureStore keys must only contain alphanumeric, '.', '-', and '_'
- Change key delimiter from ':' to '.' for consistency
- Remove unused ThinkingProvider/useThinkingLevel from WorkspaceScreen
(reasoning level now comes from useWorkspaceDefaults)
Fixes: Failed to read thinking level [Error: Invalid key provided to SecureStore]
_Generated with cmux_
Change-Id: I5365806bfddc9950d14ca09278a762403f143bab
Signed-off-by: Test <test@example.com>
🤖 refactor: Redesign workspace screen for messaging app UX
- Simplify header to show 'project › workspace' instead of multi-line layout
- Remove Surface containers around chat and input areas for full-width display
- Style input like iMessage/WhatsApp:
- Rounded pill input (borderRadius: 20)
- Circular send button with arrow-up icon
- Button disabled when input is empty (grayed out)
- Compact sizing (38x38 button, minHeight: 38 input)
- Add proper borders and backgrounds for visual separation
- Save additional ~50dp of vertical space from simplified header
Result: Clean messaging app feel with maximized screen real estate for chat.
_Generated with cmux_
Change-Id: I58a979b4117c0ccb176cf250dd80d024387bbb20
Signed-off-by: Test <test@example.com>
🤖 fix: Add surfaceSecondary color and remove Surface component usage
- Add surfaceSecondary color to theme for header/footer backgrounds
- Replace Surface component with View in TimelineRow fallback
- Fixes TypeScript errors from removed Surface import
_Generated with cmux_
Change-Id: I39bce5ff5f2125c52e3f90371bfac45b8df0fd7e
Signed-off-by: Test <test@example.com>
🤖 fix: Add safe area padding for iOS home indicator
- Import and use useSafeAreaInsets from react-native-safe-area-context
- Apply bottom inset to input area: paddingBottom = max(spacing.sm, insets.bottom)
- Prevents send button from colliding with iOS swipe-up bar
- Ensures minimum padding of spacing.sm on devices without notch
_Generated with cmux_
Change-Id: Icc613ae56f3c86e7f9ae050007110b8161db3fce
Signed-off-by: Test <test@example.com>
🤖 feat: Auto-scroll to latest message when entering chat
- Add FlatList ref for programmatic scrolling
- Scroll to bottom on initial load when timeline has messages
- Auto-scroll when new messages arrive (onContentSizeChange)
- Use 100ms delay on mount to ensure layout is complete
- Animated scroll for new messages, instant scroll on initial load
Result: Chat always starts at the latest message, not showing entire history.
_Generated with cmux_
Change-Id: I4df172ca29ebb345ce84d16d6981fb369d8d8c4e
Signed-off-by: Test <test@example.com>
🤖 feat: Enable real-time streaming in React Native mobile app
Implement full streaming with real-time message updates (Option 1):
**Message Streaming:**
- Emit partial messages on every stream-delta, reasoning-delta, tool-call events
- Track active streams for real-time emission
- Mark messages as isStreaming:true during streaming
- Update messages in-place using upsert logic (replace by ID)
- Show complete messages with isStreaming:false on stream-end
**Streaming UI Indicators:**
- Add animated pulsing cursor to assistant messages while streaming
- Add streaming cursor to reasoning messages
- Cursor animation: fade between opacity 1.0 ↔ 0.3 every 530ms
**Auto-Scroll Improvements:**
- Initial load: Jump instantly to last message (no animation)
- During chat: Animated scroll to bottom (iMessage/WhatsApp style)
- Track hasInitiallyScrolled to control animation behavior
- Reset scroll tracking when switching workspaces
**Architecture Changes:**
- normalizeChatEvent.ts: Emit on every delta instead of buffering until stream-end
- applyChatEvent: Upsert logic checks for existing message by ID and replaces
- MessageRenderer: Animated streaming cursor component with useNativeDriver
Result: True real-time streaming like desktop app. Messages appear incrementally
as tokens arrive, reasoning streams in real-time, smooth UX with proper animations.
Net change: +120 LoC (streaming logic + cursor component + auto-scroll fixes)
_Generated with cmux_
Change-Id: I5beac5c9e9105f02ef62029f43256565edd0db6c
Signed-off-by: Test <test@example.com>
🤖 fix: Make initial scroll instant when entering workspace
- Replace setTimeout(100ms) with requestAnimationFrame for instant scroll
- Change useEffect dependency from timeline.length to timeline.length > 0
(only triggers once when messages first arrive, not on every update)
- Clear timeline when workspace changes for fresh start
- Ensures no visible animation when opening chat
Result: Chat jumps directly to bottom instantly, no delay or animation on entry.
_Generated with cmux_
Change-Id: Icf2116a1c24f88a0c660d3a92c0723db1b7b5675
Signed-off-by: Test <test@example.com>
🤖 fix: Make scroll instant and fix Metro module resolution
**Instant Scroll Fix:**
- Replace scrollToEnd with scrollToIndex for true instant jump
- scrollToEnd({ animated: false }) still uses default animation duration
- scrollToIndex with animated:false is immediate with no transition
- Use viewPosition:1 to position item at bottom of viewport
**Metro Resolution Fix:**
- Add watchFolders to include parent src directory
- Configure extraNodeModules to resolve @shared alias
- Fixes: Could not resolve @shared/types/message, @shared/types/toolParts
- Allows mobile app to import shared code from parent src/
Result: Opening workspace jumps instantly to bottom. No slow scroll animation.
Expo bundler can now resolve parent directory imports.
_Generated with cmux_
Change-Id: If5850c71b2714ab7eb3b583fecdd304ddcfff62f
Signed-off-by: Test <test@example.com>
🤖 fix: Use inverted FlatList for instant chat scrolling
Replace manual scroll logic with FlatList inverted prop (standard for chat apps):
**Why inverted:**
- FlatList with inverted prop naturally starts at bottom (newest messages)
- No need for scrollToEnd() calls or animation management
- Instantly shows latest messages on mount (0ms, no scroll animation)
- New messages automatically appear at bottom without manual scroll
- Standard pattern used by WhatsApp, Slack, Stream Chat, etc.
**Changes:**
- Add inverted prop to FlatList
- Reverse timeline data array for proper ordering (oldest→newest becomes newest→oldest)
- Remove all scroll tracking code (hasInitiallyScrolledRef)
- Remove scroll effects (initial scroll + onContentSizeChange)
- Simplify workspace change effect (just clear timeline)
Result: Opening workspace shows latest message instantly. No visible scroll
animation or delay. New messages appear at bottom automatically.
Net change: -35 LoC (removed complex scroll management)
_Generated with cmux_
Change-Id: I4322d061980c55b0fd79e038e80a79c4234d060f
Signed-off-by: Test <test@example.com>
🤖 fix: Handle sendMessage response and add dismiss button to errors
**SendMessage Response Fix:**
- Server returns Result type directly: { success: true } or { success: false, error }
- Mobile client was not checking the inner Result, treating all responses as success
- Now properly checks result.success and returns error when false
- Fixes: "Request failed" appearing even when message sends successfully
**Dismissible Error Messages:**
- Add dismiss button (X) to error messages in chat
- Errors are displayed as "raw" timeline entries
- Add onDismiss callback to RawEventCard component
- Add handleDismissRawEvent to filter out dismissed errors by key
- Pass dismiss handler only for raw events (not displayed messages)
- hitSlop={8} for easy tapping on small X button
Result: No more false "Request failed" errors. Error messages can be dismissed.
_Generated with cmux_
Change-Id: I97d2f350d6a9c70d58b258fb9336638007527c3c
Signed-off-by: Test <test@example.com>
🤖 feat: Add special propose_plan tool rendering for mobile
Implement mobile-optimized plan card matching desktop UX:
**New Component: ProposePlanCard (~180 LoC)**
- Purple-themed card with left border (plan mode color #8b5cf6)
- Title with 📋 emoji displayed prominently
- Markdown-rendered plan content in scrollable view (max 400dp)
- Action buttons: Copy to clipboard, Show Text/Markdown toggle
- Copy uses expo-clipboard with 2s feedback ("✓ Copied")
- Footer hint when completed: "Ask to implement in Exec mode"
- Mobile-optimized: Pressable buttons, larger tap targets, scrollable
**MessageRenderer Dispatch Logic (~25 LoC)**
- Add isProposePlanTool() type guard with proper type narrowing
- Dispatch to ProposePlanCard when tool is propose_plan
- Fall back to generic ToolMessageCard for other tools
- Import ProposePlanCard component
**Dependencies:**
- Add expo-clipboard for copy-to-clipboard functionality
**Differences from Desktop:**
- Simplified styling (no complex gradients, just purple tint + border)
- No "Start Here" button (skip complex modal for v1)
- Touch-first interactions (no hover states)
- Scrollable content area for long plans
Result: Plans now render beautifully on mobile with readable markdown,
actionable buttons, and clear visual distinction from other tools.
Net change: +205 LoC (new component + dispatch + dependency)
_Generated with cmux_
Change-Id: Iaf7a2a5c14af6962cb3a53dc665ef2077b49ad92
Signed-off-by: Test <test@example.com>
🤖 feat: Add todo list rendering to React Native mobile app
Implement comprehensive todo list UX matching desktop functionality:
**New Components:**
1. TodoItemView.tsx (~95 LoC) - Shared todo item renderer
- Status icons: ✓ (completed), ⟳ (in_progress), ○ (pending)
- Color-coded by status (green/blue/gray)
- Left border matching status color
- Strikethrough for completed items
- Reusable by both live and historical displays
2. FloatingTodoCard.tsx (~95 LoC) - Live progress indicator
- Appears above input during streaming
- Compact header: "📋 TODO (2/5)" shows progress
- Collapsible (tap chevron to expand/collapse)
- Dismissible (X button to hide)
- Scrollable list (max 150dp height)
- Auto-disappears when stream ends
- Re-appears when new todos arrive
3. TodoToolCard.tsx (~80 LoC) - Historical tool call display
- Shows past todo_write calls in chat
- Collapsed by default (expandable)
- Shows completion progress
- Status badge (✓ Completed, etc.)
- Uses TodoItemView for consistent styling
**Event Tracking (~40 LoC):**
- Listen for tool-call-end events (todo_write)
- Extract todos from event args
- Update currentTodos state
- Clear on stream-end
- Track dismiss state per workspace
**MessageRenderer Dispatch (~30 LoC):**
- Add isTodoWriteTool() type guard
- Route todo_write to TodoToolCard
- Import and integrate components
**Architecture:**
- Event-based tracking (no backend changes needed)
- Real-time updates from WebSocket
- Auto-clears when stream ends
- Matches desktop behavior (live + historical)
**UX Benefits:**
- Live progress visible during streaming
- Clean, compact mobile-optimized design
- User control (collapse/dismiss)
- Historical record in chat
- Color-coded status at a glance
Result: Todo lists now render beautifully on mobile with live progress
indicator and historical tool call display. Full feature parity with desktop.
Net change: +340 LoC (3 new components + integration)
_Generated with cmux_
Change-Id: Ia6855bad58eb2ef91f7d6513ef7d7787b7621604
Signed-off-by: Test <test@example.com>
🤖 fix: Remove back button text, show only arrow (<)
Set headerBackTitle: '' for workspace and settings screens.
Removes 'index' label from back button, shows only < arrow.
Cleaner, more minimal navigation matching iOS standards.
_Generated with cmux_
Change-Id: I6e8d2b67e526b0a782aaefc1c48e9df1155f3f8f
Signed-off-by: Test <test@example.com>
🤖 fix: Add safe area insets to header and restore back button
**Safe Area Insets:**
- Add paddingTop: Math.max(spacing.sm, insets.top) to custom header
- Prevents clash with iOS status bar (time, battery, etc.)
- Uses useSafeAreaInsets() already available in component
**Back Button:**
- Add chevron-back icon button to header
- Uses router.back() to navigate to projects list
- Position: Left side of header before workspace title
- hitSlop={8} for easy tapping
- Icon size 28 for prominence
Result: Header respects iOS status bar and has intuitive back navigation.
_Generated with cmux_
Change-Id: I3943bf4fd53dfb2603a0703bbc278f8444340c02
Signed-off-by: Test <test@example.com>
🤖 fix: Use Expo Router header with proper back navigation
Revert custom header approach and use Expo Router's built-in navigation:
**Changes:**
- Re-enable headerShown for workspace screen (use Expo Router header)
- Set title: 'Workspace' for proper back button reference
- Set headerBackTitle: '' on both workspace and settings (just < arrow)
- Remove custom back button from WorkspaceScreen (use router's)
- Remove safe area inset handling (router header handles it)
**Result:**
- Settings back button now shows '< Workspace' or just '<' (not '< workspace/[id]')
- Expo Router manages navigation context properly
- iOS safe area handled automatically by router
- Consistent navigation UX
Note: Custom workspace info bar (project › workspace + icons) is still below
the router header. This is intentional - router header provides navigation,
custom bar provides workspace context and actions.
_Generated with cmux_
Change-Id: I39902665987e4fb8f2bf0fd5f47f907bf84bf0e5
Signed-off-by: Test <test@example.com>
🤖 fix: Set index screen title to 'Workspaces' for proper back button
Change index screen from headerShown:false to title:'Workspaces'.
This ensures Settings back button shows '<' (or '< Workspaces') instead of '< index'.
Now Expo Router has proper screen titles for navigation context.
_Generated with cmux_
Change-Id: I7ab9e14eb4c364c5643d73049e43843fce9e12e5
Signed-off-by: Test <test@example.com>
🤖 fix: Hide header on Workspaces page to reduce offset
Set headerShown: false for index screen.
ProjectsScreen has its own "Projects" label that should be at the top.
Removes duplicate header that was pushing content down.
Result: Projects label appears higher up, better use of screen space.
_Generated with cmux_
Change-Id: I7aca5cda675edead021138b9d3945d6cbd25fdd6
Signed-off-by: Test <test@example.com>
🤖 fix: Keep Workspaces header and reduce Projects page top padding
**Changes:**
- Restore headerShown for index screen with title: 'Workspaces'
- Reduce ProjectsScreen paddingTop from 'insets.top + spacing.lg' to 'spacing.md'
- Expo Router header already handles safe area, no need for duplicate insets
**Result:**
- Workspaces header is visible with proper title
- Projects label appears higher up (less offset)
- Back buttons work correctly (Workspaces context)
_Generated with cmux_
Change-Id: I265861ea7ccc2d8b39676f03cf85174804df8b6a
Signed-off-by: Test <test@example.com>
🤖 refactor: Move workspace name to Expo Router header
**Changes:**
- Set router header title dynamically to 'project › workspace' using navigation.setOptions()
- Remove workspace name from custom header bar
- Keep only action icons in custom bar (terminal, secrets, settings)
- Align icons to the right (justifyContent: flex-end)
**Result:**
- Expo Router header shows: 'cmux › react-native'
- Custom bar shows only: 🖥️ 🔑 ⚙️ (right-aligned)
- No duplication of workspace name
- Cleaner, more organized layout
_Generated with cmux_
Change-Id: I00082dc5ce9056a586017657c23e41e00a320e6b
Signed-off-by: Test <test@example.com>
🤖 fix: Remove static workspace title to prevent flicker
Set title: '' for workspace screen in router config.
WorkspaceScreen sets the title dynamically via navigation.setOptions().
Prevents flicker from 'Workspace' → 'cmux › react-native'.
Now shows empty title until metadata loads, then smoothly updates to workspace name.
_Generated with cmux_
Change-Id: I972fb1937346ca27e2fda3ebc4312bc15327bd83
Signed-off-by: Test <test@example.com>
🤖 fix: Pass workspace title as route param to prevent flicker
**Changes:**
- Pass title as route param when navigating to workspace
- Set title in workspace/[id].tsx using Stack.Screen with params.title
- Remove useEffect that dynamically updates title (no longer needed)
- Remove useNavigation import (no longer needed)
**Flow:**
1. User taps workspace in list
2. Router navigates with params: { id, title: 'cmux › react-native' }
3. Route file sets Stack.Screen title immediately from params
4. No flicker - title is correct from the start
Result: Clean navigation with proper title from first render. No 'Workspace' flicker.
_Generated with cmux_
Change-Id: Ib2764c1aa7178a56f466738ee51c973c524052f9
Signed-off-by: Test <test@example.com>
🤖 refactor: Remove terminal button from workspace action bar
Remove terminal icon since mobile app doesn't have built-in terminal emulator yet.
Custom action bar now shows only: 🔑 ⚙️
Can be re-added later when terminal functionality is implemented.
_Generated with cmux_
Change-Id: I9182f734cee4d8b116c0ccbf075a9ba433b68cf5
Signed-off-by: Test <test@example.com>
🤖 feat: Add todo list toggle button to workspace action bar
**Changes:**
- Remove terminal button (not implemented yet)
- Add todo list toggle button (list icon)
- Button only appears when todos exist (currentTodos.length > 0)
- Icon changes: 'list' (filled) when visible, 'list-outline' when hidden
- Color changes: accent blue when visible, normal when hidden
- Rename todoCardDismissed to todoCardVisible (clearer intent)
- Toggle button positioned left of secrets icon
**Behavior:**
- Shows when agent creates todos during streaming
- Tap to hide/show todo card
- Auto-shows when new todos arrive
- Persists visibility state until workspace change
**Action bar now shows:**
- When todos exist: 📋 🔑 ⚙️
- When no todos: 🔑 ⚙️
_Generated with cmux_
Change-Id: I2608960c3bd124e058239b63bce3f4fbe87d4585
Signed-off-by: Test <test@example.com>
🤖 fix: Forward delete events to ChatEventProcessor
Fix bug where delete events (from truncation/compaction) were silently ignored.
**Root Cause:**
After refactoring to ChatEventProcessor, message state moved from
StreamingMessageAggregator.messages to processor's internal storage.
But handleDeleteMessage still iterated this.messages (never populated),
so deletions did nothing.
**Fix:**
1. Add deleteByHistorySequence() method to ChatEventProcessor interface
2. Implement in ChatEventProcessor to delete messages by historySequence
3. Update StreamingMessageAggregator.handleDeleteMessage to delegate to processor
4. Remove stale iteration of this.messages
**Result:**
Delete events now properly remove messages from UI. History truncation
and compaction correctly clear messages from chat display.
Fixes review comment P0: Delete events now take effect.
_Generated with cmux_
Change-Id: I27e1bdfcd01c8d0a1569b57842b4aac901933482
Signed-off-by: Test <test@example.com>
🤖 debug: Add logging for todo_write event tracking
Add console.log to debug why todo button might not be appearing.
This will help identify if events are being received correctly.
_Generated with cmux_
Change-Id: I266f2f3d0c8b34e2ccc53d97b386d8e4c693d9bd
Signed-off-by: Test <test@example.com>
🤖 debug: Add verbose event logging to diagnose todo event issue
Log all incoming events to see if tool-call-end is arriving.
This will help identify if:
- Events aren't being sent via WebSocket
- Event structure is different than expected
- Events are being filtered somewhere
_Generated with cmux_
Change-Id: Idbe222cfef4792cd8a13614b1a4d9ce501dd09d0
Signed-off-by: Test <test@example.com>
🤖 debug: Add test button to manually trigger todo UI
Add bug icon button that populates currentTodos with test data.
This lets us validate the todo UI without waiting for agent to call todo_write.
Tap the bug icon (🐛) and you should see:
- Todo toggle button (📋) appear
- Floating todo card with 3 test items
Remove this debug button once todo tracking is confirmed working.
_Generated with cmux_
Change-Id: Ib3d2f6f408b6cc91777e9eb6f6e56d5e2a71fb8e
Signed-off-by: Test <test@example.com>
🤖 fix: Prevent timeline jumping with multi-client connections
**Problem:**
When web and mobile both connected to same workspace, every event triggered
a full timeline re-sort, causing FlatList to jump and reload messages.
**Fix:**
1. Deduplicate messages - skip if already exists (same ID)
2. Update in place for streaming deltas (no sort)
3. Append without sort when message is in order (common case)
4. Only sort when message is out of order (rare)
**Performance:**
- Before: O(n log n) sort on EVERY event
- After: O(1) append for ordered messages, O(n) dedup check
**Result:**
- No more jumping when multiple clients connected
- Smooth scrolling during streaming
- Messages only re-sort when truly out of order
_Generated with cmux_
Change-Id: I665680a9eb72150b7cb7d35627e5d6c818620baf
Signed-off-by: Test <test@example.com>
🤖 fix: Fix sendMessage error handling and add streaming indicator
**SendMessage Fix:**
- Wrap sendMessage in try-catch to handle exceptions properly
- Add console.error logging to debug actual failures
- Move setInput("") to finally block so it always clears
- Only show error toast for actual failures (not false positives)
**Streaming Indicator:**
- Track isStreaming state from stream-start/stream-end events
- Extract model name from stream-start event
- Display compact indicator above input: "model streaming..."
- Styled with accent color, compact size
- Positioned between todos and input area
- Disappears when stream ends
**Result:**
- No more false "Request failed" errors on successful sends
- Shows "claude-sonnet-4-5 streaming..." during agent work
- Matches desktop UX (model name + streaming indicator)
_Generated with cmux_
Change-Id: I4f2fffe4b82ec8ce0d439ca36bd009979cd7de05
Signed-off-by: Test <test@example.com>
🤖 fix: Add detailed sendMessage logging and prevent unnecessary FlatList updates
**SendMessage Debugging:**
- Log server response to see actual structure
- Handle multiple response formats (void, Result, undefined)
- Assume success if no error thrown (more lenient)
- Detailed console logging for debugging
**Timeline Stability:**
- Only update timeline state if events actually changed it
- Return same reference when applyChatEvent returns unchanged array
- Prevents FlatList re-render when no actual changes
- Reduces jumping from duplicate/no-op events
Check console for: [sendMessage] Server response: {...}
This will show what the server is actually returning.
_Generated with cmux_
Change-Id: I1c9c51e3acfc502b565fd9db18062f233bb07f61
Signed-off-by: Test <test@example.com>
🤖 fix: Make sendMessage fire-and-forget to prevent false errors
**Problem:**
sendMessage was waiting for HTTP response, but server returns immediately
while streaming happens async via WebSocket. This caused "Request failed"
errors even though stream-start arrived successfully.
**Solution:**
- Fire and forget: Don't wait for HTTP response
- Errors come via stream-error WebSocket events (not HTTP response)
- Only validation errors (empty message, etc.) shown via HTTP
- Clear input immediately for better UX (like iMessage)
**Timeline Jumping:**
- Return same array reference when no changes (prevents FlatList update)
- Only update state when events actually modify timeline
**Result:**
- No more "Request failed" on successful sends
- Input clears immediately (better UX)
- Stream errors arrive via WebSocket (proper error handling)
- Less timeline jumping
_Generated with cmux_
Change-Id: I247fc41a7095ba4657a0eaff6fe453851f380993
Signed-off-by: Test <test@example.com>
🤖 feat: Transform send button to cancel button when streaming
**Send/Cancel Button Transformation:**
- When not streaming: Blue circle with arrow-up (send)
- When streaming: Red square with stop icon (cancel)
- Button always enabled during streaming (can cancel anytime)
- Calls interruptStream API when tapped during streaming
**Visual States:**
- Circle (blue) → Send message
- Square (red) → Cancel streaming
**API:**
- Add interruptStream method to mobile API client
- Calls WORKSPACE_INTERRUPT_STREAM IPC channel
**UX:**
- Matches ChatGPT/Claude web behavior
- Clear visual indication of streaming state
- Easy to cancel mid-stream
- Button shape change makes function obvious
_Generated with cmux_
Change-Id: Ife2300ca9641e734eb321237f180da9d6ab01313
Signed-off-by: Test <test@example.com>
🤖 fix: Use accent color for cancel button and center with input
**Color Fix:**
- Change cancel button from danger (red) to accent (blue)
- Matches project color scheme and web version
- Pressed state uses accentHover (darker blue)
- Consistent with send button color
**Alignment Fix:**
- Change alignItems from 'flex-end' to 'center'
- Button now vertically centers with input as it grows
- Looks natural when input expands to multiple lines
- Matches iMessage/WhatsApp behavior
**Result:**
- Cancel button is blue square (not red)
- Button stays centered as input grows
- Consistent with project design language
_Generated with cmux_
Change-Id: I71f3e22952bb08f14dad7e010e94e06583a7bf81
Signed-off-by: Test <test@example.com>
🤖 fix: Silence spurious sendMessage HTTP errors
Remove console.error logging for async sendMessage errors.
These are not real errors - the server HTTP response may return before
the stream completes, causing false "Request failed" errors.
Actual errors arrive via stream-error WebSocket events and are shown in chat.
HTTP response errors are noise and should be silently ignored.
_Generated with cmux_
Change-Id: I97fc6961a53e58401adfc265e0595a6c524b7b04
Signed-off-by: Test <test@example.com>
🤖 fix: Use 60-second trailing window for TPS like desktop
**Problem:**
Mobile app used simple calculation: TPS = total tokens / elapsed time from start
This becomes increasingly inaccurate and doesn't match desktop display.
Desktop uses sophisticated 60-second trailing window that reflects recent speed.
**Fix:**
- Track deltas with timestamps in array (not just cumulative count)
- Calculate TPS using 60-second trailing window (matches desktop)
- Include tool-call-start tokens (was missing)
- Prune old deltas outside window for accurate recent TPS
- Use same algorithm as StreamingTPSCalculator.ts
**Algorithm (matches desktop):**
1. Store deltas: { tokens, timestamp }
2. Filter to last 60 seconds
3. TPS = recent tokens / time span of recent deltas
4. Total = sum of all deltas
**Result:**
- Mobile TPS now matches desktop/web display
- More accurate (reflects current speed, not average from start)
- Includes all token sources (stream, reasoning, tool args)
_Generated with cmux_
Change-Id: I2b05b0095e19faf868eba9e365283fa036fc9a26
Signed-off-by: Test <test@example.com>
🤖 feat: Add collapsible reasoning matching desktop behavior
**Desktop Behavior:**
- Reasoning expands while streaming to show full content
- Shows 'Thinking' title with 💭 emoji
- Auto-collapses to just title when complete
- User can tap to manually expand/collapse
**Mobile Implementation:**
- Add Pressable header with chevron icon
- State: isExpanded (default: isStreaming)
- useEffect: Auto-collapse when isStreaming → false
- Disabled while streaming (can't collapse)
- Shows ⟳ spinner when streaming, chevron when complete
**Visual States:**
While streaming (auto-expanded):
💭 Thinking ⟳
──────────────────────────
Reasoning deltas appear
in real-time... ▌
After complete (auto-collapsed):
💭 Thinking ▶
Tap to expand:
💭 Thinking ▼
──────────────────────────
Full completed reasoning
Result: Matches desktop - reasoning visible while generating,
auto-collapses when done to save space.
_Generated with cmux_
Change-Id: I6be888e6c92cbc1254e48c04d0eaaeb7adc60b29
Signed-off-by: Test <test@example.com>
🤖 refactor: Simplify reasoning - no icons, auto-collapse on finish
**Changes:**
- Remove all icons (chevrons, spinners, emojis)
- Default to expanded (show content immediately)
- Auto-collapse when isStreaming becomes false (reasoning finished)
- Simple Pressable header - just 'Thinking' text
- User can tap to expand/collapse anytime
**Behavior:**
1. Reasoning starts → Message appears expanded with deltas streaming
2. Reasoning finishes → Auto-collapses to just 'Thinking' title
3. User taps → Expands to show full content
**Visual:**
While reasoning (expanded):
Thinking
─────────────────────
Reasoning deltas... ▌
After finished (auto-collapsed):
Thinking
Tap to expand:
Thinking
─────────────────────
Full reasoning content
Simple, clean, matches user request.
_Generated with cmux_
Change-Id: Ib82a131d55207b1c3fefe2636f12932b223aaf42
Signed-off-by: Test <test@example.com>
🤖 feat: Add special status_set tool rendering for mobile
Implement mobile-optimized status_set display matching desktop UX:
**New Component: StatusSetToolCard (~55 LoC)**
- Compact inline display (no expand/collapse)
- Shows emoji + message in single line
- Subtle background with left border
- Border color matches status (green=success, red=failed)
- Italic text for status message
- Always visible (not hideable like generic tools)
**MessageRenderer Dispatch:**
- Add isStatusSetTool() type guard
- Route status_set to StatusSetToolCard
- Validates emoji and message in args
**Desktop Comparison:**
- Desktop: Shows 'emoji message' in collapsed tool header
- Mobile: Shows 'emoji message' in compact card
- Both: Always visible, no expansion needed
**Visual Example:**
┌─────────────────────────────┐
│ 🔧 Investigating token calc │
└─────────────────────────────┘
Clean, compact, matches desktop minimal display.
_Generated with cmux_
Change-Id: Ieb5e87638b950e99a058ac099bc6118db804edad
Signed-off-by: Test <test@example.com>
🤖 fix: Track tool-call-delta tokens to match desktop count
**Root Cause:**
Mobile was missing tool-call-delta from token tracking, causing lower
token counts than desktop.
**What Desktop Tracks:**
- stream-delta (text tokens)
- reasoning-delta (thinking tokens)
- tool-call-start (initial tool arg tokens)
- tool-call-delta (streaming tool arg tokens) ← MOBILE WAS MISSING THIS
**Impact:**
When tools have large arguments (e.g., file_edit_replace_string with big
code blocks), tool-call-delta events stream additional tokens.
Example:
- tool-call-start: 100 tokens
- tool-call-delta: 50 tokens (mobile was ignoring)
- tool-call-delta: 30 tokens (mobile was ignoring)
Desktop: 180 tokens
Mobile (before): 100 tokens
Mobile (after): 180 tokens ✅
**Fix:**
Add 'tool-call-delta' to event type check in token tracking.
Result: Mobile token count now matches desktop exactly.
_Generated with cmux_
Change-Id: Ieb6415afca133083ef4190460446e10cdd81e48c
Signed-off-by: Test <test@example.com>
🤖 refactor: Change 'Projects' label to 'Workspaces' on main screen
Change main screen title from 'Projects' to 'Workspaces' for consistency
with Expo Router header title and workspace-centric UX.
_Generated with cmux_
Change-Id: Id3a7b4ac341daf45c92c92fb7152f50ec3845c26
Signed-off-by: Test <test@example.com>
🤖 refactor: Remove duplicate 'Workspaces' label
Remove 'Workspaces' title from page content since it's already shown
in the Expo Router header. Keep only the subtitle and settings icon.
Result: Cleaner, less redundant UI. More space for workspace list.
_Generated with cmux_
Change-Id: I20fea3acc58430b6758a854021634dd372484236
Signed-off-by: Test <test@example.com>
🤖 refactor: Simplify subtitle to 'chat' instead of 'chat timeline'
Change 'Select a workspace to open the chat timeline.' to
'Select a workspace to open the chat.' for simpler, clearer language.
_Generated with cmux_
Change-Id: Iefcb14e0af5f61705a72e83d84ad1abaab9baa34
Signed-off-by: Test <test@example.com>
🤖 feat: implement secrets management for React Native app
Add full secrets management functionality to mobile app:
- Add IPC channel constants for PROJECT_SECRETS_GET and PROJECT_SECRETS_UPDATE
- Create Secret and SecretsConfig types mirroring desktop implementation
- Extend API client with secrets.get() and secrets.update() methods
- Create mobile-optimized SecretsModal component with:
- Key-value pair management
- Password visibility toggles
- Auto-capitalization of keys for env var convention
- Keyboard avoidance for mobile UX
- Integrate secrets button into ProjectsScreen with handlers
Secrets are stored server-side in ~/.cmux/secrets.json and accessed via IPC,
ensuring sensitive data never leaves the backend. Compatible with existing
desktop secrets infrastructure.
_Generated with `cmux`_
Change-Id: I9125b73fca5596f9c86e7ab868a943c4cb499de5
Signed-off-by: Test <test@example.com>
🤖 fix: replace gap with marginRight for React Native compatibility
The gap property is not fully supported in React Native, causing runtime errors.
Replace with marginRight on the first button for proper spacing.
_Generated with `cmux`_
Change-Id: Ib6c055af3b0e8057ebbb9f17fae234df3ed3dfe7
Signed-off-by: Test <test@example.com>
🤖 fix: improve secrets modal and remove settings gear icon
- Add debug logging to track secrets loading and modal behavior
- Fix ScrollView layout by replacing flex:1 with maxHeight
- Remove settings gear icon from workspace screen header
The ScrollView was causing layout issues by using flex:1 inside a flex
container. Switching to maxHeight provides better constraint behavior.
Debug logs will help identify why secrets aren't displaying correctly.
_Generated with `cmux`_
Change-Id: I126f4dac39f757a469432ad19d3432337bc80bbe
Signed-off-by: Test <test@example.com>
🤖 refactor: redesign secrets modal with iOS-native styling
Major UX improvements for mobile:
**Workspace Screen:**
- Restored settings gear icon (removed key icon - no per-workspace secrets)
**Secrets Modal:**
- iOS-native header with Cancel/Done buttons
- Full-screen modal with pageSheet presentation style
- Cleaner card-based layout for each secret
- Visibility toggle positioned inside value input
- Larger touch targets and better spacing
- Empty state with icon and helpful text
- Prominent "Add Secret" button with icon
- Removed bottom action buttons (now in header)
- Better typography with uppercase labels
- Improved input styling with rounded corners
The modal now follows iOS design patterns with a navigation-style header,
better visual hierarchy, and more touch-friendly controls.
_Generated with `cmux`_
Change-Id: I5ff28487c2481282b26e998a578ce09a43113d88
Signed-off-by: Test <test@example.com>
🤖 chore: remove debug logs from secrets modal
Remove console.log statements added during debugging.
_Generated with `cmux`_
Change-Id: Ie2b847c0cf98be8430534b96ebb917a363a8f0e2
Signed-off-by: Test <test@example.com>
🤖 fix: add safe area insets to secrets modal
Respect iOS safe area insets (notch, status bar, home indicator) by:
- Adding top padding with insets.top
- Adding bottom padding with insets.bottom
- Import and use useSafeAreaInsets hook
Prevents content from being overlaid by system UI elements.
_Generated with `cmux`_
Change-Id: Iebd4bb2f43d84e1a81f4942a3b5fc9d1e7619534
Signed-off-by: Test <test@example.com>
🤖 fix: remove transparent prop from modal to fix warning
Remove transparent={true} from Modal as it's incompatible with
presentationStyle='pageSheet'. This fixes the warning:
"Modal with 'pageSheet' presentation style and 'transparent' value is not supported."
_Generated with `cmux`_
Change-Id: Ib910b59678763e499918b028f3af1ab3a4d93565
Signed-off-by: Test <test@example.com>
🤖 fix: remove manual safe area insets from pageSheet modal
pageSheet presentation style handles safe area insets automatically,
so manual insets were causing double padding. Removed useSafeAreaInsets
and manual padding adjustments.
_Generated with `cmux`_
Change-Id: I5cec60c1a0fa97871c4ccabcb59cce96365cd7ae
Signed-off-by: Test <test@example.com>
🤖 style: increase header padding in secrets modal
Increase top and bottom padding from spacing.md to spacing.lg
to make the header look less cramped and more iOS-native.
_Generated with `cmux`_
Change-Id: I8edeb3e670304559d00904c9e01a83c91b332685
Signed-off-by: Test <test@example.com>
🤖 style: adjust Cancel and Done button positioning
Add horizontal padding to Cancel and Done buttons to move them
slightly toward center, creating better visual balance and larger
touch targets. Also reduce header container padding accordingly.
_Generated with `cmux`_
Change-Id: Ic580a0a3467fdc3ab4f005b21f8a59fe96bf17ab
Signed-off-by: Test <test@example.com>
🤖 style: vertically center message input placeholder
Add textAlignVertical='center' to message input to ensure placeholder
text is vertically centered while remaining left-aligned.
_Generated with `cmux`_
Change-Id: Ia161befad85413f833f99716b343c69b1752d929
Signed-off-by: Test <test@example.com>
🤖 feat: implement native workspace creation for React Native app
Adds platform-native workspace creation modal for the mobile app, enabling
users to create workspaces directly from their mobile devices.
New Components:
- NewWorkspaceModal: Platform-native modal with branch selection, runtime
configuration, and SSH support
- Runtime type definitions: Copied from web version for consistency
- AsyncStorage utilities: Persist runtime preferences per project
Features:
- Branch name input with validation
- Trunk branch selection (dropdown or manual input)
- Runtime mode selection (Local/SSH)
- SSH host configuration
- Real-time workspace path preview
- Runtime preference persistence
- Loading states and error handling
- Navigation to new workspace after creation
- Automatic workspace list refresh
IPC Integration:
- Added PROJECT_LIST_BRANCHES method to fetch available branches
- Added WORKSPACE_CREATE method to create workspaces
- Follows same IPC patterns as web version
Implementation follows SecretsModal.tsx pattern for consistency with
existing mobile UI patterns. Includes race condition guards for async
branch loading and defensive input validation.
_Generated with `cmux`_
Change-Id: I823970f8591267e4848e1e046823398e1fac7257
Signed-off-by: Test <test@example.com>
🤖 fix: correct projectPath prop reference in NewWorkspaceModal
The prop was renamed to _projectPath in destructuring but referenced as
projectPath in the useEffect dependency array, causing a ReferenceError.
Removed the underscore prefix since projectPath is actually used in the
component for loading runtime preferences.
_Generated with `cmux`_
Change-Id: I3f7c9284c35e37176eb75a1273d73338fd216e0b
Signed-off-by: Test <test@example.com>
🤖 refactor: remove workspace path info display from modal
Removes the workspace path preview section as it's unnecessary for the
mobile UX. Users can see where workspaces are created from the workspace
list after creation.
_Generated with `cmux`_
Change-Id: I2d482887e92c187a7b709df84412b590cfe8d7c4
Signed-off-by: Test <test@example.com>
🤖 refactor: remove FAB and filesystem path display
Removes two unnecessary UI elements:
1. Floating action button (FAB) with "coming soon" alert - no longer
needed since workspace creation is now available via the + button in
each project header
2. Filesystem path display under project names - not relevant on mobile
devices where users don't interact with filesystem paths
This streamlines the mobile UI and removes confusion about workspace
creation availability.
_Generated with `cmux`_
Change-Id: I9f67f4d5d4c555d3c6fef924aca79f214137029d
Signed-off-by: Test <test@example.com>
🤖 feat: add workspace deletion with long-press gesture in React Native
- Extended mobile API client with workspace.remove() and workspace.rename() methods
- Updated metadata subscription to handle deletion events (null metadata)
- Added long-press gesture on workspace items to show platform-native action sheet
- Implemented delete handler with two-step confirmation and force delete option
- Added rename stub for future implementation
Workspace deletion now works via long-press → action sheet → confirmation dialog,
with proper handling of uncommitted changes and real-time UI updates via WebSocket.
_Generated with `cmux`_
Change-Id: Ia71754155fea86c4a5c3baa0757f76aca08184a8
Signed-off-by: Test <test@example.com>
🤖 feat: implement workspace renaming for React Native app
- Add workspace validation utility with comprehensive tests (16 test cases)
- Create RenameWorkspaceModal component with platform-native styling
- Wire up rename functionality to ProjectsScreen with long-press action
- Support real-time validation with inline error messages
- Handle loading states and keyboard interactions
- Fix text contrast issues for proper dark mode support
The modal follows platform conventions (iOS bottom sheet, Android center modal)
and provides identical validation to the web version.
Change-Id: If9a9d59a9e809d72ccd416cf6c6c5917459a37db
Signed-off-by: Test <test@example.com>
🤖 feat: implement Start from Here for React Native mobile app
Add "Start from Here" functionality to compact chat history by replacing
all messages with a single compacted message (typically a plan).
**Changes:**
- Add replaceChatHistory method to mobile API client
- Create messageHelpers utility with createCompactedMessage function
- Add StartHereModal component for confirmation
- Update ProposePlanCard with "📦 Start Here" button
- Wire up handler in WorkspaceScreen and MessageRenderer
- Display compacted badge on assistant messages
**Architecture:**
- Uses existing IPC channel workspace:replaceHistory
- Backend atomically clears history and sends delete events
- UI updates automatically via WebSocket subscription
- Inline types avoid shared type imports (mobile is separate package)
**UX Flow:**
1. User taps "📦 Start Here" on plan
2. Modal confirms action
3. Client calls replaceChatHistory with compacted message
4. Backend clears history, sends delete event for old messages
5. Backend sends new compacted message
6. UI updates automatically, old messages disappear
Generated with `cmux`
Change-Id: I16454ebe06781a250cc45b37876211988cf34e55
Signed-off-by: Test <test@example.com>
🤖 fix: WebSocket history replay broadcasting to all clients
Fix bug where history replay was broadcast to ALL connected WebSocket clients
instead of only the newly subscribing client.
**Changes:**
- Add WORKSPACE_CHAT_GET_HISTORY IPC handler for fetching history without broadcasts
- Modify WebSocket subscription to fetch and send history directly to client
- Send 'caught-up' message after history replay (required by frontend)
- Add integration test to verify fix
**Implementation:**
- src/constants/ipc-constants.ts: Add WORKSPACE_CHAT_GET_HISTORY constant
- src/services/ipcMain.ts: Add getWorkspaceChatHistory() method and handler
- src/main-server.ts: Add getHandler() and modify subscription logic
- tests/ipcMain/websocketHistoryReplay.test.ts: New test file
**Technical Details:**
WebSocket server now uses request-response pattern (getHistory) instead of
event broadcasting for history replay. Electron app behavior unchanged.
Fixes issue where multiple clients subscribed to same workspace would all
receive duplicate history when any new client subscribed.
Generated with cmux
Change-Id: Icf4b5c461bc8141aa9039dbe5d2b5884f46f9cad
Signed-off-by: Test <test@example.com>
🤖 fix: WebSocket replay now includes active streams and partial messages
**Critical Bug Fix:** WebSocket history replay was only sending persisted
messages, missing active streams and partial/interrupted responses. This
caused "stream-delta for unknown message" errors when clients joined during
active streaming.
**Root Cause:**
- Original fix used getWorkspaceChatHistory() which only returns chat.jsonl
- Missing: active streams, partial messages, init state from full replay
- Client received stream-delta events for messages it never got during replay
**Solution:**
WebSocket now uses the FULL replay mechanism by:
1. Temporarily intercepting events during workspace:chat:subscribe
2. Collecting all replay events (history + active streams + partial + init)
3. Sending collected events directly to subscribing WebSocket client only
4. Removing temporary listener to prevent future broadcasts
**Changes:**
- src/main-server.ts: Use full replay with temporary event interception
- src/main-server.ts: Add getListeners() for listener management
- tests/ipcMain/websocketHistoryReplay.test.ts: Update documentation
**Technical Details:**
The fix preserves all benefits of the original targeted replay (no broadcast
to other clients) while including ALL replay data that AgentSession.replayHistory()
provides. This matches Electron app behavior for feature parity.
Fixes: "Received stream-delta for unknown message" errors in mobile app
Generated with cmux
Change-Id: Ic7528b432e9823ec0c41d330fe179efb6676e023
Signed-off-by: Test <test@example.com>
🤖 feat: add git review feature for React Native app
- Add executeBash method to mobile API client
- Copy git utilities (diffParser, numstatParser, gitCommands) from web app
- Add review types for diff hunks and file changes
- Create GitReviewScreen with trunk branch comparison
- Create DiffHunkView component with syntax highlighting
- Create ReviewFilters component for base branch selection
- Add review route at /workspace/[id]/review
- Add git-branch icon to workspace header for quick access
- Fix nested data access for executeBash API responses
- Add defensive type checks in git parsers
Defaults to comparing against 'main' branch with uncommitted changes included.
Users can switch between main/master/origin/main/HEAD via filter UI.
Generated with `cmux`
Change-Id: I97b430941ea5634f4d4c6bd95c709fe31b776815
Signed-off-by: Test <test@example.com>
🤖 refactor: replace workspace header buttons with iOS action sheet
- Create WorkspaceActionSheet component with native iOS design
- Add WorkspaceActionsContext for state sharing between route and screen
- Replace custom header bar with ellipsis menu button
- Implement proper iOS animations (blur fades, sheet slides)
- Add safe area insets for home indicator devices
- Consolidate Code Review, Todo List, Settings into menu
- Remove inline header buttons to gain ~50px vertical space
Menu items:
- Code Review (navigate to git diff viewer)
- Todo List (toggle, only shown if todos exist)
- Workspace Settings (navigate to settings)
Uses Animated API for smooth slide-up animation with spring physics.
Includes haptic feedback and blur background following iOS HIG.
Generated with `cmux`
Change-Id: Ia81dc618ae9249d8bfcf72f292331e10e44f4e7d
Signed-off-by: Test <test@example.com>
🤖 refactor: move settings to header and clean up workspace list
- Add settings icon to main screen header (expo router navbar)
- Remove 'Select a workspace to open the chat.' label
- Remove settings button from workspace list content
- Remove unused IconButton import
Gains ~60px vertical space for workspace list.
Generated with `cmux`
Change-Id: I56807a6320651bafd536f7a8fa5714d9bd9f8739
Signed-off-by: Test <test@example.com>
🤖 fix: restore IconButton import in ProjectsScreen
IconButton is still used for add workspace and secrets buttons.
Generated with `cmux`
Change-Id: If5e63385a4b4e5805f99a57f260ed2de11132dd3
Signed-off-by: Test <test@example.com>
🤖 feat: add cost/usage display to React Native app
- Extended mobile API client with tokenizer IPC channels (calculateStats, countTokens)
- Added WorkspaceCostProvider context to track usage history and consumer breakdown
- Implemented CostUsageSheet bottom sheet with session/last-response toggle
- Wired cost action into workspace action sheet menu
- Updated Metro/Babel/TypeScript configs to resolve @shared alias with explicit .ts extensions
- Fixed React hook ordering in CostUsageSheet (moved early return after all hooks)
Generated with `cmux`
Change-Id: Ib2e093adfc770afa5927223632ce2004704f7dc5
Signed-off-by: Test <test@example.com>
feat: add message editing and copy to React Native
Change-Id: Icd4268db6cb26272ec679bc044abff4428ba72d7
Signed-off-by: Test <test@example.com>
fix: resolve fontWeight type error in mobile header styles
- Add 'as any' cast to fontWeight to prevent 'expected dynamic type boolean, but had type string' error
- Clean up attempted unstable_headerRightItems implementation
- Revert to standard headerRight with Ionicons (react-native-screens 4.16.0)
The fontWeight property was causing a runtime error with react-native-screens
when passing string values from the theme typography weights. The 'as any' cast
resolves this type mismatch.
Change-Id: I1b17a868d941f47935cca405cffe886f02397351
Signed-off-by: Test <test@example.com>
🤖 feat: initial React Native mobile app setup
- Expo SDK 54 with React Native 0.81
- Workspace chat interface with streaming support
- Message rendering with react-native-markdown-display
- Copy functionality via long-press on user messages
- Provider configuration and workspace management
- WebSocket integration for real-time updates
Generated with `cmux`
Change-Id: I3dc6771753e9aa1789acaacb1f258dc2335f69ea
Signed-off-by: Test <test@example.com>
🤖 feat: add long-press copy to all messages
Adds long-press functionality to all message types for quick copying:
**AssistantMessageCard:**
- Long-press → Shows "Copy Message" option
- iOS: Native ActionSheet with haptic feedback
- Android: Custom modal bottom sheet
- Uses expo-clipboard for cross-platform clipboard access
**UserMessageCard:**
- Already had long-press with "Edit Message" and "Copy Message"
- No changes needed - works as before
**Implementation:**
- Added handleLongPress handler to AssistantMessageCard
- Added handleCopy async function using expo-clipboard
- Wrapped message Surface in Pressable with 500ms delay
- Platform-specific UI: ActionSheet (iOS) vs Modal (Android)
- Haptic feedback on long-press for tactile response
**User Experience:**
✅ Long-press any assistant message → Copy Message
✅ Long-press user message → Edit Message + Copy Message
✅ Native platform conventions (ActionSheet/Modal)
✅ Haptic feedback for confirmation
✅ Cancel option to dismiss
Generated with `cmux`
Change-Id: Ib844fbb377f3d3091e2cdb086e85a6285ee3be5c
Signed-off-by: Test <test@example.com>
🤖 fix: align React Native colors with web/Electron (Plan Mode now blue)
- Add mode-specific colors to React Native theme (planMode, execMode, editMode, thinkingMode)
- Replace hardcoded purple colors in ProposePlanCard with theme.colors.planMode (blue)
- Update StartHereModal button to use theme.colors.planMode
- Update MessageRenderer thinking/compacted labels to use theme colors
- Remove 📦 emoji from Start Here button to match desktop
Plan Mode now displays consistently in blue across all platforms instead of purple on mobile.
_Generated with `cmux`_
Change-Id: I37394282b94cd0d8c3aaf2bda4aed3e2e63b3c05
Signed-off-by: Test <test@example.com>
🤖 fix: correct async IIFE closure in main-server.ts after rebase
During the rebase conflict resolution, the async function wasn't properly closed.
This fix ensures all server initialization code is within the async IIFE and
adds the proper error handling catch block.
_Generated with `mux`_
Change-Id: Icf4ae912147f3daac160aab5a05ae89987a1757b
Signed-off-by: Test <test@example.com>
🤖 refactor: align mobile with desktop AI workspace creation flow
Implements Option 1 from investigation: maximizes code sharing between
desktop and mobile apps while bringing AI-generated workspace naming
to mobile.
**DRY Improvements:**
- Consolidated runtime.ts: Mobile now imports from @shared/types/runtime
(removed 76-line duplicate file)
- Added @/ path alias to mobile tsconfig for consistency with desktop
- Shared backend AI naming service works for both platforms via IPC
**Mobile Changes:**
- Added FirstMessageModal component (465 lines)
- Equivalent to desktop's FirstMessageInput
- Users describe task, AI generates workspace name/branch
- Full runtime configuration support (Local/SSH)
- Updated API client sendMessage() to support workspaceId: null
- Matches desktop IPC signature
- Returns workspace metadata on creation
- Integrated into ProjectsScreen
- New "Quick Start" button (⚡️ icon) opens FirstMessageModal
- Existing "+" button still opens manual NewWorkspaceModal
- Users can choose between AI naming or manual naming
**Desktop Cleanup:**
- Removed dead workspace modal code from App.tsx
- Deleted 9 unused state variables
- Removed 48-line handleCreateWorkspace function
- Removed modal rendering (24 lines)
- Modal was never triggered after FirstMessageInput implementation
**Backward Compatibility:**
- Both apps support both workspace.create() and sendMessage(null) flows
- Mobile keeps manual naming modal for users who prefer control
- No breaking changes to IPC or backend services
**Testing:**
- ✅ Mobile TypeScript compilation passes (no new errors)
- ✅ Desktop TypeScript compilation passes (no new errors)
- ✅ Runtime types shared without duplication
- ✅ Both creation flows coexist in mobile
Addresses divergence identified in investigation where desktop moved
to AI-based workspace creation (Nov 13) while mobile used manual modal
approach (implemented Nov 6).
Change-Id: I6b5717928b6cb7be6a05c455c9eb5bae86c5e3f5
Signed-off-by: Test <test@example.com>
fix: rename CmuxMessage to MuxMessage in mobile app
Change-Id: Ib0bf591b9ba7143272e72a903c99de703a48ae26
Signed-off-by: Test <test@example.com>
🤖 feat: streamline mobile workspace creation + fix TS errors
Integrate creation into WorkspaceScreen (no modals, -938 lines)
Fix Cmux→Mux typo breaking streaming, 22 TS errors, keyboard dismiss
_Generated with `mux`_
Change-Id: I35647bf5395b74d8cddd3eba9e47f1e45098dcff
Signed-off-by: Test <test@example.com>
🤖 feat: add trunk branch & runtime selection to mobile workspace creation
Add collapsible "Advanced Options" section to mobile workspace creation banner:
- Trunk branch picker (select base branch for workspace)
- Runtime picker (Local or SSH Remote)
- Conditional SSH host input
Features:
- Progressive disclosure - collapsed by default, tap to expand
- Runtime preferences persist per-project in AsyncStorage
- Smart defaults: recommended trunk branch, local runtime
- Follows existing mobile pattern (chevron toggle like FloatingTodoCard)
Technical changes:
- Import Picker, runtime types, and preference utilities
- Add state for showAdvanced, runtimeMode, sshHost
- Load runtime preference on mount in creation mode
- Build RuntimeConfig in onSend, save preference on success
- Replace creation banner with collapsible UI (~135 lines)
_Generated with `mux`_
Change-Id: I1aed7e83d26920bceb4f12ad71d69a1a9dae9199
Signed-off-by: Test <test@example.com>
🤖 fix: remove leftover conflict marker in ChatInput
Removed incomplete conflict marker from line 105 that was accidentally
committed in 67dbec07. This was not an active conflict - just cleanup of
a stray <<<<<<< HEAD marker with no corresponding separator or ending.
_Generated with `mux`_
Change-Id: I398c227b1ef53bf14baa771e8cfc8509834be8d6
Signed-off-by: Test <test@example.com>
🤖 fix: resolve undefined defaultModel reference errors
Fixed two TypeScript/runtime errors:
1. AIView.tsx - Added missing WORKSPACE_DEFAULTS import
2. useModelLRU.ts - Replaced undefined defaultModel with WORKSPACE_DEFAULTS.model
These errors were causing 'Can't find variable: defaultModel' in the browser
console and preventing the web version from loading correctly.
_Generated with `mux`_
Change-Id: I8701fd231c84bdb873435a1479dfc9849d0ce431
Signed-off-by: Test <test@example.com>
🤖 feat: add model picker and shared catalog to mobile app
- Import KNOWN_MODELS from desktop constants for single source of truth
- Add modelCatalog.ts with validation, display formatting, and LRU sanitization helpers
- Implement useModelHistory hook with SecureStore persistence (max 8 recent models)
- Create ModelPickerSheet component with search, recent chips, and grouped list by provider
- Update useWorkspaceSettings/useWorkspaceDefaults to validate models against catalog
- Wire picker into WorkspaceScreen above composer with model summary display
- Add assertKnownModelId guard in sendMessage IPC call
- Display human-friendly model names in streaming indicator
Generated with `mux`
Change-Id: I60fd0945056c26dea2e16d4c5d652cadf919c654
Signed-off-by: Test <test@example.com>
🤖 feat: expand mobile run settings sheet
- Replace ModelPickerSheet with multi-section RunSettingsSheet
- Add mode, reasoning, and 1M context controls alongside model search
- Wire WorkspaceScreen to set modes/thinking/context directly from sheet
Generated with
Change-Id: I5fe86aa251a08fe5d3201860af01f74e60e6d926
Signed-off-by: Test <test@example.com>
🤖 feat: consolidate run settings in chat screen
- Remove standalone workspace settings route and menu entry
- Expand run settings pill to show mode and reasoning level summary
- Keep RunSettingsSheet as the single place to adjust per-workspace configuration
Generated with
Change-Id: Ie424deb12dff3f5829f2225801dc60a241c32e44
Signed-off-by: Test <test@example.com>
🤖 fix: avoid nested virtualized lists in run settings
- Replace FlatList with simple mapped view inside RunSettingsSheet to prevent ScrollView nesting errors
- Add capped height container while keeping separators and empty state messaging
Generated with
Change-Id: I53779b91607177dcce74d40c196be48ab9a8570c
Signed-off-by: Test <test@example.com>
🤖 fix: restore scrolling in run settings model list
- Wrap model list in nested ScrollView with max height and indicators
- Keep non-virtualized implementation to avoid ScrollView + VirtualizedList warning
Generated with
Change-Id: I4411270d61b9697e4cdac35300ff8c938c5dcdaa
Signed-off-by: Test <test@example.com>
🤖 fix: simplify mobile run settings layout
- Remove collapsible sections and present model/mode/reasoning/context inline
- Add extra header inset so the Run settings title clears safe areas
- Keep model list scrollable via nested ScrollView without virtualized nesting
Generated with
Change-Id: If60679bf6bff95af019a232d2c02c4721e0131d2
Signed-off-by: Test <test@example.com>
🤖 feat: add slash commands to RN chat
_Generated with _
Change-Id: I54985c290c02f22d3e6592bfce7dad28159161f1
Signed-off-by: Test <test@example.com>
feat: render markdown in mobile reasoning
Change-Id: I5cc34c9c14fa379086608d9229c32acb65f85a1e
Signed-off-by: Test <test@example.com>
🤖 feat: align mobile TODO lifecycle with desktop behavior
Remove WorkspaceActionsContext and manual toggle from action menu
Drive TODO visibility purely from stream events (caught-up boundary)
Hide TODOs on stream-end and when reopening idle workspaces
Add todoLifecycle utilities with defensive assertions and tests
_Generated with mux_
Change-Id: I7a74e287009f8e178ce9d8f8b6f1dcd360411f73
Signed-off-by: Test <test@example.com>
feat: align mobile workspace ordering w/ activity
Change-Id: Ia47a538b0c8d74105be6c20a6e07238bfc359606
Signed-off-by: Test <test@example.com>
fix: simplify mobile composer autosize
Change-Id: I7d080ee0feec5a84d22f1a6144fca53c5bf1308f
Signed-off-by: Test <test@example.com>
🤖 feat: align RN message chrome with desktop
_Generated with _
Change-Id: I57ed98ae9b9e8459ae773a5eb42d25c6f274cf43
Signed-off-by: Test <test@example.com>
🤖 feat: streamline RN tool rendering with web parity
- Add specialized tool cards (bash, file_read, file_edit_*) with diff syntax highlighting
- Implement proper streaming state management matching desktop reducer
- Fix reasoning auto-collapse and streaming cursor visibility
- Add MessageBubble chrome with consistent action buttons/timestamps
- Pin react-native-worklets to 0.5.1 for Expo Go compatibility
_Generated with `mux`_
Change-Id: I3d11d83d956a909a52501346b98afeb24eecd40f
Signed-off-by: Test <test@example.com>
chore: move mobile app to top level
Change-Id: Id66bb660223ccbc7fbf7c306d4f1de96f0e7e24c
Signed-off-by: Test <test@example.com>
chore: rename cmux references to mux
Change-Id: I2160509c1397552b547117dce280b02c7a884146
Signed-off-by: Test <test@example.com>
🤖 fix: narrow mux message type guard for mobile
Change-Id: I89207a670656a722fbcfed3cdf4057db195c85b3
Signed-off-by: Test <test@example.com>
🤖 fix: bind workspace activity mocks in tests
Change-Id: I936398372f0f8d33854597e32a257a63b294557a
Signed-off-by: Test <test@example.com>
🤖 fix: honor MUX_SERVER_AUTH_TOKEN environment variable
Change-Id: I0c52409fe13da349fcd97962b7c39427addc5da2
Signed-off-by: Test <test@example.com>
🤖 fix: persist chat processors across navigation to preserve streaming state
Change-Id: Id26c2f8f576aa25052337c08bd99979d3b6327ee
Signed-off-by: Test <test@example.com>
🤖 fix: remove duplicate history broadcast on WebSocket subscribe
Change-Id: Ife1ad65b13630fb9a3827c830e76270d9830e7fd
Signed-off-by: Test <test@example.com>
🤖 fix: make typecheck-react-native fail on TypeScript errors
Change-Id: I1a63d66551b51a0f759003f9754d4f5d8232b374
Signed-off-by: Test <test@example.com>
🤖 feat: add mobile/node_modules/.installed target to ensure deps
Change-Id: I9185f5a30c15a9befd7b1abf96b274ed31e62104
Signed-off-by: Test <test@example.com>
🤖 fix: exclude mobile Bun tests from Jest suite
Change-Id: Ib70c9d4bbd68bed6e2f24313fce4a96c16ebbc41
Signed-off-by: Test <test@example.com>
🤖 fix: actually add mobile/node_modules/.installed target
Change-Id: I81d25b1e2d660351f2939cf1a637f04e8bfba089
Signed-off-by: Test <test@example.com>
🤖 fix: restore launch project handler and --add-project support in server
Change-Id: Ia79d817a829f8d4131cca2615038198a22eaaa57
Signed-off-by: Test <test@example.com>
🤖 fix: make mobile client use persisted app settings
Change-Id: I42983df01f38fcc82cbcfc074c62f4dd55244bd9
Signed-off-by: Test <test@example.com>
🤖 fix: allow editing server URL with custom ports
Change-Id: I58fda0ef9758ed7b7ec5df2ade65e4e10d8f61b9
Signed-off-by: Test <test@example.com>
Change-Id: I3f108b2cab17c662f174fa842ae8834df65dc976 Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I3c4996ce3176aa912d7abce68dc38f9a0ff86c44 Signed-off-by: Thomas Kosiewski <tk@coder.com>
Fixes inefficient reasoning delta merging in ChatEventProcessor and ensures IPC handlers are registered before project initialization in server CLI. Change-Id: I2636196cb0470471451125ec0a058e59b9e6f27d Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: Ibc14e49ce41eb3fc4a9c67101cb5bf1ddfda0798 Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I12d1b54d74b88bb2b290d2d41eb49bcbe3e6406f Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I74e31e60eb7f1c9290747937792bb4fc953a2b76 Signed-off-by: Thomas Kosiewski <tk@coder.com>
fc4e0f0 to
908f32e
Compare
## Summary - add an Expo React Native app under mobile/ with chat, workspace, project, git review, and settings flows ready to talk to the mux backend over HTTP/WebSocket - expose mux's IPC surface via a new CLI server entrypoint (`src/cli/server.ts`) plus bearer-token auth so mobile clients can connect securely - share chat/event processing (`ChatEventProcessor`), workspace defaults (`WORKSPACE_DEFAULTS`), and activity telemetry across desktop, browser, and mobile to keep experiences consistent ## Testing - `make typecheck` - `make typecheck-react-native` - `cd mobile && bun test src/messages/normalizeChatEvent.test.ts` - `bun test tests/ipcMain/websocketHistoryReplay.test.ts` _Generated with `mux`_ --------- Signed-off-by: Test <test@example.com> Signed-off-by: Thomas Kosiewski <tk@coder.com>
Summary
src/cli/server.ts) plus bearer-token auth so mobile clients can connect securelyChatEventProcessor), workspace defaults (WORKSPACE_DEFAULTS), and activity telemetry across desktop, browser, and mobile to keep experiences consistentTesting
make typecheckmake typecheck-react-nativecd mobile && bun test src/messages/normalizeChatEvent.test.tsbun test tests/ipcMain/websocketHistoryReplay.test.tsGenerated with
mux