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/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/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
id }})" class="w-full p-4 rounded-xl" bg="#FFFFFF">
{{ $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
+ StatusActive
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.
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..604bb096 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/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..9fd2cbe2 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,90 @@ 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.
+
+### 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
+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
+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
+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`):
+
+```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
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/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/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/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()`.
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);