From 76acb295fc0fb64b61eecc7f452845fdec7ab4ee Mon Sep 17 00:00:00 2001 From: Shane Rosenthal Date: Wed, 8 Jul 2026 21:53:01 -0400 Subject: [PATCH 1/8] docs(mobile/v4): Device/Dialog/File/System are now core built-ins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These four APIs moved from standalone plugins into nativephp/mobile core. Add SuperNative reference pages for each — documented from the actual core source (the old plugin READMEs were stale) — remove the empty plugins/core stubs from the sidebar, and 301-redirect their old URLs to the new pages. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../docs/mobile/4/plugins/core/device.md | 4 - .../docs/mobile/4/plugins/core/dialog.md | 4 - .../views/docs/mobile/4/plugins/core/file.md | 4 - .../docs/mobile/4/plugins/core/system.md | 4 - .../docs/mobile/4/super-native/device.md | 112 ++++++++++++++++++ .../docs/mobile/4/super-native/dialog.md | 111 +++++++++++++++++ .../views/docs/mobile/4/super-native/file.md | 63 ++++++++++ .../docs/mobile/4/super-native/system.md | 78 ++++++++++++ routes/web.php | 6 + 9 files changed, 370 insertions(+), 16 deletions(-) delete mode 100644 resources/views/docs/mobile/4/plugins/core/device.md delete mode 100644 resources/views/docs/mobile/4/plugins/core/dialog.md delete mode 100644 resources/views/docs/mobile/4/plugins/core/file.md delete mode 100644 resources/views/docs/mobile/4/plugins/core/system.md create mode 100644 resources/views/docs/mobile/4/super-native/device.md create mode 100644 resources/views/docs/mobile/4/super-native/dialog.md create mode 100644 resources/views/docs/mobile/4/super-native/file.md create mode 100644 resources/views/docs/mobile/4/super-native/system.md diff --git a/resources/views/docs/mobile/4/plugins/core/device.md b/resources/views/docs/mobile/4/plugins/core/device.md deleted file mode 100644 index 8ed74240..00000000 --- a/resources/views/docs/mobile/4/plugins/core/device.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Device -order: 400 ---- diff --git a/resources/views/docs/mobile/4/plugins/core/dialog.md b/resources/views/docs/mobile/4/plugins/core/dialog.md deleted file mode 100644 index 788aec6e..00000000 --- a/resources/views/docs/mobile/4/plugins/core/dialog.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Dialog -order: 500 ---- diff --git a/resources/views/docs/mobile/4/plugins/core/file.md b/resources/views/docs/mobile/4/plugins/core/file.md deleted file mode 100644 index 9136481d..00000000 --- a/resources/views/docs/mobile/4/plugins/core/file.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: File -order: 600 ---- diff --git a/resources/views/docs/mobile/4/plugins/core/system.md b/resources/views/docs/mobile/4/plugins/core/system.md deleted file mode 100644 index 44f7e38d..00000000 --- a/resources/views/docs/mobile/4/plugins/core/system.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: System -order: 1500 ---- diff --git a/resources/views/docs/mobile/4/super-native/device.md b/resources/views/docs/mobile/4/super-native/device.md new file mode 100644 index 00000000..b196bad9 --- /dev/null +++ b/resources/views/docs/mobile/4/super-native/device.md @@ -0,0 +1,112 @@ +--- +title: Device +order: 100 +--- + +## Overview + +The `Device` API exposes device hardware and information — vibration, the flashlight, a stable device +identifier, and device + battery details. + +It's a **core built-in** in v4: the facade resolves with nothing to install or register. + +```php +use Native\Mobile\Facades\Device; +``` + +## Vibrate + +Trigger a short haptic tap: + +```php +$fired = Device::vibrate(); // bool — true if the haptic actually fired +``` + +On Android this uses the `VIBRATE` permission, which ships in the app manifest automatically. iOS needs no +permission. + +## Flashlight + +Toggle the rear flashlight on or off and read back the new state: + +```php +$result = Device::flashlight(); +// ['success' => true, 'state' => true] state: true = on, false = off +``` + +| Key | Type | Description | +| --- | --- | --- | +| `success` | bool | Whether the toggle succeeded | +| `state` | bool | The flashlight state after toggling (`true` = on) | + +On Android this uses the `FLASHLIGHT` permission, included in the manifest automatically. + +## Device identifier + +```php +$id = Device::getId(); // ?string +``` + +A stable per-install identifier — `identifierForVendor` on iOS, `ANDROID_ID` on Android. Returns `null` when +called off-device (e.g. in tests or the web preview). + +## Device info + +`getInfo()` returns a JSON **string**, so decode it before use: + +```php +$info = json_decode(Device::getInfo(), true); + +$info['platform']; // 'ios' | 'android' +$info['model']; // e.g. 'iPhone15,3' +``` + +| Field | Description | +| --- | --- | +| `name` | Device name | +| `model` | Device model identifier | +| `platform` | `'ios'` or `'android'` | +| `operatingSystem` | OS name | +| `osVersion` | OS version string | +| `manufacturer` | Device manufacturer | +| `language` | Device language as a BCP 47 tag (e.g. `en-US`) | +| `isVirtual` | Whether running in a simulator/emulator | +| `memUsed` | Memory usage in bytes | +| `webViewVersion` | WebView version | + + + +## Battery + +`getBatteryInfo()` also returns a JSON string: + +```php +$battery = json_decode(Device::getBatteryInfo(), true); + +$battery['batteryLevel']; // 0.0 – 1.0 +$battery['isCharging']; // bool +``` + +| Field | Description | +| --- | --- | +| `batteryLevel` | Battery level from `0.0` to `1.0` | +| `isCharging` | Whether the device is charging | + +## Permissions + +| Platform | Permissions | +| --- | --- | +| Android | `VIBRATE`, `FLASHLIGHT` — merged into your manifest automatically | +| iOS | None | + + diff --git a/resources/views/docs/mobile/4/super-native/dialog.md b/resources/views/docs/mobile/4/super-native/dialog.md new file mode 100644 index 00000000..64680c31 --- /dev/null +++ b/resources/views/docs/mobile/4/super-native/dialog.md @@ -0,0 +1,111 @@ +--- +title: Dialog +order: 110 +--- + +## Overview + +The `Dialog` API shows native alert dialogs and toast/snackbar notifications — a real `UIAlertController` on +iOS and `AlertDialog` on Android, so they look and feel exactly like the platform. + +It's a **core built-in** in v4: the facade resolves with nothing to install or register. + +```php +use Native\Mobile\Facades\Dialog; +``` + +## Alerts + +Show an alert with a title, message, and optional buttons: + +```php +// Simple alert with a default OK button +Dialog::alert('Hello', 'Welcome to the app!'); + +// Custom buttons +Dialog::alert('Confirm', 'Are you sure?', ['Cancel', 'Delete']); +``` + +`alert()` returns a `PendingAlert` you can configure fluently. If you don't call `->show()`, the alert displays +automatically when the object goes out of scope. + +| Method | Description | +| --- | --- | +| `->id(string $id)` | Tag the alert so you can tell which one a button press came from | +| `->event(string $class)` | Dispatch a custom event class instead of the default `ButtonPressed` | +| `->remember()` | Flash the alert's `id` to the session so you can read it back later with `PendingAlert::lastId()` | +| `->show()` | Display the alert explicitly (otherwise it shows on destruct) | +| `->buttonPressed(Closure $cb)` | Run a callback when a button is tapped (see below) | +| `->on(string $class, Closure $cb)` | Callback for a custom event set via `->event()` | + +### Handling button presses + +Chain a callback directly onto the alert. It runs on your live component, so `$this` works just like a method: + +```php +Dialog::alert('Confirm', 'Are you sure?', ['Cancel', 'Delete']) + ->buttonPressed(function ($event) { + if ($event->label === 'Delete') { + $this->deleteItem(); + } + }); +``` + +Or listen from the component with `#[On]`. The event's public properties bind to your method parameters by name: + +```php +use Native\Mobile\Attributes\On; +use Native\Mobile\Events\Alert\ButtonPressed; + +#[On(ButtonPressed::class)] +public function onButton(int $index, string $label, ?string $id = null): void +{ + if ($id === 'delete-confirm' && $label === 'Delete') { + $this->deleteItem(); + } +} +``` + +The `ButtonPressed` event carries: + +| Property | Type | Description | +| --- | --- | --- | +| `index` | int | The tapped button's index (0-based) | +| `label` | string | The button's label text | +| `id` | ?string | The alert's `id`, if one was set | + +If you didn't set an explicit `id`, call `->remember()` to flash the auto-generated one to the session, then +read it back in your listener with `PendingAlert::lastId()`: + +```php +Dialog::alert('Confirm', 'Delete this item?', ['Cancel', 'Delete'])->remember(); + +#[On(ButtonPressed::class)] +public function onButton(string $label, ?string $id = null): void +{ + if ($id === \Native\Mobile\PendingAlert::lastId() && $label === 'Delete') { + $this->deleteItem(); + } +} +``` + +## Toasts + +A brief, non-blocking message — a `Snackbar` on Android, an overlay on iOS: + +```php +Dialog::toast('Item saved!'); // 'long' (~4s) by default +Dialog::toast('Copied', 'short'); // 'short' (~2s) +``` + +| Parameter | Type | Default | Description | +| --- | --- | --- | --- | +| `message` | string | required | The text to display | +| `duration` | string | `'long'` | `'short'` (~2s) or `'long'` (~4s) | + + diff --git a/resources/views/docs/mobile/4/super-native/file.md b/resources/views/docs/mobile/4/super-native/file.md new file mode 100644 index 00000000..0820be46 --- /dev/null +++ b/resources/views/docs/mobile/4/super-native/file.md @@ -0,0 +1,63 @@ +--- +title: File +order: 120 +--- + +## Overview + +The `File` API performs native file operations — moving and copying files — using each platform's file system +directly. + +It's a **core built-in** in v4: the facade resolves with nothing to install or register. + +```php +use Native\Mobile\Facades\File; +``` + +## Move + +```php +$ok = File::move('/path/to/source.txt', '/path/to/destination.txt'); // bool +``` + +## Copy + +```php +$ok = File::copy('/path/to/source.txt', '/path/to/copy.txt'); // bool +``` + +Both methods return a `bool` — `true` on success, `false` if the operation failed. + +| Parameter | Type | Description | +| --- | --- | --- | +| `from` | string | Source file path | +| `to` | string | Destination file path | + +## Behavior + +- Parent directories are created automatically if they don't exist. +- An existing destination file is overwritten. +- File integrity is verified after a copy. +- On Android, if a rename fails across file systems, it falls back to copy + delete. + +## Example + +Move a recording out of temporary storage into a permanent location: + +```php +use Native\Mobile\Facades\File; + +$temp = sys_get_temp_dir().'/recording.m4a'; +$permanent = storage_path('recordings/recording.m4a'); + +if (File::move($temp, $permanent)) { + // saved +} +``` + + diff --git a/resources/views/docs/mobile/4/super-native/system.md b/resources/views/docs/mobile/4/super-native/system.md new file mode 100644 index 00000000..a3a12c29 --- /dev/null +++ b/resources/views/docs/mobile/4/super-native/system.md @@ -0,0 +1,78 @@ +--- +title: System +order: 130 +--- + +## Overview + +The `System` API covers system-level concerns — platform detection and opening the app's settings screen. + +It's a **core built-in** in v4: the facade resolves with nothing to install or register. + +```php +use Native\Mobile\Facades\System; +``` + +## Platform detection + +```php +System::isIos(); // true on iOS +System::isAndroid(); // true on Android +System::isMobile(); // true on either platform +``` + +Use these to branch behavior or conditionally render UI for a specific platform. + +Each is also available as a global helper function — `isIos()`, `isAndroid()`, `isMobile()` — for terse use in +Blade and components: + +```php +if (isAndroid()) { + // ... +} +``` + +In Blade, the same checks are available as conditional directives: + +@verbatim +```blade +@ios + {{-- iOS-only markup --}} +@endios + +@android + {{-- Android-only markup --}} +@endandroid + +@mobile + {{-- running inside the native app (iOS or Android) --}} +@endmobile + +@web + {{-- running in a browser / outside the native app --}} +@endweb +``` +@endverbatim + +Each also supports the usual `@@else…` and `@@unless…` forms — `@@elseios`, `@@unlessandroid`, and so on. + +## App settings + +Open the app's page in the device Settings app — useful for sending a user to re-grant a permission they +previously denied: + +```php +System::appSettings(); +``` + +## Appearance (light / dark) + +Reading the current appearance and reacting to theme changes lives with the rest of theming — see +[Theming → Appearance in PHP](theming#appearance-in-php) for `System::appearance()`, `isDarkMode()`, +`isLightMode()`, the `isDark()` / `theme()` helpers, and the `AppearanceChanged` event. + + diff --git a/routes/web.php b/routes/web.php index 1396ef7e..c825879b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -72,6 +72,12 @@ Route::redirect('t-shirt', 'blog/nativephp-for-mobile-is-now-free'); Route::redirect('tshirt', 'blog/nativephp-for-mobile-is-now-free'); +// v4: Device/Dialog/File/System moved from plugins into core built-ins; their docs +// now live in the SuperNative section (must precede the generic core-plugin redirect below). +foreach (['device', 'dialog', 'file', 'system'] as $corePage) { + Route::redirect("docs/mobile/4/plugins/core/{$corePage}", "/docs/mobile/4/super-native/{$corePage}", 301); +} + // Redirect mobile core plugin docs to plugin directory pages Route::get('docs/mobile/{version}/plugins/core/{page}', function (string $version, string $page) { return redirect("/plugins/nativephp/mobile-{$page}", 301); From c776a887d498fff54b21c5f2d3b3178391510fd9 Mon Sep 17 00:00:00 2001 From: Shane Rosenthal Date: Wed, 8 Jul 2026 21:53:17 -0400 Subject: [PATCH 2/8] =?UTF-8?q?docs(mobile/v4):=20SuperNative=20feature=20?= =?UTF-8?q?docs=20=E2=80=94=20appearance,=20text,=20navbar,=20bottom-bar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Theming: "Appearance in PHP" — System::appearance()/isDarkMode(), the isDark()/isLight()/theme() helpers, and the AppearanceChanged event. - Text: inline runs and select-text/select-none selection. - Layouts: NavBar::titleView()/logo() and keyboard-aware . - Text Input: cross-link to the keyboard-aware bottom bar. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../mobile/4/edge-components/text-input.md | 8 ++ .../docs/mobile/4/edge-components/text.md | 47 ++++++++++- .../docs/mobile/4/super-native/layouts.md | 77 +++++++++++++++++++ .../docs/mobile/4/super-native/theming.md | 65 ++++++++++++++++ 4 files changed, 195 insertions(+), 2 deletions(-) diff --git a/resources/views/docs/mobile/4/edge-components/text-input.md b/resources/views/docs/mobile/4/edge-components/text-input.md index 5c3634cf..2d36dd10 100644 --- a/resources/views/docs/mobile/4/edge-components/text-input.md +++ b/resources/views/docs/mobile/4/edge-components/text-input.md @@ -80,6 +80,14 @@ Both variants are self-closing. They do not accept children. + + ## Two-way Binding Use the `native:model` directive for automatic two-way binding with a component property. The directive expands to diff --git a/resources/views/docs/mobile/4/edge-components/text.md b/resources/views/docs/mobile/4/edge-components/text.md index eec6ba8f..bd7f6015 100644 --- a/resources/views/docs/mobile/4/edge-components/text.md +++ b/resources/views/docs/mobile/4/edge-components/text.md @@ -34,8 +34,50 @@ All [shared layout and style attributes](layout) are supported, plus: + +## Inline runs + +Nest `` elements inside a `` to style spans within a single paragraph. The nested runs +and the surrounding text compose into **one** attributed string that wraps together as a unit — each run carries +its own classes (weight, color, size): + +@verbatim +```blade + + Use bold and + color inline. + +``` +@endverbatim + +Runs render in document order, so interleaved text and nested `` stay in sequence. A `` +with no nested runs behaves exactly like a plain string. + +## Text selection + +Text isn't selectable by default. Add `select-text` to make a subtree long-press-selectable (the native Copy +menu). It's container-scoped and inherited, so it covers every descendant — put it on a wrapping element to make a +whole region selectable. Use `select-none` to opt a nested subtree back out: + +@verbatim +```blade + + Selectable heading + This body copy can be selected and copied. + + Not selectable + +``` +@endverbatim + + @@ -135,3 +177,4 @@ Text::make('Hello') - `color(string $hex)` - Text color - `textAlign(int $align)` - `0`=start, `1`=center, `2`=end - `maxLines(int $lines)` - Truncate after N lines +- `selectable(bool $on = true)` - Make the subtree selectable (mirrors `select-text` / `select-none`) diff --git a/resources/views/docs/mobile/4/super-native/layouts.md b/resources/views/docs/mobile/4/super-native/layouts.md index f8f68b9d..9724f93b 100644 --- a/resources/views/docs/mobile/4/super-native/layouts.md +++ b/resources/views/docs/mobile/4/super-native/layouts.md @@ -101,6 +101,8 @@ in a screen's Blade. - `make()` — create a builder - `title(?string)` / `subtitle(?string)` — title and the small line under it +- `titleView(Element|View)` — render a custom element or Blade view in the bar's centered slot instead of the string title (a logo lockup, wordmark, …) +- `logo(string $src, float $height = 28)` — convenience over `titleView()` for a bundled logo image - `back(bool $show = true)` — show the back chevron - `backgroundColor(string)` / `textColor(string)` — bar background and title/icon tint - `elevation(int $px)` — hairline thickness at the bottom of the bar @@ -119,6 +121,29 @@ in a screen's Blade. - `destructive(bool = true)` — render in the destructive tint - `items(array $actions)` — nest `NavAction`s to render a pull-down menu; `NavAction::divider()` adds a separator +#### Custom title view / logo + +Use `logo()` for the common case — a bundled brand image in place of the string title — or `titleView()` for any +element tree or Blade view: + +```php +public function navBar(NativeComponent $screen): ?NavBar +{ + return NavBar::make() + ->logo('images/logo.png'); // bundled asset, ~28pt tall + // ->titleView(Image::make('images/logo.png')->height(24)) + // ->titleView(view('native.brand-lockup')) +} +``` + + + ### `TabBar` — the bottom tabs - `make()` — create a builder; `add(Tab $tab)` — append a tab (up to 5) @@ -157,6 +182,58 @@ class AppLayout extends NativeLayout } ``` +## Keyboard-aware bottom content + +Beyond the tab bar, a layout or an individual screen can pin its own content to the bottom of the screen — a chat +input, a search field, a contextual action bar. This content **stays above the software keyboard automatically**: +on iOS via `.safeAreaInset(.bottom)`, on Android via the `Scaffold` bottom bar plus `imePadding()`. The main +content region sits above it and keeps its own scroll. + +The most ergonomic way is an inline `` at the root of a screen's Blade. It's lifted out of the +content flow and pinned, and it overrides the layout's `bottomBar()` for that screen: + +@verbatim +```blade + + + @foreach($messages as $message) + {{ $message->body }} + @endforeach + + + {{-- Pinned to the bottom, riding above the keyboard while typing --}} + + + + + + + +``` +@endverbatim + +To pin the same content across every screen under a layout, override `bottomBar()` instead — it returns any +element tree: + +```php +public function bottomBar(NativeComponent $screen): ?Element +{ + // Return an element tree, or null for no bottom bar. + // An inline on a screen overrides this for that screen. +} +``` + +Style the bar with the `glass` / `glass-thick` classes for a Liquid Glass capsule. Bottom-pinned content is only +rendered by layouts using native chrome (`usesNativeChrome()` is `true`). + + + ## How chrome wraps the screen When a screen renders, the framework's `wrapWithChrome` flow: diff --git a/resources/views/docs/mobile/4/super-native/theming.md b/resources/views/docs/mobile/4/super-native/theming.md index 8d57317b..50da3b63 100644 --- a/resources/views/docs/mobile/4/super-native/theming.md +++ b/resources/views/docs/mobile/4/super-native/theming.md @@ -76,6 +76,71 @@ Specify any token under `dark` to override just that value: ], ``` +## Appearance in PHP + +The renderers switch between the `light` and `dark` token blocks automatically as the system appearance changes. +When you need the current appearance in PHP — to pick an asset, resolve a token, or branch logic — read it from +the `System` facade: + +```php +use Native\Mobile\Facades\System; + +System::appearance(); // 'light' | 'dark' +System::isDarkMode(); // bool +System::isLightMode(); // bool +``` + +Global helper functions wrap the same calls for terse use in Blade and components: + +```php +isDark(); // bool +isLight(); // bool +``` + +### Resolving a token in PHP + +The `theme()` helper returns a token's value **for the current appearance** — the PHP-side counterpart to the +`bg-theme-*` / `text-theme-*` classes, reading from the same `config/native-ui.php` theme config: + +```php +theme('primary'); // config('native-ui.theme.dark.primary') in dark mode, light otherwise +theme('primary', '#0F766E'); // fall back to a value when the token is unset +``` + +Pass a fallback when a setter needs a non-null string — `theme()` returns your default when the key is missing +(or `native-ui` isn't installed). + +### Reacting to changes + +When the OS flips the theme — a Control Center toggle, or the sunset auto-switch — the `AppearanceChanged` event +fires. React in a component with `#[On]`; `$mode` is `'light'` or `'dark'`: + +```php +use Native\Mobile\Attributes\On; +use Native\Mobile\Events\System\AppearanceChanged; + +#[On(AppearanceChanged::class)] +public function appearanceChanged(string $mode): void +{ + // re-resolve anything appearance-dependent +} +``` + +`AppearanceChanged` also dispatches globally, so code anywhere in the app can listen — not just the active +screen: + +```php +use Illuminate\Support\Facades\Event; +use Native\Mobile\Events\System\AppearanceChanged; + +Event::listen(AppearanceChanged::class, function (AppearanceChanged $e) { + // $e->mode +}); +``` + +The query side (`System::appearance()` / `isDark()`) is kept in sync off this event, so reads stay fresh without +a bridge round-trip. + ## Runtime theming For per-tenant or user-selectable themes, merge tokens at runtime from a service provider with `Theme::merge()`. From 0b62dac942b32e558497035e609eafd08b61213b Mon Sep 17 00:00:00 2001 From: Shane Rosenthal Date: Wed, 8 Jul 2026 21:53:30 -0400 Subject: [PATCH 3/8] =?UTF-8?q?docs(mobile/v4):=20release=20notes=20?= =?UTF-8?q?=E2=80=94=20core-plugin=20migration=20+=20Vite=20opt-in?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Upgrade Guide: new "Upgrading To 4.0 From 3.x" — remove the mobile-device/ dialog/file/system plugins before upgrading; Vite dev server is now opt-in. - Changelog: Breaking Changes for both. - Commands/Development: document the --vite flag (Vite no longer auto-starts; pass --vite for JS/CSS HMR). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../mobile/4/getting-started/changelog.md | 5 ++ .../docs/mobile/4/getting-started/commands.md | 10 +++ .../mobile/4/getting-started/development.md | 21 +++++- .../mobile/4/getting-started/upgrade-guide.md | 66 +++++++++++++++++++ 4 files changed, 99 insertions(+), 3 deletions(-) diff --git a/resources/views/docs/mobile/4/getting-started/changelog.md b/resources/views/docs/mobile/4/getting-started/changelog.md index 81384a58..2ce7b918 100644 --- a/resources/views/docs/mobile/4/getting-started/changelog.md +++ b/resources/views/docs/mobile/4/getting-started/changelog.md @@ -26,6 +26,11 @@ For changes prior to v4, see the [v3 documentation](/docs/mobile/3/getting-start - **[Native navigation stack](../super-native/navigation)** — register screens with `Route::native()`, then push, pop, and replace them with native transitions - **[Component testing suite](../testing/introduction)** — mount a `NativeComponent`, drive interactions, and assert on state and output entirely in-process, with no device or simulator +### Breaking Changes + +- **Device, Dialog, File and System are now core built-ins** — the `nativephp/mobile-device`, `nativephp/mobile-dialog`, `nativephp/mobile-file`, and `nativephp/mobile-system` plugins have moved into `nativephp/mobile`. **Remove the standalone plugins before upgrading** (see the [Upgrade Guide](upgrade-guide)). The facades and events are unchanged, so no application code changes are needed. +- **The Vite dev server is now opt-in** — `native:run` / `native:watch` no longer start Vite automatically. Pass `--vite` to enable JS/CSS HMR. The `--no-vite` flag still works but is now redundant. See [Hot Reloading](development#hot-reloading). + ### For Plugin Developers - **No breaking changes** to the plugin architecture — add the `^4.0` constraint to your plugin's `nativephp/mobile` dependency and you're done diff --git a/resources/views/docs/mobile/4/getting-started/commands.md b/resources/views/docs/mobile/4/getting-started/commands.md index ae22359e..8b8f0e0e 100644 --- a/resources/views/docs/mobile/4/getting-started/commands.md +++ b/resources/views/docs/mobile/4/getting-started/commands.md @@ -38,6 +38,7 @@ php artisan native:run {os?} {udid?} | `udid` | Specific device/simulator UDID | | `--build=debug` | Build type: `debug`, `release`, or `bundle` | | `--watch` | Enable hot reloading during development | +| `--vite` | Start the Vite dev server for JS/CSS HMR (opt-in; off by default) | | `--start-url=` | Initial URL/path to load (e.g., `/dashboard`) | | `--no-tty` | Disable TTY mode for non-interactive environments | @@ -60,6 +61,15 @@ php artisan native:watch {platform?} {target?} |--------|-----------------------------------------| | `platform` | Target platform: `ios/i` or `android/a` | | `target` | The device/simulator UDID to watch | +| `--vite` | Start the Vite dev server for JS/CSS HMR (opt-in; off by default) | + + ### native:jump diff --git a/resources/views/docs/mobile/4/getting-started/development.md b/resources/views/docs/mobile/4/getting-started/development.md index cbd99fae..bfd44ee3 100644 --- a/resources/views/docs/mobile/4/getting-started/development.md +++ b/resources/views/docs/mobile/4/getting-started/development.md @@ -177,12 +177,27 @@ application to the target device and _then_ start the watcher, all in one go. This will start a long-lived process that watches your application's source files for changes, pushing them into the emulator after any updates and reloading the current screen. -If you're using Vite, we'll also use your Node CLI tool of choice (`npm`, `bun`, `pnpm`, or `yarn`) to run Vite's HMR -server. +If you're using Vite for your UI (React/Vue/Tailwind, etc.), pass the `--vite` flag and we'll also start Vite's HMR +server using your Node CLI tool of choice (`npm`, `bun`, `pnpm`, or `yarn`): + +```shell +php artisan native:watch --vite + +# or build, deploy, and start watching in one go: +php artisan native:run --watch --vite +``` + + ### Enabling HMR -To make HMR work, you'll need to add the `hot` file helper to your `laravel` plugin's config in your `vite.config.js`: +To make HMR work, add the `hot` file helper to your `laravel` plugin's config in your `vite.config.js`, then run +the watcher with `--vite` (above): ```js import { nativephpMobile, nativephpHotFile } from './vendor/nativephp/mobile/resources/js/vite-plugin.js'; // [tl! focus] diff --git a/resources/views/docs/mobile/4/getting-started/upgrade-guide.md b/resources/views/docs/mobile/4/getting-started/upgrade-guide.md index ea5cca07..3f49bba7 100644 --- a/resources/views/docs/mobile/4/getting-started/upgrade-guide.md +++ b/resources/views/docs/mobile/4/getting-started/upgrade-guide.md @@ -3,6 +3,72 @@ title: Upgrade Guide order: 3 --- +## Upgrading To 4.0 From 3.x + +v4's headline is [SuperNative](../super-native/introduction) — fully native UI. Most of the release is additive, +but there is **one breaking change to your dependencies**: a handful of APIs that used to be separate plugins are +now core built-ins. + +### Device, Dialog, File and System are now built in + +`Device`, `Dialog`, `File`, and `System` now ship inside `nativephp/mobile` — their native bridge functions are +registered by core. If you have the standalone plugins installed, **remove them before upgrading**, otherwise +their bridge functions register twice. + +Uninstall each one you have (this also unregisters it from your `NativeServiceProvider`): + +```shell +php artisan native:plugin:uninstall nativephp/mobile-device +php artisan native:plugin:uninstall nativephp/mobile-dialog +php artisan native:plugin:uninstall nativephp/mobile-file +php artisan native:plugin:uninstall nativephp/mobile-system +``` + +Or remove them directly with Composer if they were never registered in your `NativeServiceProvider`: + +```shell +composer remove nativephp/mobile-device nativephp/mobile-dialog nativephp/mobile-file nativephp/mobile-system +``` + +**No application code changes are required.** The `Native\Mobile\Facades\{Device, Dialog, File, System}` facades +and their events (`ButtonPressed`, etc.) are unchanged. Their docs now live in the SuperNative section: +[Device](../super-native/device), [Dialog](../super-native/dialog), [File](../super-native/file), and +[System](../super-native/system). + +### The Vite dev server is now opt-in + +`native:run` and `native:watch` no longer start the Vite dev server automatically. If you rely on Vite HMR during +development (React/Vue/Tailwind, etc.), add the `--vite` flag: + +```shell +php artisan native:watch --vite +php artisan native:run --watch --vite +``` + +The old `--no-vite` flag still exists but is now redundant — Vite is off unless you ask for it. If you have +`--no-vite` in your scripts, you can drop it. + +### Update your dependency + +```json +"require": { + "nativephp/mobile": "~3.1.0" // [tl! remove] + "nativephp/mobile": "~4.0.0" // [tl! add] +} +``` + +```sh +composer update +php artisan native:install --force +``` + + + + ## Upgrading To 3.1 From 3.0 v3.1 is a drop-in upgrade with no breaking changes. The headline feature is a **persistent PHP runtime** that From 3c1bae8f7759fa1f2cdf3a35f8176f42409b91c3 Mon Sep 17 00:00:00 2001 From: Shane Rosenthal Date: Wed, 8 Jul 2026 21:57:42 -0400 Subject: [PATCH 4/8] docs(mobile/v4): list select-text/select-none in class reference; note Composer conflict guard - Add the text-selection classes to the "Supported Tailwind classes" table. - Upgrade Guide: v4 declares a Composer conflict with the four ex-plugins, so the update hard-blocks until they're removed (clearer than "registers twice"). Co-Authored-By: Claude Opus 4.8 (1M context) --- resources/views/docs/mobile/4/edge-components/layout.md | 1 + .../views/docs/mobile/4/getting-started/upgrade-guide.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/views/docs/mobile/4/edge-components/layout.md b/resources/views/docs/mobile/4/edge-components/layout.md index b0957bf4..04b716a8 100644 --- a/resources/views/docs/mobile/4/edge-components/layout.md +++ b/resources/views/docs/mobile/4/edge-components/layout.md @@ -295,6 +295,7 @@ The parser recognizes the classes listed below. | Text size | `text-xs`, `text-sm`, `text-base`, `text-lg`, `text-xl`, `text-2xl`, `text-3xl`, `text-4xl`, `text-5xl`, `text-6xl`, arbitrary `text-[N]` | | Font weight | `font-thin`, `font-extralight`, `font-light`, `font-normal`, `font-medium`, `font-semibold`, `font-bold`, `font-extrabold`, `font-black` | | Text align | `text-left`, `text-center`, `text-right` | +| Text selection | `select-text`, `select-none` (container-scoped; descendants inherit) | | Safe area | `safe-area` (top + bottom), `safe-area-top`, `safe-area-bottom` | | Liquid Glass | `glass`, `glass:prominent`, `glass:interactive`, `glass:clear` (compose: `glass:clear:interactive`) | diff --git a/resources/views/docs/mobile/4/getting-started/upgrade-guide.md b/resources/views/docs/mobile/4/getting-started/upgrade-guide.md index 3f49bba7..efe95bbf 100644 --- a/resources/views/docs/mobile/4/getting-started/upgrade-guide.md +++ b/resources/views/docs/mobile/4/getting-started/upgrade-guide.md @@ -12,8 +12,8 @@ now core built-ins. ### Device, Dialog, File and System are now built in `Device`, `Dialog`, `File`, and `System` now ship inside `nativephp/mobile` — their native bridge functions are -registered by core. If you have the standalone plugins installed, **remove them before upgrading**, otherwise -their bridge functions register twice. +registered by core. `nativephp/mobile` v4 declares a Composer **conflict** with the four standalone plugins, so +`composer update` will refuse to resolve until you **remove them**. Uninstall each one you have (this also unregisters it from your `NativeServiceProvider`): From 6cdc6d1ff74e7b780c019572a8bfe6c17ecbe250 Mon Sep 17 00:00:00 2001 From: Shane Rosenthal Date: Wed, 8 Jul 2026 22:03:52 -0400 Subject: [PATCH 5/8] docs(mobile/v4): add "Why SuperNative" rationale to the upgrade guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Frame the strategic shift: the web view tied the UI to upstream stacks we didn't own (Inertia 3 dropping axios, Livewire 4 emoji filenames breaking iOS builds). SuperNative is a stack we own end to end — PHP to SwiftUI/Compose. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../mobile/4/getting-started/upgrade-guide.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/resources/views/docs/mobile/4/getting-started/upgrade-guide.md b/resources/views/docs/mobile/4/getting-started/upgrade-guide.md index efe95bbf..37c532a8 100644 --- a/resources/views/docs/mobile/4/getting-started/upgrade-guide.md +++ b/resources/views/docs/mobile/4/getting-started/upgrade-guide.md @@ -9,6 +9,24 @@ v4's headline is [SuperNative](../super-native/introduction) — fully native UI but there is **one breaking change to your dependencies**: a handful of APIs that used to be separate plugins are now core built-ins. +### Why SuperNative + +For its first three major versions, NativePHP for Mobile was built around a fast, opinionated web view. It worked +well — but it tied your app's UI to a stack of moving parts we didn't own, and every upstream release was a chance +for something to break. Inertia 3 dropped its axios dependency and broke Inertia apps. Livewire 4 began emitting +compiled filenames containing an emoji (🔥) that iOS builds refused to package. We spent real energy chasing a +target that kept moving, on layers we couldn't control. + +SuperNative changes that equation. Instead of rendering your UI in a browser and hoping the layers above stay +compatible, it renders real SwiftUI and Jetpack Compose views driven directly by your PHP — a stack we own end to +end, from your Laravel app all the way down to the native view tree. That means our effort goes into making *that* +fast, stable, and capable, rather than reacting to churn elsewhere. The web view's advantages have narrowed as +SuperNative's rendering has matured, and for new work SuperNative is where the platform — and our focus — is +headed. + +The web view isn't going away: [it's still available as a component](../edge-components/web-view) for the cases +that genuinely need HTML. But it's now opt-in, not the foundation. + ### Device, Dialog, File and System are now built in `Device`, `Dialog`, `File`, and `System` now ship inside `nativephp/mobile` — their native bridge functions are From 09a5b22b3e3547f761b6111240ef128fecd5d5c2 Mon Sep 17 00:00:00 2001 From: Shane Rosenthal Date: Wed, 8 Jul 2026 22:11:37 -0400 Subject: [PATCH 6/8] docs(mobile/v4): correct Livewire 4 emoji-filename detail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The breakage was our bundled PHP binaries initially failing to read the emoji (🔥) filenames, not the iOS packager rejecting them. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../views/docs/mobile/4/getting-started/upgrade-guide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/docs/mobile/4/getting-started/upgrade-guide.md b/resources/views/docs/mobile/4/getting-started/upgrade-guide.md index 37c532a8..9fd2cbe2 100644 --- a/resources/views/docs/mobile/4/getting-started/upgrade-guide.md +++ b/resources/views/docs/mobile/4/getting-started/upgrade-guide.md @@ -14,8 +14,8 @@ now core built-ins. For its first three major versions, NativePHP for Mobile was built around a fast, opinionated web view. It worked well — but it tied your app's UI to a stack of moving parts we didn't own, and every upstream release was a chance for something to break. Inertia 3 dropped its axios dependency and broke Inertia apps. Livewire 4 began emitting -compiled filenames containing an emoji (🔥) that iOS builds refused to package. We spent real energy chasing a -target that kept moving, on layers we couldn't control. +filenames containing an emoji (🔥) that our bundled PHP binaries initially couldn't read. We spent real energy +chasing a target that kept moving, on layers we couldn't control. SuperNative changes that equation. Instead of rendering your UI in a browser and hoping the layers above stay compatible, it renders real SwiftUI and Jetpack Compose views driven directly by your PHP — a stack we own end to From bfbcd281dd4945c3f24713decc532d4011f87b77 Mon Sep 17 00:00:00 2001 From: Shane Rosenthal Date: Thu, 9 Jul 2026 08:30:20 -0400 Subject: [PATCH 7/8] docs(mobile/v4): use theme tokens in the new Text examples Swap raw palette classes (text-slate-*/text-blue-*) for theme tokens (text-theme-on-surface/-primary/-on-surface-variant) in the inline-runs and text-selection examples. Co-Authored-By: Claude Opus 4.8 (1M context) --- resources/views/docs/mobile/4/edge-components/text.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/views/docs/mobile/4/edge-components/text.md b/resources/views/docs/mobile/4/edge-components/text.md index bd7f6015..604bb096 100644 --- a/resources/views/docs/mobile/4/edge-components/text.md +++ b/resources/views/docs/mobile/4/edge-components/text.md @@ -47,9 +47,9 @@ its own classes (weight, color, size): @verbatim ```blade - + Use bold and - color inline. + color inline. ``` @endverbatim @@ -69,7 +69,7 @@ whole region selectable. Use `select-none` to opt a nested subtree back out: Selectable heading This body copy can be selected and copied. - Not selectable + Not selectable ``` @endverbatim From 603fc3f55095b51868655ea5fd368771d0e620d4 Mon Sep 17 00:00:00 2001 From: Shane Rosenthal Date: Thu, 9 Jul 2026 08:43:52 -0400 Subject: [PATCH 8/8] docs(mobile/v4): use theme tokens for neutral colors in component examples Replace incidental palette classes with semantic theme tokens across the edge-component examples: muted text (text-slate-400/500) -> text-theme-on- surface-variant, divider lines (bg-zinc-200) -> bg-theme-outline. Deliberately left alone: decorative/status colors with no token equivalent (badge dots, over-image white text, gold stars, green "active"), the color=/bg= hex prop demonstrations, layout.md's class-reference and Tailwind-teaching sections, web-view CSS, and dark-mode `dark:` examples. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../docs/mobile/4/edge-components/activity-indicator.md | 4 ++-- .../views/docs/mobile/4/edge-components/bottom-sheet.md | 2 +- resources/views/docs/mobile/4/edge-components/carousel.md | 2 +- resources/views/docs/mobile/4/edge-components/divider.md | 2 +- resources/views/docs/mobile/4/edge-components/icon.md | 2 +- resources/views/docs/mobile/4/edge-components/image.md | 2 +- resources/views/docs/mobile/4/edge-components/pressable.md | 6 +++--- resources/views/docs/mobile/4/edge-components/row.md | 2 +- .../views/docs/mobile/4/edge-components/scroll-view.md | 4 ++-- resources/views/docs/mobile/4/edge-components/spacer.md | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/resources/views/docs/mobile/4/edge-components/activity-indicator.md b/resources/views/docs/mobile/4/edge-components/activity-indicator.md index c6f17dc3..9051ae7c 100644 --- a/resources/views/docs/mobile/4/edge-components/activity-indicator.md +++ b/resources/views/docs/mobile/4/edge-components/activity-indicator.md @@ -40,7 +40,7 @@ a non-theme-styled container (e.g. a light spinner over a dark image overlay). ```blade - Loading... + Loading... ``` @endverbatim @@ -51,7 +51,7 @@ a non-theme-styled container (e.g. a light spinner over a dark image overlay). ```blade - Refreshing + Refreshing ``` @endverbatim diff --git a/resources/views/docs/mobile/4/edge-components/bottom-sheet.md b/resources/views/docs/mobile/4/edge-components/bottom-sheet.md index 1f52110a..128bf064 100644 --- a/resources/views/docs/mobile/4/edge-components/bottom-sheet.md +++ b/resources/views/docs/mobile/4/edge-components/bottom-sheet.md @@ -22,7 +22,7 @@ Per Model 3, the container color resolves from `theme.surface`. For a custom sur Sheet Title - Sheet content goes here. + Sheet content goes here. diff --git a/resources/views/docs/mobile/4/edge-components/carousel.md b/resources/views/docs/mobile/4/edge-components/carousel.md index 3d5e9540..033b0639 100644 --- a/resources/views/docs/mobile/4/edge-components/carousel.md +++ b/resources/views/docs/mobile/4/edge-components/carousel.md @@ -14,7 +14,7 @@ stack with `item-spacing` between items. @foreach($posts as $post) {{ $post->title }} - {{ $post->excerpt }} + {{ $post->excerpt }} @endforeach diff --git a/resources/views/docs/mobile/4/edge-components/divider.md b/resources/views/docs/mobile/4/edge-components/divider.md index c5ee2f99..f8f94149 100644 --- a/resources/views/docs/mobile/4/edge-components/divider.md +++ b/resources/views/docs/mobile/4/edge-components/divider.md @@ -21,7 +21,7 @@ the platform separator color (`UIColor.separator` on iOS, Material `outlineVaria `` is a self-closing element. It does not accept children. The line is **always 1pt high**. For thicker rules, drop in a styled column instead: -`` for 1px, `` for 4dp, etc. +`` for 1px, `` for 4dp, etc. diff --git a/resources/views/docs/mobile/4/edge-components/icon.md b/resources/views/docs/mobile/4/edge-components/icon.md index 0fa7734b..a352c1a4 100644 --- a/resources/views/docs/mobile/4/edge-components/icon.md +++ b/resources/views/docs/mobile/4/edge-components/icon.md @@ -66,7 +66,7 @@ and platform-specific usage, see the [Icons](icons) reference page. ```blade - No messages + No messages ``` @endverbatim diff --git a/resources/views/docs/mobile/4/edge-components/image.md b/resources/views/docs/mobile/4/edge-components/image.md index 0734445a..66b5e723 100644 --- a/resources/views/docs/mobile/4/edge-components/image.md +++ b/resources/views/docs/mobile/4/edge-components/image.md @@ -79,7 +79,7 @@ The renderer collapses fit modes to two effective behaviors: `fit` and `fill`. M Article Title - A brief description of the article. + A brief description of the article. ``` diff --git a/resources/views/docs/mobile/4/edge-components/pressable.md b/resources/views/docs/mobile/4/edge-components/pressable.md index 17678339..d7f9d5bf 100644 --- a/resources/views/docs/mobile/4/edge-components/pressable.md +++ b/resources/views/docs/mobile/4/edge-components/pressable.md @@ -15,7 +15,7 @@ provides a clear tap target that wraps multiple children. ```blade {{ $item->name }} - {{ $item->description }} + {{ $item->description }} ``` @endverbatim @@ -50,7 +50,7 @@ Accepts any EDGE elements as children. Children are arranged vertically (like a {{ $item->name }} - {{ $item->subtitle }} + {{ $item->subtitle }} @@ -74,7 +74,7 @@ Accepts any EDGE elements as children. Children are arranged vertically (like a :elevation="2" > {{ $post->title }} - {{ $post->excerpt }} + {{ $post->excerpt }} ``` @endverbatim diff --git a/resources/views/docs/mobile/4/edge-components/row.md b/resources/views/docs/mobile/4/edge-components/row.md index 4aee7259..a957e7de 100644 --- a/resources/views/docs/mobile/4/edge-components/row.md +++ b/resources/views/docs/mobile/4/edge-components/row.md @@ -67,7 +67,7 @@ Everything else from the shared list applies the same as on any element (`w-*`, @verbatim ```blade - Status + Status Active diff --git a/resources/views/docs/mobile/4/edge-components/scroll-view.md b/resources/views/docs/mobile/4/edge-components/scroll-view.md index 3e12ef48..c438ba56 100644 --- a/resources/views/docs/mobile/4/edge-components/scroll-view.md +++ b/resources/views/docs/mobile/4/edge-components/scroll-view.md @@ -51,7 +51,7 @@ scrollable content. @foreach($posts as $post) {{ $post->title }} - {{ $post->excerpt }} + {{ $post->excerpt }} @endforeach @@ -88,7 +88,7 @@ scrollable content. Welcome - + Scroll down to see more content. {{-- Long content here --}} diff --git a/resources/views/docs/mobile/4/edge-components/spacer.md b/resources/views/docs/mobile/4/edge-components/spacer.md index 9ff6e82b..384d87ab 100644 --- a/resources/views/docs/mobile/4/edge-components/spacer.md +++ b/resources/views/docs/mobile/4/edge-components/spacer.md @@ -50,7 +50,7 @@ See the full shared list at [Layout & Styling](layout#supported-tailwind-classes ```blade Welcome - Get started with your app. + Get started with your app.