Skip to content

Commit 33e2b72

Browse files
matej21jonasnobileclaude
authored
feat(mobile): rebase iOS PR #17 onto main + scaffold Flutter→RN migration (#136)
* fix(terminal): clamp resize to minimum 1 col/row to prevent panic Prevents alacritty from panicking when receiving zero-dimension resize events, which can occur during layout transitions on mobile. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(core): add terminal size propagation in layout tree Add collect_terminal_sizes() to walk the layout tree and build a size map. Update ConnectionHandler::create_terminal to accept cols/rows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(desktop): include folders, project order, and terminal sizes in state response Build terminal size map from registry and use to_api_with_sizes() to populate cols/rows in layout nodes. Include folders and project_order in state responses for mobile/web clients. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore(mobile/ios): configure iOS project with CocoaPods and signing Set up CocoaPods integration, development team, scene manifest, local networking permissions, and rename native library to okena_mobile_native. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(mobile): add design system with OkenaColors and OkenaTypography Introduce centralized color palette (backgrounds, borders, accent, text hierarchy, glass effects) and typography system (SF Pro Text) for iOS-native dark theme. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(mobile): add terminal scroll, resize, and display offset FFI Add scroll_terminal, get_display_offset, and resize_local functions. Improve wide char spacer handling and move inverse flag to painter. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(mobile): add folders, project ordering, and terminal management FFI Add folder info, project ordering, server terminal size, and create/close/focus terminal actions. Update handler to use server terminal dimensions when creating terminals. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(mobile): redesign app with iOS-native dark theme and terminal improvements Rewrite all screens and widgets with Cupertino-inspired design: frosted glass headers, card-based layouts, haptic feedback, animated status indicators. Add pinch-to-zoom, scroll support, auto-fit font sizing, modifier key system, and text batching optimization to terminal rendering. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore(mobile): regenerate flutter_rust_bridge bindings Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore(mobile): remove google_fonts dependency and add devtools config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(mobile): add LayoutNode sealed class model and update tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(mobile): extract send_action_with_response in ConnectionManager Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(mobile): add fullscreen, git, services, and terminal management FFI Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore(mobile): regenerate flutter_rust_bridge bindings Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(mobile): add fullscreen, services, git status, and layout management UI Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(mobile): add terminal selection and scroll info APIs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(mobile): add layout management, project reorder, and git file contents FFI Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore(mobile): regenerate flutter_rust_bridge bindings Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(mobile): add resizable splits, tab management, and minimized terminal UI Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(mobile): add project drawer enhancements (add project, reorder, color picker) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(mobile): add git diff viewer and file contents viewer Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ui): make find_word_boundaries work with byte offsets for UTF-8 correctness Rewrite to operate on byte offsets with char boundary checks instead of collecting into a Vec<char>, which gave wrong results for multi-byte characters. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(terminal): deregister inactive tab panes and add tab-aware navigation Deregister pane map entries for inactive tabs so stale bounds don't interfere with spatial navigation. Add try_switch_tab() so Left/Right keys cycle tabs before falling through to cross-pane navigation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(rebase): pass window_id to deregister_pane_bounds in tabs container The tab-pane deregistration added in feat/ios predates main's multi-window support, which gave deregister_pane_bounds a leading WindowId parameter. Adapt the inactive-tab cleanup callsite to main's signature so the rebased branch compiles. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(mobile): add React Native migration plan (uniffi + native Skia rendering) Plan to replace the Flutter UI with React Native while keeping okena-core and alacritty emulation. Primary path: uniffi-bindgen-react-native (JSI) + react-native-skia for native terminal rendering; xterm.js explicitly rejected. Documents the FFI seam, the hot-path packed-buffer bridge, a spike-gated phased plan, and the "drop Rust" fallback (reuse the web TS protocol client, still native rendering). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(mobile-ffi): uniffi binding crate for the React Native migration (Phase 1) New crate `crates/okena-mobile-ffi` re-expresses mobile/native's ~60-function FFI surface via uniffi proc-macros (uniffi 0.29, JSI/ubrn-ready) — no logic duplication: every fn delegates to okena_mobile_native's ConnectionManager, which is reused verbatim (its crate-type gains "lib" so it can be a path dep). - 31 genuinely-async fns exported with #[uniffi::export(async_runtime="tokio")] → JS Promises; sync getters (cells/cursor/scroll/selection/state) stay sync for the render hot path. - Adds get_visible_cells_packed(): the visible grid as a compact little-endian buffer (4B cols/rows header + 13B/cell: codepoint u32, fg u32, bg u32, flags u8) for the RN Skia renderer (RN_MIGRATION.md Decision C). - connect() accepts tls + pinned cert fingerprint at the boundary, but they are a documented no-op pass-through: client-side TLS lives on arch-review-fixes (PR #134), not on this branch's okena-core. Wiring is a follow-up. Verified: cargo check passes for okena-mobile-ffi AND okena_mobile_native (Flutter Rust side intact). Does not touch okena-core, Dart, or frb codegen. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(mobile-rn): RN binding contract + native Skia terminal renderer (Phase 2/3 scaffold) Scaffolds the React Native side under mobile/rn/ — the two technically-meaty pieces, not a full app: - src/native/okena.ts — the native↔TS binding contract: typed OkenaNative interface for all ~60 FFI fns + record/enum types, sync/async split mirroring the Rust side. ubrn generates the real impl from crates/okena-mobile-ffi. - src/native/cells.ts — packed cell-buffer decoder, byte-for-byte matching the Rust get_visible_cells_packed format (+ a zero-alloc PackedCells view). - src/components/TerminalView.tsx — terminal_painter.dart's 3-pass paint ported to @shopify/react-native-skia (no xterm.js): bg rects, style-batched glyph runs, cursor; rAF repaint gated on isDirty (Decision C); onLayout sizing. - src/theme.ts, package.json, tsconfig.json, README.md (exact local build steps). Verified on this box: npm install + tsc --noEmit (strict) pass; Skia APIs type-checked against @shopify/react-native-skia@1.5.x. Device build is not possible here (no Android/iOS SDK, no ubrn-generated module) — README documents the local steps. node_modules gitignored. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(mobile-rn): app shell — state, navigation, screens, drawer, toolbar, layout (Phase 2) Builds the RN UI on the existing contract + Skia renderer: - Foundation: SavedServer + LayoutNode models (parseLayout mirrors the Flutter parser), zustand connection/workspace stores (polling cadence ported from the Flutter providers, native module injectable for tests), AsyncStorage-backed persistence behind a swappable interface, and a minimal state-driven router (no react-navigation native deps). App.tsx wires connection status → nav and drives workspace polling. - Screens: ServerList (list + add-server sheet), Pairing (connect→code→paired with TLS-fingerprint footnote), Workspace (app bar + drawer + layout + toolbar). - Widgets: ProjectDrawer (custom slide-in; projects/folders, add/reorder/color), KeyToolbar (ESC/TAB + sticky CTRL/ALT/CMD with control-char + CSI encoding, shared modifier store), LayoutRenderer (recursive split/tabs with portrait rotation), TerminalPane (hidden TextInput, tap-focus, scroll/selection gestures around the Skia TerminalView), StatusIndicator. Ported from mobile/lib/src/{models,providers,screens,widgets}. Verified: npm install + npx tsc --noEmit (strict) pass over all 22 RN source files. No device build here (no SDK / ubrn module) — see mobile/rn/README.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(mobile-ffi): make okena-mobile-ffi self-contained on uniffi 0.31 Move the plain-Rust engine (ConnectionManager, MobileConnectionHandler, TerminalHolder) and the api data structs out of the retired flutter_rust_bridge crate mobile/native into crates/okena-mobile-ffi, stripping the #[frb] attributes. Drop the okena_mobile_native path dependency (and the transitive flutter_rust_bridge build) so the crate depends only on okena-core + uniffi. Remove the now-dead execute_action/spawn helpers, and bump uniffi 0.29 -> 0.31 to match uniffi-bindgen-react-native 0.31 used by mobile/rn. Drop mobile/native from the workspace members. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(mobile-rn): add RN host project, tooling, and ubrn wiring Turn the mobile/rn scaffold into a complete React Native 0.76 project (minus the machine-generated native host dirs): index.js, app.json, metro/babel/ react-native config, jest + eslint + prettier, and the RN 0.76 devDependency set. Add ubrn.config.yaml and ubrn:* scripts, and wire getOkenaNative() to load the generated module from src/generated. Relocate the JetBrainsMono fonts to mobile/rn/assets. npm run typecheck + lint + test all pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(mobile): remove the Flutter app; update workspace, CI, and docs Delete the retired Flutter client (lib, ios/android/linux shells, tests, cargokit, pubspec/flutter configs). Replace the Flutter build-mobile CI job with a mobile-rn job (npm typecheck + lint) and drop it from the release deps. Update the root README/CLAUDE, docs/mobile-status.md, .gitignore, and the RN_MIGRATION status to describe the React Native + uniffi stack. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): make clippy --all-targets green on the branch Pre-existing breakage surfaced by `cargo clippy --workspace --all-targets` (CI's "Check compilation" runs `cargo check --release`, which skips tests, so these test-only failures slipped through): - remote_apply.rs / client/state.rs test helpers: add the `cols`/`rows` fields added in ac0a91b, plus the missing ApiProject fields and the `is_visible` → `show_in_overview` rename. - text_utils.rs: silence newer clippy lints (is_some_and, drop unwrap()). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(remote-client): use rustls `ring` provider instead of `aws-lc-rs` `aws-lc-rs`'s jitter-entropy init (`jent_entropy_init`) segfaults on Android when rustls builds a TLS client connection, crashing the mobile app on connect (SIGSEGV in libokena-mobile-ffi during the TLS handshake setup). Switch the rustls crypto provider to `ring`, which is portable on every target (incl. the Android NDK) and is already what reqwest 0.12's `rustls-tls` feature pulls in for hyper/tokio-rustls — so a single provider is shared across reqwest + tungstenite. Applied to both the shared client (okena-core) and the desktop remote server (src/remote/tls.rs) so the whole workspace uses one provider; this also drops aws-lc-rs/aws-lc-sys from the build entirely. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(mobile-rn): ubrn enum/record adapters, skia setEmbolden, @ubjs/core alias Reconcile the hand-written `OkenaNative` contract with the shapes ubrn 0.31 actually emits, plus two build/render fixes surfaced by running the app: - okena.ts: translate at the `getOkenaNative` boundary — ConnectionStatus (PascalCase `.tag` → lowercase `.kind`, Error payload from `.inner.message`), CursorShape (numeric enum → string union), and ProjectInfo.terminalNames (JS `Map` → plain object). Without these the status pill and terminal cursor crash on render (`Cannot read property 'color' of undefined`). - TerminalView.tsx: drop the synthetic `setEmbolden` calls. react-native-skia 1.12.4's native binding rejects the (typed `boolean`) arg with "Value is false, expected a number"; all four JetBrainsMono variants are bundled so the fallback is unused, and italic is still synthesized via `setSkewX`. - metro.config.js: alias `@ubjs/core` (ubrn's renamed TS runtime, imported by the generated bindings) to the runtime already shipped inside uniffi-bindgen-react-native, avoiding a second, version-skewed copy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(mobile-rn): commit okena-mobile-ffi package manifest + document Android integration The ubrn turbo module is kept as a local package (`modules/okena-mobile-ffi/`, a `file:` dependency of the app) rather than letting ubrn clobber the app's root `android/build.gradle`. Track only its hand-authored `package.json` (so the `file:` dep resolves on a fresh checkout / in CI — `npm ci` no longer fails); everything else under it is ubrn-generated and stays gitignored (incl. the multi-hundred-MB Rust static lib — must never be committed). README: add a "Verified Android run" section documenting what the end-to-end run actually required — the local-package layout, the committed fixes (rustls `ring`, ubrn enum/record adapters, dropped Skia `setEmbolden`, `@ubjs/core` Metro alias), the post-generation fixups (Node≥20 CMakeLists `require.resolve`, missing `AndroidManifestNew.xml`), `okena pair` for the pairing code, and the emulator GL-present crash that makes a physical device necessary for rendering. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(rebase): adapt mobile-ffi and CLI to rebased main API Post-rebase fixes for API changes that landed on main under the branch: - send_special_key: bind to_bytes() result before borrowing, since SpecialKey::to_bytes() now returns Cow<'static, [u8]> (Ctrl(char) support). - FocusTerminal / SetFullscreen: pass window: None, matching the new Option<String> window field added for multi-window targeting. - render_layout / render_terminal_line + resolve.rs test helper: account for the new cols/rows fields on ApiLayoutNode::Terminal (terminal-size propagation). - code_view test_col_beyond_length: update expectation to the byte-offset find_word_boundaries behavior (clicking past end-of-line selects nothing). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): satisfy clippy manual_checked_ops in mobile-ffi Clippy (-D warnings) flags the manual zero-check before division in the cols computation. Use checked_div().unwrap_or(0) — behavior-identical (visible == 0 still yields 0 cols), lint-clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: jonasnobile <mesons_rates0p@icloud.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ca0d3fc commit 33e2b72

236 files changed

Lines changed: 21855 additions & 14995 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build.yml

Lines changed: 26 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -67,100 +67,38 @@ jobs:
6767
- name: Run tests
6868
run: cargo test
6969

70-
# Mobile builds: Android APK
71-
# iOS temporarily disabled: macos-latest Xcode now requires a Development Team /
72-
# provisioning profile even with `flutter build ios --no-codesign`, which breaks
73-
# the unsigned device build. Re-enable once codesigning is sorted.
74-
build-mobile:
75-
if: ${{ startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' }}
76-
strategy:
77-
fail-fast: false
78-
matrix:
79-
include:
80-
- os: ubuntu-latest
81-
platform: android
82-
artifact: okena-android
83-
84-
runs-on: ${{ matrix.os }}
85-
70+
# React Native mobile client — TypeScript checks.
71+
#
72+
# The native app build (uniffi-bindgen-react-native cross-compiling
73+
# crates/okena-mobile-ffi to an Android NDK .so / iOS xcframework, then
74+
# `react-native run-*`) needs the mobile toolchain and is NOT yet wired into
75+
# CI — see mobile/rn/README.md for the device-side steps. Until then CI
76+
# type-checks and lints the RN/TS sources; the Rust FFI crate itself is
77+
# already covered by the `check` job's workspace build + clippy + tests.
78+
mobile-rn:
79+
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
80+
runs-on: ubuntu-latest
81+
defaults:
82+
run:
83+
working-directory: mobile/rn
8684
steps:
8785
- uses: actions/checkout@v4
8886

89-
- name: Install Rust toolchain
90-
uses: dtolnay/rust-toolchain@master
91-
with:
92-
toolchain: "1.93"
93-
94-
- name: Add Android Rust targets
95-
if: matrix.platform == 'android'
96-
run: |
97-
rustup target add armv7-linux-androideabi
98-
rustup target add aarch64-linux-android
99-
rustup target add x86_64-linux-android
100-
rustup target add i686-linux-android
101-
102-
- name: Add iOS Rust targets
103-
if: matrix.platform == 'ios'
104-
run: |
105-
rustup target add aarch64-apple-ios
106-
rustup target add x86_64-apple-ios
107-
108-
- name: Setup Rust cache
109-
uses: Swatinem/rust-cache@v2
87+
- name: Setup Node
88+
uses: actions/setup-node@v4
11089
with:
111-
shared-key: mobile-${{ matrix.platform }}
112-
workspaces: mobile/native
113-
114-
- name: Setup Java (Android)
115-
if: matrix.platform == 'android'
116-
uses: actions/setup-java@v4
117-
with:
118-
distribution: temurin
119-
java-version: 17
120-
121-
- name: Setup Flutter
122-
uses: subosito/flutter-action@v2
123-
with:
124-
channel: stable
125-
126-
- name: Flutter pub get
127-
working-directory: mobile
128-
run: flutter pub get
90+
node-version: 20
91+
cache: npm
92+
cache-dependency-path: mobile/rn/package-lock.json
12993

130-
- name: Build Android APKs
131-
if: matrix.platform == 'android'
132-
working-directory: mobile
133-
run: |
134-
flutter build apk --release --split-per-abi
135-
flutter build apk --release
94+
- name: Install dependencies
95+
run: npm ci
13696

137-
- name: Prepare Android artifact
138-
if: matrix.platform == 'android'
139-
run: |
140-
mkdir -p dist
141-
cp mobile/build/app/outputs/flutter-apk/app-arm64-v8a-release.apk dist/okena-arm64-v8a.apk
142-
cp mobile/build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk dist/okena-armeabi-v7a.apk
143-
cp mobile/build/app/outputs/flutter-apk/app-x86_64-release.apk dist/okena-x86_64.apk
144-
cp mobile/build/app/outputs/flutter-apk/app-release.apk dist/okena-universal.apk
145-
146-
- name: Build iOS (no codesign)
147-
if: matrix.platform == 'ios'
148-
working-directory: mobile
149-
run: flutter build ios --release --no-codesign
150-
151-
- name: Prepare iOS artifact
152-
if: matrix.platform == 'ios'
153-
run: |
154-
mkdir -p dist
155-
cd mobile/build/ios/iphoneos
156-
zip -r ../../../../dist/okena-ios.zip Runner.app
97+
- name: Typecheck
98+
run: npm run typecheck
15799

158-
- name: Upload artifact
159-
uses: actions/upload-artifact@v4
160-
with:
161-
name: ${{ matrix.artifact }}
162-
path: dist/
163-
retention-days: 7
100+
- name: Lint
101+
run: npm run lint
164102

165103
# Full multi-platform build: tags + manual trigger
166104
build:
@@ -260,7 +198,7 @@ jobs:
260198

261199
# Create release when a tag is pushed
262200
release:
263-
needs: [build, build-mobile]
201+
needs: [build]
264202
if: startsWith(github.ref, 'refs/tags/v')
265203
runs-on: ubuntu-latest
266204
permissions:

.gitignore

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,18 @@ settings.local.json
1818

1919
dist
2020

21-
# Mobile (Flutter)
22-
mobile/.dart_tool/
23-
mobile/build/
24-
mobile/.flutter-plugins
25-
mobile/.flutter-plugins-dependencies
26-
mobile/pubspec.lock
27-
mobile/native/target/
21+
# Mobile (React Native)
22+
mobile/rn/node_modules/
23+
mobile/rn/ios/Pods/
24+
mobile/rn/ios/build/
25+
mobile/rn/android/.gradle/
26+
mobile/rn/android/build/
27+
mobile/rn/android/app/build/
28+
mobile/rn/.cxx/
29+
# uniffi-bindgen-react-native generated output (regenerated by `npm run ubrn:*`)
30+
mobile/rn/src/generated/
31+
mobile/rn/cpp/generated/
32+
mobile/rn/rust_modules/
2833

2934
# Web client
3035
web/node_modules/

CLAUDE.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ On Windows, build from **x64 Native Tools Command Prompt for VS 2022** to avoid
2020

2121
```
2222
src/ # Desktop app — main binary, GPUI views, app coordinator
23-
crates/ # Library crates (23 crates, see below)
24-
mobile/ # Mobile app (Flutter + Rust FFI)
23+
crates/ # Library crates (24 crates, see below)
24+
mobile/ # Mobile app — React Native UI (mobile/rn) over the Rust core via uniffi (crates/okena-mobile-ffi)
2525
web/ # Web client (React + TypeScript + xterm.js)
2626
assets/ # Fonts, icons (assets/icons/*.svg referenced as icons/*.svg)
2727
scripts/ # Build & utility scripts
@@ -56,6 +56,7 @@ Most logic lives in `crates/`. The `src/` modules are thin re-exports (`pub use
5656
| `okena-ext-github` | GitHub status extension |
5757
| `okena-ext-updater` | Self-update system |
5858
| `okena-core` | Shared types, API client, key handling |
59+
| `okena-mobile-ffi` | uniffi FFI surface for the React Native mobile app (`mobile/rn`); self-contained ConnectionManager / TerminalHolder engine over `okena-core` |
5960

6061
## Module-Specific Context
6162

@@ -68,5 +69,5 @@ Read these when working in the corresponding areas:
6869
- `crates/okena-workspace/CLAUDE.md` — State management, LayoutNode tree, persistence
6970
- `crates/okena-terminal/CLAUDE.md` — PTY threading model, shell detection
7071
- `crates/okena-git/CLAUDE.md` — Diff parsing, worktree operations
71-
- `mobile/CLAUDE.md`Flutter + Rust FFI mobile app
72+
- `mobile/rn/CLAUDE.md`React Native mobile app (uniffi over `okena-mobile-ffi`)
7273
- `web/CLAUDE.md` — React web client

0 commit comments

Comments
 (0)