Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
a8d5c39
🤖 feat: build native Xum mobile workspace and chat interface
ThomasK33 Sep 5, 2026
1b17b84
🤖 feat: add mobile remote transport and transcript reducer
ThomasK33 Sep 5, 2026
65ac4a4
🤖 fix: replace mobile connections on explicit retry
ThomasK33 Sep 5, 2026
40fbfe4
🤖 feat: scaffold and validate the Expo Xum mobile companion
ThomasK33 Sep 5, 2026
e323034
🤖 fix: complete mobile model discovery and history recovery
ThomasK33 Sep 5, 2026
f53be0f
🤖 feat: polish native mobile forms and sheets
ThomasK33 Sep 5, 2026
41e39b0
🤖 feat: refine native mobile navigation and conversation UX
ThomasK33 Sep 5, 2026
43e839f
🤖 fix: explain empty mobile assistant history
ThomasK33 Sep 5, 2026
2f170dc
🤖 feat: refine mobile transcript and tool inspection
ThomasK33 Sep 5, 2026
5fc6af6
🤖 feat: refine mobile UI from Claude and Codex references
ThomasK33 Sep 5, 2026
cb15511
🤖 fix: keep mobile login copy native-facing
ThomasK33 Sep 5, 2026
c6773d5
🤖 feat: use focused native mobile pickers
ThomasK33 Sep 5, 2026
41febc6
🤖 fix: search all visible models in the mobile picker
ThomasK33 Sep 5, 2026
eed1626
🤖 feat: refine mobile navigation and bottom composer
ThomasK33 Sep 6, 2026
8162824
🤖 fix: support native abort signals in mobile transport
ThomasK33 Sep 7, 2026
3d8ea5a
🤖 fix(mobile): align workspace and transcript presentation with desktop
ThomasK33 Sep 7, 2026
a2e0171
🤖 fix(mobile): measure native keyboard avoidance in window coordinates
ThomasK33 Sep 7, 2026
66f6854
🤖 fix(mobile): expose context progress to native and web accessibility
ThomasK33 Sep 7, 2026
557c812
🤖 fix(mobile): fetch changes across repositories and validate mobile …
ThomasK33 Sep 7, 2026
9ddd05e
🤖 fix(mobile): preserve canonical AI settings and provider preferences
ThomasK33 Sep 7, 2026
0792887
🤖 fix: recover mobile sessions and replace sidebar detail routes
ThomasK33 Sep 7, 2026
d7bb515
🤖 docs: refresh bundled mobile compatibility guidance
ThomasK33 Sep 7, 2026
5c1d1e1
🤖 tests: deduplicate merged mobile icon aliases
ThomasK33 Sep 7, 2026
5d88915
🤖 fix(mobile): retain recovery retries and app goal capability
ThomasK33 Sep 7, 2026
d65367b
🤖 fix(mobile): support structured questions and searchable subagents
ThomasK33 Sep 7, 2026
1467973
🤖 tests: exercise recovery with canonical question choices
ThomasK33 Sep 7, 2026
5ea0951
🤖 fix(mobile): enforce live server policy before conversation actions
ThomasK33 Sep 7, 2026
6640ae8
🤖 fix(mobile): keep custom question option identities distinct
ThomasK33 Sep 7, 2026
1dce2bb
🤖 fix(mobile): pin shared schema runtime for production bundles
ThomasK33 Sep 7, 2026
795b5a5
🤖 fix(mobile): preserve active context capacity and deleted file names
ThomasK33 Sep 7, 2026
3cf93cc
🤖 fix(mobile): refresh conversation settings from live config changes
ThomasK33 Sep 7, 2026
c675587
🤖 fix(mobile): keep live interruption independent of settings readiness
ThomasK33 Sep 7, 2026
5153b3f
🤖 fix(mobile): recover saved question answers after replay
ThomasK33 Sep 7, 2026
d55fb0b
🤖 fix(stream): pin effective context capacity to provider requests
ThomasK33 Sep 7, 2026
7b835b4
🤖 fix(mobile): render active context from the request capacity snapshot
ThomasK33 Sep 7, 2026
f12fa5e
🤖 fix(mobile): restore complete queued input after interruption
ThomasK33 Sep 7, 2026
7225a8c
🤖 fix(mobile): retain composer cleanup across restored draft sends
ThomasK33 Sep 7, 2026
7548f3a
🤖 fix: restore queued reviews before message boundaries are flattened
ThomasK33 Sep 7, 2026
81964ab
🤖 tests: align queue restoration fixtures with lint rules
ThomasK33 Sep 7, 2026
5ff65d8
🤖 refactor: reconcile mobile branch with current main
ThomasK33 Sep 7, 2026
d5d122d
🤖 fix: validate queued review metadata before restoring input
ThomasK33 Sep 7, 2026
4a0e9d8
🤖 fix(stream): publish non-destructive fallback model metadata
ThomasK33 Sep 7, 2026
41e757c
🤖 fix(stream): replace optional fallback metadata explicitly
ThomasK33 Sep 7, 2026
8ff11e1
🤖 fix: validate reviews in live and replayed queue snapshots
ThomasK33 Sep 7, 2026
e751c94
🤖 fix(stream): reset live usage at fallback attempt boundaries
ThomasK33 Sep 7, 2026
88a2fd3
🤖 tests: expect fallback metadata in compaction event ordering
ThomasK33 Sep 7, 2026
f2cd06b
🤖 fix(mobile): gate actions on actual route and authentication availa…
ThomasK33 Sep 7, 2026
82a27de
🤖 fix(mobile): recognize effective API keys alongside OpenAI OAuth
ThomasK33 Sep 7, 2026
dcdd3ca
🤖 fix(mobile): project native colors from the canonical CSS theme
ThomasK33 Sep 7, 2026
a76b293
🤖 fix: reconcile mobile User Stop with monitor scheduling
ThomasK33 Sep 7, 2026
c11a2f9
🤖 fix: keep live answers independent of next-turn routing
ThomasK33 Sep 7, 2026
e398737
🤖 fix(mobile): share canonical tool icon semantics
ThomasK33 Sep 7, 2026
f49d463
🤖 tests: stabilize persisted sidebar split fixture
ThomasK33 Sep 7, 2026
a5c4708
🤖 fix(mobile): add focused web composer keyboard shortcuts
ThomasK33 Sep 7, 2026
00416d4
🤖 refactor: reconcile mobile branch with current main
ThomasK33 Sep 7, 2026
a2cbc9f
🤖 fix(mobile): restore target-agent reasoning mode on mode switches
ThomasK33 Sep 7, 2026
232c639
🤖 fix(mobile): respect delegated identity and transcript-only workspaces
ThomasK33 Sep 7, 2026
d95c80f
🤖 fix(mobile): initialize structured question prefills
ThomasK33 Sep 7, 2026
9bfd4cf
🤖 tests(mobile): cover untrusted question prefill keys
ThomasK33 Sep 7, 2026
57498bb
🤖 fix(mobile): refresh project catalogs on config changes
ThomasK33 Sep 8, 2026
c5bf5c2
🤖 fix(mobile): submit final fields and gate queued questions
ThomasK33 Sep 8, 2026
43828f8
🤖 fix(mobile): preserve durable Stop intent during question recovery
ThomasK33 Sep 8, 2026
be7aa44
🤖 tests(mobile): model executing questions and newer recovery turns
ThomasK33 Sep 8, 2026
2ec59b4
🤖 fix(mobile): block creation from removed project selections
ThomasK33 Sep 8, 2026
bddc6c7
🤖 fix(mobile): require trusted worktree-capable project selections
ThomasK33 Sep 8, 2026
6f65c26
🤖 fix(chat): use pinned active context limits in desktop meters
ThomasK33 Sep 8, 2026
ccdda28
🤖 fix: isolate mobile drafts and hide model-only transcript rows
ThomasK33 Sep 8, 2026
ffda71e
🤖 fix(mobile): render inspectable nested tool calls
ThomasK33 Sep 8, 2026
6e61688
🤖 fix(mobile): share legacy nested tool replay reconstruction
ThomasK33 Sep 8, 2026
19639e5
🤖 fix(auth): authorize oRPC WebSockets with single-use upgrade tickets
ThomasK33 Sep 8, 2026
c257637
🤖 fix(auth): add renderer-safe WebSocket ticket acquisition
ThomasK33 Sep 8, 2026
5ff7494
🤖 fix(auth): migrate browser token sockets to single-use tickets
ThomasK33 Sep 8, 2026
c7dc714
🤖 fix(mobile): authenticate WebSockets with single-use tickets
ThomasK33 Sep 8, 2026
b350322
🤖 fix(mobile): require the negotiated oRPC application protocol
ThomasK33 Sep 8, 2026
9106b6e
🤖 docs(mobile): explain secure WebSocket ticket requirements
ThomasK33 Sep 8, 2026
d1c1a11
🤖 fix(mobile): require TLS for every non-loopback endpoint
ThomasK33 Sep 8, 2026
3c6f3e3
🤖 fix(mobile): refresh agent catalogs with live settings
ThomasK33 Sep 8, 2026
e478b03
🤖 fix(mobile): enforce runtime policy during workspace creation
ThomasK33 Sep 8, 2026
cd97460
🤖 perf(mobile): coalesce streamed text display updates
ThomasK33 Sep 8, 2026
fd0a60c
🤖 fix(mobile): preserve header-like lines inside diff hunks
ThomasK33 Sep 8, 2026
900ea0c
🤖 fix(mobile): disable Git changes for transcript-only workspaces
ThomasK33 Sep 8, 2026
de171a0
🤖 fix(auth): retry rejected stored tokens without bearer credentials
ThomasK33 Sep 8, 2026
20cdf97
🤖 tests(auth): inspect request URLs without implicit object conversion
ThomasK33 Sep 8, 2026
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
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,6 @@ Thumbs.db
!docs/**/*.md
!docs/**/*.mdx
LICENSE

# Native companion is built separately from the desktop server.
packages/mobile/
3 changes: 3 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ jobs:
- run: make -j static-check-full
env:
MUX_ESLINT_CONCURRENCY: 4
# Mobile has an isolated dependency graph and is excluded from the root checks.
- name: Validate mobile companion
run: make -j1 mobile-check

flake-hash-check:
name: Flake Hash Check
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ docs/theme/pagetoc.js
# Removed mobile/ Expo prototype: upgraded checkouts keep untracked leftovers
# (node_modules, .expo, ios, android) that its deleted nested .gitignore used
# to hide. Ignore the whole dead path so git status stays clean.
mobile/
/mobile/
# Terminal-Bench leaderboard cache
benchmarks/terminal_bench/.leaderboard_cache/
# Terminal-Bench job output
Expand Down
59 changes: 57 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,8 @@ build/icon.png: docs/img/logo-white.svg scripts/generate-icons.ts
# Keep the default local path fast. Docs link crawling and lockfile-free bench-agent
# verification stay in static-check-full so local validation remains responsive.
static-check: lint typecheck fmt-check check-eager-imports check-code-docs-links lint-shellcheck lint-hadolint ## Run fast local static checks
@node packages/mobile/scripts/generateTheme.cjs --check
@bun test ./packages/mobile/scripts/generateTheme.test.ts

static-check-full: static-check check-bench-agent check-docs-links ## Run the full CI static check suite

Expand All @@ -393,14 +395,14 @@ lint-zizmor: ## Run zizmor security analysis on GitHub Actions workflows
# Prune any node_modules tree (not just ./node_modules) plus the removed mobile/
# prototype: upgraded checkouts may keep stale untracked leftovers there (e.g.
# CocoaPods .sh scripts under mobile/ios) that would otherwise be scanned.
SHELL_SRC_FILES := $(shell find . -not \( -path '*/.git/*' -o -path '*/node_modules/*' -o -path './mobile/*' -o -path './build/*' -o -path './dist/*' -o -path './release/*' -o -path './benchmarks/terminal_bench/.leaderboard_cache/*' \) -type f -name '*.sh' 2>/dev/null)
SHELL_SRC_FILES := $(shell find . -not \( -path '*/.git/*' -o -path '*/node_modules/*' -o -path './mobile/*' -o -path './packages/mobile/ios/*' -o -path './packages/mobile/android/*' -o -path './build/*' -o -path './dist/*' -o -path './release/*' -o -path './benchmarks/terminal_bench/.leaderboard_cache/*' \) -type f -name '*.sh' 2>/dev/null)

lint-shellcheck: ## Run shellcheck on shell scripts
@echo "Running shellcheck on $(words $(SHELL_SRC_FILES)) shell scripts..."
@shellcheck --external-sources $(SHELL_SRC_FILES)

