Skip to content

feat(mobile): rebase iOS PR #17 onto main + scaffold Flutter→RN migration#136

Draft
matej21 wants to merge 30 commits into
mainfrom
feat/mobile-rn
Draft

feat(mobile): rebase iOS PR #17 onto main + scaffold Flutter→RN migration#136
matej21 wants to merge 30 commits into
mainfrom
feat/mobile-rn

Conversation

@matej21

@matej21 matej21 commented Jun 8, 2026

Copy link
Copy Markdown
Member

What this is

Two layers on one branch:

  1. PR feat/ios #17 (feat/ios) rebased onto current main. PR feat/ios #17 was ~242 commits behind main and CONFLICTING. This branch replays it on top of current main (5 desktop conflicts resolved + one post-rebase API fixup). Builds and tests pass.
  2. Start of the Flutter → React Native migration (uniffi + native Skia rendering, no xterm.js), per mobile/RN_MIGRATION.md.

Closes the rebase need of #17; the RN work is additive and does not touch the desktop app.

Layer 1 — rebased iOS/mobile work (from #17)

Conflicts resolved against main's evolution:

  • okena-terminal/.../resize.rsterminal.rs was split into a terminal/ dir in main; the min-1 col/row clamp was re-applied in the new location.
  • remote_commands.rs — combined main's api_project_visibility with feat/ios #17's to_api_with_sizes (terminal sizes in state).
  • navigation.rs — semantic merge of main's multi-window + FocusManager with feat/ios #17's tab-aware navigation.
  • views/window/{mod,render}.rs — took main's side; dropped feat/ios #17's desktop "ensure-visible vs center scroll" tweak (it renamed a field main references in 5 other places; unrelated to mobile).
  • fix(rebase): deregister_pane_bounds gained a window_id arg in main; adapted the tab-container callsite.

Verified: cargo check -p okena -p okena_mobile_native ✅ · cargo test -p okena-terminal -p okena-layout → 92 passed ✅

Layer 2 — React Native migration (Phase 1–2 scaffold)

  • mobile/RN_MIGRATION.md — the plan: keep okena-core + alacritty, swap the binding (flutter_rust_bridge → uniffi/ubrn, JSI), render the terminal natively via react-native-skia. xterm.js explicitly rejected. Documents the hot-path packed-buffer bridge and a "drop Rust" fallback.
  • crates/okena-mobile-ffi — uniffi binding crate (uniffi 0.29, 61 exports) that reuses okena_mobile_native's ConnectionManager/handler/holder verbatim (no logic duplication; its crate-type gains "lib"). Adds get_visible_cells_packed() (compact LE cell buffer for the renderer). cargo check passes for it and okena_mobile_native (Flutter Rust side intact).
  • mobile/rn/ — the RN app: a typed OkenaNative binding contract + packed-cell decoder, a Skia TerminalView (3-pass paint ported from terminal_painter.dart), zustand state stores (ported from the Flutter providers), models, navigation, persistence, and the screens/widgets (ServerList, Pairing, Workspace, ProjectDrawer, KeyToolbar, LayoutRenderer, TerminalPane). All 15 Flutter app source files have an RN counterpart. Verified: npm install + npx tsc --noEmit (strict) pass over all 22 RN source files.

Not done yet (honest status — why this is a draft)

  • Never built/run on a device. No Android/iOS SDK here, and the ubrn-generated native module is not wired — getOkenaNative() throws until it is. So this is a structurally-complete, type-checked port, not a proven-equivalent running app.
  • TLS is a no-op in the uniffi connect() — client-side TLS lives on arch-review-fixes (PR Architecture review fixes: PTY starvation, persistence durability, remote/mobile leaks #134), not on main. Forward-compatible signature is in place.
  • Dart tests not ported (layout_node_test, saved_server_test, terminal_flags_test, widget_test); no RN test runner yet.
  • Deliberate adaptations: clipboard write, swipe-delete→long-press, split-divider drag-resize, SafeArea, and per-cell metrics are simplified/omitted (spike-level). Build tooling (cargokit) is replaced by ubrn, not ported.

Next steps (require a local RN toolchain)

  1. ubrn build android/ios --and-generate over crates/okena-mobile-ffi; replace getOkenaNative()'s body with the generated module (mobile/rn/README.md has exact commands).
  2. Run on device → validate the Skia renderer holds 60fps (Phase 0 spike S2).
  3. Wire TLS once PR Architecture review fixes: PTY starvation, persistence durability, remote/mobile leaks #134 lands on main.

Reviewing

The diff is large because it includes the #17 rebase. The desktop/Flutter portion was reviewed in #17; the new work in this PR is the RN migration: mobile/RN_MIGRATION.md, crates/okena-mobile-ffi/, and mobile/rn/.

🤖 Generated with Claude Code

jonasnobile and others added 30 commits June 8, 2026 16:28
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>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ApiFolder struct, folders/project_order fields to StateResponse,
folder_color to ApiProject, and cols/rows to ApiLayoutNode::Terminal
for terminal size propagation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
…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>
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>
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>
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>
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>
…provements

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>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ment UI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ntents FFI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…minal UI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…color picker)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…orrectness

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>
…tion

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>
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>
…endering)

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>
… (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>
…(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>
…bar, 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants