feat(mobile): rebase iOS PR #17 onto main + scaffold Flutter→RN migration#136
Draft
matej21 wants to merge 37 commits into
Draft
feat(mobile): rebase iOS PR #17 onto main + scaffold Flutter→RN migration#136matej21 wants to merge 37 commits into
matej21 wants to merge 37 commits into
Conversation
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>
…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>
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>
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>
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>
`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>
…re 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>
… 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
Two layers on one branch:
feat/ios) rebased onto currentmain. PR feat/ios #17 was ~242 commits behindmainandCONFLICTING. This branch replays it on top of currentmain(5 desktop conflicts resolved + one post-rebase API fixup). Builds and tests pass.xterm.js), permobile/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.rs—terminal.rswas split into aterminal/dir inmain; the min-1 col/row clamp was re-applied in the new location.remote_commands.rs— combinedmain'sapi_project_visibilitywith feat/ios #17'sto_api_with_sizes(terminal sizes in state).navigation.rs— semantic merge ofmain's multi-window +FocusManagerwith feat/ios #17's tab-aware navigation.views/window/{mod,render}.rs— tookmain's side; dropped feat/ios #17's desktop "ensure-visible vs center scroll" tweak (it renamed a fieldmainreferences in 5 other places; unrelated to mobile).fix(rebase):deregister_pane_boundsgained awindow_idarg inmain; 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: keepokena-core+ alacritty, swap the binding (flutter_rust_bridge→ uniffi/ubrn, JSI), render the terminal natively viareact-native-skia.xterm.jsexplicitly 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 reusesokena_mobile_native'sConnectionManager/handler/holder verbatim (no logic duplication; itscrate-typegains"lib"). Addsget_visible_cells_packed()(compact LE cell buffer for the renderer).cargo checkpasses for it andokena_mobile_native(Flutter Rust side intact).mobile/rn/— the RN app: a typedOkenaNativebinding contract + packed-cell decoder, a SkiaTerminalView(3-pass paint ported fromterminal_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)
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.connect()— client-side TLS lives onarch-review-fixes(PR Architecture review fixes: PTY starvation, persistence durability, remote/mobile leaks #134), not onmain. Forward-compatible signature is in place.layout_node_test,saved_server_test,terminal_flags_test,widget_test); no RN test runner yet.SafeArea, and per-cell metrics are simplified/omitted (spike-level). Build tooling (cargokit) is replaced byubrn, not ported.Next steps (require a local RN toolchain)
ubrn build android/ios --and-generateovercrates/okena-mobile-ffi; replacegetOkenaNative()'s body with the generated module (mobile/rn/README.mdhas exact commands).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/, andmobile/rn/.🤖 Generated with Claude Code