# Dockerfiles to lint (excludes node_modules, build artifacts, .git)
DOCKERFILES := $(shell find . -not \( -path '*/.git/*' -o -path '*/node_modules/*' -o -path './mobile/*' -o -path './build/*' -o -path './dist/*' -o -path './release/*' -o -path './benchmarks/terminal_bench/.leaderboard_cache/*' \) -type f -name 'Dockerfile' 2>/dev/null)
DOCKERFILES := $(shell find . -not \( -path '*/.git/*' -o -path '*/node_modules/*' -o -path './mobile/*' -o -path './packages/mobile/ios/*' -o -path './packages/mobile/android/*' -o -path './build/*' -o -path './dist/*' -o -path './release/*' -o -path './benchmarks/terminal_bench/.leaderboard_cache/*' \) -type f -name 'Dockerfile' 2>/dev/null)

lint-hadolint: ## Run hadolint on Dockerfiles
@echo "Running hadolint on $(words $(DOCKERFILES)) Dockerfiles..."
Expand Down Expand Up @@ -588,6 +590,59 @@ test-storybook: node_modules/.installed ## Run Storybook interaction tests (requ
@# Storybook story transitions can exceed Jest's default 15s timeout on loaded CI runners.
@bun x test-storybook --testTimeout 30000

## React Native companion (isolated Expo dependency graph)
MOBILE_METRO_PORT ?= 8081

.PHONY: mobile-install mobile-web mobile-native mobile-preview mobile-export mobile-export-ios mobile-typecheck mobile-test mobile-test-web mobile-lint mobile-fmt mobile-check
mobile-install: packages/mobile/node_modules/.installed ## Install pinned mobile dependencies

packages/mobile/node_modules/.installed: packages/mobile/package.json packages/mobile/bun.lock
@cd packages/mobile && bun install --frozen-lockfile
@touch $@

# Node owns the preview's WebSocket upgrades; Bun's node:http compatibility can
# accept the upgrade but stall forwarding oRPC frames.
packages/mobile/.expo/preview.mjs: packages/mobile/scripts/preview.ts packages/mobile/src/endpoint.ts packages/mobile/node_modules/.installed
@mkdir -p packages/mobile/.expo
@bun build packages/mobile/scripts/preview.ts --target=node --format=esm --packages=external --outfile=$@

mobile-web: mobile-install packages/mobile/.expo/preview.mjs ## Run RN Web + fixed-target proxy (set XUM_MOBILE_ENDPOINT)
@test -n "$$XUM_MOBILE_ENDPOINT" || (echo 'Set XUM_MOBILE_ENDPOINT to your Xum server URL'; exit 1)
@XUM_MOBILE_METRO=http://127.0.0.1:$(MOBILE_METRO_PORT) bun x concurrently -k \
"cd packages/mobile && BROWSER=none bun x expo start --web --port $(MOBILE_METRO_PORT)" \
"node packages/mobile/.expo/preview.mjs"

mobile-native: mobile-install ## Start Expo for a native device or simulator
@cd packages/mobile && bun x expo start --port $(MOBILE_METRO_PORT)

mobile-preview: mobile-install packages/mobile/.expo/preview.mjs ## Serve exported RN Web through the fixed-target proxy
@XUM_MOBILE_STATIC_DIR=$(CURDIR)/packages/mobile/dist node packages/mobile/.expo/preview.mjs

mobile-export: mobile-install ## Export production React Native Web
@cd packages/mobile && bun x expo export --platform web

mobile-export-ios: mobile-install ## Compile the iOS JS bundle (not a native simulator build)
@cd packages/mobile && bun x expo export --platform ios --output-dir dist-ios

mobile-typecheck: mobile-install ## Typecheck the mobile app against shared Xum contracts
@cd packages/mobile && bun x tsc --noEmit

mobile-test: mobile-install packages/mobile/.expo/preview.mjs ## Test mobile protocol, transcript, and preview safety
@cd packages/mobile && bun test src scripts

mobile-test-web: mobile-install ## Test native-web navigation/layout against a disposable running preview
@cd packages/mobile && bun x playwright test

mobile-lint: mobile-install ## Lint native components and mobile infrastructure
@cd packages/mobile && ../../node_modules/.bin/eslint . --max-warnings 0

mobile-fmt: ## Format mobile source and configuration
@cd packages/mobile && ../../node_modules/.bin/prettier --write '**/*.{ts,tsx,json,mjs,cjs}'

mobile-check: mobile-typecheck mobile-test mobile-lint ## Run mobile validation independently of desktop dependencies
@node packages/mobile/scripts/generateTheme.cjs --check
@cd packages/mobile && ../../node_modules/.bin/prettier --check '**/*.{ts,tsx,json,mjs,cjs}'
Comment thread
ThomasK33 marked this conversation as resolved.

## Benchmarks
benchmark-terminal: ## Run Terminal-Bench 2.0 with Harbor (use TB_HARBOR_PACKAGE/TB_HARBOR_DAYTONA_PACKAGE/TB_DATASET/TB_CONCURRENCY/TB_TIMEOUT/TB_ENV/TB_MODEL/TB_ARGS to customize)
@# Pin Harbor with the Daytona extra so scheduled ingestion does not break on future CLI or adapter API drift.
Expand Down
6 changes: 5 additions & 1 deletion docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,11 @@
},
{
"group": "Integrations",
"pages": ["integrations/vscode-extension", "integrations/acp"]
"pages": [
"integrations/vscode-extension",
"integrations/acp",
"integrations/mobile-app"
]
},
{
"group": "Reference",
Expand Down
126 changes: 126 additions & 0 deletions docs/integrations/mobile-app.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
---
title: Mobile companion
description: Develop the React Native Xum companion and connect it to your server.
---

The experimental mobile companion lives in `packages/mobile`. It uses native React Native views, with React Native Web for browser development—not an embedded copy of the desktop website.

It connects to your existing Xum server for projects, workspace creation, conversations, agent/model selection, and read-only changes. On phones, a searchable workspace list opens conversations in a native navigation stack; wider screens keep the workspace sidebar visible. Phone workspace search and creation stay in a bottom dock, while wide layouts keep their controls above the list. Conversation headers show project and server context with grouped navigation actions. Drafts and unsent model choices survive returning to the list. The composer stays compact when empty and unfocused, expands for writing, and stays at the bottom with mode/model controls directly above it. Creation uses a sheet with a pinned action. The composer's separate model and mode controls open focused pickers; selections apply immediately, and changing mode preserves the chosen model and effort. Search models directly in the model picker by name, provider, or alias. The grouped list follows model visibility in Settings and includes models available through configured gateway routes; the current selection remains visible even if subsequently hidden. Use Effort to adjust thinking. Custom model IDs require confirmation. Tool activity stays compact in the conversation; tap a tool to inspect its input, output, and status. Provider configuration, terminal/desktop access, and advanced administration remain in the main Xum app.

## Connect to a server

Enable [server access](/config/server-access), or start `xum server`. Use a trusted HTTPS endpoint accessible from the device and enter the server's bearer token separately. Include any reverse-proxy path prefix in the endpoint. A Coder login page or another upstream authentication layer may require additional network access; the Xum token does not authenticate to that outer layer.

During development, run the mobile client and server from the same branch/revision. Their shared API contract evolves together; for example, the multi-repository changes view requires the server's bulk project-diff endpoint.

The token grants access to the server, including its code-execution capabilities. Treat it like a password. Native builds save connection details in device secure storage. The web preview keeps them in memory only; refreshing requires entering them again. Disconnect clears the saved native connection.

Before opening a WebSocket, the companion exchanges the token in an HTTP Authorization header for a short-lived, single-use upgrade ticket. The long-lived token is not included in the WebSocket URL or subprotocols. Older servers without ticket support must be updated; there is no credential-URL fallback.

All non-loopback endpoints—including private LAN, ULA, and link-local addresses—require HTTPS. HTTP is accepted only for `localhost`, IPv4 loopback (`127.0.0.0/8`), or IPv6 loopback (`[::1]`) for development, with a plaintext-token warning. A phone's `localhost` refers to the phone, not your development computer: use a trusted HTTPS endpoint to reach that computer from a device.

## Develop with React Native Web

Install the repository's Bun dependencies and use Node.js 22.19 or later for Expo and the preview proxy:

```bash
bun install
make mobile-install

# Point this at a running Xum instance. Do not put the token in the URL.
XUM_MOBILE_ENDPOINT=http://127.0.0.1:3000 make mobile-web
```

Open `http://127.0.0.1:8082` in a current Chromium browser, then enter that configured **server endpoint** and its token. Metro runs on port 8081; use the proxy on 8082, not Metro's direct URL, for API access. The web preview uses CSS content sizing for the composer; native builds use React Native's text measurement.

With the Message composer focused, Enter sends in desktop-sized windows with a fine pointer; Shift+Enter inserts a newline. Narrow windows and coarse-pointer devices keep Enter for newlines. Ctrl+Enter (or Cmd+Enter on macOS) sends while idle on either layout. Escape uses the active conversation's Stop action. Shortcuts respect disabled actions and composition, do not run from other inputs or modal dialogs, and do not queue messages or stop a turn when Enter is pressed during streaming.

The preview forwards to exactly one endpoint configured at startup. It checks the request Host and Origin before forwarding, strips preview cookies/forwarded identity, and preserves the upstream path prefix. It does not relax the production server's origin protections. Native builds connect directly and do not need this proxy.

Optional development settings:

- `MOBILE_METRO_PORT`: Metro port (Make variable).
- `XUM_MOBILE_PORT`: preview port, default 8082.
- `XUM_MOBILE_ORIGIN`: exact public preview origin when forwarding this loopback-bound server; the forwarding proxy must preserve that Host.
- `XUM_MOBILE_ENDPOINT`: the fixed Xum target; restart the preview to change it.

Serve a production web export:

```bash
make mobile-export
XUM_MOBILE_ENDPOINT=http://127.0.0.1:3000 make mobile-preview
```

The preview is development tooling, not a general-purpose public proxy. It intentionally runs under Node: Bun's Node HTTP compatibility can stall forwarded WebSocket frames.

## Native development

```bash
make mobile-native
```

Use Expo's device/simulator workflow with the installed SDK-compatible client or development build. The native app uses `expo-secure-store` and safe-area/keyboard-aware layouts. Native networking, keyboard behavior, secure storage, and background/resume behavior still need device testing; a successful JavaScript export does not establish that they work on iOS.

```bash
# Compiles the iOS JavaScript/Hermes bundle; does not launch or build a simulator app.
make mobile-export-ios
```

Send/Stop keyboard shortcuts currently apply only to React Native Web. Native software-keyboard Enter behavior is unchanged; native hardware-keyboard Send/Stop bindings are not implemented. The installed native TextInput APIs do not expose the modifier/source information needed for that behavior without additional native integration.

The mobile dependency graph and lockfile are isolated from desktop React. Update SDK-compatible versions together and run `bun x expo install --check` from `packages/mobile` after dependency changes.

## Validation and dogfooding

```bash
make mobile-check
make mobile-export
make mobile-export-ios
```

`mobile-check` runs typechecking against the shared API schemas, lint/format checks, and endpoint, transport, transcript, lifecycle, and preview-proxy tests. For the opt-in real-server test, use a **disposable** Xum root with `XUM_MOCK_AI=1`, then run:

```bash
cd packages/mobile
XUM_MOBILE_TEST_ENDPOINT=http://127.0.0.1:3000 \
XUM_MOBILE_TEST_TOKEN=your-disposable-server-token \
bun test ./scripts/server.integration.test.ts
```

That test creates and removes a scratch workspace. It exercises real authentication, persistence, streaming and reconnect/replay; only the model response is deterministic.

With the production preview running against that same disposable server, run the full-app browser regressions from the repository root:

```bash
# One-time browser installation
(cd packages/mobile && bun x playwright install chromium)
XUM_MOBILE_TEST_ENDPOINT=http://127.0.0.1:3000 \
XUM_MOBILE_TEST_TOKEN=your-disposable-server-token \
make mobile-test-web
```

These tests pin 375px, 390px, and 1200px viewports, create and remove scratch chats, and check draft/model retention, keyboard focus, and reachable sheet actions. Set `XUM_MOBILE_TEST_WEB_URL` if the preview is not at `http://127.0.0.1:8082`.

For a browser walkthrough, use the production preview and a phone viewport around 375–390 pixels, then repeat at tablet/desktop width:

1. Check invalid URL, wrong token, and successful connection.
2. Open the workspace navigator; create a scratch chat and a project workspace.
3. Send a message, observe streamed text/tools/reasoning, and interrupt a running turn.
4. Change agent/model settings, switch workspaces, and verify conversations do not mix.
5. Open Changes and Settings; disconnect and confirm credentials are not retained in browser storage.
6. Drop the connection, retry, and verify authoritative history reloads before sending is enabled.
7. Capture screenshots and a short recording of the walkthrough, including narrow layouts and any failure/recovery steps.

## Can Xum run inside the native JS engine?

**Not the existing backend unchanged.** Hermes executes JavaScript, but it does not provide Xum's Node filesystem/process APIs, shell/git toolchain, PTY bindings, or database/native addons. The companion runs its UI and client logic locally; agent execution stays on the server.

A native Node sidecar would be a separate runtime and substantial platform port. It would not make the desktop shell tools available inside an iOS sandbox. This app does not advertise an embedded backend mode.

Research references:

- [React Native core/native views](https://reactnative.dev/docs/intro-react-native-components)
- [Hermes](https://reactnative.dev/docs/hermes)
- [Expo web support](https://docs.expo.dev/workflow/web/)
- [SecureStore](https://docs.expo.dev/versions/latest/sdk/securestore/)
- [Node.js Mobile's separate native runtime](https://nodejs-mobile.github.io/docs/guide/guide-react-native/getting-started/)
7 changes: 7 additions & 0 deletions packages/mobile/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.expo/
node_modules/
dist/
dist-ios/
ios/
android/
*.tsbuildinfo
6 changes: 6 additions & 0 deletions packages/mobile/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.expo/
dist/
dist-ios/
ios/
android/
node_modules/
Loading
Loading